From 2604627883d221da62cfd1adf74e0928834a7218 Mon Sep 17 00:00:00 2001 From: Donald Merand Date: Wed, 12 Aug 2026 19:25:38 -0400 Subject: [PATCH 1/2] Reject invalid store execute variables Assisted-By: devx/02b9a955-e9da-4fba-97d1-f625c956a647 --- .changeset/fuzzy-store-variables.md | 5 +++ .../services/store/execute/request.test.ts | 41 +++++++++++++++++++ .../src/cli/services/store/execute/request.ts | 29 +++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 .changeset/fuzzy-store-variables.md diff --git a/.changeset/fuzzy-store-variables.md b/.changeset/fuzzy-store-variables.md new file mode 100644 index 00000000000..ee89cd880fc --- /dev/null +++ b/.changeset/fuzzy-store-variables.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': patch +--- + +Reject invalid variable values before store execute requests are sent. diff --git a/packages/store/src/cli/services/store/execute/request.test.ts b/packages/store/src/cli/services/store/execute/request.test.ts index e0bebbfc557..75798ad910d 100644 --- a/packages/store/src/cli/services/store/execute/request.test.ts +++ b/packages/store/src/cli/services/store/execute/request.test.ts @@ -110,6 +110,24 @@ describe('prepareStoreExecuteRequest', () => { expect(request.parsedOperation.operationDefinition.operation).toBe('mutation') }) + test.each(['null', '[]', '"value"', 'true', '42'])('throws when inline variables are %s', async (variables) => { + await expect( + prepareStoreExecuteRequest({ + query: 'query { shop { name } }', + variables, + }), + ).rejects.toThrow('expected a non-null JSON object') + }) + + test('throws when inline variables are empty', async () => { + await expect( + prepareStoreExecuteRequest({ + query: 'query { shop { name } }', + variables: ' ', + }), + ).rejects.toThrow('--variables') + }) + test('throws when variables contain invalid JSON', async () => { await expect( prepareStoreExecuteRequest({ @@ -119,6 +137,29 @@ describe('prepareStoreExecuteRequest', () => { ).rejects.toThrow('Invalid JSON') }) + test.each(['null', '[]', '"value"', 'true', '42'])('throws when file variables are %s', async (variables) => { + await inTemporaryDirectory(async (tmpDir) => { + const variableFile = joinPath(tmpDir, 'variables.json') + await writeFile(variableFile, variables) + + await expect( + prepareStoreExecuteRequest({ + query: 'query { shop { name } }', + variableFile, + }), + ).rejects.toThrow('expected a non-null JSON object') + }) + }) + + test('accepts empty and populated variable objects', async () => { + await expect( + prepareStoreExecuteRequest({ + query: 'query { shop { name } }', + variables: '{}', + }), + ).resolves.toMatchObject({parsedVariables: {}}) + }) + test('reads variables from a file', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given diff --git a/packages/store/src/cli/services/store/execute/request.ts b/packages/store/src/cli/services/store/execute/request.ts index 71289bdaba3..12a22b482da 100644 --- a/packages/store/src/cli/services/store/execute/request.ts +++ b/packages/store/src/cli/services/store/execute/request.ts @@ -47,14 +47,36 @@ async function readQuery(input: {query?: string; queryFile?: string}): Promise | ReturnType, +): {[key: string]: unknown} { + if (value === null || typeof value !== 'object' || Array.isArray(value)) { + throw new AbortError( + outputContent`Invalid JSON in ${source}: expected a non-null JSON object.`, + 'Please provide a JSON object for variables.', + ) + } + + return value as {[key: string]: unknown} +} + async function parseVariables( variables?: string, variableFile?: string, ): Promise<{[key: string]: unknown} | undefined> { - if (variables) { + if (variables !== undefined) { + if (!variables.trim()) { + throw new AbortError( + outputContent`The ${outputToken.yellow('--variables')} flag value is empty.`, + 'Please provide a non-null JSON object for variables.', + ) + } + try { - return JSON.parse(variables) + return validateVariables(JSON.parse(variables), outputToken.yellow('--variables')) } catch (error) { + if (error instanceof AbortError) throw error const errorMessage = error instanceof Error ? error.message : 'Unknown error' throw new AbortError( outputContent`Invalid JSON in ${outputToken.yellow('--variables')} flag: ${errorMessage}`, @@ -72,8 +94,9 @@ async function parseVariables( const fileContent = await readFile(variableFile, {encoding: 'utf8'}) try { - return JSON.parse(fileContent) + return validateVariables(JSON.parse(fileContent), outputToken.path(variableFile)) } catch (error) { + if (error instanceof AbortError) throw error const errorMessage = error instanceof Error ? error.message : 'Unknown error' throw new AbortError( outputContent`Invalid JSON in variable file ${outputToken.path(variableFile)}: ${errorMessage}`, From df54d2854ba137d2fd7223dc3cf2ac9172a435a5 Mon Sep 17 00:00:00 2001 From: Donald Merand Date: Wed, 12 Aug 2026 20:05:33 -0400 Subject: [PATCH 2/2] Fix store execute variable validation wording Assisted-By: devx/2f4bde52-9531-4910-bbc1-5ed7182a4a59 --- packages/store/src/cli/services/store/execute/request.test.ts | 2 +- packages/store/src/cli/services/store/execute/request.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/store/src/cli/services/store/execute/request.test.ts b/packages/store/src/cli/services/store/execute/request.test.ts index 75798ad910d..9fc0672e8ec 100644 --- a/packages/store/src/cli/services/store/execute/request.test.ts +++ b/packages/store/src/cli/services/store/execute/request.test.ts @@ -151,7 +151,7 @@ describe('prepareStoreExecuteRequest', () => { }) }) - test('accepts empty and populated variable objects', async () => { + test('accepts an empty variable object', async () => { await expect( prepareStoreExecuteRequest({ query: 'query { shop { name } }', diff --git a/packages/store/src/cli/services/store/execute/request.ts b/packages/store/src/cli/services/store/execute/request.ts index 12a22b482da..618c1fa370e 100644 --- a/packages/store/src/cli/services/store/execute/request.ts +++ b/packages/store/src/cli/services/store/execute/request.ts @@ -53,7 +53,7 @@ function validateVariables( ): {[key: string]: unknown} { if (value === null || typeof value !== 'object' || Array.isArray(value)) { throw new AbortError( - outputContent`Invalid JSON in ${source}: expected a non-null JSON object.`, + outputContent`Invalid variables in ${source}: expected a non-null JSON object.`, 'Please provide a JSON object for variables.', ) }