mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-13 20:28:08 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43cb6df841 | ||
|
|
f7841c2988 | ||
|
|
d94a0f8055 | ||
|
|
312fe7ddab |
No files matched your search
+2
-1
@@ -9,10 +9,11 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
### 🎉 New features
|
||||
|
||||
- Show fingerprints in build:view and build:list commands. ([#3137](https://github.com/expo/eas-cli/pull/3137) by [@douglowder](https://github.com/douglowder))
|
||||
- Add `eas run` alias for `eas workflow:run`. Accept more inputs for workflow file input. ([#3138](https://github.com/expo/eas-cli/pull/3138) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
- Make EXPO_PUBLIC_ env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman))
|
||||
- Make `EXPO_PUBLIC_` env vars plain text, rest sensitive ([#3121](https://github.com/expo/eas-cli/pull/3121) by [@kadikraman](https://github.com/kadikraman))
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
|
||||
@@ -79,8 +79,15 @@ const EXIT_CODES = {
|
||||
|
||||
export default class WorkflowRun extends EasCommand {
|
||||
static override description = 'run an EAS workflow';
|
||||
static override aliases = ['run'];
|
||||
|
||||
static override args = [{ name: 'file', description: 'Path to the workflow file to run' }];
|
||||
static override args = [
|
||||
{
|
||||
name: 'workflow_file_input',
|
||||
description:
|
||||
'Path to the workflow file to run or a workflow file basename (for a file under `.eas/workflows/[name].yml`)',
|
||||
},
|
||||
];
|
||||
|
||||
static override flags = {
|
||||
...EASNonInteractiveFlag,
|
||||
@@ -109,7 +116,10 @@ export default class WorkflowRun extends EasCommand {
|
||||
};
|
||||
|
||||
async runAsync(): Promise<void> {
|
||||
const { flags, args } = await this.parse(WorkflowRun);
|
||||
const {
|
||||
flags,
|
||||
args: { workflow_file_input: workflowFileInput },
|
||||
} = await this.parse(WorkflowRun);
|
||||
|
||||
if (flags.json) {
|
||||
enableJsonOutput();
|
||||
@@ -125,14 +135,15 @@ export default class WorkflowRun extends EasCommand {
|
||||
withServerSideEnvironment: null,
|
||||
});
|
||||
|
||||
let filePath: string;
|
||||
let yamlConfig: string;
|
||||
|
||||
try {
|
||||
const workflowFileContents = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
({ yamlConfig, filePath } = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath: args.file,
|
||||
});
|
||||
Log.log(`Using workflow file from ${workflowFileContents.filePath}`);
|
||||
yamlConfig = workflowFileContents.yamlConfig;
|
||||
filePath: workflowFileInput,
|
||||
}));
|
||||
Log.log(`Using workflow file from ${filePath}`);
|
||||
} catch (err) {
|
||||
Log.error('Failed to read workflow file.');
|
||||
|
||||
@@ -260,7 +271,7 @@ export default class WorkflowRun extends EasCommand {
|
||||
({ id: workflowRunId } = await WorkflowRunMutation.createWorkflowRunAsync(graphqlClient, {
|
||||
appId: projectId,
|
||||
workflowRevisionInput: {
|
||||
fileName: path.basename(args.file),
|
||||
fileName: path.basename(filePath),
|
||||
yamlConfig,
|
||||
},
|
||||
workflowRunInput: {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import path from 'path';
|
||||
import { vol } from 'memfs';
|
||||
|
||||
import { WorkflowFile } from '../workflowFile';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('WorkflowFile.readWorkflowFileContentsAsync', () => {
|
||||
beforeEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
describe('absolute paths', () => {
|
||||
it('should read a file with absolute path', async () => {
|
||||
const absolutePath = '/Users/test/workflow.yml';
|
||||
const yamlContent = 'name: test\njobs:\n build:\n runs-on: ubuntu-latest';
|
||||
|
||||
vol.fromJSON({
|
||||
[absolutePath]: yamlContent,
|
||||
});
|
||||
|
||||
const result = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir: '/some/project',
|
||||
filePath: absolutePath,
|
||||
});
|
||||
|
||||
expect(result.yamlConfig).toBe(yamlContent);
|
||||
expect(result.filePath).toBe(absolutePath);
|
||||
});
|
||||
|
||||
it('should only try the exact absolute path when provided', async () => {
|
||||
const absolutePath = '/Users/test/workflow.yml';
|
||||
const yamlContent = 'name: test\njobs:\n build:\n runs-on: ubuntu-latest';
|
||||
|
||||
vol.fromJSON({
|
||||
[absolutePath]: yamlContent,
|
||||
'/Users/test/workflow.yaml': 'different content', // This should not be used
|
||||
});
|
||||
|
||||
const result = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir: '/some/project',
|
||||
filePath: absolutePath,
|
||||
});
|
||||
|
||||
expect(result.yamlConfig).toBe(yamlContent);
|
||||
expect(result.filePath).toBe(absolutePath);
|
||||
});
|
||||
|
||||
it('should fail if absolute path does not exist (no extension fallback)', async () => {
|
||||
const absolutePath = '/Users/test/nonexistent.yml';
|
||||
|
||||
vol.fromJSON({
|
||||
'/Users/test/nonexistent.yaml': 'some content', // This should not be tried
|
||||
});
|
||||
|
||||
await expect(
|
||||
WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir: '/some/project',
|
||||
filePath: absolutePath,
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('relative paths', () => {
|
||||
it('should prioritize .eas/workflows directory for relative paths', async () => {
|
||||
const projectDir = '/project';
|
||||
const relativeFilePath = 'deploy';
|
||||
const yamlContent = 'name: deploy\njobs:\n deploy:\n runs-on: ubuntu-latest';
|
||||
|
||||
vol.fromJSON({
|
||||
[`${projectDir}/.eas/workflows/${relativeFilePath}.yml`]: yamlContent,
|
||||
[`${projectDir}/${relativeFilePath}.yml`]: 'different content',
|
||||
});
|
||||
|
||||
const result = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir,
|
||||
filePath: relativeFilePath,
|
||||
});
|
||||
|
||||
expect(result.yamlConfig).toBe(yamlContent);
|
||||
expect(result.filePath).toBe(`${projectDir}/.eas/workflows/${relativeFilePath}.yml`);
|
||||
});
|
||||
|
||||
it('should fall back to resolving relative path if not found in .eas/workflows', async () => {
|
||||
const relativeFilePath = 'deploy.yml';
|
||||
const yamlContent = 'name: deploy\njobs:\n deploy:\n runs-on: ubuntu-latest';
|
||||
const resolvedPath = path.resolve(relativeFilePath);
|
||||
|
||||
vol.fromJSON({
|
||||
[resolvedPath]: yamlContent,
|
||||
});
|
||||
|
||||
const result = await WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir: '/some/project',
|
||||
filePath: relativeFilePath,
|
||||
});
|
||||
|
||||
expect(result.yamlConfig).toBe(yamlContent);
|
||||
expect(result.filePath).toBe(resolvedPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should throw error if no file is found in any of the search paths', async () => {
|
||||
await expect(
|
||||
WorkflowFile.readWorkflowFileContentsAsync({
|
||||
projectDir: '/project',
|
||||
filePath: 'nonexistent',
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,27 +14,34 @@ export namespace WorkflowFile {
|
||||
projectDir: string;
|
||||
filePath: string;
|
||||
}): Promise<{ yamlConfig: string; filePath: string }> {
|
||||
const [yamlFromEasWorkflowsFile, yamlFromFile] = await Promise.allSettled([
|
||||
fs.promises.readFile(path.join(projectDir, '.eas', 'workflows', filePath), 'utf8'),
|
||||
fs.promises.readFile(path.join(process.cwd(), filePath), 'utf8'),
|
||||
]);
|
||||
|
||||
// We prioritize .eas/workflows/${file} over ${file}, because
|
||||
// in the worst case we'll try to read .eas/workflows/.eas/workflows/test.yml,
|
||||
// which is likely not to exist.
|
||||
if (yamlFromEasWorkflowsFile.status === 'fulfilled') {
|
||||
return {
|
||||
yamlConfig: yamlFromEasWorkflowsFile.value,
|
||||
filePath: path.join(projectDir, '.eas', 'workflows', filePath),
|
||||
};
|
||||
} else if (yamlFromFile.status === 'fulfilled') {
|
||||
return {
|
||||
yamlConfig: yamlFromFile.value,
|
||||
filePath: path.join(process.cwd(), filePath),
|
||||
};
|
||||
// If the input is an absolute path, the user was clear about the file they wanted to run.
|
||||
// We only check that file path.
|
||||
if (path.isAbsolute(filePath)) {
|
||||
return { yamlConfig: await fs.promises.readFile(filePath, 'utf8'), filePath };
|
||||
}
|
||||
|
||||
throw yamlFromFile.reason;
|
||||
// If the input is a relative path (which "deploy-to-production", "deploy-to-production.yml"
|
||||
// and ".eas/workflows/deploy-to-production.yml" are), we try to find the file.
|
||||
const pathsToSearch = [
|
||||
path.join(projectDir, '.eas', 'workflows', `${filePath}.yaml`),
|
||||
path.join(projectDir, '.eas', 'workflows', `${filePath}.yml`),
|
||||
path.join(projectDir, '.eas', 'workflows', filePath),
|
||||
path.resolve(filePath),
|
||||
];
|
||||
|
||||
let lastError: any = null;
|
||||
|
||||
for (const path of pathsToSearch) {
|
||||
try {
|
||||
const yamlConfig = await fs.promises.readFile(path, 'utf8');
|
||||
return { yamlConfig, filePath: path };
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export function maybePrintWorkflowFileValidationErrors({
|
||||
|
||||
Reference in new issue
Block a user