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
2 changes: 2 additions & 0 deletions packages/void/src/cli/domain-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export function parseDomainArgs(args: Array<string>): DomainArgs {
projectSlug = rest[++i];
} else if (rest[i] === '--verbose') {
verbose = true;
} else if (rest[i].startsWith('-')) {
throw new Error(`domain: Unknown flag '${rest[i]}'.`);
} else if (!hostname) {
hostname = rest[i];
}
Expand Down
17 changes: 13 additions & 4 deletions packages/void/src/cli/gen-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ const COLUMN_TYPES = new Set(['string', 'text', 'datetime', 'integer', 'boolean'

const VALID_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete']);

// Generated-file name validators (hoisted once); *_HYPHENS variant also allows hyphens (cron/queue/middleware).
const NAME_RE = /^[a-z_][a-z0-9_]*$/;
const NAME_WITH_HYPHENS_RE = /^[a-z_][a-z0-9_-]*$/;

function parseModelArgs(args: Array<string>): GenArgs {
const [name, ...rest] = args;
if (!name) {
throw new Error('gen: Usage: void gen model <name> [columns...].');
}
if (!/^[a-z_][a-z0-9_]*$/.test(name)) {
if (!NAME_RE.test(name)) {
throw new Error(
'gen: Model name must be lowercase alphanumeric with underscores and no leading digits.',
);
Expand Down Expand Up @@ -94,7 +98,7 @@ function parseMigrationArgs(args: Array<string>): GenArgs {
if (extra) {
throw new Error(`gen: Unexpected argument '${extra}'.`);
}
if (!/^[a-z_][a-z0-9_]*$/.test(name)) {
if (!NAME_RE.test(name)) {
throw new Error(
'gen: Migration name must be lowercase alphanumeric with underscores and no leading digits.',
);
Expand Down Expand Up @@ -145,6 +149,11 @@ function parseMiddlewareArgs(args: Array<string>): GenArgs {
if (extra) {
throw new Error(`gen: Unexpected argument '${extra}'.`);
}
if (!NAME_WITH_HYPHENS_RE.test(name)) {
throw new Error(
'gen: Middleware name must be lowercase alphanumeric with underscores or hyphens and no leading digits.',
);
}
return { subcommand: 'middleware', name };
}

Expand Down Expand Up @@ -181,7 +190,7 @@ function parseCronArgs(args: Array<string>): GenArgs {
if (extra) {
throw new Error(`gen: Unexpected argument '${extra}'.`);
}
if (!/^[a-z_][a-z0-9_-]*$/.test(name)) {
if (!NAME_WITH_HYPHENS_RE.test(name)) {
throw new Error(
'gen: Cron name must be lowercase alphanumeric with underscores or hyphens and no leading digits.',
);
Expand All @@ -197,7 +206,7 @@ function parseQueueArgs(args: Array<string>): GenArgs {
if (extra) {
throw new Error(`gen: Unexpected argument '${extra}'.`);
}
if (!/^[a-z_][a-z0-9_-]*$/.test(name)) {
if (!NAME_WITH_HYPHENS_RE.test(name)) {
throw new Error(
'gen: Queue name must be lowercase alphanumeric with underscores or hyphens and no leading digits.',
);
Expand Down
12 changes: 12 additions & 0 deletions packages/void/src/cli/project-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export function parseProjectArgs(args: Array<string>): ProjectArgs {

if (subcommand === 'status') {
const [name, extra] = rest;
if (name?.startsWith('-')) {
throw new Error(`project: Unknown flag '${name}'.`);
}
if (extra) {
throw new Error(`project: Unexpected argument '${extra}'.`);
}
Expand All @@ -46,6 +49,9 @@ export function parseProjectArgs(args: Array<string>): ProjectArgs {

if (subcommand === 'link') {
const [name, extra] = rest;
if (name?.startsWith('-')) {
throw new Error(`project: Unknown flag '${name}'.`);
}
if (extra) {
throw new Error(`project: Unexpected argument '${extra}'.`);
}
Expand Down Expand Up @@ -88,6 +94,9 @@ export function parseProjectArgs(args: Array<string>): ProjectArgs {

if (subcommand === 'rollback') {
const [deployId, extra] = rest;
if (deployId?.startsWith('-')) {
throw new Error(`project: Unknown flag '${deployId}'.`);
}
if (extra) {
throw new Error(`project: Unexpected argument '${extra}'.`);
}
Expand All @@ -96,6 +105,9 @@ export function parseProjectArgs(args: Array<string>): ProjectArgs {

if (subcommand === 'cancel') {
const [deployId, extra] = rest;
if (deployId?.startsWith('-')) {
throw new Error(`project: Unknown flag '${deployId}'.`);
}
if (extra) {
throw new Error(`project: Unexpected argument '${extra}'.`);
}
Expand Down
34 changes: 34 additions & 0 deletions packages/void/test/unit/domain-args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vite-plus/test';
import { parseDomainArgs } from '../../src/cli/domain-args.ts';

describe('parseDomainArgs', () => {
it('parses a hostname positional', () => {
expect(parseDomainArgs(['add', 'example.com'])).toEqual({
subcommand: 'add',
hostname: 'example.com',
projectSlug: undefined,
verbose: false,
});
});

it('parses --project and --verbose', () => {
expect(parseDomainArgs(['status', 'example.com', '--project', 'my-app', '--verbose'])).toEqual({
subcommand: 'status',
hostname: 'example.com',
projectSlug: 'my-app',
verbose: true,
});
});

it('rejects an unknown flag instead of capturing it as the hostname', () => {
expect(() => parseDomainArgs(['add', '--foo', 'example.com'])).toThrow(
"domain: Unknown flag '--foo'.",
);
});

it('rejects an unknown flag even when a hostname is already present', () => {
expect(() => parseDomainArgs(['status', 'example.com', '--foo'])).toThrow(
"domain: Unknown flag '--foo'.",
);
});
});
13 changes: 13 additions & 0 deletions packages/void/test/unit/gen-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ describe('parseGenArgs', () => {
"gen: Unexpected argument 'extra'.",
);
});

it('rejects an invalid name (e.g. a flag) instead of scaffolding a bogus file', () => {
expect(() => parseGenArgs(['middleware', '--foo'])).toThrow(
'gen: Middleware name must be lowercase alphanumeric with underscores or hyphens and no leading digits.',
);
});

it('allows hyphenated middleware names', () => {
expect(parseGenArgs(['middleware', 'rate-limit'])).toEqual({
subcommand: 'middleware',
name: 'rate-limit',
});
});
});

describe('ssr', () => {
Expand Down
28 changes: 28 additions & 0 deletions packages/void/test/unit/project-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,32 @@ describe('parseProjectArgs', () => {
);
});
});

describe('unknown-flag rejection', () => {
// status/link/rollback/cancel previously captured a leading flag as the positional id.
it('rejects an unknown flag for status instead of capturing it as the name', () => {
expect(() => parseProjectArgs(['status', '--foo'])).toThrow("project: Unknown flag '--foo'.");
});

it('rejects an unknown flag for link', () => {
expect(() => parseProjectArgs(['link', '--foo'])).toThrow("project: Unknown flag '--foo'.");
});

it('rejects an unknown flag for rollback', () => {
expect(() => parseProjectArgs(['rollback', '--foo'])).toThrow(
"project: Unknown flag '--foo'.",
);
});

it('rejects an unknown flag for cancel', () => {
expect(() => parseProjectArgs(['cancel', '--foo'])).toThrow("project: Unknown flag '--foo'.");
});

it('still accepts a valid positional id', () => {
expect(parseProjectArgs(['status', 'my-app'])).toEqual({
subcommand: 'status',
name: 'my-app',
});
});
});
});