Skip to content

Commit 94375f2

Browse files
authored
Merge pull request #5000 from Shopify/11-29-store_dev_store_url_in_a_hidden_folder
Save `dev_store_url` under `./shopify`
2 parents 95b8209 + 44312b5 commit 94375f2

File tree

8 files changed

+213
-86
lines changed

8 files changed

+213
-86
lines changed

packages/app/src/cli/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const configurationFileNames = {
1111
web: 'shopify.web.toml',
1212
appEnvironments: 'shopify.environments.toml',
1313
lockFile: '.shopify.lock',
14+
hiddenConfig: 'project.json',
15+
hiddenFolder: '.shopify',
1416
} as const
1517

1618
export const dotEnvFileNames = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export function testApp(app: Partial<AppInterface> = {}, schemaType: 'current' |
119119
specifications: app.specifications ?? [],
120120
configSchema: (app.configSchema ?? AppConfigurationSchema) as any,
121121
remoteFlags: app.remoteFlags ?? [],
122+
hiddenConfig: app.hiddenConfig ?? {},
122123
})
123124

124125
if (app.updateDependencies) {

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import {UIExtensionSchema} from '../extensions/specifications/ui_extension.js'
1111
import {CreateAppOptions, Flag} from '../../utilities/developer-platform-client.js'
1212
import {AppAccessSpecIdentifier} from '../extensions/specifications/app_config_app_access.js'
1313
import {WebhookSubscriptionSchema} from '../extensions/specifications/app_config_webhook_schemas/webhook_subscription_schema.js'
14+
import {configurationFileNames} from '../../constants.js'
1415
import {ZodObjectOf, zod} from '@shopify/cli-kit/node/schema'
1516
import {DotEnvFile} from '@shopify/cli-kit/node/dot-env'
1617
import {getDependencies, PackageManager, readAndParsePackageJson} from '@shopify/cli-kit/node/node-package-manager'
17-
import {fileRealPath, findPathUp} from '@shopify/cli-kit/node/fs'
18+
import {fileRealPath, findPathUp, writeFile} from '@shopify/cli-kit/node/fs'
1819
import {joinPath} from '@shopify/cli-kit/node/path'
1920
import {AbortError} from '@shopify/cli-kit/node/error'
2021
import {normalizeDelimitedString} from '@shopify/cli-kit/common/string'
@@ -80,6 +81,15 @@ export const AppSchema = zod.object({
8081
web_directories: zod.array(zod.string()).optional(),
8182
})
8283

84+
/**
85+
* Hidden configuration for an app. Stored inside ./shopify/project.json
86+
* This is a set of values that are needed by the CLI that are not part of the app configuration.
87+
* These are not meant to be git tracked and the user doesn't need to know about their existence.
88+
*/
89+
export interface AppHiddenConfig {
90+
dev_store_url?: string
91+
}
92+
8393
/**
8494
* Utility schema that matches freshly minted or normal, linked, apps.
8595
*/
@@ -179,6 +189,10 @@ export function usesLegacyScopesBehavior(config: AppConfiguration) {
179189
return false
180190
}
181191

192+
export function appHiddenConfigPath(appDirectory: string) {
193+
return joinPath(appDirectory, configurationFileNames.hiddenFolder, configurationFileNames.hiddenConfig)
194+
}
195+
182196
/**
183197
* Get the field names from the configuration that aren't found in the basic built-in app configuration schema.
184198
*/
@@ -256,6 +270,7 @@ export interface AppInterface<
256270
realExtensions: ExtensionInstance[]
257271
draftableExtensions: ExtensionInstance[]
258272
errors?: AppErrors
273+
hiddenConfig: AppHiddenConfig
259274
includeConfigOnDeploy: boolean | undefined
260275
updateDependencies: () => Promise<void>
261276
extensionsForType: (spec: {identifier: string; externalIdentifier: string}) => ExtensionInstance[]
@@ -274,6 +289,7 @@ export interface AppInterface<
274289
creationDefaultOptions(): CreateAppOptions
275290
manifest: () => Promise<JsonMapType>
276291
removeExtension: (extensionUid: string) => void
292+
updateHiddenConfig: (values: Partial<AppHiddenConfig>) => Promise<void>
277293
}
278294

279295
type AppConstructor<
@@ -290,6 +306,7 @@ type AppConstructor<
290306
errors?: AppErrors
291307
specifications: ExtensionSpecification[]
292308
remoteFlags?: Flag[]
309+
hiddenConfig: AppHiddenConfig
293310
}
294311

295312
export class App<
@@ -311,6 +328,7 @@ export class App<
311328
configSchema: ZodObjectOf<Omit<TConfig, 'path'>>
312329
remoteFlags: Flag[]
313330
realExtensions: ExtensionInstance[]
331+
hiddenConfig: AppHiddenConfig
314332

315333
constructor({
316334
name,
@@ -326,6 +344,7 @@ export class App<
326344
specifications,
327345
configSchema,
328346
remoteFlags,
347+
hiddenConfig,
329348
}: AppConstructor<TConfig, TModuleSpec>) {
330349
this.name = name
331350
this.directory = directory
@@ -340,6 +359,7 @@ export class App<
340359
this.specifications = specifications
341360
this.configSchema = configSchema ?? AppSchema
342361
this.remoteFlags = remoteFlags ?? []
362+
this.hiddenConfig = hiddenConfig
343363
}
344364

345365
get allExtensions() {
@@ -388,6 +408,11 @@ export class App<
388408
this.nodeDependencies = nodeDependencies
389409
}
390410

411+
async updateHiddenConfig(values: Partial<AppHiddenConfig>) {
412+
this.hiddenConfig = {...this.hiddenConfig, ...values}
413+
await writeFile(appHiddenConfigPath(this.directory), JSON.stringify(this.hiddenConfig, null, 2))
414+
}
415+
391416
async preDeployValidation() {
392417
const functionExtensionsWithUiHandle = this.allExtensions.filter(
393418
(ext) => ext.isFunctionExtension && (ext.configuration as unknown as FunctionConfigType).ui?.handle,

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
BasicAppConfigurationWithoutModules,
1717
SchemaForConfig,
1818
AppLinkedInterface,
19+
appHiddenConfigPath,
20+
AppHiddenConfig,
1921
} from './app.js'
2022
import {showMultipleCLIWarningIfNeeded} from './validation/multi-cli-warning.js'
2123
import {configurationFileNames, dotEnvFileNames} from '../../constants.js'
@@ -32,7 +34,7 @@ import {WebhooksSchema} from '../extensions/specifications/app_config_webhook_sc
3234
import {loadLocalExtensionsSpecifications} from '../extensions/load-specifications.js'
3335
import {UIExtensionSchemaType} from '../extensions/specifications/ui_extension.js'
3436
import {deepStrict, zod} from '@shopify/cli-kit/node/schema'
35-
import {fileExists, readFile, glob, findPathUp, fileExistsSync} from '@shopify/cli-kit/node/fs'
37+
import {fileExists, readFile, glob, findPathUp, fileExistsSync, writeFile, mkdir} from '@shopify/cli-kit/node/fs'
3638
import {readAndParseDotEnv, DotEnvFile} from '@shopify/cli-kit/node/dot-env'
3739
import {
3840
getDependencies,
@@ -346,6 +348,8 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
346348
const packageManager = this.previousApp?.packageManager ?? (await getPackageManager(directory))
347349
const usesWorkspaces = this.previousApp?.usesWorkspaces ?? (await appUsesWorkspaces(directory))
348350

351+
const hiddenConfig = await loadHiddenConfig(directory)
352+
349353
if (!this.previousApp) {
350354
await showMultipleCLIWarningIfNeeded(directory, nodeDependencies)
351355
}
@@ -368,6 +372,7 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
368372
specifications: this.specifications,
369373
configSchema,
370374
remoteFlags: this.remoteFlags,
375+
hiddenConfig,
371376
})
372377

373378
// Show CLI notifications that are targetted for when your app has specific extension types
@@ -1067,6 +1072,18 @@ async function getAllLinkedConfigClientIds(
10671072
return Object.fromEntries(entries)
10681073
}
10691074

1075+
async function loadHiddenConfig(appDirectory: string): Promise<AppHiddenConfig> {
1076+
const hiddenConfigPath = appHiddenConfigPath(appDirectory)
1077+
if (fileExistsSync(hiddenConfigPath)) {
1078+
return JSON.parse(await readFile(hiddenConfigPath, {encoding: 'utf8'}))
1079+
} else {
1080+
// If the hidden config file doesn't exist, create an empty one.
1081+
await mkdir(dirname(hiddenConfigPath))
1082+
await writeFile(hiddenConfigPath, '{}')
1083+
return {}
1084+
}
1085+
}
1086+
10701087
export async function loadAppName(appDirectory: string): Promise<string> {
10711088
const packageJSONPath = joinPath(appDirectory, 'package.json')
10721089
return (await getPackageName(packageJSONPath)) ?? basename(appDirectory)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ async function prepareForDev(commandOptions: DevOptions): Promise<DevConfig> {
102102
organization: commandOptions.organization,
103103
})
104104

105-
// Update the dev_store_url in the app configuration if it doesn't match the store domain
106-
if (app.configuration.build?.dev_store_url !== store.shopDomain) {
105+
// If the dev_store_url is set in the app configuration, keep updating it.
106+
// If not, `store-context.ts` will take care of caching it in the hidden config.
107+
if (app.configuration.build?.dev_store_url) {
107108
app.configuration.build = {
108109
...app.configuration.build,
109110
dev_store_url: store.shopDomain,

packages/app/src/cli/services/info.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ class AppInfo {
153153
['App name', this.remoteApp.title ? {userInput: this.remoteApp.title} : NOT_CONFIGURED_TOKEN],
154154
['Client ID', this.remoteApp.apiKey || NOT_CONFIGURED_TOKEN],
155155
['Access scopes', getAppScopes(this.app.configuration)],
156-
['Dev store', this.app.configuration.build?.dev_store_url ?? NOT_CONFIGURED_TOKEN],
156+
[
157+
'Dev store',
158+
this.app.configuration.build?.dev_store_url ?? this.app.hiddenConfig.dev_store_url ?? NOT_CONFIGURED_TOKEN,
159+
],
157160
['Update URLs', updateUrls],
158161
userAccountInfo,
159162
],

0 commit comments

Comments
 (0)