Skip to content

Commit f4bb99b

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

File tree

5 files changed

+104
-23
lines changed

5 files changed

+104
-23
lines changed

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

Lines changed: 4 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,15 @@ function fixSingleWildcards(value: string[] | undefined) {
8081
*/
8182
export const AppSchema = zod.object({
8283
client_id: zod.string(),
84+
extension_directories: ExtensionDirectoriesSchema,
85+
handle: zod.string().optional(),
8386
build: zod
8487
.object({
8588
automatically_update_urls_on_dev: zod.boolean().optional(),
8689
dev_store_url: zod.string().optional(),
8790
include_config_on_deploy: zod.boolean().optional(),
8891
})
8992
.optional(),
90-
extension_directories: ExtensionDirectoriesSchema,
9193
web_directories: zod.array(zod.string()).optional(),
9294
})
9395

@@ -168,7 +170,7 @@ export function isLegacyAppSchema(item: AppConfiguration): item is LegacyAppConf
168170
*/
169171
export function isCurrentAppSchema(item: AppConfiguration): item is CurrentAppConfiguration {
170172
const {path, ...rest} = item
171-
return isType(AppSchema.nonstrict(), rest)
173+
return isType(AppSchema.passthrough(), rest)
172174
}
173175

174176
/**

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/app/config/link.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,8 +1596,8 @@ embedded = false
15961596
15971597
client_id = "12345"
15981598
extension_directories = [ ]
1599-
name = "app1"
16001599
handle = "handle"
1600+
name = "app1"
16011601
application_url = "https://example.com"
16021602
embedded = true
16031603
@@ -1665,8 +1665,8 @@ embedded = false
16651665
const expectedContent = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration
16661666
16671667
client_id = "12345"
1668-
name = "app1"
16691668
handle = "handle"
1669+
name = "app1"
16701670
application_url = "https://example.com"
16711671
embedded = true
16721672
@@ -1744,8 +1744,8 @@ embedded = false
17441744
const expectedContent = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration
17451745
17461746
client_id = "12345"
1747-
name = "app1"
17481747
handle = "handle"
1748+
name = "app1"
17491749
application_url = "https://example.com"
17501750
embedded = true
17511751
@@ -1823,8 +1823,8 @@ embedded = false
18231823
const expectedContent = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration
18241824
18251825
client_id = "12345"
1826-
name = "app1"
18271826
handle = "handle"
1827+
name = "app1"
18281828
application_url = "https://example.com"
18291829
embedded = true
18301830

0 commit comments

Comments
 (0)