Skip to content

prepend api_version to schema#7480

Merged
lopert merged 1 commit into
mainfrom
lopert.prepend-api-version
May 14, 2026
Merged

prepend api_version to schema#7480
lopert merged 1 commit into
mainfrom
lopert.prepend-api-version

Conversation

@lopert
Copy link
Copy Markdown
Contributor

@lopert lopert commented May 6, 2026

WHY are these changes introduced?

Part of https://github.com/shop/issues-shopifyvm/issues/945

When a developer bumps api_version in a function's shopify.extension.toml, the on-disk schema.graphql becomes stale. The next shopify app function build then fails with cryptic graphql-code-generator errors like:

Cannot query field "parentRelationship" on type "CartLine"

…with no indication that the underlying problem is a stale schema that just needs to be re-fetched. Today the build path is fully local — it never consults api_version and schema.graphql carries no version metadata, so staleness is undetectable.

The error surfaced gives them no actionable hint pointing at shopify app function schema.

WHAT is this pull request doing?

Stamps the api_version into schema.graphql when shopify app function schema writes it, then validates that marker against the extension's TOML at the start of every shopify app function build. When they disagree, the build aborts up-front with an actionable message instead of letting codegen fail mysteriously.

New file

  • packages/app/src/cli/services/function/schema-version.ts — self-contained helpers:
    • prependSchemaVersionHeader(definition, version) — adds a # api_version: <ver> marker line to the top of the SDL.
    • readSchemaApiVersion(path) — scans the leading comment block for the marker; returns undefined for missing files or legacy schemas (no marker).
    • validateSchemaApiVersion({directory, localIdentifier, apiVersion}) — throws an AbortError with remediation when the on-disk marker disagrees with the configured api_version.

Modified files

  • services/generate-schema.tsshopify app function schema now writes the marker as the first line of the SDL (both file and stdout output).
  • services/build/extension.tsbuildFunctionExtension invokes validateSchemaApiVersion once, up-front, before any function build work (typegen, esbuild, javy, wasm-opt, trampoline).

Behavior

  • Mismatched marker → fails fast with:

    The schema.graphql file for <extension> was generated for api_version 2025-07 but your function is now on api_version 2025-10. Run shopify app function schema to refresh it.

  • Missing schema file → no-op (out of scope for this change; codegen handles its own missing-file error).
  • Legacy schema with no marker → no-op (don't break existing setups; the marker self-heals on the next shopify app function schema).

The schema header is a single line:

# api_version: 2025-10

<SDL>

How to test your changes?

  • Checkout this branch
  • In a Shopify app with a function (e.g. discount function), set api_version = "2025-07" in the function's shopify.extension.toml.
  • Run the schema command locally, and confirm schema.graphql now starts with # api_version: 2025-07.
pnpm shopify app function schema --path /Users/lopert/code/functions/schema-sync
  • Bump api_version to "2025-10" in the TOML.
  • Run the build command locally, it should abort immediately with a message naming both versions and pointing at shopify app function schema.
pnpm shopify app function build --path /Users/lopert/code/functions/schema-sync
  • Run the schema command again to refresh, then the build command — it should now succeed.
  • Sanity check the legacy path: delete the marker line from schema.graphql (simulate an older fetched file). The build command should behave exactly as it did before this change (no false-positive abort).

Post-release steps

N/A.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label May 6, 2026
@lopert lopert marked this pull request as ready for review May 6, 2026 20:52
@lopert lopert requested a review from a team as a code owner May 6, 2026 20:52
@lopert lopert requested review from adampetro and davejcameron May 7, 2026 12:00
Comment thread packages/app/src/cli/services/function/schema-version.test.ts Outdated
Comment thread packages/app/src/cli/services/function/schema-version.test.ts Outdated
Comment thread packages/app/src/cli/services/function/schema-version.test.ts Outdated
Comment thread packages/app/src/cli/services/function/schema-version.ts Outdated
Comment thread packages/app/src/cli/services/function/schema-version.ts
Comment thread packages/app/src/cli/services/function/schema-version.ts Outdated
Comment thread packages/app/src/cli/services/build/extension.test.ts Outdated
Comment thread packages/app/src/cli/services/build/extension.ts Outdated
Comment thread packages/app/src/cli/services/function/schema-version.ts
const {api_version: version, type, targeting} = extension.configuration
const usingTargets = Boolean(targeting?.length)
const definition = await (usingTargets
const fetchedDefinition = await (usingTargets
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 dont understand the difference between definition and fetched_definition, what if we keep this definition and call the other definition_with_version or something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

definition is essentially what was here before, the contents we're about to write to the local schema.

I introduced fetchedDefinition to name the intermediate value: the raw schema we just grabbed from the server, before the prepending step.

Reading top-down: fetch -> add version header -> write.
Whereas before it was just fetch -> write.
Open to renaming if it still reads confusingly, but it felt clearer to me separated.

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.

Yeah I guess fetched_definition doesn't immediately make sense to me when I read the code but its okay, its just a variable name, I won't let it block you from shipping the PR.

@lopert lopert force-pushed the lopert.prepend-api-version branch 4 times, most recently from d672631 to 5f79c3a Compare May 12, 2026 16:12
@lopert lopert requested review from adampetro and saga-dasgupta May 12, 2026 17:27
Comment thread packages/app/src/cli/services/function/schema-version.ts
Copy link
Copy Markdown
Contributor

@saga-dasgupta saga-dasgupta left a comment

Choose a reason for hiding this comment

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

Was able to tophat it works as expected.

@gonzaloriestra
Copy link
Copy Markdown
Contributor

/snapit

@github-actions
Copy link
Copy Markdown
Contributor

🫰✨ Thanks @gonzaloriestra! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20260513114658

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Comment on lines +72 to +76
outputContent`The ${outputToken.cyan(
'schema.graphql',
)} file for ${outputToken.yellow(localIdentifier)} was generated for api_version ${outputToken.yellow(
versionFromSchema,
)} but your function is now on api_version ${outputToken.yellow(apiVersion)}.`,
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.

Maybe it's something we can ask to UX, but I think it's better to just use outputToken.genericShellCommand instead of custom colors for consistency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmmmm, I went back and forth on this.
A couple of reasons I'd lean toward keeping raw color tokens here rather than genericShellCommand:

  • genericShellCommand renders as bright magenta in backticks and is used across the codebase exclusively for actual commands (including the shopify app function schema token on the very next line).
  • Raw colors are the prevailing pattern for highlighting tokens — including inside services/function/ (build.ts uses yellow/green in an AbortError, info.ts uses cyan for a function target).

That said, your comment made me notice the message had three concepts but only two colors. localIdentifier was sharing yellow with the two version strings. I've tweaked it so the two "named things" (filename + function name) are both cyan, and yellow is reserved for the mismatched versions, which I think improves the readability.

@lopert lopert force-pushed the lopert.prepend-api-version branch from 5f79c3a to f80f6ea Compare May 14, 2026 14:13
@lopert lopert added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit 95e9788 May 14, 2026
26 of 28 checks passed
@lopert lopert deleted the lopert.prepend-api-version branch May 14, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants