Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/create-void/src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
61 changes: 59 additions & 2 deletions packages/create-void/test/unit/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'.",
Expand All @@ -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', () => {
Expand Down
Loading