diff --git a/.changeset/handle-app-management-app-not-found.md b/.changeset/handle-app-management-app-not-found.md new file mode 100644 index 00000000000..d46d776591b --- /dev/null +++ b/.changeset/handle-app-management-app-not-found.md @@ -0,0 +1,5 @@ +--- +'@shopify/app': patch +--- + +Display a friendly error message instead of a stack trace when running `shopify app config link --client-id=` against a Dev Dashboard app and the client ID does not exist. diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts index a9c636c4b0f..6078cd16f45 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.test.ts @@ -1388,6 +1388,64 @@ describe('AppManagementClient', () => { expect(client.bundleFormat).toBe('br') }) }) + + describe('appFromIdentifiers', () => { + test('returns undefined when the app is not found', async () => { + // Given + const client = AppManagementClient.getInstance() + client.token = () => Promise.resolve('token') + vi.mocked(appManagementRequestDoc).mockResolvedValueOnce({app: null}) + + // When + const got = await client.appFromIdentifiers('non-existent-api-key') + + // Then + expect(got).toBeUndefined() + }) + + test('returns the app when the app is found', async () => { + // Given + const client = AppManagementClient.getInstance() + client.token = () => Promise.resolve('token') + const apiKey = 'existing-api-key' + const mockedResponse = { + app: { + id: 'gid://shopify/App/1', + key: apiKey, + organizationId: 'gid://shopify/Organization/2', + activeRoot: { + grantedShopifyApprovalScopes: ['read_products'], + clientCredentials: {secrets: [{key: 'secret-1'}]}, + }, + activeRelease: { + id: 'gid://shopify/Release/1', + version: { + name: 'My App', + appModules: [], + }, + }, + }, + } + vi.mocked(appManagementRequestDoc).mockResolvedValueOnce(mockedResponse) + + // When + const got = await client.appFromIdentifiers(apiKey) + + // Then + expect(got).toEqual({ + id: 'gid://shopify/App/1', + title: 'My App', + apiKey, + apiSecretKeys: [{secret: 'secret-1'}], + organizationId: '2', + grantedScopes: ['read_products'], + applicationUrl: undefined, + embedded: undefined, + flags: [], + developerPlatformClient: client, + }) + }) + }) }) describe('ensureUserAccessToStore', () => { diff --git a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts index 89e4cdc7a9c..2fe18713d03 100644 --- a/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts +++ b/packages/app/src/cli/utilities/developer-platform-client/app-management-client.ts @@ -356,6 +356,7 @@ export class AppManagementClient implements DeveloperPlatformClient { async appFromIdentifiers(apiKey: string): Promise { const {app} = await this.activeAppVersionRawResult(apiKey) + if (!app) return undefined const {name, appModules} = app.activeRelease.version const appHomeModule = appModules.find((mod) => mod.specification.externalIdentifier === 'app_home') const apiSecretKeys = app.activeRoot.clientCredentials.secrets.map((secret) => ({secret: secret.key}))