Skip to content

[eas-build-job][build-tools][steps] Accept legacy command/path custom function shape in function.yml; rename references in the consumers - #4095

Open
sswrk wants to merge 9 commits into
mainfrom
szymonswierk/eng-25402-eas-build-job-legacy-function-shape
Open

[eas-build-job][build-tools][steps] Accept legacy command/path custom function shape in function.yml; rename references in the consumers#4095
sswrk wants to merge 9 commits into
mainfrom
szymonswierk/eng-25402-eas-build-job-legacy-function-shape

Conversation

@sswrk

@sswrk sswrk commented Jul 27, 2026

Copy link
Copy Markdown
Member

Why

We want to grandfather the legacy reusable functions from custom builds (https://docs.expo.dev/custom-builds/functions/) into the new reusable functions that can be used in workflows. This is to make the migration from custom builds to workflows easier.

Currently, we only support composite functions. The first step towards extending the support is modifying the eas-build-job Zod schema.

How

  • Added the legacyFunction module with Zod schemas to match the legacy custom builds functions.
    • Two separate schemas: LegacyCommandFunctionConfigZ (functions with command: present) and LegacyPathFunctionConfigZ (functions with path: present).
  • Extracted compositeFunction and created a new localFunction module with schema that is a union of composite function schema and legacy function schemas.
  • Renamed references of "composite local functions" schema in steps and build-tools that should reference "local functions".

Test Plan

Added unit tests.

@linear-code

linear-code Bot commented Jul 27, 2026

Copy link
Copy Markdown

ENG-25402

@sswrk sswrk added the no changelog PR that doesn't require a changelog entry label Jul 27, 2026
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from e893fae to 3b018d6 Compare July 28, 2026 09:04
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 3b018d6 to 891ef23 Compare July 28, 2026 12:58
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 891ef23 to 27f53d7 Compare July 28, 2026 13:11
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 27f53d7 to e8d69a0 Compare July 28, 2026 15:30
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from e8d69a0 to 702bea2 Compare July 29, 2026 09:01
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch 2 times, most recently from 961f32c to 9d5f159 Compare July 29, 2026 13:40
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 9d5f159 to 36a1945 Compare July 30, 2026 15:09
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🤖 AI code review

Decision: Ready for human review (with comments)

Overall PR risk: Medium. The change modifies an existing shared path — the composite/local function loading and expansion used by EAS workflows and build hooks — rather than being purely additive. Existing composite-function workflows keep working because CompositeFunctionConfigZ still accepts the same inputs, but the new legacy command/path function support is not actually wired into the one production loader, so the feature this PR aims to deliver is unreachable at runtime today. Blast radius is bounded to local-function parsing and expansion, and the change is a direct revert if needed, so this is a warning-level gap, not a release blocker.

🟡 Warning (1)

  • Local function loader still parses only the composite shapepackages/steps/src/utils/localCompositeFunctions.ts:146 (correctness) · id:ab7e4e76f73a
    Confidence: High — two reviewers independently trace the loader, the catalog type, and the new legacy check to the same gap.
    Impact if shipped: Medium — the new legacy command/path function support this PR adds cannot be reached from a disk-loaded function.yml file.
    Suggested remediation: Parse with LocalFunctionConfigZ in loadLocalCompositeFunctionConfigAsync and update its return type to Promise<LocalFunctionConfig> so legacy function.yml files can actually load.

    Evidence and reasoning

    loadLocalCompositeFunctionConfigAsync (used by createLocalCompositeFunctionLoader, the only loader wired into jobHooks.ts and StepsConfigParser) still calls CompositeFunctionConfigZ.safeParse(parsed). This PR adds LocalFunctionConfigZ in localFunction.ts, which unions the composite shape with the new legacy command/path shapes, and widens compositeFunctionCatalog to LocalFunctionCatalog everywhere (jobHooks.ts, StepsConfigParser.ts, hooks.ts, CompositeFunctionExpander.ts). But because the loader still validates with CompositeFunctionConfigZ (now .strict() with command/path/shell/supported_platforms all forced to z.never()), a real function.yml using the legacy command: or path: shape fails to parse with a raw "Invalid input: expected never" Zod error before it reaches the catalog.

    This also makes the new isLegacyFunctionConfig check in CompositeFunctionExpander.lookupCompositeFunction (packages/steps/src/CompositeFunctionExpander.ts:195) unreachable in the real loading path: the catalog it reads from can never contain a LegacyFunctionConfig, because the only production loader that populates it never produces one. A user who authors a legacy function.yml gets a confusing generic parse error instead of the intended handling. This gap was already raised on a previous revision of this PR and remains unaddressed.


This review is advisory — it never blocks a merge and never auto-approves.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.22642% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.36%. Comparing base (520c63f) to head (cec1583).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
packages/eas-build-job/src/localFunction.ts 95.24% 1 Missing ⚠️
packages/steps/src/CompositeFunctionExpander.ts 66.67% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4095      +/-   ##
==========================================
+ Coverage   63.28%   63.36%   +0.08%     
==========================================
  Files        1024     1027       +3     
  Lines       46699    46833     +134     
  Branches     9848     9872      +24     
==========================================
+ Hits        29547    29669     +122     
- Misses      15698    15709      +11     
- Partials     1454     1455       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from ef13e90 to ca56231 Compare August 5, 2026 10:19
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch 3 times, most recently from 975902e to 6faae3b Compare August 5, 2026 11:08
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 6faae3b to 709f6de Compare August 5, 2026 12:14
Base automatically changed from szymonswierk/eng-22387-eas-cli-validate-hook-composite-functions to main August 5, 2026 12:29
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from 709f6de to fb38166 Compare August 5, 2026 12:29
@sswrk sswrk changed the title [eas-build-job] Accept legacy command/path custom function shape in function.yml [eas-build-job][build-tools][steps] Accept legacy command/path custom function shape in function.yml; rename references in the consumers Aug 5, 2026
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch 2 times, most recently from abf928e to c4b9c7b Compare August 6, 2026 13:28
@sswrk
sswrk marked this pull request as ready for review August 6, 2026 14:39
@sswrk
sswrk requested review from hSATAC and sjchmiela August 6, 2026 14:39

@hSATAC hSATAC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a pass through this and left two inline questions. I don't have much context on legacy custom build functions, so I'm mostly calling out a couple of compatibility differences I noticed rather than making a call on the intended behavior.

Neither is meant to block from my side, and I'll leave the final approval to Stanley since he has more context on the intended compatibility scope.


const LegacyFunctionPlatformZ = z.enum(['darwin', 'linux']);

const LegacyFunctionBaseZ = z.object({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, and this may be intentional: should the legacy schema also accept the camelCase spellings?

From what I can tell, those are the native keys in the old Joi schema and snake_case is added as a rename alias, so supportedRuntimePlatforms, defaultValue, allowedValues, and allowedValueType work in custom builds today. Here they fail with the generic union error:

command: echo hi
supportedRuntimePlatforms: [darwin]

Maybe only grandfathering the documented snake_case shape is what we want. If so, could we add a rejection test to make that boundary explicit? Otherwise I think these branches should accept both.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good point, we should just keep full compatibility

return config.runs === undefined;
}

export type LocalFunctionCatalog = Record<string, LocalFunctionConfig>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor and non-blocking: do we want to keep a deprecated alias for CompositeFunctionCatalog? It has been exported from the published package since 21.3.0, so removing it would be a source break for existing importers.

/** @deprecated Use `LocalFunctionCatalog`. */
export type CompositeFunctionCatalog = LocalFunctionCatalog;

The widened return types of buildCompositeFunctionCatalogFromStepsAsync and buildLocalCompositeFunctionCatalogAsync look like another possible source break: the loader still only returns CompositeFunctionConfig, but downstream code accessing catalog[path].runs.steps may stop compiling. Would it make sense to keep those return types composite-only until the loader starts returning legacy functions?

If we'd rather make both changes now, should they go under breaking changes in the changelog so the version bump comes out right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored the old names via aliases to avoid breaking changes. My thinking was that we're fine to change this, thes packages are for internal stuff only, but right, this is public and can be used by some outside consumer.

@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from c4b9c7b to c72448e Compare August 12, 2026 15:17
@sswrk
sswrk force-pushed the szymonswierk/eng-25402-eas-build-job-legacy-function-shape branch from c47e9c1 to e704845 Compare August 12, 2026 17:30
@github-actions

Copy link
Copy Markdown

⏩ The changelog entry check has been skipped since the "no changelog" label is present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Commits pushed to PRs with this label be automatically reviewed. no changelog PR that doesn't require a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants