Skip to content

Commit 0e3c352

Browse files
naokihabafengmk2
andauthored
fix(create): show helpful error for unknown vite: templates (#1130)
### Linked issue <!-- resolves # --> resolves #1128 ### Description When an unknown template with a vite: prefix (e.g., vite:test) was specified in the vp create command, the CLI would crash with an unhandled ENOENT error instead of displaying a user-friendly error message. This happened because the vite:application command is rewritten to create-vite@latest before being passed to subsequent logic. Consequently, any vite:* command remaining at the fall-through stage is inevitably an unknown command. By detecting this and implementing an early return, we have prevented both the crash and the confusing error message. Additionally, this fix adds protection to the setPackageName call by checking the exit code. This prevents the function from attempting to read package.json from a directory that was never created. ### Manual Verification Results Specifying an unknown template -> An error message is displayed ```bash ❯ vp create vite:test Using default package name: remaining-very "vite:test" is an unknown built-in template. Run vp create --list to see available templates. ``` Specifying an existing template -> Completes successfully (no regression) ```bash ❯ vp create vite:library VITE+ - Integrated toolchain for the web ◇ Package name: receive-weigh ◇ Select package manager to use: pnpm ... ◇ Finished scaffolding receive-weigh as a TypeScript library • Node 24.14.0 pnpm 10.33.0 ✓ Dependencies installed successfully (4.1s) → Next steps: cd receive-weigh && vp run dev ``` --------- Co-authored-by: MK (fengmk2) <fengmk2@gmail.com>
1 parent c09c5a9 commit 0e3c352

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
import { executeBuiltinTemplate } from '../templates/builtin.js';
4+
5+
const { mockLogError } = vi.hoisted(() => ({ mockLogError: vi.fn() }));
6+
7+
vi.mock('../templates/remote.js', () => ({
8+
runRemoteTemplateCommand: vi.fn(),
9+
}));
10+
11+
vi.mock('@voidzero-dev/vite-plus-prompts', () => ({
12+
log: { error: mockLogError },
13+
}));
14+
15+
const workspaceInfo = {
16+
rootDir: '/tmp/workspace',
17+
} as any;
18+
19+
const baseTemplateInfo = {
20+
packageName: 'wage-meeting',
21+
targetDir: 'wage-meeting',
22+
args: [],
23+
envs: {},
24+
type: 'builtin' as any,
25+
interactive: false,
26+
};
27+
28+
describe('executeBuiltinTemplate', () => {
29+
it('returns exitCode 1 for unknown vite: template', async () => {
30+
const { runRemoteTemplateCommand } = await import('../templates/remote.js');
31+
32+
const result = await executeBuiltinTemplate(workspaceInfo, {
33+
...baseTemplateInfo,
34+
command: 'vite:test',
35+
});
36+
37+
expect(result.exitCode).toBe(1);
38+
expect(runRemoteTemplateCommand).not.toHaveBeenCalled();
39+
});
40+
41+
it('shows error message with template name and --list hint', async () => {
42+
mockLogError.mockClear();
43+
44+
await executeBuiltinTemplate(workspaceInfo, {
45+
...baseTemplateInfo,
46+
command: 'vite:unknown',
47+
});
48+
49+
expect(mockLogError).toHaveBeenCalledOnce();
50+
const message = mockLogError.mock.calls[0][0] as string;
51+
expect(message).toContain('vite:unknown');
52+
expect(message).toContain('vp create --list');
53+
});
54+
55+
it('does not show error message in silent mode', async () => {
56+
mockLogError.mockClear();
57+
58+
await executeBuiltinTemplate(
59+
workspaceInfo,
60+
{ ...baseTemplateInfo, command: 'vite:test' },
61+
{ silent: true },
62+
);
63+
64+
expect(mockLogError).not.toHaveBeenCalled();
65+
});
66+
});

packages/cli/src/create/templates/builtin.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import assert from 'node:assert';
22
import path from 'node:path';
33

4+
import * as prompts from '@voidzero-dev/vite-plus-prompts';
5+
import colors from 'picocolors';
6+
47
import type { WorkspaceInfo } from '../../types/index.js';
58
import type { ExecutionResult } from '../command.js';
69
import { discoverTemplate } from '../discovery.js';
@@ -41,11 +44,24 @@ export async function executeBuiltinTemplate(
4144
false,
4245
options?.silent ?? false,
4346
);
47+
if (result.exitCode !== 0) {
48+
return { exitCode: result.exitCode };
49+
}
4450
const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir);
4551
setPackageName(fullPath, templateInfo.packageName);
4652
return { ...result, projectDir: templateInfo.targetDir };
4753
}
4854

55+
// Unknown vite: template (e.g. vite:test) — application was already rewritten to create-vite@latest
56+
if (templateInfo.command.startsWith('vite:')) {
57+
if (!options?.silent) {
58+
prompts.log.error(
59+
`Unknown builtin template "${templateInfo.command}". Run ${colors.yellow('vp create --list')} to see available templates.`,
60+
);
61+
}
62+
return { exitCode: 1 };
63+
}
64+
4965
// Handle remote/external templates with fspy monitoring
5066
const result = await runRemoteTemplateCommand(
5167
workspaceInfo,
@@ -54,6 +70,9 @@ export async function executeBuiltinTemplate(
5470
false,
5571
options?.silent ?? false,
5672
);
73+
if (result.exitCode !== 0) {
74+
return { exitCode: result.exitCode };
75+
}
5776
const fullPath = path.join(workspaceInfo.rootDir, templateInfo.targetDir);
5877
// set package name in the project directory
5978
setPackageName(fullPath, templateInfo.packageName);

0 commit comments

Comments
 (0)