Skip to content

Commit 31a730c

Browse files
committed
feat: validate the positional name argument against the project name rules
The positional CLI name argument flowed straight from input.args[0] into the generated package.json name and the init-script rename search key without ever passing through validateProjectName — only the interactive prompt path validated. Run the same validation on the positional argument in getArgs and throw with the validation message on failure, matching the other getArgs errors. Validation runs immediately after argument parsing, before fetching template data, so a network failure can't mask the name error.
1 parent 05a3e01 commit 31a730c

3 files changed

Lines changed: 41 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'create-solana-dapp': minor
3+
---
4+
5+
Validate the positional `name` CLI argument with the same rules as the interactive prompt. Previously `npx create-solana-dapp My_App` bypassed validation entirely and produced an invalid npm package name and a degenerate rename search key; it now fails with the validation message.

src/utils/get-args.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { listTemplates } from './list-templates'
1414
import { listVersions } from './list-versions'
1515
import { runVersionCheck } from './run-version-check'
1616
import { Template } from './template'
17+
import { validateProjectName } from './validate-project-name'
1718
import { PackageManager } from './vendor/package-manager'
1819

1920
const minimalTemplateName = 'nextjs-anchor'
@@ -57,6 +58,17 @@ Examples:
5758
// Get the optional name argument (positional)
5859
const name = input.args[0]
5960

61+
// The positional name flows into the generated package.json and the rename search key, so it
62+
// must pass the same validation as the interactive prompt. Validate before fetching template
63+
// data so a network failure can't mask the name error.
64+
if (name) {
65+
const nameError = validateProjectName(name)
66+
if (nameError) {
67+
log.error(nameError)
68+
throw new Error(nameError)
69+
}
70+
}
71+
6072
// Get the options from the command line
6173
const result = input.opts()
6274
const verbose = result.verbose ?? false

test/get-args.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,30 @@ describe('getArgs', () => {
130130
expect(args.template).toBe(template)
131131
})
132132

133+
it('rejects an invalid positional project name', async () => {
134+
const message =
135+
'Please enter a valid project name (lowercase letters, numbers, and single dashes, starting with a letter)'
136+
137+
await expect(
138+
getArgs(['node', 'create-solana-dapp', 'My_App', '--template', 'basic', '--skip-version-check'], app),
139+
).rejects.toThrow(message)
140+
141+
expect(log.error).toHaveBeenCalledWith(message)
142+
expect(fetchTemplateData).not.toHaveBeenCalled()
143+
expect(getPrompts).not.toHaveBeenCalled()
144+
})
145+
146+
it('rejects a positional project name whose directory already exists', async () => {
147+
// The 'test' directory exists in the repo root, which is the cwd when running vitest
148+
await expect(
149+
getArgs(['node', 'create-solana-dapp', 'test', '--template', 'basic', '--skip-version-check'], app),
150+
).rejects.toThrow('Directory already exists')
151+
152+
expect(log.error).toHaveBeenCalledWith('Directory already exists')
153+
expect(fetchTemplateData).not.toHaveBeenCalled()
154+
expect(getPrompts).not.toHaveBeenCalled()
155+
})
156+
133157
it('parses positional and registered arguments after a template-defined flag', async () => {
134158
const args = await getArgs(
135159
[

0 commit comments

Comments
 (0)