Skip to content

Commit c990440

Browse files
Merge pull request #6815 from Shopify/02-03-add_failsafe_and_improve_loging_on_unexepcete_select_app_error
Add failsafe and improve logging on unexpected select app error
2 parents 95758b1 + c41f709 commit c990440

File tree

3 files changed

+86
-18
lines changed

3 files changed

+86
-18
lines changed

packages/app/src/cli/prompts/dev.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,16 @@ export async function selectAppPrompt(
6464
}
6565
},
6666
})
67-
return currentAppChoices.find((app) => app.apiKey === apiKey)!
67+
68+
const appChoice = currentAppChoices.find((app) => app.apiKey === apiKey)!
69+
70+
if (!appChoice) {
71+
throw new Error(
72+
`Unable to select an app: the selection prompt was interrupted multiple times./n
73+
Api key ${apiKey} was selected but not found in ${currentAppChoices.map((app) => app.apiKey).join(', ')}`,
74+
)
75+
}
76+
return appChoice
6877
}
6978

7079
interface SelectStorePromptOptions {

packages/app/src/cli/services/dev/select-app.ts

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ import {getCachedCommandInfo, setCachedCommandTomlPreference} from '../local-sto
55
import {CreateAppOptions, DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
66
import {AppConfigurationFileName} from '../../models/app/loader.js'
77
import {BugError} from '@shopify/cli-kit/node/error'
8+
import {outputInfo, outputDebug} from '@shopify/cli-kit/node/output'
9+
10+
const MAX_PROMPT_RETRIES = 2
11+
12+
const TRY_MESSAGE = [
13+
'This may happen if:',
14+
' • Running in an unstable environment (container restart, resource limits)',
15+
' • Network interruption during app fetching',
16+
'',
17+
'Try running the command again. If the issue persists:',
18+
' • Check system resources and stability',
19+
' • Try running outside of containers/WSL if applicable',
20+
' • Report this issue with the error details and a verbose log',
21+
]
22+
.filter(Boolean)
23+
.join('\n')
824

925
/**
1026
* Select an app from env, list or create a new one:
@@ -30,25 +46,61 @@ export async function selectOrCreateApp(
3046
const name = await appNamePrompt(options.name)
3147
return developerPlatformClient.createApp(org, {...options, name})
3248
} else {
33-
const app = await selectAppPrompt(searchForAppsByNameFactory(developerPlatformClient, org.id), apps, hasMorePages, {
34-
directory: options.directory,
35-
})
49+
// Capture app selection context
50+
const cachedData = getCachedCommandInfo()
51+
const tomls = (cachedData?.tomls as {[key: string]: AppConfigurationFileName}) ?? {}
52+
53+
for (let attempt = 0; attempt < MAX_PROMPT_RETRIES; attempt++) {
54+
try {
55+
// eslint-disable-next-line no-await-in-loop
56+
const app = await selectAppPrompt(
57+
searchForAppsByNameFactory(developerPlatformClient, org.id),
58+
apps,
59+
hasMorePages,
60+
{directory: options.directory},
61+
)
62+
63+
const selectedToml = tomls[app.apiKey]
64+
if (selectedToml) setCachedCommandTomlPreference(selectedToml)
3665

37-
const data = getCachedCommandInfo()
38-
const tomls = (data?.tomls as {[key: string]: AppConfigurationFileName}) ?? {}
39-
const selectedToml = tomls[app.apiKey]
66+
// eslint-disable-next-line no-await-in-loop
67+
const fullSelectedApp = await developerPlatformClient.appFromIdentifiers(app.apiKey)
4068

41-
if (selectedToml) setCachedCommandTomlPreference(selectedToml)
69+
if (!fullSelectedApp) {
70+
throw new BugError(
71+
`Unable to fetch app ${app.apiKey} from Shopify`,
72+
'Try running `shopify app config link` to connect to an app you have access to.',
73+
)
74+
}
4275

43-
const fullSelectedApp = await developerPlatformClient.appFromIdentifiers(app.apiKey)
76+
return fullSelectedApp
77+
} catch (error) {
78+
// Don't retry BugError - those indicate actual bugs, not transient issues
79+
if (error instanceof BugError) {
80+
throw error
81+
}
4482

45-
if (!fullSelectedApp) {
46-
// This is unlikely, and a bug. But we still want a nice user facing message plus appropriate context logged.
47-
throw new BugError(
48-
`Unable to fetch app ${app.apiKey} from Shopify`,
49-
'Try running `shopify app config link` to connect to an app you have access to.',
50-
)
83+
const errorObj = error as Error
84+
85+
// Log each attempt for observability
86+
outputDebug(`App selection attempt ${attempt + 1}/${MAX_PROMPT_RETRIES} failed: ${errorObj.message}`)
87+
88+
// If we have retries left, inform user and retry
89+
if (attempt < MAX_PROMPT_RETRIES - 1) {
90+
outputInfo('App selection failed. Retrying...')
91+
} else {
92+
throw new BugError(errorObj.message, TRY_MESSAGE)
93+
}
94+
}
5195
}
52-
return fullSelectedApp
96+
97+
// User-facing error message with key diagnostic info
98+
const errorMessage = [
99+
'Unable to select an app: the selection prompt was interrupted multiple times.',
100+
'',
101+
`Available apps: ${apps.length}`,
102+
].join('\n')
103+
104+
throw new BugError(errorMessage, TRY_MESSAGE)
53105
}
54106
}

packages/cli-kit/src/public/node/ui.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ export async function renderAutocompletePrompt<T>(
426426
}
427427

428428
return runWithTimer('cmd_all_timing_prompts_ms')(async () => {
429-
let selectedValue: T
429+
let selectedValue: T | undefined
430430
await render(
431431
<AutocompletePrompt
432432
{...newProps}
@@ -439,7 +439,14 @@ export async function renderAutocompletePrompt<T>(
439439
exitOnCtrlC: false,
440440
},
441441
)
442-
return selectedValue!
442+
443+
if (selectedValue === undefined) {
444+
throw new Error(
445+
'Prompt was interrupted before a selection was made. This can happen if the process received a signal, was terminated, or the prompt was aborted.',
446+
)
447+
}
448+
449+
return selectedValue
443450
})
444451
}
445452

0 commit comments

Comments
 (0)