From df96a960d26cf1c1d116ee2e27a80d017fdceba0 Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Thu, 9 Apr 2026 15:55:25 -0400 Subject: [PATCH 1/7] Fix --reset flag regression: skip spurious TOML selection prompt before link flow When running `shopify app dev --reset`, `getAppConfigurationContext()` was called unconditionally before checking `forceRelink`. If the cached config file didn't exist on disk, this triggered `selectConfigFile()`, prompting the user to pick a TOML file that would be immediately discarded by `link()`. Regression introduced by #6612 ("Remove legacy app schema", commit 1f8dccea) which merged the forceRelink and !isLinked branches back together, undoing the prior fix from #5676. Fix: separate the two paths so forceRelink skips `getAppConfigurationContext()` entirely and calls `link()` directly. Fixes shop/issues-develop#22531 --- .changeset/fix-reset-spurious-toml-prompt.md | 9 +++++ .../app/src/cli/services/app-context.test.ts | 38 +++++++++++++++++++ packages/app/src/cli/services/app-context.ts | 31 ++++++++++----- 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 .changeset/fix-reset-spurious-toml-prompt.md diff --git a/.changeset/fix-reset-spurious-toml-prompt.md b/.changeset/fix-reset-spurious-toml-prompt.md new file mode 100644 index 00000000000..80e97f36405 --- /dev/null +++ b/.changeset/fix-reset-spurious-toml-prompt.md @@ -0,0 +1,9 @@ +--- +'@shopify/app': patch +--- + +Fix `--reset` flag regression: skip spurious TOML file selection prompt before the link flow + +When running `shopify app dev --reset`, `getAppConfigurationState()` was called unconditionally before checking `forceRelink`. If the cached config file didn't exist on disk, this would prompt the user to select a TOML file — only to immediately discard the result when `link()` runs. This was a regression from #6612 which merged the `forceRelink` and `!isLinked` branches back together, undoing the fix from #5676. + +The fix separates the two paths again: when `forceRelink` is true, we call `link()` directly without loading config state first. diff --git a/packages/app/src/cli/services/app-context.test.ts b/packages/app/src/cli/services/app-context.test.ts index 460ab4095c8..95fe5c3ea0b 100644 --- a/packages/app/src/cli/services/app-context.test.ts +++ b/packages/app/src/cli/services/app-context.test.ts @@ -176,6 +176,44 @@ client_id="test-api-key"` }) }) + test('forceRelink skips config selection before link to avoid spurious TOML prompt', async () => { + await inTemporaryDirectory(async (tmp) => { + // Given — no config file on disk, so getAppConfigurationContext would prompt for TOML selection + // if called before link. We verify link is called first (without a prior config load). + const content = ` +name = "test-app" +client_id="test-api-key"` + await writeAppConfig(tmp, content) + + const getAppConfigSpy = vi.spyOn(loader, 'getAppConfigurationContext') + + vi.mocked(link).mockResolvedValue({ + remoteApp: mockRemoteApp, + configFileName: 'shopify.app.toml', + configuration: { + client_id: 'test-api-key', + name: 'test-app', + path: normalizePath(joinPath(tmp, 'shopify.app.toml')), + } as any, + }) + + // When + await linkedAppContext({ + directory: tmp, + forceRelink: true, + userProvidedConfigName: undefined, + clientId: undefined, + }) + + // Then — getAppConfigurationContext should only be called AFTER link, not before + const linkCallOrder = vi.mocked(link).mock.invocationCallOrder[0]! + const configCallOrders = getAppConfigSpy.mock.invocationCallOrder + expect(configCallOrders.every((order) => order > linkCallOrder)).toBe(true) + + getAppConfigSpy.mockRestore() + }) + }) + test('logs metadata', async () => { await inTemporaryDirectory(async (tmp) => { // Given diff --git a/packages/app/src/cli/services/app-context.ts b/packages/app/src/cli/services/app-context.ts index 8ba969e20f2..8c980f1f309 100644 --- a/packages/app/src/cli/services/app-context.ts +++ b/packages/app/src/cli/services/app-context.ts @@ -82,21 +82,34 @@ export async function linkedAppContext({ userProvidedConfigName, unsafeTolerateErrors = false, }: LoadedAppContextOptions): Promise { - let {project, activeConfig} = await getAppConfigurationContext(directory, userProvidedConfigName) + let project: Project + let activeConfig: ActiveConfig let remoteApp: OrganizationApp | undefined - if (activeConfig.file.errors.length > 0) { - throw new AbortError(activeConfig.file.errors.map((err) => err.message).join('\n')) - } - - if (!activeConfig.isLinked || forceRelink) { - const configName = forceRelink ? undefined : basename(activeConfig.file.path) - const result = await link({directory, apiKey: clientId, configName}) + if (forceRelink) { + // Skip getAppConfigurationContext() when force-relinking — it may prompt the + // user to select a TOML file that will be immediately discarded by link(). + const result = await link({directory, apiKey: clientId}) remoteApp = result.remoteApp - // Re-load project and re-select active config since link may have written new config const reloaded = await getAppConfigurationContext(directory, result.configFileName) project = reloaded.project activeConfig = reloaded.activeConfig + } else { + const loaded = await getAppConfigurationContext(directory, userProvidedConfigName) + project = loaded.project + activeConfig = loaded.activeConfig + + if (activeConfig.file.errors.length > 0) { + throw new AbortError(activeConfig.file.errors.map((err) => err.message).join('\n')) + } + + if (!activeConfig.isLinked) { + const result = await link({directory, apiKey: clientId, configName: basename(activeConfig.file.path)}) + remoteApp = result.remoteApp + const reloaded = await getAppConfigurationContext(directory, result.configFileName) + project = reloaded.project + activeConfig = reloaded.activeConfig + } } // Determine the effective client ID From 9ea9def95f86a6e451aab42bb577606d05f37d45 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Fri, 10 Apr 2026 15:14:05 +0200 Subject: [PATCH 2/7] Avoid asking for a config when running link --- .changeset/fix-reset-spurious-toml-prompt.md | 6 +----- packages/app/src/cli/models/app/loader.ts | 17 ++++++++++++++--- .../app/src/cli/models/project/active-config.ts | 12 +++++++++--- .../app/src/cli/services/app/config/link.ts | 2 ++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.changeset/fix-reset-spurious-toml-prompt.md b/.changeset/fix-reset-spurious-toml-prompt.md index 80e97f36405..ac4da26bc9c 100644 --- a/.changeset/fix-reset-spurious-toml-prompt.md +++ b/.changeset/fix-reset-spurious-toml-prompt.md @@ -2,8 +2,4 @@ '@shopify/app': patch --- -Fix `--reset` flag regression: skip spurious TOML file selection prompt before the link flow - -When running `shopify app dev --reset`, `getAppConfigurationState()` was called unconditionally before checking `forceRelink`. If the cached config file didn't exist on disk, this would prompt the user to select a TOML file — only to immediately discard the result when `link()` runs. This was a regression from #6612 which merged the `forceRelink` and `!isLinked` branches back together, undoing the fix from #5676. - -The fix separates the two paths again: when `forceRelink` is true, we call `link()` directly without loading config state first. +Avoid config prompt when using --reset flag diff --git a/packages/app/src/cli/models/app/loader.ts b/packages/app/src/cli/models/app/loader.ts index 2ff054bc0bf..285754067ca 100644 --- a/packages/app/src/cli/models/app/loader.ts +++ b/packages/app/src/cli/models/app/loader.ts @@ -253,8 +253,13 @@ export async function loadApp> { - const {project, activeConfig} = await getAppConfigurationContext(options.directory, options.userProvidedConfigName) + const {project, activeConfig} = await getAppConfigurationContext( + options.directory, + options.userProvidedConfigName, + options.skipPrompts ? {skipPrompts: true} : undefined, + ) return loadAppFromContext({ project, activeConfig, @@ -395,11 +400,16 @@ export async function loadOpaqueApp(options: { configName?: string specifications: ExtensionSpecification[] remoteFlags?: Flag[] + skipPrompts?: boolean }): Promise { // Try to load the app normally first — the loader always collects validation errors, // so only structural failures (TOML parse, missing files) will throw. try { - const {project, activeConfig} = await getAppConfigurationContext(options.directory, options.configName) + const {project, activeConfig} = await getAppConfigurationContext( + options.directory, + options.configName, + options.skipPrompts ? {skipPrompts: true} : undefined, + ) const app = await loadAppFromContext({ project, activeConfig, @@ -913,9 +923,10 @@ type ConfigurationLoaderResult< export async function getAppConfigurationContext( workingDirectory: string, userProvidedConfigName?: string, + options?: {skipPrompts?: boolean}, ): Promise<{project: Project; activeConfig: ActiveConfig}> { const project = await Project.load(workingDirectory) - const activeConfig = await selectActiveConfig(project, userProvidedConfigName) + const activeConfig = await selectActiveConfig(project, userProvidedConfigName, options) return {project, activeConfig} } diff --git a/packages/app/src/cli/models/project/active-config.ts b/packages/app/src/cli/models/project/active-config.ts index dbf8dd86eeb..5f8ac1eccc2 100644 --- a/packages/app/src/cli/models/project/active-config.ts +++ b/packages/app/src/cli/models/project/active-config.ts @@ -53,15 +53,20 @@ export interface ActiveConfig { * config's client_id. * @public */ -export async function selectActiveConfig(project: Project, userProvidedConfigName?: string): Promise { +export async function selectActiveConfig( + project: Project, + userProvidedConfigName?: string, + options?: {skipPrompts?: boolean}, +): Promise { let configName = userProvidedConfigName // Check cache for previously selected config const cachedConfigName = getCachedAppInfo(project.directory)?.configFile const cachedConfigPath = cachedConfigName ? joinPath(project.directory, cachedConfigName) : null + const cacheIsStale = Boolean(!configName && cachedConfigPath && !fileExistsSync(cachedConfigPath)) // Handle stale cache: cached config file no longer exists - if (!configName && cachedConfigPath && !fileExistsSync(cachedConfigPath)) { + if (cacheIsStale && !options?.skipPrompts) { const warningContent = { headline: `Couldn't find ${cachedConfigName}`, body: [ @@ -71,7 +76,8 @@ export async function selectActiveConfig(project: Project, userProvidedConfigNam configName = await use({directory: project.directory, warningContent, shouldRenderSuccess: false}) } - configName = configName ?? cachedConfigName + // Don't fall back to stale cached name — it points to a non-existent file + configName = configName ?? (cacheIsStale ? undefined : cachedConfigName) // Determine source after resolution so it reflects the actual selection path let source: ConfigSource diff --git a/packages/app/src/cli/services/app/config/link.ts b/packages/app/src/cli/services/app/config/link.ts index 5db56b3905c..54900513349 100644 --- a/packages/app/src/cli/services/app/config/link.ts +++ b/packages/app/src/cli/services/app/config/link.ts @@ -165,6 +165,7 @@ async function getAppCreationDefaultsFromLocalApp(options: LinkOptions): Promise directory: options.directory, userProvidedConfigName: options.configName, remoteFlags: undefined, + skipPrompts: true, }) return {creationOptions: app.creationDefaultOptions(), appDirectory: app.directory} @@ -231,6 +232,7 @@ export async function loadLocalAppOptions( configName: options.configName, specifications, remoteFlags, + skipPrompts: true, }) switch (result.state) { From 021b86de49948bae4d8910b946ff302b49ebccdc Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Mon, 13 Apr 2026 17:01:58 -0400 Subject: [PATCH 3/7] Fix: use default shopify.app.toml when no TOMLs exist Addresses https://community.shopify.dev/t/shopify-app-config-link-client-id-prompts-for-config-name/33039 When running `shopify app config link --client-id=x` with no existing TOML files, the CLI was prompting for a config name (or throwing an error in non-interactive mode). Now it uses the default shopify.app.toml. --- .changeset/fix-reset-spurious-toml-prompt.md | 4 +- .../src/cli/services/app/config/link.test.ts | 59 +++++++++++++++---- .../app/src/cli/services/app/config/link.ts | 3 + 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/.changeset/fix-reset-spurious-toml-prompt.md b/.changeset/fix-reset-spurious-toml-prompt.md index ac4da26bc9c..8ab7de76401 100644 --- a/.changeset/fix-reset-spurious-toml-prompt.md +++ b/.changeset/fix-reset-spurious-toml-prompt.md @@ -2,4 +2,6 @@ '@shopify/app': patch --- -Avoid config prompt when using --reset flag +Avoid spurious config prompts: +- Skip TOML selection prompt when using --reset flag +- Use default shopify.app.toml without prompting when running `config link --client-id` with no existing TOML files diff --git a/packages/app/src/cli/services/app/config/link.test.ts b/packages/app/src/cli/services/app/config/link.test.ts index 11dc5e9308f..cc74bc16e9d 100644 --- a/packages/app/src/cli/services/app/config/link.test.ts +++ b/packages/app/src/cli/services/app/config/link.test.ts @@ -453,6 +453,9 @@ url = "https://api-client-config.com/preferences" }, } as CurrentAppConfiguration, } + // Write actual TOML file so getTomls() finds it and reuses existing config + const filePath = joinPath(tmp, 'shopify.app.development.toml') + writeFileSync(filePath, 'client_id = "12345"\nname = "my app"') await mockLoadOpaqueAppWithApp(tmp, localApp, [], 'current') vi.mocked(fetchOrCreateOrganizationApp).mockResolvedValue( testOrganizationApp({ @@ -460,7 +463,6 @@ url = "https://api-client-config.com/preferences" developerPlatformClient, }), ) - vi.mocked(selectConfigName).mockResolvedValue('shopify.app.staging.toml') const remoteConfiguration = { ...DEFAULT_REMOTE_CONFIGURATION, name: 'my app', @@ -472,8 +474,9 @@ url = "https://api-client-config.com/preferences" // When const {configuration} = await link(options) - // Then - const content = await readFile(joinPath(tmp, 'shopify.app.staging.toml')) + // Then - since client_id matches and file exists, reuse shopify.app.development.toml + expect(selectConfigName).not.toHaveBeenCalled() + const content = await readFile(joinPath(tmp, 'shopify.app.development.toml')) const expectedContent = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration client_id = "12345" @@ -501,14 +504,14 @@ embedded = false ` expect(setCurrentConfigPreference).toHaveBeenCalledWith(configuration, { - configFileName: 'shopify.app.staging.toml', + configFileName: 'shopify.app.development.toml', directory: tmp, }) expect(renderSuccess).toHaveBeenCalledWith({ - headline: 'shopify.app.staging.toml is now linked to "my app" on Shopify', - body: 'Using shopify.app.staging.toml as your default config.', + headline: 'shopify.app.development.toml is now linked to "my app" on Shopify', + body: 'Using shopify.app.development.toml as your default config.', nextSteps: [ - [`Make updates to shopify.app.staging.toml in your local project`], + [`Make updates to shopify.app.development.toml in your local project`], ['To upload your config, run', {command: 'yarn shopify app deploy'}], ], reference: [ @@ -569,6 +572,9 @@ embedded = false }, }, } + // Write actual TOML file so getTomls() finds it (needed for selectConfigName logic) + const filePath = joinPath(tmp, 'shopify.app.toml') + writeFileSync(filePath, 'client_id = "12345"\nname = "my app"') await mockLoadOpaqueAppWithApp(tmp, localApp, [], 'current') vi.mocked(fetchOrCreateOrganizationApp).mockResolvedValue( testOrganizationApp({ @@ -981,6 +987,30 @@ embedded = false }, }) expect(content).toEqual(expectedContent) + // Should use default filename without prompting when no TOMLs exist + expect(selectConfigName).not.toHaveBeenCalled() + }) + }) + + test('uses default shopify.app.toml without prompting when no config files exist and client-id is provided', async () => { + await inTemporaryDirectory(async (tmp) => { + // Given - no TOML files in directory, but client-id is provided + // This simulates: shopify app config link --client-id=12345 + const developerPlatformClient = buildDeveloperPlatformClient() + const options: LinkOptions = { + directory: tmp, + apiKey: '12345', + developerPlatformClient, + } + mockLoadOpaqueAppWithError() + vi.mocked(appFromIdentifiers).mockResolvedValue(mockRemoteApp({developerPlatformClient})) + + // When + const {configFileName} = await link(options) + + // Then - should use default filename without prompting + expect(configFileName).toBe('shopify.app.toml') + expect(selectConfigName).not.toHaveBeenCalled() }) }) @@ -1323,6 +1353,9 @@ embedded = false embedded: true, }, } + // Write actual TOML file so getTomls() finds it and reuses the existing config + const filePath = joinPath(tmp, 'shopify.app.toml') + writeFileSync(filePath, 'client_id = "12345"\nname = "my app"') await mockLoadOpaqueAppWithApp(tmp, localApp, [], 'current') vi.mocked(fetchOrCreateOrganizationApp).mockResolvedValue( testOrganizationApp({ @@ -1330,7 +1363,6 @@ embedded = false developerPlatformClient, }), ) - vi.mocked(selectConfigName).mockResolvedValue('shopify.app.staging.toml') const remoteConfiguration = { ...DEFAULT_REMOTE_CONFIGURATION, name: 'my app', @@ -1347,8 +1379,9 @@ embedded = false // When await link(options) - // Then - const content = await readFile(joinPath(tmp, 'shopify.app.staging.toml')) + // Then - since client_id matches, the existing shopify.app.toml is reused (no prompt) + expect(selectConfigName).not.toHaveBeenCalled() + const content = await readFile(joinPath(tmp, 'shopify.app.toml')) const expectedContent = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration client_id = "12345" @@ -1375,10 +1408,10 @@ embedded = true ` expect(content).toEqual(expectedContent) expect(renderSuccess).toHaveBeenCalledWith({ - headline: 'shopify.app.staging.toml is now linked to "my app" on Shopify', - body: 'Using shopify.app.staging.toml as your default config.', + headline: 'shopify.app.toml is now linked to "my app" on Shopify', + body: 'Using shopify.app.toml as your default config.', nextSteps: [ - [`Make updates to shopify.app.staging.toml in your local project`], + [`Make updates to shopify.app.toml in your local project`], ['To upload your config, run', {command: 'yarn shopify app deploy'}], ], reference: [ diff --git a/packages/app/src/cli/services/app/config/link.ts b/packages/app/src/cli/services/app/config/link.ts index 54900513349..8655d59312f 100644 --- a/packages/app/src/cli/services/app/config/link.ts +++ b/packages/app/src/cli/services/app/config/link.ts @@ -310,6 +310,9 @@ async function loadConfigurationFileName( const currentToml = existingTomls[remoteApp.apiKey] if (currentToml) return currentToml + // If no TOML files exist at all, use the default filename without prompting + if (Object.keys(existingTomls).length === 0) return 'shopify.app.toml' + return selectConfigName(localAppInfo.appDirectory ?? options.directory, remoteApp.title) } From d049f4c30aa2609fea958a1ec5a08f8b2b15a376 Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Mon, 13 Apr 2026 18:05:22 -0400 Subject: [PATCH 4/7] chore: regenerate graphql types after rebase --- .../generated/types.d.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts index e6d67fd75a1..200b94d9837 100644 --- a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts @@ -80,16 +80,13 @@ export type Scalars = { URL: { input: string; output: string; } }; -/** Operators for filter queries. */ +/** Operators for user filter queries. */ export type Operator = /** Between operator. */ | 'BETWEEN' /** Equals operator. */ | 'EQUALS' - /** - * In operator. Accepts a comma-separated string of values (e.g. - * "value1,value2,value3"). Not supported for all filter fields. - */ + /** In operator. */ | 'IN'; export type OrganizationUserProvisionShopAccessInput = { @@ -116,23 +113,17 @@ export type ShopFilterField = * `inactive`, `cancelled`, `client_transfer`, `plus_client_transfer`, * `development_legacy`, `custom`, `fraudulent`, `staff`, `trial`, * `plus_development`, `retail`, `shop_pay_commerce_components`, `non_profit`. - * With the `In` operator, use raw plan names (e.g. "professional,shopify_plus"). */ | 'SHOP_PLAN' /** The active/inactive status of the shop. Values: `active`, `inactive`. */ | 'STORE_STATUS' /** - * The type of the shop. Does not support the `In` operator. Values: - * `development`, `production`, `app_development`, `development_superset`, - * `client_transfer`, `collaborator`. + * The type of the shop. Values: `development`, `production`, `app_development`, + * `development_superset`, `client_transfer`, `collaborator`. */ | 'STORE_TYPE'; -/** - * Represents a single filter option for shop queries. When using the `In` - * operator, pass a comma-separated string of values (e.g. "value1,value2"). - * Maximum 20 values. - */ +/** Represents a single filter option for shop queries. */ export type ShopFilterInput = { field: ShopFilterField; operator: Operator; From 8e3e29b11a537c8355e34aad1eaac70aa884ca0d Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Mon, 13 Apr 2026 18:13:56 -0400 Subject: [PATCH 5/7] chore: sync graphql types with main --- .../generated/types.d.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts index 200b94d9837..e6d67fd75a1 100644 --- a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts @@ -80,13 +80,16 @@ export type Scalars = { URL: { input: string; output: string; } }; -/** Operators for user filter queries. */ +/** Operators for filter queries. */ export type Operator = /** Between operator. */ | 'BETWEEN' /** Equals operator. */ | 'EQUALS' - /** In operator. */ + /** + * In operator. Accepts a comma-separated string of values (e.g. + * "value1,value2,value3"). Not supported for all filter fields. + */ | 'IN'; export type OrganizationUserProvisionShopAccessInput = { @@ -113,17 +116,23 @@ export type ShopFilterField = * `inactive`, `cancelled`, `client_transfer`, `plus_client_transfer`, * `development_legacy`, `custom`, `fraudulent`, `staff`, `trial`, * `plus_development`, `retail`, `shop_pay_commerce_components`, `non_profit`. + * With the `In` operator, use raw plan names (e.g. "professional,shopify_plus"). */ | 'SHOP_PLAN' /** The active/inactive status of the shop. Values: `active`, `inactive`. */ | 'STORE_STATUS' /** - * The type of the shop. Values: `development`, `production`, `app_development`, - * `development_superset`, `client_transfer`, `collaborator`. + * The type of the shop. Does not support the `In` operator. Values: + * `development`, `production`, `app_development`, `development_superset`, + * `client_transfer`, `collaborator`. */ | 'STORE_TYPE'; -/** Represents a single filter option for shop queries. */ +/** + * Represents a single filter option for shop queries. When using the `In` + * operator, pass a comma-separated string of values (e.g. "value1,value2"). + * Maximum 20 values. + */ export type ShopFilterInput = { field: ShopFilterField; operator: Operator; From 1a332d0e7249e0519863ee62a3102daca3f974b5 Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Tue, 14 Apr 2026 11:15:40 -0400 Subject: [PATCH 6/7] refactor: use isEmpty helper instead of Object.keys().length --- packages/app/src/cli/services/app/config/link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/cli/services/app/config/link.ts b/packages/app/src/cli/services/app/config/link.ts index 8655d59312f..cbd3d52c838 100644 --- a/packages/app/src/cli/services/app/config/link.ts +++ b/packages/app/src/cli/services/app/config/link.ts @@ -311,7 +311,7 @@ async function loadConfigurationFileName( if (currentToml) return currentToml // If no TOML files exist at all, use the default filename without prompting - if (Object.keys(existingTomls).length === 0) return 'shopify.app.toml' + if (isEmpty(existingTomls)) return 'shopify.app.toml' return selectConfigName(localAppInfo.appDirectory ?? options.directory, remoteApp.title) } From 1370036d8ee748dacdb456fb0762697fdc2de580 Mon Sep 17 00:00:00 2001 From: Timothy Khan Date: Tue, 14 Apr 2026 17:08:11 -0400 Subject: [PATCH 7/7] Regenerate GraphQL types --- .../generated/types.d.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts index e6d67fd75a1..200b94d9837 100644 --- a/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts +++ b/packages/app/src/cli/api/graphql/business-platform-organizations/generated/types.d.ts @@ -80,16 +80,13 @@ export type Scalars = { URL: { input: string; output: string; } }; -/** Operators for filter queries. */ +/** Operators for user filter queries. */ export type Operator = /** Between operator. */ | 'BETWEEN' /** Equals operator. */ | 'EQUALS' - /** - * In operator. Accepts a comma-separated string of values (e.g. - * "value1,value2,value3"). Not supported for all filter fields. - */ + /** In operator. */ | 'IN'; export type OrganizationUserProvisionShopAccessInput = { @@ -116,23 +113,17 @@ export type ShopFilterField = * `inactive`, `cancelled`, `client_transfer`, `plus_client_transfer`, * `development_legacy`, `custom`, `fraudulent`, `staff`, `trial`, * `plus_development`, `retail`, `shop_pay_commerce_components`, `non_profit`. - * With the `In` operator, use raw plan names (e.g. "professional,shopify_plus"). */ | 'SHOP_PLAN' /** The active/inactive status of the shop. Values: `active`, `inactive`. */ | 'STORE_STATUS' /** - * The type of the shop. Does not support the `In` operator. Values: - * `development`, `production`, `app_development`, `development_superset`, - * `client_transfer`, `collaborator`. + * The type of the shop. Values: `development`, `production`, `app_development`, + * `development_superset`, `client_transfer`, `collaborator`. */ | 'STORE_TYPE'; -/** - * Represents a single filter option for shop queries. When using the `In` - * operator, pass a comma-separated string of values (e.g. "value1,value2"). - * Maximum 20 values. - */ +/** Represents a single filter option for shop queries. */ export type ShopFilterInput = { field: ShopFilterField; operator: Operator;