Skip to content

Commit cb06c5d

Browse files
committed
Extract path from AppConfiguration into App.configPath
Path is not data — it's metadata about where the config file lives, not part of what the TOML file contains. This is the first step toward making AppConfiguration a trustworthy, file-shaped type. - Add `configPath: string` to App, AppInterface, AppConfigurationInterface - Remove `& {path: string}` from AppConfiguration, BasicAppConfigurationWithoutModules, LegacyAppConfiguration - Delete AppConfigurationWithoutPath (replaced by AppConfiguration) - Simplify SchemaForConfig (no more Omit<..., 'path'>) - parseConfigurationFile no longer bolts path onto return value - writeAppConfigurationFile takes explicit configPath parameter - Type guards pass objects directly (no destructuring to strip path) - Migrate ~24 callsites from app.configuration.path → app.configPath - Remove 10 workarounds (delete file.path, blocklist, Omit wrappers) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> # Conflicts: # packages/app/src/cli/models/app/app.test-data.ts # packages/app/src/cli/models/app/app.test.ts # packages/app/src/cli/models/app/app.ts # packages/app/src/cli/models/app/loader.test.ts # packages/app/src/cli/models/app/loader.ts # packages/app/src/cli/services/app-context.test.ts # packages/app/src/cli/services/app/config/link.test.ts # packages/app/src/cli/services/app/config/use.test.ts # packages/app/src/cli/services/app/env/pull.test.ts # packages/app/src/cli/services/app/env/show.test.ts # packages/app/src/cli/services/context.ts # packages/app/src/cli/services/context/identifiers-extensions.test.ts # packages/app/src/cli/services/dev.ts # packages/app/src/cli/services/dev/app-events/app-event-watcher.test.ts # packages/app/src/cli/services/dev/app-events/file-watcher.test.ts # packages/app/src/cli/services/dev/select-app.test.ts # packages/app/src/cli/services/info.test.ts # packages/app/src/cli/utilities/developer-platform-client/partners-client.test.ts
1 parent 9015ca2 commit cb06c5d

43 files changed

Lines changed: 136 additions & 172 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/app/src/cli/commands/app/config/pull.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,17 @@ This command reuses the existing linked app and organization and skips all inter
3030
userProvidedConfigName: flags.config,
3131
})
3232

33-
const {configuration} = await pull({
33+
const {configuration, configPath} = await pull({
3434
directory: flags.path,
3535
configName: flags.config,
36+
configPath: app.configPath,
3637
configuration: app.configuration,
3738
remoteApp,
3839
})
3940

4041
renderSuccess({
4142
headline: `Pulled latest configuration for "${configuration.name}"`,
42-
body: `Updated ${basename(configuration.path)} with the remote data.`,
43+
body: `Updated ${basename(configPath)} with the remote data.`,
4344
})
4445

4546
return {app}

packages/app/src/cli/commands/app/env/pull.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class EnvPull extends AppLinkedCommand {
3636
forceRelink: flags.reset,
3737
userProvidedConfigName: flags.config,
3838
})
39-
const envFile = joinPath(app.directory, flags['env-file'] ?? getDotEnvFileName(app.configuration.path))
39+
const envFile = joinPath(app.directory, flags['env-file'] ?? getDotEnvFileName(app.configPath))
4040
outputResult(await pullEnv({app, remoteApp, organization, envFile}))
4141
return {app}
4242
}

packages/app/src/cli/models/app/app.test-data.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
App,
33
AppSchema,
4-
AppConfigurationWithoutPath,
4+
AppConfiguration,
55
AppInterface,
66
AppLinkedInterface,
77
CurrentAppConfiguration,
@@ -82,7 +82,6 @@ import {vi} from 'vitest'
8282
import {joinPath} from '@shopify/cli-kit/node/path'
8383

8484
export const DEFAULT_CONFIG = {
85-
path: '/tmp/project/shopify.app.toml',
8685
application_url: 'https://myapp.com',
8786
client_id: 'api-key',
8887
name: 'my app',
@@ -96,14 +95,15 @@ export const DEFAULT_CONFIG = {
9695
},
9796
}
9897

99-
export function testApp(app: Partial<AppInterface> = {}, schemaType: 'current' | 'legacy' = 'legacy'): AppInterface {
98+
export function testApp(app: Partial<AppInterface> = {}): AppInterface {
10099
const getConfig = () => {
101100
return DEFAULT_CONFIG as CurrentAppConfiguration
102101
}
103102

104103
const newApp = new App({
105104
name: app.name ?? 'App',
106105
directory: app.directory ?? '/tmp/project',
106+
configPath: app.configPath ?? '/tmp/project/shopify.app.toml',
107107
packageManager: app.packageManager ?? 'yarn',
108108
configuration: app.configuration ?? getConfig(),
109109
nodeDependencies: app.nodeDependencies ?? {},
@@ -188,7 +188,7 @@ export function testOrganizationApp(app: Partial<OrganizationApp> = {}): Organiz
188188
return {...defaultApp, ...app}
189189
}
190190

191-
export const placeholderAppConfiguration: AppConfigurationWithoutPath = {
191+
export const placeholderAppConfiguration: AppConfiguration = {
192192
client_id: '',
193193
}
194194

packages/app/src/cli/models/app/app.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
AppConfiguration,
23
AppSchema,
34
CurrentAppConfiguration,
45
getAppScopes,
@@ -29,7 +30,6 @@ import {joinPath} from '@shopify/cli-kit/node/path'
2930
import {AbortError} from '@shopify/cli-kit/node/error'
3031

3132
const CORRECT_CURRENT_APP_SCHEMA: CurrentAppConfiguration = {
32-
path: '',
3333
name: 'app 1',
3434
client_id: '12345',
3535
extension_directories: ['extensions/*'],

packages/app/src/cli/models/app/app.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,12 @@ export interface AppHiddenConfig {
8484
*
8585
* Try to avoid using this: generally you should be working with a more specific type.
8686
*/
87-
export type AppConfiguration = zod.infer<typeof AppSchema> & {path: string}
88-
export type AppConfigurationWithoutPath = zod.infer<typeof AppSchema>
87+
export type AppConfiguration = zod.infer<typeof AppSchema>
8988

9089
/**
9190
* App configuration for a normal, linked, app. Doesn't include properties that are module derived.
9291
*/
93-
export type BasicAppConfigurationWithoutModules = zod.infer<typeof AppSchema> & {path: string}
92+
export type BasicAppConfigurationWithoutModules = zod.infer<typeof AppSchema>
9493

9594
/**
9695
* The build section for a normal, linked app. The options here tweak the CLI's behavior when working with the app.
@@ -103,12 +102,12 @@ export type CliBuildPreferences = BasicAppConfigurationWithoutModules['build']
103102
export type CurrentAppConfiguration = BasicAppConfigurationWithoutModules & AppConfigurationUsedByCli
104103

105104
/** Validation schema that produces a provided app configuration type */
106-
export type SchemaForConfig<TConfig extends {path: string}> = ZodObjectOf<Omit<TConfig, 'path'>>
105+
export type SchemaForConfig<TConfig> = ZodObjectOf<TConfig>
107106

108107
export function getAppVersionedSchema(
109108
specs: ExtensionSpecification[],
110109
allowDynamicallySpecifiedConfigs = true,
111-
): ZodObjectOf<Omit<CurrentAppConfiguration, 'path'>> {
110+
): ZodObjectOf<CurrentAppConfiguration> {
112111
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113112
const schema = specs.reduce<any>((schema, spec) => spec.contributeToAppConfigurationSchema(schema), AppSchema)
114113

@@ -144,7 +143,7 @@ export function appHiddenConfigPath(appDirectory: string) {
144143
* Get the field names from the configuration that aren't found in the basic built-in app configuration schema.
145144
*/
146145
export function filterNonVersionedAppFields(configuration: object): string[] {
147-
const builtInFieldNames = Object.keys(AppSchema.shape).concat('path', 'organization_id')
146+
const builtInFieldNames = Object.keys(AppSchema.shape).concat('organization_id')
148147
return Object.keys(configuration).filter((fieldName) => {
149148
return !builtInFieldNames.includes(fieldName)
150149
})
@@ -194,6 +193,7 @@ export interface AppConfigurationInterface<
194193
TModuleSpec extends ExtensionSpecification = ExtensionSpecification,
195194
> {
196195
directory: string
196+
configPath: string
197197
configuration: TConfig
198198
configSchema: SchemaForConfig<TConfig>
199199
specifications: TModuleSpec[]
@@ -284,6 +284,7 @@ export class App<
284284
name: string
285285
idEnvironmentVariableName: 'SHOPIFY_API_KEY' = 'SHOPIFY_API_KEY' as const
286286
directory: string
287+
configPath: string
287288
packageManager: PackageManager
288289
configuration: TConfig
289290
nodeDependencies: {[key: string]: string}
@@ -292,7 +293,7 @@ export class App<
292293
dotenv?: DotEnvFile
293294
errors?: AppErrors
294295
specifications: TModuleSpec[]
295-
configSchema: ZodObjectOf<Omit<TConfig, 'path'>>
296+
configSchema: SchemaForConfig<TConfig>
296297
remoteFlags: Flag[]
297298
realExtensions: ExtensionInstance[]
298299
devApplicationURLs?: ApplicationURLs
@@ -301,6 +302,7 @@ export class App<
301302
constructor({
302303
name,
303304
directory,
305+
configPath,
304306
packageManager,
305307
configuration,
306308
nodeDependencies,
@@ -317,6 +319,7 @@ export class App<
317319
}: AppConstructor<TConfig, TModuleSpec>) {
318320
this.name = name
319321
this.directory = directory
322+
this.configPath = configPath
320323
this.packageManager = packageManager
321324
this.configuration = configuration
322325
this.nodeDependencies = nodeDependencies

packages/app/src/cli/models/app/identifiers.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,10 @@ describe('updateAppIdentifiers', () => {
4646
const app = testAppWithConfig({
4747
app: {
4848
directory: tmpDir,
49+
configPath: joinPath(tmpDir, 'shopify.app.staging.toml'),
4950
allExtensions: [uiExtension],
5051
},
51-
config: {
52-
path: joinPath(tmpDir, 'shopify.app.staging.toml'),
53-
},
52+
config: {},
5453
})
5554

5655
// When

packages/app/src/cli/models/app/identifiers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function updateAppIdentifiers(
5454

5555
if (!dotenvFile) {
5656
dotenvFile = {
57-
path: joinPath(app.directory, getDotEnvFileName(app.configuration.path)),
57+
path: joinPath(app.directory, getDotEnvFileName(app.configPath)),
5858
variables: {},
5959
}
6060
}

packages/app/src/cli/models/app/loader.test.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
loadHiddenConfig,
1414
} from './loader.js'
1515
import {parseHumanReadableError} from './error-parsing.js'
16-
import {App, AppInterface, AppLinkedInterface, AppSchema, WebConfigurationSchema} from './app.js'
16+
import {App, AppConfiguration, AppInterface, AppLinkedInterface, AppSchema, WebConfigurationSchema} from './app.js'
1717
import {DEFAULT_CONFIG, buildVersionedAppSchema, getWebhookConfig} from './app.test-data.js'
1818
import {ExtensionInstance} from '../extensions/extension-instance.js'
1919
import {configurationFileNames, blocks} from '../../constants.js'
@@ -2908,8 +2908,7 @@ describe('parseConfigurationObject', () => {
29082908
const abortOrReport = vi.fn()
29092909

29102910
const {schema} = await buildVersionedAppSchema()
2911-
const {path, ...toParse} = configurationObject
2912-
await parseConfigurationObject(schema, 'tmp', toParse, abortOrReport)
2911+
await parseConfigurationObject(schema, 'tmp', configurationObject, abortOrReport)
29132912

29142913
expect(abortOrReport).toHaveBeenCalledWith(expectedFormatted, {}, 'tmp')
29152914
})
@@ -3479,7 +3478,7 @@ describe('WebhooksSchema', () => {
34793478
)} in tmp:\n\n${parseHumanReadableError(err)}`
34803479
const abortOrReport = vi.fn()
34813480

3482-
const {path, ...toParse} = getWebhookConfig(webhookConfigOverrides)
3481+
const toParse = getWebhookConfig(webhookConfigOverrides)
34833482
const parsedConfiguration = await parseConfigurationObject(WebhooksSchema, 'tmp', toParse, abortOrReport)
34843483
return {abortOrReport, expectedFormatted, parsedConfiguration}
34853484
}
@@ -3491,7 +3490,6 @@ describe('getAppConfigurationState', () => {
34913490
`client_id="abcdef"`,
34923491
{
34933492
basicConfiguration: {
3494-
path: expect.stringMatching(/shopify.app.toml$/),
34953493
client_id: 'abcdef',
34963494
},
34973495
isLinked: true,
@@ -3502,7 +3500,6 @@ describe('getAppConfigurationState', () => {
35023500
something_extra="keep"`,
35033501
{
35043502
basicConfiguration: {
3505-
path: expect.stringMatching(/shopify.app.toml$/),
35063503
client_id: 'abcdef',
35073504
something_extra: 'keep',
35083505
},
@@ -3513,7 +3510,6 @@ describe('getAppConfigurationState', () => {
35133510
`client_id=""`,
35143511
{
35153512
basicConfiguration: {
3516-
path: expect.stringMatching(/shopify.app.toml$/),
35173513
client_id: '',
35183514
},
35193515
isLinked: false,
@@ -3692,9 +3688,8 @@ describe('loadHiddenConfig', () => {
36923688
await inTemporaryDirectory(async (tmpDir) => {
36933689
// Given
36943690
const configuration = {
3695-
path: joinPath(tmpDir, 'shopify.app.toml'),
36963691
client_id: '12345',
3697-
}
3692+
} as AppConfiguration
36983693
await writeFile(joinPath(tmpDir, '.gitignore'), '')
36993694

37003695
// When
@@ -3714,9 +3709,8 @@ describe('loadHiddenConfig', () => {
37143709
await inTemporaryDirectory(async (tmpDir) => {
37153710
// Given
37163711
const configuration = {
3717-
path: joinPath(tmpDir, 'shopify.app.toml'),
37183712
client_id: '12345',
3719-
}
3713+
} as AppConfiguration
37203714
const hiddenConfigPath = joinPath(tmpDir, '.shopify', 'project.json')
37213715
await mkdir(dirname(hiddenConfigPath))
37223716
await writeFile(
@@ -3739,9 +3733,8 @@ describe('loadHiddenConfig', () => {
37393733
await inTemporaryDirectory(async (tmpDir) => {
37403734
// Given
37413735
const configuration = {
3742-
path: joinPath(tmpDir, 'shopify.app.toml'),
37433736
client_id: 'not-found',
3744-
}
3737+
} as AppConfiguration
37453738
const hiddenConfigPath = joinPath(tmpDir, '.shopify', 'project.json')
37463739
await mkdir(dirname(hiddenConfigPath))
37473740
await writeFile(
@@ -3763,9 +3756,8 @@ describe('loadHiddenConfig', () => {
37633756
await inTemporaryDirectory(async (tmpDir) => {
37643757
// Given
37653758
const configuration = {
3766-
path: joinPath(tmpDir, 'shopify.app.toml'),
37673759
client_id: 'not-found',
3768-
}
3760+
} as AppConfiguration
37693761
const hiddenConfigPath = joinPath(tmpDir, '.shopify', 'project.json')
37703762
await mkdir(dirname(hiddenConfigPath))
37713763
await writeFile(
@@ -3787,9 +3779,8 @@ describe('loadHiddenConfig', () => {
37873779
await inTemporaryDirectory(async (tmpDir) => {
37883780
// Given
37893781
const configuration = {
3790-
path: joinPath(tmpDir, 'shopify.app.toml'),
37913782
client_id: '12345',
3792-
}
3783+
} as AppConfiguration
37933784
const hiddenConfigPath = joinPath(tmpDir, '.shopify', 'project.json')
37943785
await mkdir(dirname(hiddenConfigPath))
37953786
await writeFile(hiddenConfigPath, 'invalid json')

0 commit comments

Comments
 (0)