create-esmx 3.0.0-rc.104
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/LICENSE +21 -0
- package/README.md +52 -0
- package/README.zh-CN.md +52 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.integration.test.d.ts +1 -0
- package/dist/cli.integration.test.mjs +238 -0
- package/dist/cli.mjs +166 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.mjs +6 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +2 -0
- package/dist/project.d.ts +5 -0
- package/dist/project.mjs +46 -0
- package/dist/project.test.d.ts +1 -0
- package/dist/project.test.mjs +155 -0
- package/dist/template.d.ts +17 -0
- package/dist/template.mjs +76 -0
- package/dist/template.test.d.ts +1 -0
- package/dist/template.test.mjs +106 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.mjs +0 -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 +48 -0
- package/dist/utils/project-name.mjs +42 -0
- package/dist/utils/project-name.test.d.ts +1 -0
- package/dist/utils/project-name.test.mjs +332 -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 +75 -0
- package/src/cli.integration.test.ts +289 -0
- package/src/cli.ts +214 -0
- package/src/create.ts +8 -0
- package/src/index.ts +3 -0
- package/src/project.test.ts +200 -0
- package/src/project.ts +75 -0
- package/src/template.test.ts +135 -0
- package/src/template.ts +117 -0
- package/src/types.ts +32 -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 +441 -0
- package/src/utils/project-name.ts +101 -0
- package/src/utils/template.test.ts +234 -0
- package/src/utils/template.ts +34 -0
- package/template/react-csr/README.md +81 -0
- package/template/react-csr/package.json +29 -0
- package/template/react-csr/src/app.css +98 -0
- package/template/react-csr/src/app.tsx +26 -0
- package/template/react-csr/src/components/hello-world.css +48 -0
- package/template/react-csr/src/components/hello-world.tsx +29 -0
- package/template/react-csr/src/create-app.tsx +9 -0
- package/template/react-csr/src/entry.client.ts +13 -0
- package/template/react-csr/src/entry.node.ts +35 -0
- package/template/react-csr/src/entry.server.tsx +27 -0
- package/template/react-csr/tsconfig.json +27 -0
- package/template/react-ssr/README.md +81 -0
- package/template/react-ssr/package.json +29 -0
- package/template/react-ssr/src/app.css +98 -0
- package/template/react-ssr/src/app.tsx +26 -0
- package/template/react-ssr/src/components/hello-world.css +48 -0
- package/template/react-ssr/src/components/hello-world.tsx +29 -0
- package/template/react-ssr/src/create-app.tsx +9 -0
- package/template/react-ssr/src/entry.client.ts +13 -0
- package/template/react-ssr/src/entry.node.ts +32 -0
- package/template/react-ssr/src/entry.server.tsx +36 -0
- package/template/react-ssr/tsconfig.json +27 -0
- package/template/shared-modules/README.md +85 -0
- package/template/shared-modules/package.json +28 -0
- package/template/shared-modules/src/entry.client.ts +50 -0
- package/template/shared-modules/src/entry.node.ts +67 -0
- package/template/shared-modules/src/entry.server.ts +299 -0
- package/template/shared-modules/src/index.ts +3 -0
- package/template/shared-modules/src/vue/index.ts +1 -0
- package/template/shared-modules/src/vue2/index.ts +1 -0
- package/template/shared-modules/tsconfig.json +26 -0
- package/template/vue-csr/README.md +80 -0
- package/template/vue-csr/package.json +26 -0
- package/template/vue-csr/src/app.vue +127 -0
- package/template/vue-csr/src/components/hello-world.vue +77 -0
- package/template/vue-csr/src/create-app.ts +9 -0
- package/template/vue-csr/src/entry.client.ts +5 -0
- package/template/vue-csr/src/entry.node.ts +35 -0
- package/template/vue-csr/src/entry.server.ts +26 -0
- package/template/vue-csr/tsconfig.json +26 -0
- package/template/vue-ssr/README.md +80 -0
- package/template/vue-ssr/package.json +27 -0
- package/template/vue-ssr/src/app.vue +127 -0
- package/template/vue-ssr/src/components/hello-world.vue +77 -0
- package/template/vue-ssr/src/create-app.ts +9 -0
- package/template/vue-ssr/src/entry.client.ts +5 -0
- package/template/vue-ssr/src/entry.node.ts +37 -0
- package/template/vue-ssr/src/entry.server.ts +30 -0
- package/template/vue-ssr/tsconfig.json +26 -0
- package/template/vue2-csr/README.md +80 -0
- package/template/vue2-csr/package.json +26 -0
- package/template/vue2-csr/src/app.vue +127 -0
- package/template/vue2-csr/src/components/hello-world.vue +77 -0
- package/template/vue2-csr/src/create-app.ts +11 -0
- package/template/vue2-csr/src/entry.client.ts +5 -0
- package/template/vue2-csr/src/entry.node.ts +35 -0
- package/template/vue2-csr/src/entry.server.ts +26 -0
- package/template/vue2-csr/tsconfig.json +26 -0
- package/template/vue2-ssr/README.md +80 -0
- package/template/vue2-ssr/package.json +27 -0
- package/template/vue2-ssr/src/app.vue +127 -0
- package/template/vue2-ssr/src/components/hello-world.vue +77 -0
- package/template/vue2-ssr/src/create-app.ts +11 -0
- package/template/vue2-ssr/src/entry.client.ts +5 -0
- package/template/vue2-ssr/src/entry.node.ts +32 -0
- package/template/vue2-ssr/src/entry.server.ts +37 -0
- package/template/vue2-ssr/tsconfig.json +26 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { existsSync, readFileSync } 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 { cli } from './cli';
|
|
7
|
+
import { getAvailableTemplates } from './template';
|
|
8
|
+
|
|
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
|
+
async function verifyProjectStructure(
|
|
22
|
+
projectPath: string,
|
|
23
|
+
projectName: string
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
26
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
27
|
+
|
|
28
|
+
const requiredFiles = [
|
|
29
|
+
'package.json',
|
|
30
|
+
'tsconfig.json',
|
|
31
|
+
'README.md',
|
|
32
|
+
'src/entry.node.ts'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const file of requiredFiles) {
|
|
36
|
+
expect(existsSync(join(projectPath, file))).toBe(true);
|
|
37
|
+
if (!existsSync(join(projectPath, file))) {
|
|
38
|
+
throw new Error(`Missing required file: ${file}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check for entry.client.ts (only .ts extension is allowed)
|
|
43
|
+
const entryClientTs = join(projectPath, 'src/entry.client.ts');
|
|
44
|
+
expect(existsSync(entryClientTs)).toBe(true);
|
|
45
|
+
if (!existsSync(entryClientTs)) {
|
|
46
|
+
throw new Error('Missing required file: src/entry.client.ts');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const packageJson = JSON.parse(
|
|
50
|
+
readFileSync(join(projectPath, 'package.json'), 'utf-8')
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const typeCheckCommands = ['vue-tsc --noEmit', 'tsc --noEmit'];
|
|
54
|
+
const typeGenCommands = [
|
|
55
|
+
'vue-tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src',
|
|
56
|
+
'tsc --declaration --emitDeclarationOnly --outDir dist/src && tsc-alias -p tsconfig.json --outDir dist/src'
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
expect(packageJson).toMatchObject({
|
|
60
|
+
name: projectName,
|
|
61
|
+
type: 'module',
|
|
62
|
+
private: true,
|
|
63
|
+
scripts: {
|
|
64
|
+
dev: 'esmx dev',
|
|
65
|
+
build: 'esmx build',
|
|
66
|
+
preview: 'esmx preview',
|
|
67
|
+
start: 'esmx start'
|
|
68
|
+
},
|
|
69
|
+
dependencies: {
|
|
70
|
+
'@esmx/core': expect.any(String)
|
|
71
|
+
},
|
|
72
|
+
devDependencies: {
|
|
73
|
+
typescript: expect.any(String),
|
|
74
|
+
'@types/node': expect.any(String),
|
|
75
|
+
'tsc-alias': expect.any(String)
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(packageJson.scripts['lint:type']).toBeOneOf(typeCheckCommands);
|
|
80
|
+
expect(packageJson.scripts['build:type']).toBeOneOf(typeGenCommands);
|
|
81
|
+
|
|
82
|
+
const tsconfig = JSON.parse(
|
|
83
|
+
readFileSync(join(projectPath, 'tsconfig.json'), 'utf-8')
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(tsconfig).toMatchObject({
|
|
87
|
+
compilerOptions: {
|
|
88
|
+
module: 'ESNext',
|
|
89
|
+
moduleResolution: 'node',
|
|
90
|
+
target: 'ESNext',
|
|
91
|
+
strict: true,
|
|
92
|
+
baseUrl: '.',
|
|
93
|
+
paths: {
|
|
94
|
+
[`${projectName}/src/*`]: ['./src/*'],
|
|
95
|
+
[`${projectName}/*`]: ['./*']
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
include: ['src'],
|
|
99
|
+
exclude: ['dist', 'node_modules']
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const readmeContent = readFileSync(join(projectPath, 'README.md'), 'utf-8');
|
|
103
|
+
expect(readmeContent.length).toBeGreaterThan(0);
|
|
104
|
+
expect(readmeContent).toContain(projectName);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe('create-esmx CLI integration tests', () => {
|
|
108
|
+
let tmpDir: string;
|
|
109
|
+
|
|
110
|
+
beforeEach(async () => {
|
|
111
|
+
tmpDir = await createTempDir();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
afterEach(async () => {
|
|
115
|
+
await cleanupTempDir(tmpDir);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should create project with all available templates', async () => {
|
|
119
|
+
const templates = getAvailableTemplates();
|
|
120
|
+
expect(templates.length).toBeGreaterThan(0);
|
|
121
|
+
|
|
122
|
+
for (const template of templates) {
|
|
123
|
+
const projectName = `test-${template.folder}`;
|
|
124
|
+
const projectPath = join(tmpDir, projectName);
|
|
125
|
+
|
|
126
|
+
await cli({
|
|
127
|
+
argv: [projectName, '--template', template.folder],
|
|
128
|
+
cwd: tmpDir,
|
|
129
|
+
userAgent: 'npm/test'
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await verifyProjectStructure(projectPath, projectName);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should handle --force parameter correctly', async () => {
|
|
137
|
+
const projectPath = join(tmpDir, 'test-project');
|
|
138
|
+
|
|
139
|
+
await cli({
|
|
140
|
+
argv: ['test-project', '--template', 'vue2-csr'],
|
|
141
|
+
cwd: tmpDir,
|
|
142
|
+
userAgent: 'npm/test'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
146
|
+
|
|
147
|
+
await cli({
|
|
148
|
+
argv: ['test-project', '--template', 'vue2-csr', '--force'],
|
|
149
|
+
cwd: tmpDir,
|
|
150
|
+
userAgent: 'npm/test'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
154
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
155
|
+
expect(existsSync(join(projectPath, 'src/entry.client.ts'))).toBe(true);
|
|
156
|
+
expect(existsSync(join(projectPath, 'src/entry.node.ts'))).toBe(true);
|
|
157
|
+
expect(existsSync(join(projectPath, 'src/entry.server.ts'))).toBe(true);
|
|
158
|
+
expect(existsSync(join(projectPath, 'src/create-app.ts'))).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should show help information', async () => {
|
|
162
|
+
const originalLog = console.log;
|
|
163
|
+
const logOutput: string[] = [];
|
|
164
|
+
console.log = (...args: any[]) => {
|
|
165
|
+
logOutput.push(args.join(' '));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await cli({
|
|
170
|
+
argv: ['--help'],
|
|
171
|
+
cwd: tmpDir,
|
|
172
|
+
userAgent: 'npm/test'
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const output = logOutput.join('\n');
|
|
176
|
+
expect(output).toContain('Usage');
|
|
177
|
+
expect(output).toContain('Options');
|
|
178
|
+
expect(output).toContain('Examples');
|
|
179
|
+
} finally {
|
|
180
|
+
console.log = originalLog;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should show version information', async () => {
|
|
185
|
+
const originalLog = console.log;
|
|
186
|
+
const logOutput: string[] = [];
|
|
187
|
+
console.log = (...args: any[]) => {
|
|
188
|
+
logOutput.push(args.join(' '));
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await cli({
|
|
193
|
+
argv: ['--version'],
|
|
194
|
+
cwd: tmpDir,
|
|
195
|
+
userAgent: 'npm/test'
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const output = logOutput.join('\n');
|
|
199
|
+
expect(output).toMatch(/^\d+\.\d+\.\d+/);
|
|
200
|
+
} finally {
|
|
201
|
+
console.log = originalLog;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle creating directory when target directory does not exist', async () => {
|
|
206
|
+
const projectPath = join(tmpDir, 'non-existent-parent', 'test-project');
|
|
207
|
+
|
|
208
|
+
await cli({
|
|
209
|
+
argv: [
|
|
210
|
+
'non-existent-parent/test-project',
|
|
211
|
+
'--template',
|
|
212
|
+
'vue2-csr'
|
|
213
|
+
],
|
|
214
|
+
cwd: tmpDir,
|
|
215
|
+
userAgent: 'npm/test'
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
219
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
220
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should handle force overwrite for non-empty directory', async () => {
|
|
224
|
+
const projectPath = join(tmpDir, 'test-project');
|
|
225
|
+
|
|
226
|
+
await mkdir(projectPath, { recursive: true });
|
|
227
|
+
await writeFile(
|
|
228
|
+
join(projectPath, 'existing-file.txt'),
|
|
229
|
+
'existing content'
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
await cli({
|
|
233
|
+
argv: ['test-project', '--template', 'vue2-csr', '--force'],
|
|
234
|
+
cwd: tmpDir,
|
|
235
|
+
userAgent: 'npm/test'
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
239
|
+
expect(existsSync(join(projectPath, 'src'))).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle force overwrite in current directory', async () => {
|
|
243
|
+
const testFile = join(tmpDir, 'existing-file.txt');
|
|
244
|
+
await writeFile(testFile, 'existing content');
|
|
245
|
+
|
|
246
|
+
await cli({
|
|
247
|
+
argv: ['.', '--template', 'vue2-csr', '--force'],
|
|
248
|
+
cwd: tmpDir,
|
|
249
|
+
userAgent: 'npm/test'
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(existsSync(join(tmpDir, 'package.json'))).toBe(true);
|
|
253
|
+
expect(existsSync(join(tmpDir, 'src'))).toBe(true);
|
|
254
|
+
expect(existsSync(join(tmpDir, 'src/entry.client.ts'))).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should create project in current directory when target is "."', async () => {
|
|
258
|
+
await cli({
|
|
259
|
+
argv: ['.', '--template', 'vue2-csr'],
|
|
260
|
+
cwd: tmpDir,
|
|
261
|
+
userAgent: 'npm/test'
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(existsSync(join(tmpDir, 'package.json'))).toBe(true);
|
|
265
|
+
expect(existsSync(join(tmpDir, 'src'))).toBe(true);
|
|
266
|
+
expect(existsSync(join(tmpDir, 'src/entry.client.ts'))).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should handle various project name formats', async () => {
|
|
270
|
+
const testCases = [
|
|
271
|
+
'simple-name',
|
|
272
|
+
'nested/project-name',
|
|
273
|
+
'deep/nested/project-name'
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
for (const projectName of testCases) {
|
|
277
|
+
const projectPath = join(tmpDir, projectName);
|
|
278
|
+
|
|
279
|
+
await cli({
|
|
280
|
+
argv: [projectName, '--template', 'vue2-csr'],
|
|
281
|
+
cwd: tmpDir,
|
|
282
|
+
userAgent: 'npm/test'
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(existsSync(projectPath)).toBe(true);
|
|
286
|
+
expect(existsSync(join(projectPath, 'package.json'))).toBe(true);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cancel,
|
|
3
|
+
intro,
|
|
4
|
+
isCancel,
|
|
5
|
+
note,
|
|
6
|
+
outro,
|
|
7
|
+
select,
|
|
8
|
+
text
|
|
9
|
+
} from '@clack/prompts';
|
|
10
|
+
import minimist from 'minimist';
|
|
11
|
+
import color from 'picocolors';
|
|
12
|
+
import { createProjectFromTemplate } from './project';
|
|
13
|
+
import { getAvailableTemplates, getEsmxVersion } from './template';
|
|
14
|
+
import type { CliOptions } from './types';
|
|
15
|
+
import { formatProjectName, getCommand } from './utils/index';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Display help information
|
|
19
|
+
*/
|
|
20
|
+
function showHelp(userAgent?: string): void {
|
|
21
|
+
const createCmd = getCommand('create', userAgent);
|
|
22
|
+
|
|
23
|
+
console.log(`
|
|
24
|
+
${color.reset(color.bold(color.blue('🚀 Create Esmx Project')))}
|
|
25
|
+
|
|
26
|
+
${color.bold('Usage:')}
|
|
27
|
+
${createCmd} [project-name]
|
|
28
|
+
${createCmd} [project-name] [options]
|
|
29
|
+
|
|
30
|
+
${color.bold('Options:')}
|
|
31
|
+
-t, --template <template> Template to use (default: vue2-csr)
|
|
32
|
+
-n, --name <name> Project name or path
|
|
33
|
+
-f, --force Force overwrite existing directory
|
|
34
|
+
-h, --help Show help information
|
|
35
|
+
-v, --version Show version number
|
|
36
|
+
|
|
37
|
+
${color.bold('Examples:')}
|
|
38
|
+
${createCmd} my-project
|
|
39
|
+
${createCmd} my-project -t vue2-csr
|
|
40
|
+
${createCmd} my-project --force
|
|
41
|
+
${createCmd} . -f -t vue2-csr
|
|
42
|
+
|
|
43
|
+
${color.bold('Available Templates:')}
|
|
44
|
+
${getAvailableTemplates()
|
|
45
|
+
.map((t) => ` ${t.folder.padEnd(25)} ${t.description}`)
|
|
46
|
+
.join('\n')}
|
|
47
|
+
|
|
48
|
+
For more information, visit: ${color.cyan('https://esmx.dev')}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get project name from arguments or prompt user
|
|
54
|
+
*/
|
|
55
|
+
async function getProjectName(
|
|
56
|
+
argName?: string,
|
|
57
|
+
positionalName?: string
|
|
58
|
+
): Promise<string | symbol> {
|
|
59
|
+
const providedName = argName || positionalName;
|
|
60
|
+
if (providedName) {
|
|
61
|
+
return providedName;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const projectName = await text({
|
|
65
|
+
message: 'Project name or path:',
|
|
66
|
+
placeholder: 'my-esmx-project',
|
|
67
|
+
validate: (value: string) => {
|
|
68
|
+
if (!value.trim()) {
|
|
69
|
+
return 'Project name or path is required';
|
|
70
|
+
}
|
|
71
|
+
if (!/^[a-zA-Z0-9_./@-]+$/.test(value.trim())) {
|
|
72
|
+
return 'Project name or path should only contain letters, numbers, hyphens, underscores, dots, and slashes';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return String(projectName).trim();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get template type from arguments or prompt user
|
|
82
|
+
*/
|
|
83
|
+
async function getTemplateType(argTemplate?: string): Promise<string> {
|
|
84
|
+
const availableTemplates = getAvailableTemplates();
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
argTemplate &&
|
|
88
|
+
availableTemplates.some((t) => t.folder === argTemplate)
|
|
89
|
+
) {
|
|
90
|
+
return argTemplate;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const options = availableTemplates.map((t) => ({
|
|
94
|
+
label: color.reset(color.gray(`${t.folder} - `) + color.bold(t.name)),
|
|
95
|
+
value: t.folder,
|
|
96
|
+
hint: t.description
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const template = await select({
|
|
100
|
+
message: 'Select a template:',
|
|
101
|
+
options: options
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return String(template);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Main function to create a project
|
|
109
|
+
*/
|
|
110
|
+
export async function cli(options: CliOptions = {}): Promise<void> {
|
|
111
|
+
const { argv, cwd, userAgent, version } = options;
|
|
112
|
+
const commandLineArgs = argv || process.argv.slice(2);
|
|
113
|
+
const workingDir = cwd || process.cwd();
|
|
114
|
+
|
|
115
|
+
const parsedArgs = minimist(commandLineArgs, {
|
|
116
|
+
string: ['template', 'name'],
|
|
117
|
+
boolean: ['help', 'version', 'force'],
|
|
118
|
+
alias: {
|
|
119
|
+
t: 'template',
|
|
120
|
+
n: 'name',
|
|
121
|
+
f: 'force',
|
|
122
|
+
h: 'help',
|
|
123
|
+
v: 'version'
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (parsedArgs.help) {
|
|
128
|
+
showHelp(userAgent);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsedArgs.version) {
|
|
133
|
+
console.log(getEsmxVersion());
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log();
|
|
138
|
+
intro(
|
|
139
|
+
color.reset(
|
|
140
|
+
color.bold(color.blue('🚀 Welcome to Esmx Project Creator!'))
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const projectNameInput = await getProjectName(
|
|
145
|
+
parsedArgs.name,
|
|
146
|
+
parsedArgs._[0]
|
|
147
|
+
);
|
|
148
|
+
if (isCancel(projectNameInput)) {
|
|
149
|
+
cancel('Operation cancelled');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let name: string;
|
|
154
|
+
let root: string;
|
|
155
|
+
|
|
156
|
+
if (parsedArgs.name && parsedArgs._[0]) {
|
|
157
|
+
name = parsedArgs.name;
|
|
158
|
+
root = formatProjectName(parsedArgs._[0], workingDir).root;
|
|
159
|
+
} else {
|
|
160
|
+
const result = formatProjectName(projectNameInput, workingDir);
|
|
161
|
+
name = result.name;
|
|
162
|
+
root = result.root;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const templateType = await getTemplateType(parsedArgs.template);
|
|
166
|
+
if (isCancel(templateType)) {
|
|
167
|
+
cancel('Operation cancelled');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const installCommand = getCommand('install', userAgent);
|
|
172
|
+
const devCommand = getCommand('dev', userAgent);
|
|
173
|
+
const buildCommand = getCommand('build', userAgent);
|
|
174
|
+
const startCommand = getCommand('start', userAgent);
|
|
175
|
+
const buildTypeCommand = getCommand('build:type', userAgent);
|
|
176
|
+
const lintTypeCommand = getCommand('lint:type', userAgent);
|
|
177
|
+
|
|
178
|
+
await createProjectFromTemplate(
|
|
179
|
+
root,
|
|
180
|
+
templateType,
|
|
181
|
+
workingDir,
|
|
182
|
+
parsedArgs.force,
|
|
183
|
+
{
|
|
184
|
+
projectName: name,
|
|
185
|
+
esmxVersion: version || getEsmxVersion(),
|
|
186
|
+
installCommand,
|
|
187
|
+
devCommand,
|
|
188
|
+
buildCommand,
|
|
189
|
+
startCommand,
|
|
190
|
+
buildTypeCommand,
|
|
191
|
+
lintTypeCommand
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
const installCmd = installCommand;
|
|
195
|
+
const devCmd = devCommand;
|
|
196
|
+
|
|
197
|
+
const targetDirForDisplay =
|
|
198
|
+
projectNameInput === '.' ? '.' : projectNameInput;
|
|
199
|
+
|
|
200
|
+
const steps = [
|
|
201
|
+
projectNameInput !== '.' ? `cd ${targetDirForDisplay}` : null,
|
|
202
|
+
installCmd,
|
|
203
|
+
`git init ${color.gray('(optional)')}`,
|
|
204
|
+
devCmd
|
|
205
|
+
].filter(Boolean);
|
|
206
|
+
|
|
207
|
+
const nextSteps = steps.map((step, index) => {
|
|
208
|
+
return color.reset(`${index + 1}. ${color.cyan(step)}`);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
note(nextSteps.join('\n'), 'Next steps');
|
|
212
|
+
|
|
213
|
+
outro(color.reset(color.green('Happy coding! 🎉')));
|
|
214
|
+
}
|
package/src/create.ts
ADDED
package/src/index.ts
ADDED