Skip to content

Commit

Permalink
Store dev_store_url in a hidden folder
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacroldan committed Dec 10, 2024
1 parent dd0cc14 commit 1f50940
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 11 deletions.
2 changes: 2 additions & 0 deletions packages/app/src/cli/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const configurationFileNames = {
web: 'shopify.web.toml',
appEnvironments: 'shopify.environments.toml',
lockFile: '.shopify.lock',
hiddenConfig: 'project.json',
hiddenFolder: '.shopify',
} as const

export const dotEnvFileNames = {
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function testApp(app: Partial<AppInterface> = {}, schemaType: 'current' |
specifications: app.specifications ?? [],
configSchema: (app.configSchema ?? AppConfigurationSchema) as any,
remoteFlags: app.remoteFlags ?? [],
hiddenConfig: app.hiddenConfig ?? {},
})

if (app.updateDependencies) {
Expand Down
27 changes: 26 additions & 1 deletion packages/app/src/cli/models/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import {UIExtensionSchema} from '../extensions/specifications/ui_extension.js'
import {Flag} from '../../utilities/developer-platform-client.js'
import {AppAccessSpecIdentifier} from '../extensions/specifications/app_config_app_access.js'
import {WebhookSubscriptionSchema} from '../extensions/specifications/app_config_webhook_schemas/webhook_subscription_schema.js'
import {configurationFileNames} from '../../constants.js'
import {ZodObjectOf, zod} from '@shopify/cli-kit/node/schema'
import {DotEnvFile} from '@shopify/cli-kit/node/dot-env'
import {getDependencies, PackageManager, readAndParsePackageJson} from '@shopify/cli-kit/node/node-package-manager'
import {fileRealPath, findPathUp} from '@shopify/cli-kit/node/fs'
import {fileRealPath, findPathUp, writeFile} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import {AbortError} from '@shopify/cli-kit/node/error'
import {normalizeDelimitedString} from '@shopify/cli-kit/common/string'
Expand Down Expand Up @@ -67,6 +68,15 @@ export const AppSchema = zod.object({
web_directories: zod.array(zod.string()).optional(),
})

/**
* 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.
* These are not meant to be git tracked and the user doesn't need to know about their existence.
*/
export interface AppHiddenConfig {
dev_store_url?: string
}

/**
* Utility schema that matches freshly minted or normal, linked, apps.
*/
Expand Down Expand Up @@ -166,6 +176,10 @@ export function usesLegacyScopesBehavior(config: AppConfiguration) {
return false
}

export function appHiddenConfigPath(appDirectory: string) {
return joinPath(appDirectory, configurationFileNames.hiddenFolder, configurationFileNames.hiddenConfig)
}

/**
* Get the field names from the configuration that aren't found in the basic built-in app configuration schema.
*/
Expand Down Expand Up @@ -243,6 +257,7 @@ export interface AppInterface<
realExtensions: ExtensionInstance[]
draftableExtensions: ExtensionInstance[]
errors?: AppErrors
hiddenConfig: AppHiddenConfig
includeConfigOnDeploy: boolean | undefined
updateDependencies: () => Promise<void>
extensionsForType: (spec: {identifier: string; externalIdentifier: string}) => ExtensionInstance[]
Expand All @@ -261,6 +276,7 @@ export interface AppInterface<
creationDefaultOptions(): AppCreationDefaultOptions
manifest: () => Promise<JsonMapType>
removeExtension: (extensionHandle: string) => void
updateHiddenConfig: (values: Partial<AppHiddenConfig>) => Promise<void>
}

type AppConstructor<
Expand All @@ -277,6 +293,7 @@ type AppConstructor<
errors?: AppErrors
specifications: ExtensionSpecification[]
remoteFlags?: Flag[]
hiddenConfig: AppHiddenConfig
}

export class App<
Expand All @@ -298,6 +315,7 @@ export class App<
configSchema: ZodObjectOf<Omit<TConfig, 'path'>>
remoteFlags: Flag[]
realExtensions: ExtensionInstance[]
hiddenConfig: AppHiddenConfig

constructor({
name,
Expand All @@ -313,6 +331,7 @@ export class App<
specifications,
configSchema,
remoteFlags,
hiddenConfig,
}: AppConstructor<TConfig, TModuleSpec>) {
this.name = name
this.directory = directory
Expand All @@ -327,6 +346,7 @@ export class App<
this.specifications = specifications
this.configSchema = configSchema ?? AppSchema
this.remoteFlags = remoteFlags ?? []
this.hiddenConfig = hiddenConfig
}

get allExtensions() {
Expand Down Expand Up @@ -375,6 +395,11 @@ export class App<
this.nodeDependencies = nodeDependencies
}

async updateHiddenConfig(values: Partial<AppHiddenConfig>) {
this.hiddenConfig = {...this.hiddenConfig, ...values}
await writeFile(appHiddenConfigPath(this.directory), JSON.stringify(this.hiddenConfig, null, 2))
}

async preDeployValidation() {
const functionExtensionsWithUiHandle = this.allExtensions.filter(
(ext) => ext.isFunctionExtension && (ext.configuration as unknown as FunctionConfigType).ui?.handle,
Expand Down
19 changes: 18 additions & 1 deletion packages/app/src/cli/models/app/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
SchemaForConfig,
AppCreationDefaultOptions,
AppLinkedInterface,
appHiddenConfigPath,
AppHiddenConfig,
} from './app.js'
import {showMultipleCLIWarningIfNeeded} from './validation/multi-cli-warning.js'
import {configurationFileNames, dotEnvFileNames} from '../../constants.js'
Expand All @@ -33,7 +35,7 @@ import {WebhooksSchema} from '../extensions/specifications/app_config_webhook_sc
import {loadLocalExtensionsSpecifications} from '../extensions/load-specifications.js'
import {UIExtensionSchemaType} from '../extensions/specifications/ui_extension.js'
import {deepStrict, zod} from '@shopify/cli-kit/node/schema'
import {fileExists, readFile, glob, findPathUp, fileExistsSync} from '@shopify/cli-kit/node/fs'
import {fileExists, readFile, glob, findPathUp, fileExistsSync, writeFile, mkdir} from '@shopify/cli-kit/node/fs'
import {readAndParseDotEnv, DotEnvFile} from '@shopify/cli-kit/node/dot-env'
import {
getDependencies,
Expand Down Expand Up @@ -342,6 +344,8 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
const packageManager = this.previousApp?.packageManager ?? (await getPackageManager(directory))
const usesWorkspaces = this.previousApp?.usesWorkspaces ?? (await appUsesWorkspaces(directory))

const hiddenConfig = await loadHiddenConfig(directory)

if (!this.previousApp) {
await showMultipleCLIWarningIfNeeded(directory, nodeDependencies)
}
Expand All @@ -364,6 +368,7 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
specifications: this.specifications,
configSchema,
remoteFlags: this.remoteFlags,
hiddenConfig,
})

// Show CLI notifications that are targetted for when your app has specific extension types
Expand Down Expand Up @@ -1063,6 +1068,18 @@ async function getAllLinkedConfigClientIds(
return Object.fromEntries(entries)
}

async function loadHiddenConfig(appDirectory: string): Promise<AppHiddenConfig> {
const hiddenConfigPath = appHiddenConfigPath(appDirectory)
if (fileExistsSync(hiddenConfigPath)) {
return JSON.parse(await readFile(hiddenConfigPath, {encoding: 'utf8'}))
} else {
// If the hidden config file doesn't exist, create an empty one.
await mkdir(dirname(hiddenConfigPath))
await writeFile(hiddenConfigPath, '{}')
return {}
}
}

export async function loadAppName(appDirectory: string): Promise<string> {
const packageJSONPath = joinPath(appDirectory, 'package.json')
return (await getPackageName(packageJSONPath)) ?? basename(appDirectory)
Expand Down
10 changes: 3 additions & 7 deletions packages/app/src/cli/services/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,9 @@ async function prepareForDev(commandOptions: DevOptions): Promise<DevConfig> {
organization: commandOptions.organization,
})

// Update the dev_store_url in the app configuration if it doesn't match the store domain
if (app.configuration.build?.dev_store_url !== store.shopDomain) {
app.configuration.build = {
...app.configuration.build,
dev_store_url: store.shopDomain,
}
const patch = {build: {dev_store_url: store.shopDomain}}
// If the dev_store_url is set in the app configuration, remove it.
if (app.configuration.build?.dev_store_url) {
const patch = {build: {dev_store_url: undefined}}
await patchAppConfigurationFile({path: app.configuration.path, patch, schema: app.configSchema})
}

Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/cli/services/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ class AppInfo {
['App name', this.remoteApp.title || NOT_CONFIGURED_TEXT],
['Client ID', this.remoteApp.apiKey || NOT_CONFIGURED_TEXT],
['Access scopes', getAppScopes(this.app.configuration)],
['Dev store', this.app.configuration.build?.dev_store_url || NOT_CONFIGURED_TEXT],
[
'Dev store',
this.app.configuration.build?.dev_store_url ?? this.app.hiddenConfig.dev_store_url ?? NOT_CONFIGURED_TEXT,
],
['Update URLs', updateUrls],
partnersAccountInfo,
]
Expand Down
8 changes: 7 additions & 1 deletion packages/app/src/cli/services/store-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export async function storeContext({
const {app, organization, developerPlatformClient} = appContextResult
let selectedStore: OrganizationStore

const cachedStoreURL = app.configuration.build?.dev_store_url ?? app.hiddenConfig.dev_store_url
// If forceReselectStore is true, ignore the cached storeFqdn in the app configuration.
const cachedStoreInToml = forceReselectStore ? undefined : app.configuration.build?.dev_store_url
const cachedStoreInToml = forceReselectStore ? undefined : cachedStoreURL

// An explicit storeFqdn has preference over anything else.
const storeFqdnToUse = storeFqdn ?? cachedStoreInToml
Expand All @@ -53,6 +54,11 @@ export async function storeContext({
await logMetadata(selectedStore, forceReselectStore)
selectedStore.shopDomain = await normalizeStoreFqdn(selectedStore.shopDomain)

// Save the selected store in the configFile
if (selectedStore.shopDomain !== cachedStoreURL) {
await app.updateHiddenConfig({dev_store_url: selectedStore.shopDomain})
}

return selectedStore
}

Expand Down

0 comments on commit 1f50940

Please sign in to comment.