From 5962676d33c91a76dd2cec87cd838a6c6c8c342f Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 20 Oct 2026 12:53:42 -0700 Subject: [PATCH 1/2] fix(create-void): require lowercase project names --- packages/create-void/README.md | 2 ++ packages/create-void/src/args.ts | 14 +++++++++++++ packages/create-void/src/index.ts | 8 ++------ packages/create-void/test/unit/args.test.ts | 22 ++++++++++++++++++++- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/create-void/README.md b/packages/create-void/README.md index 48fa89f13..970a6a1f7 100644 --- a/packages/create-void/README.md +++ b/packages/create-void/README.md @@ -12,6 +12,8 @@ cd my-app npm run dev ``` +Use a lowercase project name, such as `my-app`. + 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 07e4f404a..e1e8b50be 100644 --- a/packages/create-void/src/args.ts +++ b/packages/create-void/src/args.ts @@ -7,10 +7,24 @@ export type CreateArgs = { // `--framework react` or `--deploy=none` to `void init`. const INIT_ARG_PATTERN = /^[\w=-]+$/; +export function validateProjectName(value: string | undefined): string | undefined { + if (value && !value.trim()) { + return 'Project name cannot be blank'; + } + if (value !== undefined && value !== value.toLowerCase()) { + return 'Project name must be lowercase'; + } + return undefined; +} + /** Split `create-void [name] [void init options]` into the name and the options. */ export function parseCreateArgs(argv: ReadonlyArray): CreateArgs { const [first, ...rest] = argv; const appName = first !== undefined && !first.startsWith('-') ? first : undefined; + const nameError = validateProjectName(appName); + if (nameError) { + throw new Error(`create-void: ${nameError}`); + } const initArgs = appName === undefined ? [...argv] : rest; for (const arg of initArgs) { if (!INIT_ARG_PATTERN.test(arg)) { diff --git a/packages/create-void/src/index.ts b/packages/create-void/src/index.ts index 8e49a8013..4aaefae26 100644 --- a/packages/create-void/src/index.ts +++ b/packages/create-void/src/index.ts @@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from import * as p from '@voidzero-dev/vite-plus-prompts'; import { resolve } from 'pathslash'; import pc from 'picocolors'; -import { parseCreateArgs } from './args.ts'; +import { parseCreateArgs, validateProjectName } from './args.ts'; import { detectPM, pmExec, pmInstall, run, writePnpmBuildPolicy } from './pm.ts'; const pm = detectPM(); @@ -25,11 +25,7 @@ async function main() { message: 'Project name:', defaultValue: 'my-app', placeholder: 'my-app', - validate(value) { - if (value && !value.trim()) { - return 'Project name cannot be blank'; - } - }, + validate: validateProjectName, }); if (p.isCancel(name) || typeof name !== 'string') { p.cancel('Cancelled.'); diff --git a/packages/create-void/test/unit/args.test.ts b/packages/create-void/test/unit/args.test.ts index 86f799c59..9795a39ff 100644 --- a/packages/create-void/test/unit/args.test.ts +++ b/packages/create-void/test/unit/args.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vite-plus/test'; -import { parseCreateArgs } from '../../src/args.ts'; +import { parseCreateArgs, validateProjectName } from '../../src/args.ts'; describe('parseCreateArgs', () => { it('forwards options after the project name to void init', () => { @@ -17,9 +17,29 @@ describe('parseCreateArgs', () => { expect(parseCreateArgs([])).toEqual({ appName: undefined, initArgs: [] }); }); + it.each(['MyApp', 'my-App', 'MY-APP'])('rejects an uppercase project name: %s', (name) => { + expect(() => parseCreateArgs([name])).toThrow('create-void: Project name must be lowercase'); + }); + it('rejects arguments that are not plain flags or values', () => { expect(() => parseCreateArgs(['my-app', '--framework', 'react&calc'])).toThrow( "create-void: Unsupported argument 'react&calc'.", ); }); }); + +describe('validateProjectName', () => { + it.each(['MyApp', 'my-App', 'MY-APP'])('rejects uppercase names: %s', (name) => { + 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('allows the prompt default and rejects whitespace-only names', () => { + expect(validateProjectName(undefined)).toBeUndefined(); + expect(validateProjectName('')).toBeUndefined(); + expect(validateProjectName(' ')).toBe('Project name cannot be blank'); + }); +}); From 688eb82f5d42f59cacb6794d9f40cfac87921dc1 Mon Sep 17 00:00:00 2001 From: Christoph Nakazawa Date: Wed, 21 Oct 2026 09:01:38 +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 970a6a1f7..48fa89f13 100644 --- a/packages/create-void/README.md +++ b/packages/create-void/README.md @@ -12,8 +12,6 @@ cd my-app npm run dev ``` -Use a lowercase project name, such as `my-app`. - 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: