diff --git a/packages/app/src/cli/models/app/app.test.ts b/packages/app/src/cli/models/app/app.test.ts index 7d30b987db..34c2cb4ae6 100644 --- a/packages/app/src/cli/models/app/app.test.ts +++ b/packages/app/src/cli/models/app/app.test.ts @@ -175,16 +175,16 @@ describe('getUIExtensionRendererVersion', () => { }) describe('getAppScopes', () => { - test('returns the access_scopes.scopes key', () => { + test('returns the access_scopes.scopes key with normalization', () => { const config = {...DEFAULT_CONFIG, access_scopes: {scopes: 'read_themes,read_themes'}} - expect(getAppScopes(config)).toEqual('read_themes,read_themes') + expect(getAppScopes(config)).toEqual('read_themes') }) }) describe('getAppScopesArray', () => { - test('returns the access_scopes.scopes key', () => { + test('returns the access_scopes.scopes key as array with normalization', () => { const config = {...DEFAULT_CONFIG, access_scopes: {scopes: 'read_themes, read_order ,write_products'}} - expect(getAppScopesArray(config)).toEqual(['read_themes', 'read_order', 'write_products']) + expect(getAppScopesArray(config)).toEqual(['read_order', 'read_themes', 'write_products']) }) }) diff --git a/packages/app/src/cli/models/app/app.ts b/packages/app/src/cli/models/app/app.ts index 040f7702da..bd462abd36 100644 --- a/packages/app/src/cli/models/app/app.ts +++ b/packages/app/src/cli/models/app/app.ts @@ -13,6 +13,7 @@ import {configurationFileNames} from '../../constants.js' import {ApplicationURLs} from '../../services/dev/urls.js' import {patchAppHiddenConfigFile} from '../../services/app/patch-app-configuration-file.js' import {WebhookSubscription} from '../extensions/specifications/types/app_config_webhook.js' +import {normalizeDelimitedString} from '@shopify/cli-kit/common/string' import {joinPath} from '@shopify/cli-kit/node/path' import {ZodObjectOf, zod} from '@shopify/cli-kit/node/schema' import {DotEnvFile} from '@shopify/cli-kit/node/dot-env' @@ -72,6 +73,31 @@ export const AppSchema = zod }) .passthrough() +/** + * Schema for loading template config during app init. + * Uses passthrough() to allow any extra keys from full-featured templates + * (e.g., metafields, metaobjects, webhooks) without strict validation. + */ +export const TemplateConfigSchema = zod + .object({ + scopes: zod.string().optional(), + access_scopes: zod + .object({ + scopes: zod.string(), + }) + .optional(), + web_directories: zod.array(zod.string()).optional(), + }) + .passthrough() + +export type TemplateConfig = zod.infer + +export function getTemplateScopesArray(config: TemplateConfig): string[] { + const scopesString = normalizeDelimitedString(config.scopes ?? config.access_scopes?.scopes) + if (!scopesString || scopesString.length === 0) return [] + return scopesString.split(',').map((scope) => scope.trim()) +} + /** * Hidden configuration for an app. Stored inside ./shopify/project.json * This is a set of values that are needed by the CLI that are not part of the app configuration. @@ -127,7 +153,7 @@ export function getAppVersionedSchema( * @param config - a configuration file */ export function getAppScopes(config: CurrentAppConfiguration): string { - return config.access_scopes?.scopes ?? '' + return normalizeDelimitedString(config.access_scopes?.scopes) ?? '' } /** diff --git a/packages/app/src/cli/models/app/loader.test.ts b/packages/app/src/cli/models/app/loader.test.ts index dcde9758e9..41cc61e8ab 100644 --- a/packages/app/src/cli/models/app/loader.test.ts +++ b/packages/app/src/cli/models/app/loader.test.ts @@ -3486,6 +3486,19 @@ describe('WebhooksSchema', () => { describe('getAppConfigurationState', () => { test.each([ + [ + `scopes = " write_xyz, write_abc "`, + { + state: 'template-only', + configSource: 'cached', + configurationFileName: 'shopify.app.toml', + appDirectory: expect.any(String), + configurationPath: expect.stringMatching(/shopify.app.toml$/), + startingOptions: { + scopes: ' write_xyz, write_abc ', + }, + }, + ], [ `client_id="abcdef"`, { diff --git a/packages/app/src/cli/models/extensions/specifications/app_config_app_access.ts b/packages/app/src/cli/models/extensions/specifications/app_config_app_access.ts index b46603c58c..d8ff47a456 100644 --- a/packages/app/src/cli/models/extensions/specifications/app_config_app_access.ts +++ b/packages/app/src/cli/models/extensions/specifications/app_config_app_access.ts @@ -1,7 +1,6 @@ import {validateUrl} from '../../app/validation/common.js' import {TransformationConfig, createConfigExtensionSpecification} from '../specification.js' import {BaseSchemaWithoutHandle} from '../schemas.js' -import {normalizeDelimitedString} from '@shopify/cli-kit/common/string' import {zod} from '@shopify/cli-kit/node/schema' const AppAccessSchema = BaseSchemaWithoutHandle.extend({ @@ -17,10 +16,7 @@ const AppAccessSchema = BaseSchemaWithoutHandle.extend({ .optional(), access_scopes: zod .object({ - scopes: zod - .string() - .transform((scopes) => normalizeDelimitedString(scopes) ?? '') - .optional(), + scopes: zod.string().optional(), required_scopes: zod.array(zod.string()).optional(), optional_scopes: zod.array(zod.string()).optional(), use_legacy_install_flow: zod.boolean().optional(),