Skip to content

Commit 5c4971f

Browse files
ensure suggested app names are valid
1 parent 589631d commit 5c4971f

File tree

5 files changed

+44
-8
lines changed

5 files changed

+44
-8
lines changed

packages/app/src/cli/commands/app/init.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {validateFlavorValue, validateTemplateValue} from '../../services/init/va
77
import {MinimalOrganizationApp, Organization, OrganizationApp} from '../../models/organization.js'
88
import {appNamePrompt, createAsNewAppPrompt, selectAppPrompt} from '../../prompts/dev.js'
99
import {searchForAppsByNameFactory} from '../../services/dev/prompt-helpers.js'
10+
import {isValidName} from '../../models/app/validation/common.js'
1011
import {Flags} from '@oclif/core'
1112
import {globalFlags} from '@shopify/cli-kit/node/cli'
1213
import {resolvePath, cwd} from '@shopify/cli-kit/node/path'
@@ -72,7 +73,8 @@ export default class Init extends AppCommand {
7273
validateFlavorValue(flags.template, flags.flavor)
7374

7475
const inferredPackageManager = inferPackageManager(flags['package-manager'])
75-
const name = flags.name ?? (await generateRandomNameForSubdirectory({suffix: 'app', directory: flags.path}))
76+
const name =
77+
flags.name ?? (await generateRandomNameForSubdirectory({suffix: 'app', directory: flags.path, isValidName}))
7678

7779
// Force user authentication before prompting.
7880
let developerPlatformClient = selectDeveloperPlatformClient()

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ function isValidUrl(input: string, httpsOnly: boolean) {
2020
export function ensurePathStartsWithSlash(arg: unknown) {
2121
return typeof arg === 'string' && !arg.startsWith('/') ? `/${arg}` : arg
2222
}
23+
24+
export const APP_NAME_MAX_LENGTH = 30
25+
26+
export function isValidName(name: string): boolean {
27+
return name.length <= APP_NAME_MAX_LENGTH
28+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {Organization, MinimalOrganizationApp, OrganizationStore, MinimalAppIdent
33
import {getTomls} from '../utilities/app/config/getTomls.js'
44
import {setCachedCommandTomlMap} from '../services/local-storage.js'
55
import {Paginateable} from '../utilities/developer-platform-client.js'
6+
import {APP_NAME_MAX_LENGTH} from '../models/app/validation/common.js'
67
import {
78
RenderAutocompleteOptions,
89
renderAutocompletePrompt,
@@ -129,8 +130,8 @@ export async function appNamePrompt(currentName: string): Promise<string> {
129130
if (value.length === 0) {
130131
return "App name can't be empty"
131132
}
132-
if (value.length > 30) {
133-
return 'Enter a shorter name (30 character max.)'
133+
if (value.length > APP_NAME_MAX_LENGTH) {
134+
return `Enter a shorter name (${APP_NAME_MAX_LENGTH} character max.)`
134135
}
135136
if (value.includes('shopify')) {
136137
return 'Name can\'t contain "shopify." Enter another name.'

packages/cli-kit/src/public/node/fs.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,27 @@ describe('makeDirectoryWithRandomName', () => {
218218
expect(takeRandomFromArray).toHaveBeenCalledTimes(4)
219219
})
220220
})
221+
222+
test('rerolls the name if an invalid name is produced', async () => {
223+
await inTemporaryDirectory(async (tmpDir) => {
224+
// Given
225+
vi.mocked(takeRandomFromArray).mockReturnValueOnce('invalid')
226+
vi.mocked(takeRandomFromArray).mockReturnValueOnce('name')
227+
vi.mocked(takeRandomFromArray).mockReturnValueOnce('valid')
228+
vi.mocked(takeRandomFromArray).mockReturnValueOnce('name')
229+
230+
// When
231+
const got = await generateRandomNameForSubdirectory({
232+
suffix: 'app',
233+
directory: tmpDir,
234+
isValidName: (name: string) => name !== 'invalid-name-app',
235+
})
236+
237+
// Then
238+
expect(got).toEqual('valid-name-app')
239+
expect(takeRandomFromArray).toHaveBeenCalledTimes(4)
240+
})
241+
})
221242
})
222243

223244
describe('readFileSync', () => {

packages/cli-kit/src/public/node/fs.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,13 @@ interface GenerateRandomDirectoryOptions {
468468

469469
/** Type of word to use for random name. */
470470
family?: RandomNameFamily
471+
472+
/** Criteria predicate for generated name */
473+
isValidName?: (name: string) => boolean
471474
}
472475

476+
const defaultIsValidName = () => true
477+
473478
/**
474479
* It generates a random directory directory name for a sub-directory.
475480
* It ensures that the returned directory name doesn't exist.
@@ -479,14 +484,15 @@ interface GenerateRandomDirectoryOptions {
479484
*/
480485
export async function generateRandomNameForSubdirectory(options: GenerateRandomDirectoryOptions): Promise<string> {
481486
const generated = `${getRandomName(options.family ?? 'business')}-${options.suffix}`
487+
488+
const validName = (options.isValidName ?? defaultIsValidName)(generated)
489+
if (!validName) return generateRandomNameForSubdirectory(options)
490+
482491
const randomDirectoryPath = joinPath(options.directory, generated)
483492
const isAppDirectoryTaken = await fileExists(randomDirectoryPath)
493+
if (isAppDirectoryTaken) return generateRandomNameForSubdirectory(options)
484494

485-
if (isAppDirectoryTaken) {
486-
return generateRandomNameForSubdirectory(options)
487-
} else {
488-
return generated
489-
}
495+
return generated
490496
}
491497

492498
/**

0 commit comments

Comments
 (0)