From 99ee307eedee740b8e874aaf59a0de7fcf468096 Mon Sep 17 00:00:00 2001 From: Cameron Date: Wed, 21 Oct 2026 17:09:02 -0700 Subject: [PATCH 1/2] fix(create-void): validate Cloudflare-compatible project names --- packages/create-void/README.md | 2 + packages/create-void/src/args.ts | 15 +++++ packages/create-void/test/unit/args.test.ts | 61 ++++++++++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/packages/create-void/README.md b/packages/create-void/README.md index 48fa89f13..08ee6bed9 100644 --- a/packages/create-void/README.md +++ b/packages/create-void/README.md @@ -12,6 +12,8 @@ cd my-app npm run dev ``` +Choose a project name with 1–63 lowercase letters, numbers, or dashes. It must start and end with a letter or number; underscores, spaces, dots, and paths are not supported. + Choose Vite+ or Vite, then React, Vue, Svelte, or Solid. Pick a D1, PostgreSQL, MySQL, or Static Pages starter. The wizard installs dependencies and sets up TypeScript and coding agents; choose Cloudflare to deploy to your account, or connect to a team platform. When you're ready to deploy: diff --git a/packages/create-void/src/args.ts b/packages/create-void/src/args.ts index e1e8b50be..668b1f4b8 100644 --- a/packages/create-void/src/args.ts +++ b/packages/create-void/src/args.ts @@ -14,6 +14,21 @@ export function validateProjectName(value: string | undefined): string | undefin if (value !== undefined && value !== value.toLowerCase()) { return 'Project name must be lowercase'; } + // The prompt uses an empty value to select its default project name. + if (!value) { + return undefined; + } + // Keep scaffold names usable as workers.dev DNS labels. + // https://developers.cloudflare.com/workers/configuration/routing/workers-dev/#limitations + if (value.length > 63) { + return 'Project name must be at most 63 characters'; + } + if (/[^a-z0-9-]/.test(value)) { + return 'Project name must contain only lowercase letters, numbers, and dashes'; + } + if (value.startsWith('-') || value.endsWith('-')) { + return 'Project name cannot start or end with a dash'; + } return undefined; } diff --git a/packages/create-void/test/unit/args.test.ts b/packages/create-void/test/unit/args.test.ts index 9795a39ff..16407ac91 100644 --- a/packages/create-void/test/unit/args.test.ts +++ b/packages/create-void/test/unit/args.test.ts @@ -21,6 +21,24 @@ describe('parseCreateArgs', () => { expect(() => parseCreateArgs([name])).toThrow('create-void: Project name must be lowercase'); }); + it.each([ + 'my_app', + 'my.app', + 'my app', + 'nested/my-app', + 'my-app-', + 'a'.repeat(64), + ])('rejects invalid project names from the command line: %s', (name) => { + expect(() => parseCreateArgs([name])).toThrow('create-void: Project name'); + }); + + it.each(['a', '0', 'my-app', 'my--app', 'app123', 'a'.repeat(63)])( + 'accepts deployable project names from the command line: %s', + (name) => { + expect(parseCreateArgs([name])).toEqual({ appName: name, initArgs: [] }); + }, + ); + it('rejects arguments that are not plain flags or values', () => { expect(() => parseCreateArgs(['my-app', '--framework', 'react&calc'])).toThrow( "create-void: Unsupported argument 'react&calc'.", @@ -33,8 +51,47 @@ describe('validateProjectName', () => { expect(validateProjectName(name)).toBe('Project name must be lowercase'); }); - it.each(['my-app', 'app123', 'nested/my-app', '.'])('accepts lowercase names: %s', (name) => { - expect(validateProjectName(name)).toBeUndefined(); + it.each(['a', '0', 'my-app', 'my--app', 'app123', 'a'.repeat(63)])( + 'accepts lowercase names: %s', + (name) => { + expect(validateProjectName(name)).toBeUndefined(); + }, + ); + + it.each([ + 'my_app', + 'my.app', + 'my app', + ' my-app', + 'my-app ', + 'my-app\n', + 'my-app\t', + 'my-app\u0000', + 'café', + '项目', + 'my😀app', + 'nested/my-app', + 'nested\\my-app', + '@scope/my-app', + '.', + '..', + '../my-app', + '/tmp/my-app', + 'my&app', + 'my;app', + 'my$app', + ])('rejects characters outside the Cloudflare name alphabet: %j', (name) => { + expect(validateProjectName(name)).toBe( + 'Project name must contain only lowercase letters, numbers, and dashes', + ); + }); + + it.each(['-app', 'app-', '-', '--'])('rejects leading or trailing dashes: %s', (name) => { + expect(validateProjectName(name)).toBe('Project name cannot start or end with a dash'); + }); + + it('rejects names longer than a workers.dev DNS label', () => { + expect(validateProjectName('a'.repeat(64))).toBe('Project name must be at most 63 characters'); }); it('allows the prompt default and rejects whitespace-only names', () => { From ffb0bd905ba98aa1566383d2566279dc4062b576 Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Thu, 22 Oct 2026 09:13:57 +0900 Subject: [PATCH 2/2] Update README.md Signed-off-by: Christoph Nakazawa --- packages/create-void/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/create-void/README.md b/packages/create-void/README.md index 08ee6bed9..48fa89f13 100644 --- a/packages/create-void/README.md +++ b/packages/create-void/README.md @@ -12,8 +12,6 @@ cd my-app npm run dev ``` -Choose a project name with 1–63 lowercase letters, numbers, or dashes. It must start and end with a letter or number; underscores, spaces, dots, and paths are not supported. - Choose Vite+ or Vite, then React, Vue, Svelte, or Solid. Pick a D1, PostgreSQL, MySQL, or Static Pages starter. The wizard installs dependencies and sets up TypeScript and coding agents; choose Cloudflare to deploy to your account, or connect to a team platform. When you're ready to deploy: