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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/README.zh-CN.md +52 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.integration.test.d.ts +1 -0
  6. package/dist/cli.integration.test.mjs +238 -0
  7. package/dist/cli.mjs +166 -0
  8. package/dist/create.d.ts +2 -0
  9. package/dist/create.mjs +6 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.mjs +2 -0
  12. package/dist/project.d.ts +5 -0
  13. package/dist/project.mjs +46 -0
  14. package/dist/project.test.d.ts +1 -0
  15. package/dist/project.test.mjs +155 -0
  16. package/dist/template.d.ts +17 -0
  17. package/dist/template.mjs +76 -0
  18. package/dist/template.test.d.ts +1 -0
  19. package/dist/template.test.mjs +106 -0
  20. package/dist/types.d.ts +30 -0
  21. package/dist/types.mjs +0 -0
  22. package/dist/utils/index.d.ts +3 -0
  23. package/dist/utils/index.mjs +7 -0
  24. package/dist/utils/package-manager.d.ts +10 -0
  25. package/dist/utils/package-manager.mjs +49 -0
  26. package/dist/utils/package-manager.test.d.ts +4 -0
  27. package/dist/utils/package-manager.test.mjs +275 -0
  28. package/dist/utils/project-name.d.ts +48 -0
  29. package/dist/utils/project-name.mjs +42 -0
  30. package/dist/utils/project-name.test.d.ts +1 -0
  31. package/dist/utils/project-name.test.mjs +332 -0
  32. package/dist/utils/template.d.ts +19 -0
  33. package/dist/utils/template.mjs +8 -0
  34. package/dist/utils/template.test.d.ts +4 -0
  35. package/dist/utils/template.test.mjs +150 -0
  36. package/package.json +75 -0
  37. package/src/cli.integration.test.ts +289 -0
  38. package/src/cli.ts +214 -0
  39. package/src/create.ts +8 -0
  40. package/src/index.ts +3 -0
  41. package/src/project.test.ts +200 -0
  42. package/src/project.ts +75 -0
  43. package/src/template.test.ts +135 -0
  44. package/src/template.ts +117 -0
  45. package/src/types.ts +32 -0
  46. package/src/utils/index.ts +11 -0
  47. package/src/utils/package-manager.test.ts +540 -0
  48. package/src/utils/package-manager.ts +92 -0
  49. package/src/utils/project-name.test.ts +441 -0
  50. package/src/utils/project-name.ts +101 -0
  51. package/src/utils/template.test.ts +234 -0
  52. package/src/utils/template.ts +34 -0
  53. package/template/react-csr/README.md +81 -0
  54. package/template/react-csr/package.json +29 -0
  55. package/template/react-csr/src/app.css +98 -0
  56. package/template/react-csr/src/app.tsx +26 -0
  57. package/template/react-csr/src/components/hello-world.css +48 -0
  58. package/template/react-csr/src/components/hello-world.tsx +29 -0
  59. package/template/react-csr/src/create-app.tsx +9 -0
  60. package/template/react-csr/src/entry.client.ts +13 -0
  61. package/template/react-csr/src/entry.node.ts +35 -0
  62. package/template/react-csr/src/entry.server.tsx +27 -0
  63. package/template/react-csr/tsconfig.json +27 -0
  64. package/template/react-ssr/README.md +81 -0
  65. package/template/react-ssr/package.json +29 -0
  66. package/template/react-ssr/src/app.css +98 -0
  67. package/template/react-ssr/src/app.tsx +26 -0
  68. package/template/react-ssr/src/components/hello-world.css +48 -0
  69. package/template/react-ssr/src/components/hello-world.tsx +29 -0
  70. package/template/react-ssr/src/create-app.tsx +9 -0
  71. package/template/react-ssr/src/entry.client.ts +13 -0
  72. package/template/react-ssr/src/entry.node.ts +32 -0
  73. package/template/react-ssr/src/entry.server.tsx +36 -0
  74. package/template/react-ssr/tsconfig.json +27 -0
  75. package/template/shared-modules/README.md +85 -0
  76. package/template/shared-modules/package.json +28 -0
  77. package/template/shared-modules/src/entry.client.ts +50 -0
  78. package/template/shared-modules/src/entry.node.ts +67 -0
  79. package/template/shared-modules/src/entry.server.ts +299 -0
  80. package/template/shared-modules/src/index.ts +3 -0
  81. package/template/shared-modules/src/vue/index.ts +1 -0
  82. package/template/shared-modules/src/vue2/index.ts +1 -0
  83. package/template/shared-modules/tsconfig.json +26 -0
  84. package/template/vue-csr/README.md +80 -0
  85. package/template/vue-csr/package.json +26 -0
  86. package/template/vue-csr/src/app.vue +127 -0
  87. package/template/vue-csr/src/components/hello-world.vue +77 -0
  88. package/template/vue-csr/src/create-app.ts +9 -0
  89. package/template/vue-csr/src/entry.client.ts +5 -0
  90. package/template/vue-csr/src/entry.node.ts +35 -0
  91. package/template/vue-csr/src/entry.server.ts +26 -0
  92. package/template/vue-csr/tsconfig.json +26 -0
  93. package/template/vue-ssr/README.md +80 -0
  94. package/template/vue-ssr/package.json +27 -0
  95. package/template/vue-ssr/src/app.vue +127 -0
  96. package/template/vue-ssr/src/components/hello-world.vue +77 -0
  97. package/template/vue-ssr/src/create-app.ts +9 -0
  98. package/template/vue-ssr/src/entry.client.ts +5 -0
  99. package/template/vue-ssr/src/entry.node.ts +37 -0
  100. package/template/vue-ssr/src/entry.server.ts +30 -0
  101. package/template/vue-ssr/tsconfig.json +26 -0
  102. package/template/vue2-csr/README.md +80 -0
  103. package/template/vue2-csr/package.json +26 -0
  104. package/template/vue2-csr/src/app.vue +127 -0
  105. package/template/vue2-csr/src/components/hello-world.vue +77 -0
  106. package/template/vue2-csr/src/create-app.ts +11 -0
  107. package/template/vue2-csr/src/entry.client.ts +5 -0
  108. package/template/vue2-csr/src/entry.node.ts +35 -0
  109. package/template/vue2-csr/src/entry.server.ts +26 -0
  110. package/template/vue2-csr/tsconfig.json +26 -0
  111. package/template/vue2-ssr/README.md +80 -0
  112. package/template/vue2-ssr/package.json +27 -0
  113. package/template/vue2-ssr/src/app.vue +127 -0
  114. package/template/vue2-ssr/src/components/hello-world.vue +77 -0
  115. package/template/vue2-ssr/src/create-app.ts +11 -0
  116. package/template/vue2-ssr/src/entry.client.ts +5 -0
  117. package/template/vue2-ssr/src/entry.node.ts +32 -0
  118. package/template/vue2-ssr/src/entry.server.ts +37 -0
  119. 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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cli } from './cli';
4
+
5
+ cli().catch((error) => {
6
+ console.error('Error creating project:', error);
7
+ process.exit(1);
8
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { cli } from './cli';
2
+ export { getAvailableTemplates } from './template';
3
+ export type { TemplateInfo } from './types';