From bcac21c77ea005670ddd0c466ce91e7162d77352 Mon Sep 17 00:00:00 2001 From: freddie Date: Fri, 19 Dec 2025 11:06:20 +0000 Subject: [PATCH 1/3] feat: include request id in errors if present --- .../theme/src/cli/utilities/theme-command.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/theme/src/cli/utilities/theme-command.ts b/packages/theme/src/cli/utilities/theme-command.ts index b03a3fa515..aebddaf6fe 100644 --- a/packages/theme/src/cli/utilities/theme-command.ts +++ b/packages/theme/src/cli/utilities/theme-command.ts @@ -16,7 +16,7 @@ import { import {AbortController} from '@shopify/cli-kit/node/abort' import {AbortError} from '@shopify/cli-kit/node/error' import {recordEvent, compileData} from '@shopify/cli-kit/node/analytics' -import {addPublicMetadata, addSensitiveMetadata} from '@shopify/cli-kit/node/metadata' +import {addPublicMetadata, addSensitiveMetadata, getAllPublicMetadata} from '@shopify/cli-kit/node/metadata' import {cwd, joinPath, resolvePath} from '@shopify/cli-kit/node/path' import {fileExistsSync} from '@shopify/cli-kit/node/fs' import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn' @@ -60,6 +60,21 @@ export default abstract class ThemeCommand extends Command { _context?: {stdout?: Writable; stderr?: Writable}, ): Promise {} + async catch(error: Error & {skipOclifErrorHandling: boolean}): Promise { + const metadata = getAllPublicMetadata() + const requestId = metadata.cmd_all_last_graphql_request_id + + if ( + requestId && + /** Request id may have alreayd been added by `packages/cli-kit/src/private/node/api/graphql.ts` */ + !error.message.includes('Request ID:') + ) { + error.message += `\n\nRequest ID: ${requestId}` + } + + return super.catch(error) + } + async run< TFlags extends FlagOutput & {path?: string; verbose?: boolean}, TGlobalFlags extends FlagOutput, From 3a69d3f63ef0ca2676219eeb78d891dbbbb8fd00 Mon Sep 17 00:00:00 2001 From: freddie Date: Fri, 19 Dec 2025 11:09:43 +0000 Subject: [PATCH 2/3] test: add tests for catch --- .../src/cli/utilities/theme-command.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/packages/theme/src/cli/utilities/theme-command.test.ts b/packages/theme/src/cli/utilities/theme-command.test.ts index c4492d8d8e..79d78a47db 100644 --- a/packages/theme/src/cli/utilities/theme-command.test.ts +++ b/packages/theme/src/cli/utilities/theme-command.test.ts @@ -8,6 +8,7 @@ import {fileExistsSync} from '@shopify/cli-kit/node/fs' import {AbortError} from '@shopify/cli-kit/node/error' import {resolvePath} from '@shopify/cli-kit/node/path' import {renderConcurrent, renderConfirmationPrompt, renderError, renderWarning} from '@shopify/cli-kit/node/ui' +import {getAllPublicMetadata} from '@shopify/cli-kit/node/metadata' import type {Writable} from 'stream' vi.mock('@shopify/cli-kit/node/session') @@ -15,6 +16,7 @@ vi.mock('@shopify/cli-kit/node/environments') vi.mock('@shopify/cli-kit/node/ui') vi.mock('./theme-store.js') vi.mock('@shopify/cli-kit/node/fs') +vi.mock('@shopify/cli-kit/node/metadata') const CommandConfig = new Config({root: __dirname}) @@ -728,4 +730,67 @@ describe('ThemeCommand', () => { expect(ensureAuthenticatedThemes).not.toHaveBeenCalled() }) }) + + describe('catch', () => { + test('appends request ID to error message when available', async () => { + // Given + vi.mocked(getAllPublicMetadata).mockReturnValue({ + cmd_all_last_graphql_request_id: 'test-request-id-12345', + }) + + await CommandConfig.load() + const command = new TestThemeCommand([], CommandConfig) + const error = new Error('Something went wrong') as Error & {skipOclifErrorHandling: boolean} + error.skipOclifErrorHandling = false + + // When + await command.catch(error).catch(() => { + // Expected to throw, we just want to verify the error was modified + }) + + // Then + expect(error.message).toContain('Something went wrong') + expect(error.message).toContain('Request ID: test-request-id-12345') + }) + + test('does not append request ID when not available', async () => { + // Given + vi.mocked(getAllPublicMetadata).mockReturnValue({}) + + await CommandConfig.load() + const command = new TestThemeCommand([], CommandConfig) + const error = new Error('Something went wrong') as Error & {skipOclifErrorHandling: boolean} + error.skipOclifErrorHandling = false + + // When + await command.catch(error).catch(() => { + // Expected to throw + }) + + // Then + expect(error.message).toBe('Something went wrong') + expect(error.message).not.toContain('Request ID:') + }) + + test('does not duplicate request ID if already present in error message', async () => { + // Given + vi.mocked(getAllPublicMetadata).mockReturnValue({ + cmd_all_last_graphql_request_id: 'test-request-id-12345', + }) + + await CommandConfig.load() + const command = new TestThemeCommand([], CommandConfig) + const error = new Error('API error\n\nRequest ID: existing-id') as Error & {skipOclifErrorHandling: boolean} + error.skipOclifErrorHandling = false + + // When + await command.catch(error).catch(() => { + // Expected to throw + }) + + // Then + expect(error.message).toBe('API error\n\nRequest ID: existing-id') + expect(error.message.match(/Request ID:/g)).toHaveLength(1) + }) + }) }) From ed373ec7185bbc822983dfd4c41bda32207d80fb Mon Sep 17 00:00:00 2001 From: freddie Date: Fri, 19 Dec 2025 11:23:50 +0000 Subject: [PATCH 3/3] chore: changeset --- .changeset/dark-mirrors-appear.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dark-mirrors-appear.md diff --git a/.changeset/dark-mirrors-appear.md b/.changeset/dark-mirrors-appear.md new file mode 100644 index 0000000000..8d7f0b2edc --- /dev/null +++ b/.changeset/dark-mirrors-appear.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': patch +--- + +Command errors in the console will now include the GraphQL Request ID when present