Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This is the log of notable changes to EAS CLI and related packages.
- [build-tools] Load local composite function catalogs at build time so custom builds and generic jobs can resolve local composite functions referenced via `uses:`. ([#3930](https://github.com/expo/eas-cli/pull/3930) by [@sswrk](https://github.com/sswrk))
- [eas-cli] Validate local composite functions referenced in workflow files during `eas workflow:validate`, checking that the YAML files exist and are well-formed. ([#3931](https://github.com/expo/eas-cli/pull/3931) by [@sswrk](https://github.com/sswrk))
- [build-tools] Load local composite function catalogs for hook steps, in both steps-based and native builds. ([#4063](https://github.com/expo/eas-cli/pull/4063) by [@sswrk](https://github.com/sswrk))
- [eas-cli] Validate local composite functions referenced from workflow job hooks during `eas workflow:validate`. ([#4064](https://github.com/expo/eas-cli/pull/4064) by [@sswrk](https://github.com/sswrk))

### 🐛 Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,72 @@ describe(validateWorkflowLocalCompositeFunctionsAsync, () => {
).resolves.toBeUndefined();
});

it('validates local composite functions referenced from job hooks', async () => {
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-workflow-functions-hooks-'));
await makeProjectWithCompositeFunctionAsync(
projectRoot,
'setup',
['runs:', ' steps:', ' - run: echo setup'].join('\n')
);
const workflow = {
jobs: {
job: {
steps: [{ run: 'echo hi' }],
hooks: {
before_install_node_modules: [{ uses: './.eas/functions/setup' }],
},
},
},
};

await expect(
validateWorkflowLocalCompositeFunctionsAsync(workflow, projectRoot)
).resolves.toBeUndefined();
});

it('throws when a composite function referenced from a job hook does not exist', async () => {
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-workflow-functions-hooks-'));
const workflow = {
jobs: {
job: {
steps: [{ run: 'echo hi' }],
hooks: {
before_install_node_modules: [{ uses: './.eas/functions/setup' }],
},
},
},
};

await expect(
validateWorkflowLocalCompositeFunctionsAsync(workflow, projectRoot)
).rejects.toThrow(
/Local composite function "\.\/\.eas\/functions\/setup" was referenced by a step but no such composite function exists/
);
});

it('ignores non-object hooks and non-array hook values', async () => {
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-workflow-functions-hooks-'));
const workflow = {
jobs: {
garbageHooks: {
steps: [{ run: 'echo hi' }],
hooks: 'not an object',
},
garbageHookValue: {
steps: [{ run: 'echo hi' }],
hooks: {
before_install_node_modules: 'not an array',
after_install_node_modules: { uses: './.eas/functions/missing' },
},
},
},
};

await expect(
validateWorkflowLocalCompositeFunctionsAsync(workflow, projectRoot)
).resolves.toBeUndefined();
});

it('throws when a referenced local composite function is malformed', async () => {
const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-workflow-functions-test-'));
await makeProjectWithCompositeFunctionAsync(
Expand Down
13 changes: 12 additions & 1 deletion packages/eas-cli/src/commandUtils/workflow/compositeFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,16 @@ function stepsFromWorkflow(parsedYaml: any): any[] {
if (!jobs || typeof jobs !== 'object') {
return [];
}
return Object.values(jobs).flatMap((job: any) => (Array.isArray(job?.steps) ? job.steps : []));
return Object.values(jobs).flatMap((job: any) => [
...(Array.isArray(job?.steps) ? job.steps : []),
...hookStepsFromJob(job),
]);
}

function hookStepsFromJob(job: any): any[] {
const hooks = job?.hooks;
if (!hooks || typeof hooks !== 'object') {
return [];
}
return Object.values(hooks).flatMap((steps: any) => (Array.isArray(steps) ? steps : []));
}
Loading