create-esmx 3.0.0-rc.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -0
- package/dist/index.mjs +282 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.mjs +123 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.mjs +165 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.mjs +7 -0
- package/dist/utils/package-manager.d.ts +10 -0
- package/dist/utils/package-manager.mjs +49 -0
- package/dist/utils/package-manager.test.d.ts +4 -0
- package/dist/utils/package-manager.test.mjs +275 -0
- package/dist/utils/project-name.d.ts +30 -0
- package/dist/utils/project-name.mjs +17 -0
- package/dist/utils/project-name.test.d.ts +4 -0
- package/dist/utils/project-name.test.mjs +186 -0
- package/dist/utils/template.d.ts +19 -0
- package/dist/utils/template.mjs +8 -0
- package/dist/utils/template.test.d.ts +4 -0
- package/dist/utils/template.test.mjs +150 -0
- package/package.json +71 -0
- package/src/index.test.ts +159 -0
- package/src/index.ts +391 -0
- package/src/integration.test.ts +226 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/package-manager.test.ts +540 -0
- package/src/utils/package-manager.ts +92 -0
- package/src/utils/project-name.test.ts +345 -0
- package/src/utils/project-name.ts +55 -0
- package/src/utils/template.test.ts +234 -0
- package/src/utils/template.ts +34 -0
- package/template/vue2/README.md +80 -0
- package/template/vue2/package.json +27 -0
- package/template/vue2/src/app.vue +127 -0
- package/template/vue2/src/components/hello-world.vue +79 -0
- package/template/vue2/src/create-app.ts +11 -0
- package/template/vue2/src/entry.client.ts +5 -0
- package/template/vue2/src/entry.node.ts +29 -0
- package/template/vue2/src/entry.server.ts +37 -0
- package/template/vue2/tsconfig.json +26 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import {
|
|
14
|
+
cancel,
|
|
15
|
+
confirm,
|
|
16
|
+
intro,
|
|
17
|
+
isCancel,
|
|
18
|
+
log,
|
|
19
|
+
note,
|
|
20
|
+
outro,
|
|
21
|
+
select,
|
|
22
|
+
text
|
|
23
|
+
} from '@clack/prompts';
|
|
24
|
+
import minimist from 'minimist';
|
|
25
|
+
import color from 'picocolors';
|
|
26
|
+
import {
|
|
27
|
+
formatProjectName,
|
|
28
|
+
getCommand,
|
|
29
|
+
replaceTemplateVariables
|
|
30
|
+
} from './utils/index';
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
|
|
34
|
+
interface CreateProjectOptions {
|
|
35
|
+
argv?: string[]; // Command line arguments
|
|
36
|
+
cwd?: string; // Working directory
|
|
37
|
+
userAgent?: string; // Package manager user agent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getEsmxVersion(): string {
|
|
41
|
+
try {
|
|
42
|
+
const packageJsonPath = resolve(__dirname, '../package.json');
|
|
43
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
44
|
+
|
|
45
|
+
return packageJson.version || 'latest';
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('Failed to read esmx version, using latest version');
|
|
48
|
+
return 'latest';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface TemplateInfo {
|
|
53
|
+
folder: string;
|
|
54
|
+
name: string;
|
|
55
|
+
description: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getAvailableTemplates(): TemplateInfo[] {
|
|
59
|
+
const templateDir = resolve(__dirname, '../template');
|
|
60
|
+
|
|
61
|
+
const templates: TemplateInfo[] = [];
|
|
62
|
+
const templateFolders = readdirSync(templateDir, { withFileTypes: true })
|
|
63
|
+
.filter((dirent) => dirent.isDirectory())
|
|
64
|
+
.map((dirent) => dirent.name);
|
|
65
|
+
|
|
66
|
+
for (const folder of templateFolders) {
|
|
67
|
+
// Use folder name as display name
|
|
68
|
+
const name = folder;
|
|
69
|
+
|
|
70
|
+
// Try to read description from package.json
|
|
71
|
+
const packageJsonPath = resolve(templateDir, folder, 'package.json');
|
|
72
|
+
let description = `${name} template`;
|
|
73
|
+
|
|
74
|
+
if (existsSync(packageJsonPath)) {
|
|
75
|
+
try {
|
|
76
|
+
const packageJson = JSON.parse(
|
|
77
|
+
readFileSync(packageJsonPath, 'utf-8')
|
|
78
|
+
);
|
|
79
|
+
if (packageJson.description) {
|
|
80
|
+
description = packageJson.description;
|
|
81
|
+
}
|
|
82
|
+
templates.push({
|
|
83
|
+
folder,
|
|
84
|
+
name,
|
|
85
|
+
description
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// JSON parsing failed, skip this template
|
|
89
|
+
console.warn(
|
|
90
|
+
`Warning: Failed to parse package.json for template '${folder}', skipping.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Sort by name alphabetically
|
|
97
|
+
return templates.sort((a, b) => a.name.localeCompare(b.name));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface TemplateVariables extends Record<string, string> {
|
|
101
|
+
projectName: string;
|
|
102
|
+
esmxVersion: string;
|
|
103
|
+
installCommand: string;
|
|
104
|
+
devCommand: string;
|
|
105
|
+
buildCommand: string;
|
|
106
|
+
startCommand: string;
|
|
107
|
+
buildTypeCommand: string;
|
|
108
|
+
lintTypeCommand: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function showHelp(userAgent?: string): void {
|
|
112
|
+
const createCmd = getCommand('create', userAgent);
|
|
113
|
+
|
|
114
|
+
console.log(`
|
|
115
|
+
${color.reset(color.bold(color.blue('🚀 Create Esmx Project')))}
|
|
116
|
+
|
|
117
|
+
${color.bold('Usage:')}
|
|
118
|
+
${createCmd} [project-name]
|
|
119
|
+
${createCmd} [project-name] [options]
|
|
120
|
+
|
|
121
|
+
${color.bold('Options:')}
|
|
122
|
+
-t, --template <template> Template to use (default: vue2)
|
|
123
|
+
-n, --name <name> Project name or path
|
|
124
|
+
-f, --force Force overwrite existing directory
|
|
125
|
+
-h, --help Show help information
|
|
126
|
+
-v, --version Show version number
|
|
127
|
+
|
|
128
|
+
${color.bold('Examples:')}
|
|
129
|
+
${createCmd} my-project
|
|
130
|
+
${createCmd} my-project -t vue2
|
|
131
|
+
${createCmd} my-project --force
|
|
132
|
+
${createCmd} . -f -t vue2
|
|
133
|
+
|
|
134
|
+
${color.bold('Available Templates:')}
|
|
135
|
+
${getAvailableTemplates()
|
|
136
|
+
.map((t) => ` ${t.folder.padEnd(25)} ${t.description}`)
|
|
137
|
+
.join('\n')}
|
|
138
|
+
|
|
139
|
+
For more information, visit: ${color.cyan('https://esmnext.com')}
|
|
140
|
+
`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function createProject(
|
|
144
|
+
options: CreateProjectOptions = {}
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
const { argv, cwd, userAgent } = options;
|
|
147
|
+
const commandLineArgs = argv || process.argv.slice(2);
|
|
148
|
+
const workingDir = cwd || process.cwd();
|
|
149
|
+
|
|
150
|
+
const parsedArgs = minimist(commandLineArgs, {
|
|
151
|
+
string: ['template', 'name'],
|
|
152
|
+
boolean: ['help', 'version', 'force'],
|
|
153
|
+
alias: {
|
|
154
|
+
t: 'template',
|
|
155
|
+
n: 'name',
|
|
156
|
+
f: 'force',
|
|
157
|
+
h: 'help',
|
|
158
|
+
v: 'version'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (parsedArgs.help) {
|
|
163
|
+
showHelp(userAgent);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (parsedArgs.version) {
|
|
168
|
+
console.log(getEsmxVersion());
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log();
|
|
173
|
+
intro(
|
|
174
|
+
color.reset(
|
|
175
|
+
color.bold(color.blue('🚀 Welcome to Esmx Project Creator!'))
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const projectNameInput = await getProjectName(
|
|
180
|
+
parsedArgs.name,
|
|
181
|
+
parsedArgs._[0]
|
|
182
|
+
);
|
|
183
|
+
if (isCancel(projectNameInput)) {
|
|
184
|
+
cancel('Operation cancelled');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { packageName, targetDir } = formatProjectName(
|
|
189
|
+
projectNameInput,
|
|
190
|
+
workingDir
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const templateType = await getTemplateType(parsedArgs.template);
|
|
194
|
+
if (isCancel(templateType)) {
|
|
195
|
+
cancel('Operation cancelled');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const installCommand = getCommand('install', userAgent);
|
|
200
|
+
const devCommand = getCommand('dev', userAgent);
|
|
201
|
+
const buildCommand = getCommand('build', userAgent);
|
|
202
|
+
const startCommand = getCommand('start', userAgent);
|
|
203
|
+
const buildTypeCommand = getCommand('build:type', userAgent);
|
|
204
|
+
const lintTypeCommand = getCommand('lint:type', userAgent);
|
|
205
|
+
|
|
206
|
+
await createProjectFromTemplate(
|
|
207
|
+
targetDir,
|
|
208
|
+
templateType,
|
|
209
|
+
workingDir,
|
|
210
|
+
parsedArgs.force,
|
|
211
|
+
{
|
|
212
|
+
projectName: packageName,
|
|
213
|
+
esmxVersion: getEsmxVersion(),
|
|
214
|
+
installCommand,
|
|
215
|
+
devCommand,
|
|
216
|
+
buildCommand,
|
|
217
|
+
startCommand,
|
|
218
|
+
buildTypeCommand,
|
|
219
|
+
lintTypeCommand
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
const installCmd = installCommand;
|
|
223
|
+
const devCmd = devCommand;
|
|
224
|
+
|
|
225
|
+
const nextSteps = [
|
|
226
|
+
color.reset(`1. ${color.cyan(`cd ${targetDir}`)}`),
|
|
227
|
+
color.reset(`2. ${color.cyan(installCmd)}`),
|
|
228
|
+
color.reset(`3. ${color.cyan('git init')} ${color.gray('(optional)')}`),
|
|
229
|
+
color.reset(`4. ${color.cyan(devCmd)}`)
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
note(nextSteps.join('\n'), 'Next steps');
|
|
233
|
+
|
|
234
|
+
outro(color.reset(color.green('Happy coding! 🎉')));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function getProjectName(
|
|
238
|
+
argName?: string,
|
|
239
|
+
positionalName?: string
|
|
240
|
+
): Promise<string | symbol> {
|
|
241
|
+
const providedName = argName || positionalName;
|
|
242
|
+
if (providedName) {
|
|
243
|
+
return providedName;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const projectName = await text({
|
|
247
|
+
message: 'Project name or path:',
|
|
248
|
+
placeholder: 'my-esmx-project',
|
|
249
|
+
validate: (value: string) => {
|
|
250
|
+
if (!value.trim()) {
|
|
251
|
+
return 'Project name or path is required';
|
|
252
|
+
}
|
|
253
|
+
if (!/^[a-zA-Z0-9_.\/@-]+$/.test(value.trim())) {
|
|
254
|
+
return 'Project name or path should only contain letters, numbers, hyphens, underscores, dots, and slashes';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return String(projectName).trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function getTemplateType(argTemplate?: string): Promise<string | symbol> {
|
|
263
|
+
const availableTemplates = getAvailableTemplates();
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
argTemplate &&
|
|
267
|
+
availableTemplates.some((t) => t.folder === argTemplate)
|
|
268
|
+
) {
|
|
269
|
+
return argTemplate;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const options = availableTemplates.map((t) => ({
|
|
273
|
+
label: color.reset(color.gray(`${t.folder} - `) + color.bold(t.name)),
|
|
274
|
+
value: t.folder,
|
|
275
|
+
hint: t.description
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
const template = await select({
|
|
279
|
+
message: 'Select a template:',
|
|
280
|
+
options: options
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return template as string | symbol;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isDirectoryEmpty(dirPath: string): boolean {
|
|
287
|
+
if (!existsSync(dirPath)) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const files = readdirSync(dirPath);
|
|
292
|
+
// Only consider non-hidden files and directories
|
|
293
|
+
const nonHiddenFiles = files.filter((file) => !file.startsWith('.'));
|
|
294
|
+
return nonHiddenFiles.length === 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function createProjectFromTemplate(
|
|
298
|
+
targetDir: string,
|
|
299
|
+
templateType: string,
|
|
300
|
+
workingDir: string,
|
|
301
|
+
force: boolean,
|
|
302
|
+
variables: TemplateVariables
|
|
303
|
+
): Promise<void> {
|
|
304
|
+
const templatePath = resolve(__dirname, '../template', templateType);
|
|
305
|
+
const targetPath =
|
|
306
|
+
targetDir === '.' ? workingDir : resolve(workingDir, targetDir);
|
|
307
|
+
|
|
308
|
+
if (!existsSync(templatePath)) {
|
|
309
|
+
throw new Error(`Template "${templateType}" not found`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Handle directory existence and overwrite confirmation
|
|
313
|
+
if (targetDir !== '.' && existsSync(targetPath)) {
|
|
314
|
+
if (!isDirectoryEmpty(targetPath)) {
|
|
315
|
+
if (!force) {
|
|
316
|
+
const shouldOverwrite = await confirm({
|
|
317
|
+
message: `Directory "${targetDir}" is not empty. Do you want to overwrite it?`
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (isCancel(shouldOverwrite)) {
|
|
321
|
+
cancel('Operation cancelled');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!shouldOverwrite) {
|
|
326
|
+
throw new Error('Operation cancelled by user');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Files will be overwritten during copyTemplateFiles
|
|
331
|
+
}
|
|
332
|
+
} else if (targetDir !== '.') {
|
|
333
|
+
mkdirSync(targetPath, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handle current directory case
|
|
337
|
+
if (targetDir === '.' && !isDirectoryEmpty(targetPath)) {
|
|
338
|
+
if (!force) {
|
|
339
|
+
const shouldOverwrite = await confirm({
|
|
340
|
+
message:
|
|
341
|
+
'Current directory is not empty. Do you want to overwrite existing files?'
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (isCancel(shouldOverwrite)) {
|
|
345
|
+
cancel('Operation cancelled');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!shouldOverwrite) {
|
|
350
|
+
throw new Error('Operation cancelled by user');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
copyTemplateFiles(templatePath, targetPath, variables);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function copyTemplateFiles(
|
|
359
|
+
templatePath: string,
|
|
360
|
+
targetPath: string,
|
|
361
|
+
variables: TemplateVariables
|
|
362
|
+
): void {
|
|
363
|
+
const files = readdirSync(templatePath);
|
|
364
|
+
|
|
365
|
+
for (const file of files) {
|
|
366
|
+
const filePath = join(templatePath, file);
|
|
367
|
+
const targetFilePath = join(targetPath, file);
|
|
368
|
+
const stat = statSync(filePath);
|
|
369
|
+
|
|
370
|
+
if (stat.isDirectory()) {
|
|
371
|
+
mkdirSync(targetFilePath, { recursive: true });
|
|
372
|
+
copyTemplateFiles(filePath, targetFilePath, variables);
|
|
373
|
+
} else {
|
|
374
|
+
let content = readFileSync(filePath, 'utf-8');
|
|
375
|
+
|
|
376
|
+
// Replace all template variables using the utility function
|
|
377
|
+
content = replaceTemplateVariables(content, variables);
|
|
378
|
+
|
|
379
|
+
writeFileSync(targetFilePath, content);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export default createProject;
|
|
385
|
+
|
|
386
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
387
|
+
createProject().catch((error) => {
|
|
388
|
+
console.error('Error creating project:', error);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { createProject } from './index';
|
|
7
|
+
|
|
8
|
+
// Test utilities
|
|
9
|
+
async function createTempDir(prefix = 'esmx-test-'): Promise<string> {
|
|
10
|
+
return mkdtemp(join(tmpdir(), prefix));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function cleanupTempDir(tempDir: string): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.warn(`Failed to cleanup temp directory: ${tempDir}`, error);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('create-esmx integration tests', () => {
|
|
22
|
+
let tmpDir: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
tmpDir = await createTempDir();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
await cleanupTempDir(tmpDir);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should create project with vue2 template', async () => {
|
|
33
|
+
const projectPath = join(tmpDir, 'test-project');
|
|
34
|
+
|
|
35
|
+
await createProject({
|
|
36
|
+
argv: ['test-project', '--template', 'vue2'],
|
|
37
|
+
cwd: tmpDir,
|
|
38
|
+
userAgent: 'npm/test'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Verify project directory exists
|
|
42
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
43
|
+
|
|
44
|
+
// Verify essential common files exist
|
|
45
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
46
|
+
expect(existsSync(join(projectPath, 'tsconfig.json'))).toBe(true);
|
|
47
|
+
expect(existsSync(join(projectPath, 'README.md'))).toBe(true);
|
|
48
|
+
|
|
49
|
+
// Verify src directory exists
|
|
50
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
51
|
+
|
|
52
|
+
// Verify Esmx common entry files exist
|
|
53
|
+
expect(existsSync(join(projectPath, 'src/entry.client.ts'))).toBe(true);
|
|
54
|
+
expect(existsSync(join(projectPath, 'src/entry.node.ts'))).toBe(true);
|
|
55
|
+
expect(existsSync(join(projectPath, 'src/entry.server.ts'))).toBe(true);
|
|
56
|
+
expect(existsSync(join(projectPath, 'src/create-app.ts'))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle --force parameter correctly', async () => {
|
|
60
|
+
const projectPath = join(tmpDir, 'test-project');
|
|
61
|
+
|
|
62
|
+
// Create project first time
|
|
63
|
+
await createProject({
|
|
64
|
+
argv: ['test-project', '--template', 'vue2'],
|
|
65
|
+
cwd: tmpDir,
|
|
66
|
+
userAgent: 'npm/test'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Verify project exists
|
|
70
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
71
|
+
|
|
72
|
+
// Create project again with force flag
|
|
73
|
+
await createProject({
|
|
74
|
+
argv: ['test-project', '--template', 'vue2', '--force'],
|
|
75
|
+
cwd: tmpDir,
|
|
76
|
+
userAgent: 'npm/test'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Verify project still exists and is valid
|
|
80
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
81
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
82
|
+
|
|
83
|
+
// Verify Esmx common entry files still exist after force overwrite
|
|
84
|
+
expect(existsSync(join(projectPath, 'src/entry.client.ts'))).toBe(true);
|
|
85
|
+
expect(existsSync(join(projectPath, 'src/entry.node.ts'))).toBe(true);
|
|
86
|
+
expect(existsSync(join(projectPath, 'src/entry.server.ts'))).toBe(true);
|
|
87
|
+
expect(existsSync(join(projectPath, 'src/create-app.ts'))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should show help information', async () => {
|
|
91
|
+
// Mock console.log to capture help output
|
|
92
|
+
const originalLog = console.log;
|
|
93
|
+
const logOutput: string[] = [];
|
|
94
|
+
console.log = (...args: any[]) => {
|
|
95
|
+
logOutput.push(args.join(' '));
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await createProject({
|
|
100
|
+
argv: ['--help'],
|
|
101
|
+
cwd: tmpDir,
|
|
102
|
+
userAgent: 'npm/test'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const output = logOutput.join('\n');
|
|
106
|
+
expect(output).toContain('Usage');
|
|
107
|
+
expect(output).toContain('Options');
|
|
108
|
+
expect(output).toContain('Examples');
|
|
109
|
+
} finally {
|
|
110
|
+
console.log = originalLog;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should show version information', async () => {
|
|
115
|
+
// Mock console.log to capture version output
|
|
116
|
+
const originalLog = console.log;
|
|
117
|
+
const logOutput: string[] = [];
|
|
118
|
+
console.log = (...args: any[]) => {
|
|
119
|
+
logOutput.push(args.join(' '));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await createProject({
|
|
124
|
+
argv: ['--version'],
|
|
125
|
+
cwd: tmpDir,
|
|
126
|
+
userAgent: 'npm/test'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const output = logOutput.join('\n');
|
|
130
|
+
expect(output).toMatch(/^\d+\.\d+\.\d+/);
|
|
131
|
+
} finally {
|
|
132
|
+
console.log = originalLog;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle creating directory when target directory does not exist', async () => {
|
|
137
|
+
const projectPath = join(tmpDir, 'non-existent-parent', 'test-project');
|
|
138
|
+
|
|
139
|
+
await createProject({
|
|
140
|
+
argv: ['non-existent-parent/test-project', '--template', 'vue2'],
|
|
141
|
+
cwd: tmpDir,
|
|
142
|
+
userAgent: 'npm/test'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Verify project was created in nested directory
|
|
146
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
147
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
148
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle force overwrite for non-empty directory', async () => {
|
|
152
|
+
const projectPath = join(tmpDir, 'test-project');
|
|
153
|
+
|
|
154
|
+
// Create directory with some files
|
|
155
|
+
await mkdir(projectPath, { recursive: true });
|
|
156
|
+
await writeFile(
|
|
157
|
+
join(projectPath, 'existing-file.txt'),
|
|
158
|
+
'existing content'
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Create project with force flag in non-empty directory
|
|
162
|
+
await createProject({
|
|
163
|
+
argv: ['test-project', '--template', 'vue2', '--force'],
|
|
164
|
+
cwd: tmpDir,
|
|
165
|
+
userAgent: 'npm/test'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Verify project was created successfully
|
|
169
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
170
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should handle force overwrite in current directory', async () => {
|
|
174
|
+
// Create some files in current directory
|
|
175
|
+
const testFile = join(tmpDir, 'existing-file.txt');
|
|
176
|
+
await writeFile(testFile, 'existing content');
|
|
177
|
+
|
|
178
|
+
// Create project in current directory with force flag
|
|
179
|
+
await createProject({
|
|
180
|
+
argv: ['.', '--template', 'vue2', '--force'],
|
|
181
|
+
cwd: tmpDir,
|
|
182
|
+
userAgent: 'npm/test'
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Verify project was created successfully in current directory
|
|
186
|
+
expect(existsSync(join(tmpDir, 'package.json'))).toBe(true);
|
|
187
|
+
expect(existsSync(join(tmpDir, 'src'))).toBe(true);
|
|
188
|
+
expect(existsSync(join(tmpDir, 'src/entry.client.ts'))).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should create project in current directory when target is "."', async () => {
|
|
192
|
+
// Create project in current directory
|
|
193
|
+
await createProject({
|
|
194
|
+
argv: ['.', '--template', 'vue2'],
|
|
195
|
+
cwd: tmpDir,
|
|
196
|
+
userAgent: 'npm/test'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Verify project was created in current directory
|
|
200
|
+
expect(existsSync(join(tmpDir, 'package.json'))).toBe(true);
|
|
201
|
+
expect(existsSync(join(tmpDir, 'src'))).toBe(true);
|
|
202
|
+
expect(existsSync(join(tmpDir, 'src/entry.client.ts'))).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle various project name formats', async () => {
|
|
206
|
+
// Test with different naming styles
|
|
207
|
+
const testCases = [
|
|
208
|
+
'simple-name',
|
|
209
|
+
'nested/project-name',
|
|
210
|
+
'deep/nested/project-name'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
for (const projectName of testCases) {
|
|
214
|
+
const projectPath = join(tmpDir, projectName);
|
|
215
|
+
|
|
216
|
+
await createProject({
|
|
217
|
+
argv: [projectName, '--template', 'vue2'],
|
|
218
|
+
cwd: tmpDir,
|
|
219
|
+
userAgent: 'npm/test'
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
223
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|