diff --git a/packages/app/src/cli/services/build/extension.test.ts b/packages/app/src/cli/services/build/extension.test.ts index adf92867b8..0dfb73eb9b 100644 --- a/packages/app/src/cli/services/build/extension.test.ts +++ b/packages/app/src/cli/services/build/extension.test.ts @@ -7,7 +7,8 @@ import {beforeEach, describe, expect, test, vi} from 'vitest' import {exec} from '@shopify/cli-kit/node/system' import lockfile from 'proper-lockfile' import {AbortError} from '@shopify/cli-kit/node/error' -import {fileExistsSync} from '@shopify/cli-kit/node/fs' +import {fileExistsSync, readFile} from '@shopify/cli-kit/node/fs' +import * as outputModule from '@shopify/cli-kit/node/output' vi.mock('@shopify/cli-kit/node/system') vi.mock('../function/build.js') @@ -416,4 +417,54 @@ describe('buildFunctionExtension', () => { expect(releaseLock).toHaveBeenCalled() expect(runWasmOpt).toHaveBeenCalled() }) + + describe('schema version mismatch warning', () => { + function mockSchemaFile(content: string) { + vi.mocked(readFile).mockResolvedValueOnce(content) + } + + function mockSchemaNotFound() { + const err = new Error('ENOENT') as NodeJS.ErrnoException + err.code = 'ENOENT' + vi.mocked(readFile).mockRejectedValueOnce(err) + } + + test('warns when schema version does not match toml version', async () => { + const warnSpy = vi.spyOn(outputModule, 'outputWarn').mockImplementation(() => {}) + mockSchemaFile('schema @apiVersion(version: "2025-01") {\n query: QueryRoot\n}') + + await buildFunctionExtension(extension, {stdout, stderr, signal, app, environment: 'production'}) + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('schema.graphql was generated for API version 2025-01 but your extension targets 2022-07'), + ) + }) + + test('does not warn when schema version matches toml version', async () => { + const warnSpy = vi.spyOn(outputModule, 'outputWarn').mockImplementation(() => {}) + mockSchemaFile('schema @apiVersion(version: "2022-07") {\n query: QueryRoot\n}') + + await buildFunctionExtension(extension, {stdout, stderr, signal, app, environment: 'production'}) + + expect(warnSpy).not.toHaveBeenCalled() + }) + + test('does not warn when schema has no apiVersion directive', async () => { + const warnSpy = vi.spyOn(outputModule, 'outputWarn').mockImplementation(() => {}) + mockSchemaFile('type Query { shop: Shop }') + + await buildFunctionExtension(extension, {stdout, stderr, signal, app, environment: 'production'}) + + expect(warnSpy).not.toHaveBeenCalled() + }) + + test('does not warn when schema file does not exist', async () => { + const warnSpy = vi.spyOn(outputModule, 'outputWarn').mockImplementation(() => {}) + mockSchemaNotFound() + + await buildFunctionExtension(extension, {stdout, stderr, signal, app, environment: 'production'}) + + expect(warnSpy).not.toHaveBeenCalled() + }) + }) }) diff --git a/packages/app/src/cli/services/build/extension.ts b/packages/app/src/cli/services/build/extension.ts index a8a4c665b8..b0f26be41b 100644 --- a/packages/app/src/cli/services/build/extension.ts +++ b/packages/app/src/cli/services/build/extension.ts @@ -9,7 +9,7 @@ import {AbortSignal} from '@shopify/cli-kit/node/abort' import {AbortError, AbortSilentError} from '@shopify/cli-kit/node/error' import lockfile from 'proper-lockfile' import {dirname, joinPath} from '@shopify/cli-kit/node/path' -import {outputDebug} from '@shopify/cli-kit/node/output' +import {outputDebug, outputWarn} from '@shopify/cli-kit/node/output' import {readFile, touchFile, writeFile, fileExistsSync} from '@shopify/cli-kit/node/fs' import {Writable} from 'stream' @@ -135,6 +135,8 @@ export async function buildFunctionExtension( extension: ExtensionInstance, options: BuildFunctionExtensionOptions, ): Promise { + await warnIfSchemaMismatch(extension as ExtensionInstance) + const lockfilePath = joinPath(extension.directory, '.build-lock') let releaseLock try { @@ -232,6 +234,30 @@ async function buildOtherFunction(extension: ExtensionInstance, options: BuildFu return runCommand(extension.buildCommand, extension, options) } +const API_VERSION_DIRECTIVE_RE = /@apiVersion\(version:\s*"([^"]+)"\)/ + +async function warnIfSchemaMismatch(extension: ExtensionInstance) { + const schemaPath = joinPath(extension.directory, 'schema.graphql') + let content: string + try { + content = await readFile(schemaPath) + } catch (error) { + if (error instanceof Error && 'code' in error) return + throw error + } + + const match = API_VERSION_DIRECTIVE_RE.exec(content) + if (!match) return + + const schemaVersion = match[1]! + const tomlVersion = extension.configuration.api_version + if (schemaVersion !== tomlVersion) { + outputWarn( + `schema.graphql was generated for API version ${schemaVersion} but your extension targets ${tomlVersion}. Run \`shopify app function schema\` to update.`, + ) + } +} + async function runCommand(buildCommand: string, extension: ExtensionInstance, options: BuildFunctionExtensionOptions) { const buildCommandComponents = buildCommand.split(' ') options.stdout.write(`Building function ${extension.localIdentifier}...`)