Skip to content

Commit e2dfa11

Browse files
camc314cpojer
andauthored
fix(create-void): validate Cloudflare-compatible project names (#541)
The lowercase check added in #513 still accepts names such as `my_app`, `my.app`, `my app`, paths, and names longer than a workers.dev DNS label. The scaffolder writes the supplied name directly into package.json, so these inputs should receive actionable validation before files are created or dependencies installed. Extend the validator already shared by the interactive prompt and positional CLI argument to require lowercase ASCII letters, digits, and dashes, with an alphanumeric first/last character and a maximum of 63 characters. Preserve lowercase diagnostics and the interactive default. Add regression coverage for invalid characters, paths, Unicode/control characters, dash boundaries, single-character names, and the 63/64-character boundary; document the accepted project-name format. Cloudflare references: - [Worker name configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#inheritable-keys): alphanumeric characters and dashes; underscores are explicitly disallowed. - [workers.dev limitations](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/#limitations): at most 63 characters, with no leading/trailing dash. Workers without workers.dev can have names up to 255 characters; the scaffolder intentionally uses the workers.dev-compatible limit. Cloudflare itself permits uppercase; this change preserves Void's existing lowercase policy. This narrows scaffold inputs: paths (including `.`) and scoped names are rejected because the input is also used as the package name. Existing project deployment naming and the separately validated Void platform slug remain unchanged. Validation: five dependency-free Node test groups against the production validator/parser passed, covering valid/invalid input tables, boundaries, defaults, and existing argument forwarding. The repository Vite+ tests, affected-test checks, typecheck, and full suite could not run because `vp` is unavailable and the shell proxy is unreachable, preventing dependency installation. --------- Signed-off-by: Christoph Nakazawa <christoph.pojer@gmail.com> Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
1 parent 6ab6559 commit e2dfa11

2 files changed

Lines changed: 74 additions & 2 deletions

File tree

‎packages/create-void/src/args.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ export function validateProjectName(value: string | undefined): string | undefin
1414
if (value !== undefined && value !== value.toLowerCase()) {
1515
return 'Project name must be lowercase';
1616
}
17+
// The prompt uses an empty value to select its default project name.
18+
if (!value) {
19+
return undefined;
20+
}
21+
// Keep scaffold names usable as workers.dev DNS labels.
22+
// https://developers.cloudflare.com/workers/configuration/routing/workers-dev/#limitations
23+
if (value.length > 63) {
24+
return 'Project name must be at most 63 characters';
25+
}
26+
if (/[^a-z0-9-]/.test(value)) {
27+
return 'Project name must contain only lowercase letters, numbers, and dashes';
28+
}
29+
if (value.startsWith('-') || value.endsWith('-')) {
30+
return 'Project name cannot start or end with a dash';
31+
}
1732
return undefined;
1833
}
1934

‎packages/create-void/test/unit/args.test.ts‎

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ describe('parseCreateArgs', () => {
2121
expect(() => parseCreateArgs([name])).toThrow('create-void: Project name must be lowercase');
2222
});
2323

24+
it.each([
25+
'my_app',
26+
'my.app',
27+
'my app',
28+
'nested/my-app',
29+
'my-app-',
30+
'a'.repeat(64),
31+
])('rejects invalid project names from the command line: %s', (name) => {
32+
expect(() => parseCreateArgs([name])).toThrow('create-void: Project name');
33+
});
34+
35+
it.each(['a', '0', 'my-app', 'my--app', 'app123', 'a'.repeat(63)])(
36+
'accepts deployable project names from the command line: %s',
37+
(name) => {
38+
expect(parseCreateArgs([name])).toEqual({ appName: name, initArgs: [] });
39+
},
40+
);
41+
2442
it('rejects arguments that are not plain flags or values', () => {
2543
expect(() => parseCreateArgs(['my-app', '--framework', 'react&calc'])).toThrow(
2644
"create-void: Unsupported argument 'react&calc'.",
@@ -33,8 +51,47 @@ describe('validateProjectName', () => {
3351
expect(validateProjectName(name)).toBe('Project name must be lowercase');
3452
});
3553

36-
it.each(['my-app', 'app123', 'nested/my-app', '.'])('accepts lowercase names: %s', (name) => {
37-
expect(validateProjectName(name)).toBeUndefined();
54+
it.each(['a', '0', 'my-app', 'my--app', 'app123', 'a'.repeat(63)])(
55+
'accepts lowercase names: %s',
56+
(name) => {
57+
expect(validateProjectName(name)).toBeUndefined();
58+
},
59+
);
60+
61+
it.each([
62+
'my_app',
63+
'my.app',
64+
'my app',
65+
' my-app',
66+
'my-app ',
67+
'my-app\n',
68+
'my-app\t',
69+
'my-app\u0000',
70+
'café',
71+
'项目',
72+
'my😀app',
73+
'nested/my-app',
74+
'nested\\my-app',
75+
'@scope/my-app',
76+
'.',
77+
'..',
78+
'../my-app',
79+
'/tmp/my-app',
80+
'my&app',
81+
'my;app',
82+
'my$app',
83+
])('rejects characters outside the Cloudflare name alphabet: %j', (name) => {
84+
expect(validateProjectName(name)).toBe(
85+
'Project name must contain only lowercase letters, numbers, and dashes',
86+
);
87+
});
88+
89+
it.each(['-app', 'app-', '-', '--'])('rejects leading or trailing dashes: %s', (name) => {
90+
expect(validateProjectName(name)).toBe('Project name cannot start or end with a dash');
91+
});
92+
93+
it('rejects names longer than a workers.dev DNS label', () => {
94+
expect(validateProjectName('a'.repeat(64))).toBe('Project name must be at most 63 characters');
3895
});
3996

4097
it('allows the prompt default and rejects whitespace-only names', () => {

0 commit comments

Comments
 (0)