Skip to content

[eas-cli] Accept template expressions in workflow schema string formats - #4184

Open
giaBaoJS wants to merge 2 commits into
expo:mainfrom
giaBaoJS:fix/workflow-validate-template-formats
Open

[eas-cli] Accept template expressions in workflow schema string formats#4184
giaBaoJS wants to merge 2 commits into
expo:mainfrom
giaBaoJS:fix/workflow-validate-template-formats

Conversation

@giaBaoJS

Copy link
Copy Markdown

Why

Fixes #3404.

eas workflow:validate rejects a workflow that passes a template expression to a field the schema declares with a JSON Schema format. The reproduction from the issue:

jobs:
  notify:
    type: slack
    params:
      webhook_url: ${{ env.SLACK_WEBHOOK_URL }}
      message: Build finished

The schema served by /v2/workflows/schema declares webhook_url as { "type": "string", "format": "uri" }, and validateWorkflowStructure runs the raw parsed YAML through Ajv before any template expression is resolved. ${{ env.SLACK_WEBHOOK_URL }} is not a URI, so the format check fails.

Because params is an anyOf of two shapes, that one format failure makes neither variant match, the job then matches none of the 18 job types, and the user is shown 44 errors that all point somewhere other than the real problem:

The value at /jobs/notify/type must be equal to constant.
The value at /jobs/notify/params is missing the required field 'platform'.
The value at /jobs/notify/params has an unexpected property, webhook_url, which is not in the list of allowed properties (platform, profile, message, refresh_ad_hoc_provisioning_profile).
The value at /jobs/notify is missing the required field 'steps'.
... 40 more

The workflow itself is valid and runs fine — only the local pre-flight check rejects it.

I noticed this issue is assigned to @douglowder. This is offered as a ready-made option rather than an assumption that it is unowned — if there is a fix already in flight or a different approach you would prefer (for example, dropping format: uri server-side), please feel free to close this.

How

validation.ts already has precedent for this: buildProfileIsInterpolated skips the build-profile existence check when the profile name is interpolated, for exactly the same reason. This applies the same rule to string formats.

A new commandUtils/workflow/templateExpressions.ts exports:

  • containsTemplateExpression(value) — the shared ${{ … }} check. buildProfileIsInterpolated now delegates to it so there is a single definition.
  • allowTemplateExpressionsInStringFormats(ajv) — re-registers every string format on an Ajv instance so that a value containing a template expression satisfies the format, while every other value is still checked by the original format function.

validateWorkflowStructure calls it on its own validator. Two deliberate choices:

  • Opt-in, applied at the workflow call site. createValidator() in src/metadata/utils/ajv.ts is shared with store metadata validation (src/metadata/config/validate.ts), whose schema uses uri, date-time and email. That module is not touched at all by this PR, so metadata validation keeps its strict format checks.
  • Relaxing formats generically rather than by name. The workflow schema is fetched from the server at runtime, so the CLI cannot know which formats it will contain. Today uri is the only one, but a hardcoded list would silently stop covering any format the server adds later.

The relaxation stays narrow: a value without a template expression is still checked by the original format, so webhook_url: "not a url" is still rejected. ajv-formats registers formats in three different shapes (plain function, RegExp, and object with validate/compare), plus annotation-only formats registered as true — each is handled separately so that no format silently ends up checking nothing.

Test Plan

Two new suites, both offline — they feed a fixture schema through the EXPO_TESTING_WORKFLOW_SCHEMA_PATH escape hatch that fetchWorkflowSchemaAsync already supports.

__tests__/validation-test.ts exercises the full validateWorkflowFileAsync path against a trimmed copy of the real slack job schema:

  validateWorkflowFileAsync
    ✓ accepts a formatted value that is a template expression
    ✓ accepts a formatted value that is partially interpolated
    ✓ accepts a formatted value that is a valid literal
    ✓ still rejects a formatted value that is a malformed literal

__tests__/templateExpressions-test.ts covers the helper directly, with one case per ajv-formats shape so the relaxation cannot degenerate into accepting everything:

  containsTemplateExpression
    ✓ detects a template expression
    ✓ does not detect a template expression in a plain value
  allowTemplateExpressionsInStringFormats
    ✓ accepts a template expression for the `uri` format
    ✓ accepts a template expression for the `email` format
    ✓ accepts a template expression for the `date-time` format
    ✓ leaves formats that accept every value alone

Reverting only the allowTemplateExpressionsInStringFormats(ajv) call and keeping the tests turns the two template cases red with the original error, while the malformed-literal case stays green in both states:

    ✕ accepts a formatted value that is a template expression
    ✕ accepts a formatted value that is partially interpolated
    ✓ accepts a formatted value that is a valid literal
    ✓ still rejects a formatted value that is a malformed literal

    Rejected to value: [Error: The value at /jobs/notify/params/webhook_url must be a valid URI string but it was not.
    ...

I also ran the issue's workflow through validateWorkflowFileAsync against the live schema downloaded from https://api.expo.dev/v2/workflows/schema: 44 errors before, validates cleanly after.

cd packages/eas-cli && yarn jest: 283/285 suites, 2403 tests passing. The two failures (src/observe/__tests__/formatEvents.test.ts, src/observe/__tests__/formatCustomEvents.test.ts) are locale-dependent snapshot failures that also fail on a clean checkout of main on my machine, and are unrelated to this change. yarn jest src/metadata passes 15/15 suites and 187 tests, unchanged.

yarn typecheck, yarn lint and yarn fmt:check are clean.

@github-actions

Copy link
Copy Markdown

Subscribed to pull request

File Patterns Mentions
packages/eas-cli/** @douglowder

Generated by CodeMention

Warning: The preamble and epilogue options in commentConfiguration are deprecated. Use template instead.

`eas workflow:validate` checks the raw YAML against the workflow JSON schema
before template expressions are resolved, so a value like
`webhook_url: ${{ env.SLACK_WEBHOOK_URL }}` can never satisfy `format: uri`.
Relax string formats to accept template expressions for the workflow schema
only, leaving the store metadata schema strict.
@giaBaoJS
giaBaoJS force-pushed the fix/workflow-validate-template-formats branch from 6a71977 to bc0c90e Compare August 18, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eas workflow:validate fails on slack job type when webhook_url uses a template expression

1 participant