Skip to content

Commit 802d390

Browse files
committed
Fix how app name is set from the AppLoader
1 parent 6bfe37d commit 802d390

File tree

5 files changed

+108
-26
lines changed

5 files changed

+108
-26
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const LegacyAppSchema = zod
4848
.object({
4949
client_id: zod.number().optional(),
5050
name: zod.string().optional(),
51+
handle: zod.string().optional(),
5152
scopes: zod
5253
.string()
5354
.transform((scopes) => normalizeDelimitedString(scopes) ?? '')
@@ -80,14 +81,16 @@ function fixSingleWildcards(value: string[] | undefined) {
8081
*/
8182
export const AppSchema = zod.object({
8283
client_id: zod.string(),
84+
extension_directories: ExtensionDirectoriesSchema,
85+
name: zod.string().optional(),
86+
handle: zod.string().optional(),
8387
build: zod
8488
.object({
8589
automatically_update_urls_on_dev: zod.boolean().optional(),
8690
dev_store_url: zod.string().optional(),
8791
include_config_on_deploy: zod.boolean().optional(),
8892
})
8993
.optional(),
90-
extension_directories: ExtensionDirectoriesSchema,
9194
web_directories: zod.array(zod.string()).optional(),
9295
})
9396

@@ -168,7 +171,7 @@ export function isLegacyAppSchema(item: AppConfiguration): item is LegacyAppConf
168171
*/
169172
export function isCurrentAppSchema(item: AppConfiguration): item is CurrentAppConfiguration {
170173
const {path, ...rest} = item
171-
return isType(AppSchema.nonstrict(), rest)
174+
return isType(AppSchema.passthrough(), rest)
172175
}
173176

174177
/**

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ describe('load', () => {
6464
}
6565

6666
const appConfiguration = `
67+
name = "my_app"
6768
scopes = "read_products"
6869
`
6970
const linkedAppConfiguration = `
@@ -264,7 +265,54 @@ wrong = "property"
264265
const app = await loadApp({directory: tmpDir, specifications: [], userProvidedConfigName: undefined})
265266

266267
// Then
267-
expect(app.name).toBe('my_app')
268+
expect(app.name).toBe('for-testing')
269+
})
270+
271+
test('uses handle from configuration as app name when present', async () => {
272+
// Given
273+
const appConfiguration = `
274+
name = "display-name"
275+
handle = "app-handle"
276+
client_id = "1234567890"
277+
application_url = "https://example.com/lala"
278+
embedded = true
279+
280+
[webhooks]
281+
api_version = "2023-07"
282+
283+
[auth]
284+
redirect_urls = [ "https://example.com/api/auth" ]
285+
`
286+
await writeConfig(appConfiguration)
287+
288+
// When
289+
const app = await loadTestingApp()
290+
291+
// Then
292+
expect(app.name).toBe('app-handle')
293+
})
294+
295+
test('uses name from configuration when handle is not present', async () => {
296+
// Given
297+
const appConfiguration = `
298+
name = "config-name"
299+
client_id = "1234567890"
300+
application_url = "https://example.com/lala"
301+
embedded = true
302+
303+
[webhooks]
304+
api_version = "2023-07"
305+
306+
[auth]
307+
redirect_urls = [ "https://example.com/api/auth" ]
308+
`
309+
await writeConfig(appConfiguration)
310+
311+
// When
312+
const app = await loadTestingApp()
313+
314+
// Then
315+
expect(app.name).toBe('config-name')
268316
})
269317

270318
test('defaults to npm as the package manager when the configuration is valid', async () => {
@@ -618,6 +666,7 @@ wrong = "property"
618666
test('loads the app with custom located web blocks', async () => {
619667
// Given
620668
const {webDirectory} = await writeConfig(`
669+
name = "my_app"
621670
scopes = ""
622671
web_directories = ["must_be_here"]
623672
`)
@@ -633,6 +682,7 @@ wrong = "property"
633682
test('loads the app with custom located web blocks, only checks given directory', async () => {
634683
// Given
635684
const {webDirectory} = await writeConfig(`
685+
name = "my_app"
636686
scopes = ""
637687
web_directories = ["must_be_here"]
638688
`)
@@ -700,6 +750,7 @@ wrong = "property"
700750
test('loads the app when it has a extension with a valid configuration using a supported extension type and in a non-conventional directory configured in the app configuration file', async () => {
701751
// Given
702752
await writeConfig(`
753+
name = "my_app"
703754
scopes = ""
704755
extension_directories = ["custom_extension"]
705756
`)

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import {readAndParseDotEnv, DotEnvFile} from '@shopify/cli-kit/node/dot-env'
4242
import {
4343
getDependencies,
4444
getPackageManager,
45-
getPackageName,
4645
usesWorkspaces as appUsesWorkspaces,
4746
} from '@shopify/cli-kit/node/node-package-manager'
4847
import {resolveFramework} from '@shopify/cli-kit/node/framework'
@@ -343,7 +342,18 @@ class AppLoader<TConfig extends AppConfiguration, TModuleSpec extends ExtensionS
343342
const packageJSONPath = joinPath(directory, 'package.json')
344343

345344
// These don't need to be processed again if the app is being reloaded
346-
const name = this.previousApp?.name ?? (await loadAppName(directory))
345+
// name is required by BrandingSchema, so it must be present in the configuration
346+
const configName = 'name' in configuration ? configuration.name : undefined
347+
const name = this.previousApp?.name ?? configuration.handle ?? configName ?? ''
348+
if (!name) {
349+
this.abortOrReport(
350+
outputContent`App name is missing from the configuration. Please add a ${outputToken.yellow(
351+
'name',
352+
)} field to your ${outputToken.path(configuration.path)}`,
353+
'',
354+
configuration.path,
355+
)
356+
}
347357
const nodeDependencies = this.previousApp?.nodeDependencies ?? (await getDependencies(packageJSONPath))
348358
const packageManager = this.previousApp?.packageManager ?? (await getPackageManager(directory))
349359
const usesWorkspaces = this.previousApp?.usesWorkspaces ?? (await appUsesWorkspaces(directory))
@@ -1108,11 +1118,6 @@ export async function loadHiddenConfig(
11081118
}
11091119
}
11101120

1111-
export async function loadAppName(appDirectory: string): Promise<string> {
1112-
const packageJSONPath = joinPath(appDirectory, 'package.json')
1113-
return (await getPackageName(packageJSONPath)) ?? basename(appDirectory)
1114-
}
1115-
11161121
async function getProjectType(webs: Web[]): Promise<'node' | 'php' | 'ruby' | 'frontend' | undefined> {
11171122
const backendWebs = webs.filter((web) => isWebType(web, WebType.Backend))
11181123
const frontendWebs = webs.filter((web) => isWebType(web, WebType.Frontend))

packages/app/src/cli/services/app-context.test.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ describe('linkedAppContext', () => {
4949
test('returns linked app context when app is already linked', async () => {
5050
await inTemporaryDirectory(async (tmp) => {
5151
// Given
52-
const content = `client_id="test-api-key"`
52+
const content = `
53+
name = "test-app"
54+
client_id="test-api-key"`
5355
await writeAppConfig(tmp, content)
5456

5557
// When
@@ -65,6 +67,7 @@ describe('linkedAppContext', () => {
6567
app: expect.objectContaining({
6668
configuration: {
6769
client_id: 'test-api-key',
70+
name: 'test-app',
6871
path: normalizePath(joinPath(tmp, 'shopify.app.toml')),
6972
},
7073
}),
@@ -98,7 +101,11 @@ describe('linkedAppContext', () => {
98101
configurationPath: `${tmp}/shopify.app.stg.toml`,
99102
configSource: 'cached',
100103
configurationFileName: 'shopify.app.stg.toml',
101-
basicConfiguration: {client_id: 'test-api-key', path: normalizePath(joinPath(tmp, 'shopify.app.stg.toml'))},
104+
basicConfiguration: {
105+
client_id: 'test-api-key',
106+
name: 'test-app',
107+
path: normalizePath(joinPath(tmp, 'shopify.app.stg.toml')),
108+
},
102109
},
103110
configuration: {
104111
client_id: 'test-api-key',
@@ -133,7 +140,9 @@ describe('linkedAppContext', () => {
133140
await inTemporaryDirectory(async (tmp) => {
134141
// Given
135142
vi.mocked(appFromIdentifiers).mockResolvedValue({...mockRemoteApp, apiKey: 'test-api-key-new'})
136-
const content = `client_id="test-api-key-new"`
143+
const content = `
144+
name = "test-app"
145+
client_id="test-api-key-new"`
137146
await writeAppConfig(tmp, content)
138147
localStorage.setCachedAppInfo({
139148
appId: 'test-api-key-old',
@@ -166,7 +175,9 @@ describe('linkedAppContext', () => {
166175
test('uses provided clientId when available and updates the app configuration', async () => {
167176
await inTemporaryDirectory(async (tmp) => {
168177
// Given
169-
const content = `client_id="test-api-key"`
178+
const content = `
179+
name = "test-app"
180+
client_id="test-api-key"`
170181
await writeAppConfig(tmp, content)
171182
const newClientId = 'new-api-key'
172183

@@ -191,7 +202,9 @@ describe('linkedAppContext', () => {
191202
test('resets app when there is a valid toml but reset option is true', async () => {
192203
await inTemporaryDirectory(async (tmp) => {
193204
// Given
194-
const content = `client_id="test-api-key"`
205+
const content = `
206+
name = "test-app"
207+
client_id="test-api-key"`
195208
await writeAppConfig(tmp, content)
196209

197210
vi.mocked(link).mockResolvedValue({
@@ -202,7 +215,11 @@ describe('linkedAppContext', () => {
202215
configurationPath: `${tmp}/shopify.app.toml`,
203216
configSource: 'cached',
204217
configurationFileName: 'shopify.app.toml',
205-
basicConfiguration: {client_id: 'test-api-key', path: normalizePath(joinPath(tmp, 'shopify.app.toml'))},
218+
basicConfiguration: {
219+
client_id: 'test-api-key',
220+
name: 'test-app',
221+
path: normalizePath(joinPath(tmp, 'shopify.app.toml')),
222+
},
206223
},
207224
configuration: {
208225
client_id: 'test-api-key',
@@ -229,7 +246,9 @@ describe('linkedAppContext', () => {
229246
test('logs metadata', async () => {
230247
await inTemporaryDirectory(async (tmp) => {
231248
// Given
232-
const content = `client_id="test-api-key"`
249+
const content = `
250+
name = "test-app"
251+
client_id="test-api-key"`
233252
await writeAppConfig(tmp, content)
234253

235254
// When
@@ -255,7 +274,9 @@ describe('linkedAppContext', () => {
255274
test('uses unsafeReportMode when provided', async () => {
256275
await inTemporaryDirectory(async (tmp) => {
257276
// Given
258-
const content = `client_id="test-api-key"`
277+
const content = `
278+
name = "test-app"
279+
client_id="test-api-key"`
259280
await writeAppConfig(tmp, content)
260281
const loadSpy = vi.spyOn(loader, 'loadAppUsingConfigurationState')
261282

@@ -278,7 +299,9 @@ describe('linkedAppContext', () => {
278299
test('does not use unsafeReportMode when not provided', async () => {
279300
await inTemporaryDirectory(async (tmp) => {
280301
// Given
281-
const content = `client_id="test-api-key"`
302+
const content = `
303+
name = "test-app"
304+
client_id="test-api-key"`
282305
await writeAppConfig(tmp, content)
283306
const loadSpy = vi.spyOn(loader, 'loadAppUsingConfigurationState')
284307

packages/app/src/cli/services/context/breakdown-extensions.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
874874

875875
// Then
876876
expect(result).toEqual({
877-
existingFieldNames: ['name', 'application_url', 'embedded', 'pos', 'app_proxy', 'webhooks'],
877+
existingFieldNames: ['application_url', 'embedded', 'pos', 'app_proxy', 'webhooks'],
878878
existingUpdatedFieldNames: [],
879879
newFieldNames: [],
880880
deletedFieldNames: [],
@@ -965,7 +965,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
965965

966966
// Then
967967
expect(result).toEqual({
968-
existingFieldNames: ['name', 'application_url', 'embedded', 'webhooks'],
968+
existingFieldNames: ['application_url', 'embedded', 'webhooks'],
969969
existingUpdatedFieldNames: [],
970970
newFieldNames: [],
971971
deletedFieldNames: [],
@@ -1055,7 +1055,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
10551055

10561056
// Then
10571057
expect(result).toEqual({
1058-
existingFieldNames: ['name', 'webhooks'],
1058+
existingFieldNames: ['webhooks'],
10591059
existingUpdatedFieldNames: ['application_url', 'embedded'],
10601060
newFieldNames: [],
10611061
deletedFieldNames: [],
@@ -1114,7 +1114,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
11141114
expect(result).toEqual({
11151115
existingFieldNames: ['application_url', 'embedded'],
11161116
existingUpdatedFieldNames: [],
1117-
newFieldNames: ['name', 'webhooks', 'pos'],
1117+
newFieldNames: ['webhooks', 'pos'],
11181118
deletedFieldNames: [],
11191119
})
11201120
})
@@ -1217,7 +1217,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
12171217

12181218
// Then
12191219
expect(result).toEqual({
1220-
existingFieldNames: ['name', 'application_url', 'embedded', 'webhooks'],
1220+
existingFieldNames: ['application_url', 'embedded', 'webhooks'],
12211221
existingUpdatedFieldNames: [],
12221222
newFieldNames: [],
12231223
deletedFieldNames: ['pos'],
@@ -1808,7 +1808,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
18081808
expect(result).toEqual({
18091809
existingFieldNames: ['webhooks', 'application_url'],
18101810
existingUpdatedFieldNames: [],
1811-
newFieldNames: ['name', 'embedded'],
1811+
newFieldNames: ['embedded'],
18121812
deletedFieldNames: [],
18131813
})
18141814
})
@@ -1847,7 +1847,7 @@ describe('configExtensionsIdentifiersBreakdown', () => {
18471847
expect(result).toEqual({
18481848
existingFieldNames: [],
18491849
existingUpdatedFieldNames: [],
1850-
newFieldNames: ['name', 'application_url', 'embedded', 'webhooks'],
1850+
newFieldNames: ['application_url', 'embedded', 'webhooks'],
18511851
deletedFieldNames: [],
18521852
})
18531853
})

0 commit comments

Comments
 (0)