create-dev-to 1.1.0 → 1.3.0

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.js CHANGED
@@ -1,616 +1,648 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { spawn, execSync } from 'node:child_process';
5
- import process from 'node:process';
6
- import { randomUUID } from 'node:crypto';
7
- import readline from 'node:readline';
8
- import * as clack from '@clack/prompts';
9
- import { red, cyan, yellow, green, dim } from 'kolorist';
10
- import { InstallLogger } from './installLogger.js';
11
- import { displayInstallSummary } from './visualComponents.js';
12
- const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'];
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { spawn, execSync } from "node:child_process";
5
+ import process from "node:process";
6
+ import { randomUUID } from "node:crypto";
7
+ import readline from "node:readline";
8
+ import * as clack from "@clack/prompts";
9
+ import { red, cyan, yellow, green, dim } from "kolorist";
10
+ import { InstallLogger } from "./installLogger.js";
11
+ import { displayInstallSummary } from "./visualComponents.js";
12
+ const PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
13
+ const __BUILD_INFO__ = {
14
+ commit: "519bc16",
15
+ branch: "main",
16
+ buildTime: "2026-01-10 14:05",
17
+ version: "1.3.0"
18
+ };
13
19
  const FRAMEWORKS = [
14
- {
15
- name: 'react',
16
- display: 'React',
17
- color: cyan,
18
- supported: true,
19
- },
20
- {
21
- name: 'vue',
22
- display: 'Vue',
23
- color: green,
24
- supported: false,
25
- },
26
- {
27
- name: 'svelte',
28
- display: 'Svelte',
29
- color: red,
30
- supported: false,
31
- },
32
- {
33
- name: 'solid',
34
- display: 'Solid',
35
- color: cyan,
36
- supported: false,
37
- },
38
- {
39
- name: 'preact',
40
- display: 'Preact',
41
- color: cyan,
42
- supported: false,
43
- },
44
- {
45
- name: 'lit',
46
- display: 'Lit',
47
- color: yellow,
48
- supported: false,
49
- },
50
- {
51
- name: 'qwik',
52
- display: 'Qwik',
53
- color: cyan,
54
- supported: false,
55
- },
56
- {
57
- name: 'vanilla',
58
- display: 'Vanilla',
59
- color: yellow,
60
- supported: false,
61
- },
20
+ {
21
+ name: "react",
22
+ display: "React",
23
+ color: cyan,
24
+ supported: true
25
+ },
26
+ {
27
+ name: "vue",
28
+ display: "Vue",
29
+ color: green,
30
+ supported: false
31
+ },
32
+ {
33
+ name: "svelte",
34
+ display: "Svelte",
35
+ color: red,
36
+ supported: false
37
+ },
38
+ {
39
+ name: "solid",
40
+ display: "Solid",
41
+ color: cyan,
42
+ supported: false
43
+ },
44
+ {
45
+ name: "preact",
46
+ display: "Preact",
47
+ color: cyan,
48
+ supported: false
49
+ },
50
+ {
51
+ name: "lit",
52
+ display: "Lit",
53
+ color: yellow,
54
+ supported: false
55
+ },
56
+ {
57
+ name: "qwik",
58
+ display: "Qwik",
59
+ color: cyan,
60
+ supported: false
61
+ },
62
+ {
63
+ name: "vanilla",
64
+ display: "Vanilla",
65
+ color: yellow,
66
+ supported: false
67
+ }
62
68
  ];
63
69
  const REACT_TEMPLATES = [
64
- {
65
- name: 'react-ts',
66
- display: 'TypeScript',
67
- color: cyan,
68
- },
69
- {
70
- name: 'react-swc-ts',
71
- display: 'TypeScript + SWC',
72
- color: cyan,
73
- },
74
- {
75
- name: 'react',
76
- display: 'JavaScript',
77
- color: yellow,
78
- },
79
- {
80
- name: 'react-swc',
81
- display: 'JavaScript + SWC',
82
- color: yellow,
83
- },
70
+ {
71
+ name: "react-ts",
72
+ display: "TypeScript",
73
+ color: cyan
74
+ },
75
+ {
76
+ name: "react",
77
+ display: "JavaScript",
78
+ color: yellow
79
+ }
84
80
  ];
85
81
  const TEMPLATE_SOURCES = [
86
- {
87
- name: 'GitHub',
88
- getCloneCommand: (template, targetDir, packageManager) => {
89
- const pmCommand = getDegitCommandForPM(packageManager);
90
- return {
91
- command: pmCommand.command,
92
- args: pmCommand.args('degit', [`vitejs/vite/packages/create-vite/template-${template}`, targetDir, '--force']),
93
- };
94
- },
95
- },
96
- {
97
- name: 'Gitee Mirror (国内镜像)',
98
- isGitBased: true,
99
- getCloneCommand: (_template, targetDir) => ({
100
- command: 'git',
101
- args: [
102
- 'clone',
103
- '--depth',
104
- '1',
105
- 'https://gitee.com/mirrors/ViteJS.git',
106
- targetDir,
107
- ],
108
- }),
109
- },
82
+ {
83
+ name: "GitHub",
84
+ getCloneCommand: (template, targetDir, packageManager) => {
85
+ const pmCommand = getDegitCommandForPM(packageManager);
86
+ return {
87
+ command: pmCommand.command,
88
+ args: pmCommand.args("degit", [`vitejs/vite/packages/create-vite/template-${template}`, targetDir, "--force"])
89
+ };
90
+ }
91
+ },
92
+ {
93
+ name: "Gitee Mirror (\u56FD\u5185\u955C\u50CF)",
94
+ isGitBased: true,
95
+ getCloneCommand: (_template, targetDir) => ({
96
+ command: "git",
97
+ args: [
98
+ "clone",
99
+ "--depth",
100
+ "1",
101
+ "https://gitee.com/mirrors/ViteJS.git",
102
+ targetDir
103
+ ]
104
+ })
105
+ }
110
106
  ];
111
107
  const PM_CONFIGS = {
112
- pnpm: {
113
- install: 'pnpm install',
114
- dev: 'pnpm dev',
115
- },
116
- npm: {
117
- install: 'npm install',
118
- dev: 'npm run dev',
119
- },
120
- yarn: {
121
- install: 'yarn',
122
- dev: 'yarn dev',
123
- },
124
- bun: {
125
- install: 'bun install',
126
- dev: 'bun dev',
127
- },
108
+ pnpm: {
109
+ install: "pnpm install",
110
+ dev: "pnpm dev"
111
+ },
112
+ npm: {
113
+ install: "npm install",
114
+ dev: "npm run dev"
115
+ },
116
+ yarn: {
117
+ install: "yarn",
118
+ dev: "yarn dev"
119
+ },
120
+ bun: {
121
+ install: "bun install",
122
+ dev: "bun dev"
123
+ }
128
124
  };
129
125
  function checkCommandExists(command) {
130
- try {
131
- execSync(`${command} --version`, { stdio: 'ignore' });
132
- return true;
133
- }
134
- catch {
135
- return false;
136
- }
126
+ try {
127
+ execSync(`${command} --version`, { stdio: "ignore" });
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
137
132
  }
138
133
  function detectPackageManager(userAgent) {
139
- for (const pm of PACKAGE_MANAGERS) {
140
- if (userAgent.includes(pm))
141
- return pm;
142
- }
143
- for (const pm of PACKAGE_MANAGERS) {
144
- if (checkCommandExists(pm))
145
- return pm;
146
- }
147
- return null;
134
+ for (const pm of PACKAGE_MANAGERS) {
135
+ if (userAgent.includes(pm)) return pm;
136
+ }
137
+ for (const pm of PACKAGE_MANAGERS) {
138
+ if (checkCommandExists(pm)) return pm;
139
+ }
140
+ return null;
148
141
  }
149
142
  function formatTargetDir(targetDir) {
150
- return targetDir?.trim().replace(/\/+$/g, '');
143
+ return targetDir?.trim().replace(/\/+$/g, "");
151
144
  }
152
- function isEmpty(path) {
153
- const files = fs.readdirSync(path);
154
- return files.length === 0 || (files.length === 1 && files[0] === '.git');
145
+ function isEmpty(path2) {
146
+ const files = fs.readdirSync(path2);
147
+ return files.length === 0 || files.length === 1 && files[0] === ".git";
155
148
  }
156
149
  function toValidPackageName(projectName) {
157
- return projectName
158
- .trim()
159
- .toLowerCase()
160
- .replace(/\s+/g, '-')
161
- .replace(/^[._]/, '')
162
- .replace(/[^a-z\d\-~]+/g, '-');
150
+ return projectName.trim().toLowerCase().replace(/\s+/g, "-").replace(/^[._]/, "").replace(/[^a-z\d\-~]+/g, "-");
163
151
  }
164
152
  function emptyDir(dir) {
165
- if (!fs.existsSync(dir)) {
166
- return;
167
- }
168
- for (const file of fs.readdirSync(dir)) {
169
- if (file === '.git') {
170
- continue;
171
- }
172
- fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
153
+ if (!fs.existsSync(dir)) {
154
+ return;
155
+ }
156
+ for (const file of fs.readdirSync(dir)) {
157
+ if (file === ".git") {
158
+ continue;
173
159
  }
160
+ fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
161
+ }
174
162
  }
175
163
  function copyDir(srcDir, destDir) {
176
- fs.mkdirSync(destDir, { recursive: true });
177
- for (const file of fs.readdirSync(srcDir)) {
178
- const srcFile = path.resolve(srcDir, file);
179
- const destFile = path.resolve(destDir, file);
180
- copy(srcFile, destFile);
181
- }
164
+ fs.mkdirSync(destDir, { recursive: true });
165
+ for (const file of fs.readdirSync(srcDir)) {
166
+ const srcFile = path.resolve(srcDir, file);
167
+ const destFile = path.resolve(destDir, file);
168
+ copy(srcFile, destFile);
169
+ }
182
170
  }
183
171
  function copy(src, dest) {
184
- const stat = fs.statSync(src);
185
- if (stat.isDirectory()) {
186
- copyDir(src, dest);
187
- }
188
- else {
189
- fs.copyFileSync(src, dest);
190
- }
172
+ const stat = fs.statSync(src);
173
+ if (stat.isDirectory()) {
174
+ copyDir(src, dest);
175
+ } else {
176
+ fs.copyFileSync(src, dest);
177
+ }
191
178
  }
192
179
  function run(command, args, cwd) {
193
- return new Promise((resolve, reject) => {
194
- const child = spawn(command, args, { cwd, stdio: 'inherit' });
195
- child.on('close', (code) => {
196
- if (code !== 0) {
197
- reject(new Error(`${command} ${args.join(' ')} failed`));
198
- return;
199
- }
200
- resolve();
201
- });
202
- child.on('error', reject);
180
+ return new Promise((resolve, reject) => {
181
+ const child = spawn(command, args, { cwd, stdio: "inherit" });
182
+ child.on("close", (code) => {
183
+ if (code !== 0) {
184
+ reject(new Error(`${command} ${args.join(" ")} failed`));
185
+ return;
186
+ }
187
+ resolve();
203
188
  });
189
+ child.on("error", reject);
190
+ });
204
191
  }
205
192
  async function runWithLogger(command, args, cwd, packageManager) {
206
- const logger = new InstallLogger(packageManager);
207
- return new Promise((resolve, reject) => {
208
- const child = spawn(command, args, {
209
- cwd,
210
- stdio: ['ignore', 'pipe', 'pipe'],
211
- });
212
- logger.start();
213
- if (child.stdout) {
214
- const stdoutReader = readline.createInterface({
215
- input: child.stdout,
216
- crlfDelay: Infinity,
217
- });
218
- stdoutReader.on('line', line => logger.processLine(line, 'stdout'));
219
- }
220
- if (child.stderr) {
221
- const stderrReader = readline.createInterface({
222
- input: child.stderr,
223
- crlfDelay: Infinity,
224
- });
225
- stderrReader.on('line', line => logger.processLine(line, 'stderr'));
226
- }
227
- child.on('close', async (code) => {
228
- if (code !== 0) {
229
- logger.error();
230
- reject(new Error(`${command} ${args.join(' ')} failed`));
231
- return;
232
- }
233
- const stats = await logger.finish(cwd);
234
- resolve(stats);
235
- });
236
- child.on('error', (err) => {
237
- logger.error();
238
- reject(err);
239
- });
193
+ const logger = new InstallLogger(packageManager);
194
+ return new Promise((resolve, reject) => {
195
+ const child = spawn(command, args, {
196
+ cwd,
197
+ stdio: ["ignore", "pipe", "pipe"]
198
+ });
199
+ logger.start();
200
+ if (child.stdout) {
201
+ const stdoutReader = readline.createInterface({
202
+ input: child.stdout,
203
+ crlfDelay: Infinity
204
+ });
205
+ stdoutReader.on("line", (line) => logger.processLine(line, "stdout"));
206
+ }
207
+ if (child.stderr) {
208
+ const stderrReader = readline.createInterface({
209
+ input: child.stderr,
210
+ crlfDelay: Infinity
211
+ });
212
+ stderrReader.on("line", (line) => logger.processLine(line, "stderr"));
213
+ }
214
+ child.on("close", async (code) => {
215
+ if (code !== 0) {
216
+ logger.error();
217
+ reject(new Error(`${command} ${args.join(" ")} failed`));
218
+ return;
219
+ }
220
+ const stats = await logger.finish(cwd);
221
+ resolve(stats);
240
222
  });
223
+ child.on("error", (err) => {
224
+ logger.error();
225
+ reject(err);
226
+ });
227
+ });
241
228
  }
242
229
  function findViteConfigFile(projectDir) {
243
- const candidates = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs', 'vite.config.cjs'];
244
- for (const name of candidates) {
245
- const p = path.join(projectDir, name);
246
- if (fs.existsSync(p))
247
- return p;
248
- }
249
- return null;
230
+ const candidates = ["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"];
231
+ for (const name of candidates) {
232
+ const p = path.join(projectDir, name);
233
+ if (fs.existsSync(p)) return p;
234
+ }
235
+ return null;
250
236
  }
251
237
  async function cloneViteTemplate(template, targetDir, packageManager, spinner) {
252
- const errors = [];
253
- for (let i = 0; i < TEMPLATE_SOURCES.length; i++) {
254
- const source = TEMPLATE_SOURCES[i];
255
- let tempTargetDir = null;
238
+ const errors = [];
239
+ for (let i = 0; i < TEMPLATE_SOURCES.length; i++) {
240
+ const source = TEMPLATE_SOURCES[i];
241
+ let tempTargetDir = null;
242
+ try {
243
+ if (i > 0) {
244
+ spinner.message(`Trying ${source.name}...`);
245
+ }
246
+ const { command, args } = source.getCloneCommand(template, targetDir, packageManager);
247
+ if (source.isGitBased) {
248
+ const tempCloneDir = path.join(process.cwd(), `.tmp-clone-${randomUUID()}`);
249
+ const cloneArgs = [...args.slice(0, -1), tempCloneDir];
250
+ spinner.stop();
251
+ await run(command, cloneArgs, process.cwd());
252
+ spinner.start("Processing template");
256
253
  try {
257
- // Show which source we're trying
258
- if (i > 0) {
259
- spinner.message(`Trying ${source.name}...`);
260
- }
261
- const { command, args } = source.getCloneCommand(template, targetDir, packageManager);
262
- // For git-based sources, we need special handling
263
- if (source.isGitBased) {
264
- // Create temp directory for cloning (git clone will create the target folder)
265
- const tempCloneDir = path.join(process.cwd(), `.tmp-clone-${randomUUID()}`);
266
- const cloneArgs = [...args.slice(0, -1), tempCloneDir]; // Replace last arg (targetDir) with tempCloneDir
267
- // Stop spinner before git clone to ensure proper output formatting
268
- spinner.stop();
269
- // Clone the entire repo to temp directory
270
- await run(command, cloneArgs, process.cwd());
271
- // Restart spinner after git clone
272
- spinner.start('Processing template');
273
- try {
274
- // Extract the template folder from the cloned repo
275
- const templateSrcPath = path.join(tempCloneDir, 'packages/create-vite', `template-${template}`);
276
- if (!fs.existsSync(templateSrcPath)) {
277
- throw new Error(`Template not found at packages/create-vite/template-${template}. The repository structure may have changed.`);
278
- }
279
- // Move the template files to a temp location with unique name
280
- tempTargetDir = path.join(process.cwd(), `.tmp-template-${randomUUID()}`);
281
- copyDir(templateSrcPath, tempTargetDir);
282
- // Clean up the cloned repo
283
- fs.rmSync(tempCloneDir, { recursive: true, force: true });
284
- // Move the template to the final location
285
- fs.renameSync(tempTargetDir, targetDir);
286
- tempTargetDir = null; // Mark as successfully moved
287
- }
288
- catch (extractError) {
289
- // Clean up temp clone directory on extraction error
290
- if (fs.existsSync(tempCloneDir)) {
291
- fs.rmSync(tempCloneDir, { recursive: true, force: true });
292
- }
293
- throw extractError;
294
- }
295
- }
296
- else {
297
- // For degit sources, command and args are already properly formatted
298
- await run(command, args, process.cwd());
299
- }
300
- return;
254
+ const templateSrcPath = path.join(tempCloneDir, "packages/create-vite", `template-${template}`);
255
+ if (!fs.existsSync(templateSrcPath)) {
256
+ throw new Error(
257
+ `Template not found at packages/create-vite/template-${template}. The repository structure may have changed.`
258
+ );
259
+ }
260
+ tempTargetDir = path.join(process.cwd(), `.tmp-template-${randomUUID()}`);
261
+ copyDir(templateSrcPath, tempTargetDir);
262
+ fs.rmSync(tempCloneDir, { recursive: true, force: true });
263
+ fs.renameSync(tempTargetDir, targetDir);
264
+ tempTargetDir = null;
265
+ } catch (extractError) {
266
+ if (fs.existsSync(tempCloneDir)) {
267
+ fs.rmSync(tempCloneDir, { recursive: true, force: true });
268
+ }
269
+ throw extractError;
301
270
  }
302
- catch (error) {
303
- // Clean up any temp directory on failure
304
- if (tempTargetDir && fs.existsSync(tempTargetDir)) {
305
- fs.rmSync(tempTargetDir, { recursive: true, force: true });
306
- }
307
- const errorMsg = error instanceof Error ? error.message : String(error);
308
- errors.push({ source: source.name, error: errorMsg });
309
- if (i < TEMPLATE_SOURCES.length - 1) {
310
- // Not the last source, will try next one
311
- // Ensure spinner is running before showing retry message
312
- if (source.isGitBased) {
313
- spinner.start(`${source.name} failed, trying ${TEMPLATE_SOURCES[i + 1].name}`);
314
- }
315
- else {
316
- spinner.message(`${source.name} failed, trying ${TEMPLATE_SOURCES[i + 1].name}...`);
317
- }
318
- }
319
- else {
320
- // This is the last source, throw comprehensive error
321
- const errorDetails = errors
322
- .map(e => ` • ${e.source}: ${e.error}`)
323
- .join('\n');
324
- throw new Error(`Failed to clone template from all sources:\n${errorDetails}\n\nPlease check your network connection or try again later.`);
325
- }
271
+ } else {
272
+ await run(command, args, process.cwd());
273
+ }
274
+ return;
275
+ } catch (error) {
276
+ if (tempTargetDir && fs.existsSync(tempTargetDir)) {
277
+ fs.rmSync(tempTargetDir, { recursive: true, force: true });
278
+ }
279
+ const errorMsg = error instanceof Error ? error.message : String(error);
280
+ errors.push({ source: source.name, error: errorMsg });
281
+ if (i < TEMPLATE_SOURCES.length - 1) {
282
+ if (source.isGitBased) {
283
+ spinner.start(`${source.name} failed, trying ${TEMPLATE_SOURCES[i + 1].name}`);
284
+ } else {
285
+ spinner.message(`${source.name} failed, trying ${TEMPLATE_SOURCES[i + 1].name}...`);
326
286
  }
287
+ } else {
288
+ const errorDetails = errors.map((e) => ` \u2022 ${e.source}: ${e.error}`).join("\n");
289
+ throw new Error(
290
+ `Failed to clone template from all sources:
291
+ ${errorDetails}
292
+
293
+ Please check your network connection or try again later.`
294
+ );
295
+ }
327
296
  }
297
+ }
328
298
  }
329
299
  function getDegitCommandForPM(pm) {
330
- switch (pm) {
331
- case 'pnpm':
332
- return {
333
- command: 'pnpx',
334
- args: (cmd, cmdArgs) => [cmd, ...cmdArgs],
335
- };
336
- case 'npm':
337
- return {
338
- command: 'npx',
339
- args: (cmd, cmdArgs) => [cmd, ...cmdArgs],
340
- };
341
- case 'yarn':
342
- return {
343
- command: 'yarn',
344
- args: (cmd, cmdArgs) => ['dlx', cmd, ...cmdArgs],
345
- };
346
- case 'bun':
347
- return {
348
- command: 'bunx',
349
- args: (cmd, cmdArgs) => [cmd, ...cmdArgs],
350
- };
351
- }
300
+ switch (pm) {
301
+ case "pnpm":
302
+ return {
303
+ command: "pnpx",
304
+ args: (cmd, cmdArgs) => [cmd, ...cmdArgs]
305
+ };
306
+ case "npm":
307
+ return {
308
+ command: "npx",
309
+ args: (cmd, cmdArgs) => [cmd, ...cmdArgs]
310
+ };
311
+ case "yarn":
312
+ return {
313
+ command: "yarn",
314
+ args: (cmd, cmdArgs) => ["dlx", cmd, ...cmdArgs]
315
+ };
316
+ case "bun":
317
+ return {
318
+ command: "bunx",
319
+ args: (cmd, cmdArgs) => [cmd, ...cmdArgs]
320
+ };
321
+ }
352
322
  }
353
323
  function injectPluginIntoViteConfig(content, pluginPackage, pluginName) {
354
- const hasImport = new RegExp(`['"]${pluginPackage.replace(/\//g, '\\/')}['"]`).test(content);
355
- const hasCall = content.includes(`${pluginName}(`);
356
- let out = content;
357
- // 添加 import 语句
358
- if (!hasImport) {
359
- const importMatches = Array.from(out.matchAll(/^import .+$/gm));
360
- if (importMatches.length > 0) {
361
- const last = importMatches[importMatches.length - 1];
362
- const insertPos = (last.index ?? 0) + last[0].length;
363
- out = `${out.slice(0, insertPos)}\nimport { ${pluginName} } from '${pluginPackage}'\n${out.slice(insertPos)}`;
364
- }
365
- else {
366
- out = `import { ${pluginName} } from '${pluginPackage}'\n${out}`;
367
- }
324
+ const hasImport = new RegExp(`['"]${pluginPackage.replace(/\//g, "\\/")}['"]`).test(content);
325
+ const hasCall = content.includes(`${pluginName}(`);
326
+ let out = content;
327
+ if (!hasImport) {
328
+ const importMatches = Array.from(out.matchAll(/^import .+$/gm));
329
+ if (importMatches.length > 0) {
330
+ const last = importMatches[importMatches.length - 1];
331
+ const insertPos = (last.index ?? 0) + last[0].length;
332
+ out = `${out.slice(0, insertPos)}
333
+ import { ${pluginName} } from '${pluginPackage}'
334
+ ${out.slice(insertPos)}`;
335
+ } else {
336
+ out = `import { ${pluginName} } from '${pluginPackage}'
337
+ ${out}`;
368
338
  }
369
- // 添加插件到 plugins 数组
370
- if (!hasCall) {
371
- // 匹配 plugins: [...] 或 plugins:[...]
372
- const pluginsRegex = /plugins\s*:\s*\[([^\]]*(?:\[[^\]]*\][^\]]*)*)\]/;
373
- const m = pluginsRegex.exec(out);
374
- if (m && m.index !== undefined) {
375
- const full = m[0];
376
- const inner = m[1] || '';
377
- // 检查是否是多行格式
378
- if (inner.includes('\n')) {
379
- // 多行格式:找到缩进
380
- const lines = inner.split('\n');
381
- const pluginLines = lines.filter(line => line.trim() && !line.trim().startsWith('//'));
382
- let indent = ' '; // 默认 4 空格
383
- if (pluginLines.length > 0) {
384
- const firstPluginLine = pluginLines[0];
385
- const match = firstPluginLine.match(/^(\s+)/);
386
- if (match)
387
- indent = match[1];
388
- }
389
- // 在最后一个插件后添加新插件
390
- const trimmedInner = inner.trimEnd();
391
- const hasTrailingComma = trimmedInner.trim().endsWith(',');
392
- const newPlugin = `${indent}${pluginName}(),`;
393
- // 找到最后一个非空白行
394
- const lastContentIndex = trimmedInner.lastIndexOf('\n');
395
- if (lastContentIndex === -1) {
396
- // 单行但包含换行符的情况
397
- out = out.replace(full, `plugins: [\n${newPlugin}\n ]`);
398
- }
399
- else {
400
- const beforeLast = trimmedInner.slice(0, lastContentIndex + 1);
401
- const lastLine = trimmedInner.slice(lastContentIndex + 1);
402
- if (!hasTrailingComma && lastLine.trim()) {
403
- // 最后一行没有逗号,需要添加
404
- out = out.replace(full, `plugins: [${beforeLast}${lastLine},\n${newPlugin}\n ]`);
405
- }
406
- else {
407
- // 最后一行有逗号或为空
408
- out = out.replace(full, `plugins: [${trimmedInner}\n${newPlugin}\n ]`);
409
- }
410
- }
411
- }
412
- else {
413
- // 单行格式
414
- const compactInner = inner.trim();
415
- const nextInner = compactInner ? `${compactInner}, ${pluginName}()` : `${pluginName}()`;
416
- out = out.replace(full, `plugins: [${nextInner}]`);
417
- }
339
+ }
340
+ if (!hasCall) {
341
+ const pluginsRegex = /plugins\s*:\s*\[([^\]]*(?:\[[^\]]*\][^\]]*)*)\]/;
342
+ const m = pluginsRegex.exec(out);
343
+ if (m && m.index !== void 0) {
344
+ const full = m[0];
345
+ const inner = m[1] || "";
346
+ if (inner.includes("\n")) {
347
+ const lines = inner.split("\n");
348
+ const pluginLines = lines.filter((line) => line.trim() && !line.trim().startsWith("//"));
349
+ let indent = " ";
350
+ if (pluginLines.length > 0) {
351
+ const firstPluginLine = pluginLines[0];
352
+ const match = firstPluginLine.match(/^(\s+)/);
353
+ if (match) indent = match[1];
418
354
  }
355
+ const trimmedInner = inner.trimEnd();
356
+ const hasTrailingComma = trimmedInner.trim().endsWith(",");
357
+ const newPlugin = `${indent}${pluginName}(),`;
358
+ const lastContentIndex = trimmedInner.lastIndexOf("\n");
359
+ if (lastContentIndex === -1) {
360
+ out = out.replace(full, `plugins: [
361
+ ${newPlugin}
362
+ ]`);
363
+ } else {
364
+ const beforeLast = trimmedInner.slice(0, lastContentIndex + 1);
365
+ const lastLine = trimmedInner.slice(lastContentIndex + 1);
366
+ if (!hasTrailingComma && lastLine.trim()) {
367
+ out = out.replace(full, `plugins: [${beforeLast}${lastLine},
368
+ ${newPlugin}
369
+ ]`);
370
+ } else {
371
+ out = out.replace(full, `plugins: [${trimmedInner}
372
+ ${newPlugin}
373
+ ]`);
374
+ }
375
+ }
376
+ } else {
377
+ const compactInner = inner.trim();
378
+ const nextInner = compactInner ? `${compactInner}, ${pluginName}()` : `${pluginName}()`;
379
+ out = out.replace(full, `plugins: [${nextInner}]`);
380
+ }
419
381
  }
420
- return out;
382
+ }
383
+ return out;
421
384
  }
422
385
  function updatePluginComponentName(content, pluginName, componentName) {
423
- // 匹配 devToReactPlugin() 的调用,替换为 devToReactPlugin({ [ComponentName]: 'src/[ComponentName]/index.tsx' })
424
- const pluginCall = `${pluginName}()`;
425
- const newPluginCall = `${pluginName}({
386
+ const pluginCall = `${pluginName}()`;
387
+ const newPluginCall = `${pluginName}({
426
388
  ${componentName}: 'src/${componentName}/index.tsx',
427
389
  })`;
428
- // 检查 pluginCall 是否存在
429
- if (content.includes(pluginCall)) {
430
- return content.replace(pluginCall, newPluginCall);
431
- }
432
- return content;
390
+ if (content.includes(pluginCall)) {
391
+ return content.replace(pluginCall, newPluginCall);
392
+ }
393
+ return content;
394
+ }
395
+ function editFile(file, callback) {
396
+ const content = fs.readFileSync(file, "utf-8");
397
+ fs.writeFileSync(file, callback(content), "utf-8");
398
+ }
399
+ function setupReactSWC(root, isTs) {
400
+ editFile(path.join(root, "package.json"), (content) => {
401
+ return content.replace(
402
+ /"@vitejs\/plugin-react": ".+?"/,
403
+ '"@vitejs/plugin-react-swc": "^4.2.2"'
404
+ );
405
+ });
406
+ editFile(path.join(root, `vite.config.${isTs ? "ts" : "js"}`), (content) => {
407
+ return content.replace("@vitejs/plugin-react", "@vitejs/plugin-react-swc");
408
+ });
409
+ }
410
+ function setupReactCompiler(root, isTs) {
411
+ editFile(path.join(root, "package.json"), (content) => {
412
+ const asObject = JSON.parse(content);
413
+ const devDepsEntries = Object.entries(asObject.devDependencies || {});
414
+ devDepsEntries.push(["babel-plugin-react-compiler", "^1.0.0"]);
415
+ devDepsEntries.sort();
416
+ asObject.devDependencies = Object.fromEntries(devDepsEntries);
417
+ return JSON.stringify(asObject, null, 2) + "\n";
418
+ });
419
+ editFile(path.join(root, `vite.config.${isTs ? "ts" : "js"}`), (content) => {
420
+ return content.replace(
421
+ " plugins: [react()],",
422
+ ` plugins: [
423
+ react({
424
+ babel: {
425
+ plugins: [['babel-plugin-react-compiler']],
426
+ },
427
+ }),
428
+ ],`
429
+ );
430
+ });
431
+ }
432
+ function printBanner() {
433
+ const version = __BUILD_INFO__.version;
434
+ const commit = __BUILD_INFO__.commit;
435
+ const branch = __BUILD_INFO__.branch;
436
+ const buildTimeUTC = __BUILD_INFO__.buildTime;
437
+ const [date, time] = buildTimeUTC.split(" ");
438
+ const utcDateTime = /* @__PURE__ */ new Date(`${date}T${time}:00Z`);
439
+ const year = utcDateTime.getFullYear();
440
+ const month = String(utcDateTime.getMonth() + 1).padStart(2, "0");
441
+ const day = String(utcDateTime.getDate()).padStart(2, "0");
442
+ const hours = String(utcDateTime.getHours()).padStart(2, "0");
443
+ const minutes = String(utcDateTime.getMinutes()).padStart(2, "0");
444
+ const localTime = `${year}-${month}-${day} ${hours}:${minutes}`;
445
+ const logo = `
446
+ ${cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557")}${yellow(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${green(`v${version}`)}
447
+ ${cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551")}${yellow(" \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557")} ${dim(`${commit === "unknown" ? "" : `${commit} on ${branch}`}`)}
448
+ ${cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551")}${yellow(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551")} ${dim(`${localTime}`)}
449
+ ${cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D")}${yellow(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551")}
450
+ ${cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D")}${yellow(" \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
451
+ ${cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u255D")}${yellow(" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")}
452
+ `;
453
+ console.log(logo);
433
454
  }
434
455
  function addDevDependency(projectDir, pkgName, version) {
435
- const pkgPath = path.join(projectDir, 'package.json');
436
- const raw = fs.readFileSync(pkgPath, 'utf-8');
437
- const pkg = JSON.parse(raw);
438
- const devDependencies = (pkg['devDependencies'] ?? {});
439
- if (!devDependencies[pkgName])
440
- devDependencies[pkgName] = version;
441
- pkg['devDependencies'] = devDependencies;
442
- fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
456
+ const pkgPath = path.join(projectDir, "package.json");
457
+ const raw = fs.readFileSync(pkgPath, "utf-8");
458
+ const pkg = JSON.parse(raw);
459
+ const devDependencies = pkg["devDependencies"] ?? {};
460
+ if (!devDependencies[pkgName]) devDependencies[pkgName] = version;
461
+ pkg["devDependencies"] = devDependencies;
462
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
463
+ `);
443
464
  }
444
465
  async function init() {
445
- const userAgent = process.env.npm_config_user_agent || '';
446
- let packageManager = detectPackageManager(userAgent);
447
- clack.intro(cyan('create-dev-to'));
448
- const cwd = process.cwd();
449
- const argTargetDir = formatTargetDir(process.argv[2]);
450
- let targetDir = argTargetDir || '.';
451
- const getProjectName = () => targetDir === '.' ? path.basename(path.resolve()) : targetDir;
452
- // 项目名称
453
- const project = await clack.group({
454
- projectName: () => clack.text({
455
- message: `Project name ${dim('\n Directory where your project will be created.\n')}`,
456
- placeholder: 'dev-to-app',
457
- initialValue: argTargetDir,
458
- defaultValue: argTargetDir || 'dev-to-app',
459
- }),
460
- shouldOverwrite: ({ results }) => {
461
- targetDir = formatTargetDir(results.projectName) || 'dev-to-app';
462
- const root = path.join(cwd, targetDir);
463
- if (!fs.existsSync(root)) {
464
- return Promise.resolve(false);
465
- }
466
- if (isEmpty(root)) {
467
- return Promise.resolve(false);
468
- }
469
- return clack.confirm({
470
- message: targetDir === '.'
471
- ? 'Current directory is not empty. Remove existing files and continue?'
472
- : `Target directory "${targetDir}" is not empty. Remove existing files and continue?`,
473
- });
474
- },
475
- componentName: ({ results }) => {
476
- // Convert project name to PascalCase for component name
477
- const projectName = results.projectName || 'dev-to-app';
478
- const defaultComponentName = projectName
479
- .split(/[-_\s]+/)
480
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
481
- .join('');
482
- return clack.text({
483
- message: `First component name ${dim('\n│ Leave blank to default to project name.You can modify it in vite config later.\n')}`,
484
- placeholder: defaultComponentName,
485
- defaultValue: defaultComponentName,
486
- });
487
- },
488
- }, {
489
- onCancel: () => {
490
- clack.cancel('Operation cancelled.');
491
- process.exit(0);
492
- },
493
- });
494
- const { shouldOverwrite, componentName } = project;
495
- // 覆盖目录
496
- if (shouldOverwrite) {
497
- emptyDir(path.join(cwd, targetDir));
498
- }
499
- // 包管理器选择
500
- if (!packageManager) {
501
- const pmChoice = await clack.select({
502
- message: 'Select a package manager:',
503
- options: PACKAGE_MANAGERS.map(pm => ({
504
- value: pm,
505
- label: pm,
506
- })),
507
- });
508
- if (clack.isCancel(pmChoice)) {
509
- clack.cancel('Operation cancelled.');
510
- process.exit(0);
466
+ const userAgent = process.env.npm_config_user_agent || "";
467
+ let packageManager = detectPackageManager(userAgent);
468
+ printBanner();
469
+ const cwd = process.cwd();
470
+ const argTargetDir = formatTargetDir(process.argv[2]);
471
+ let targetDir = argTargetDir || ".";
472
+ const getProjectName = () => targetDir === "." ? path.basename(path.resolve()) : targetDir;
473
+ const project = await clack.group(
474
+ {
475
+ projectName: () => clack.text({
476
+ message: `Project name ${dim("\n\u2502 Directory where your project will be created.\n")}`,
477
+ placeholder: "dev-to-app",
478
+ initialValue: argTargetDir,
479
+ defaultValue: argTargetDir || "dev-to-app"
480
+ }),
481
+ shouldOverwrite: ({ results }) => {
482
+ targetDir = formatTargetDir(results.projectName) || "dev-to-app";
483
+ const root2 = path.join(cwd, targetDir);
484
+ if (!fs.existsSync(root2)) {
485
+ return Promise.resolve(false);
511
486
  }
512
- packageManager = pmChoice;
513
- }
514
- // 框架选择
515
- const framework = await clack.select({
516
- message: 'Select a framework:',
517
- options: FRAMEWORKS.map(fw => ({
518
- value: fw.name,
519
- label: fw.supported ? fw.color(fw.display) : `${fw.color(fw.display)} ${dim('(Coming soon)')}`,
520
- hint: fw.supported ? undefined : 'Not yet supported',
521
- })),
522
- initialValue: 'react',
523
- });
524
- if (clack.isCancel(framework)) {
525
- clack.cancel('Operation cancelled.');
526
- process.exit(0);
527
- }
528
- // 检查框架是否支持
529
- const selectedFramework = FRAMEWORKS.find(fw => fw.name === framework);
530
- if (!selectedFramework?.supported) {
531
- clack.outro(yellow(`⚠️ ${selectedFramework?.display} support is coming soon!`));
532
- clack.note(`We're working hard to add support for ${selectedFramework?.display}.\n\nFor now, please use React or stay tuned for updates!`, 'Roadmap');
533
- process.exit(0);
534
- }
535
- // React 模板选择
536
- const variant = await clack.select({
537
- message: 'Select a variant:',
538
- options: REACT_TEMPLATES.map(template => ({
539
- value: template.name,
540
- label: template.color(template.display),
541
- })),
542
- });
543
- if (clack.isCancel(variant)) {
544
- clack.cancel('Operation cancelled.');
545
- process.exit(0);
546
- }
547
- const template = variant;
548
- // 询问是否使用 Rolldown(实验性)
549
- const shouldUseRolldown = await clack.confirm({
550
- message: 'Use Rolldown for bundling? (Experimental)',
551
- initialValue: false,
552
- });
553
- if (clack.isCancel(shouldUseRolldown)) {
554
- clack.cancel('Operation cancelled.');
487
+ if (isEmpty(root2)) {
488
+ return Promise.resolve(false);
489
+ }
490
+ return clack.confirm({
491
+ message: targetDir === "." ? "Current directory is not empty. Remove existing files and continue?" : `Target directory "${targetDir}" is not empty. Remove existing files and continue?`
492
+ });
493
+ },
494
+ componentName: ({ results }) => {
495
+ const projectName = results.projectName || "dev-to-app";
496
+ const defaultComponentName = projectName.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
497
+ return clack.text({
498
+ message: `First component name ${dim("\n\u2502 Leave blank to default to project name.You can modify it in vite config later.\n")}`,
499
+ placeholder: defaultComponentName,
500
+ defaultValue: defaultComponentName
501
+ });
502
+ }
503
+ },
504
+ {
505
+ onCancel: () => {
506
+ clack.cancel("Operation cancelled.");
555
507
  process.exit(0);
508
+ }
556
509
  }
557
- const root = path.join(cwd, targetDir);
558
- const spinner = clack.spinner();
559
- spinner.start('Scaffolding project');
560
- // 使用 degit 克隆模板
561
- await cloneViteTemplate(template, root, packageManager, spinner);
562
- // 修改 package.json 名称
563
- const pkgPath = path.join(root, 'package.json');
564
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
565
- pkg.name = toValidPackageName(getProjectName());
566
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
567
- spinner.message('Adding @dev-to/react-plugin');
568
- // 添加插件依赖
569
- const pluginPackage = '@dev-to/react-plugin';
570
- const pluginName = 'devToReactPlugin';
571
- addDevDependency(root, pluginPackage, 'latest');
572
- // 注入插件到 vite.config
573
- const viteConfigPath = findViteConfigFile(root);
574
- if (viteConfigPath) {
575
- let patched = fs.readFileSync(viteConfigPath, 'utf-8');
576
- patched = injectPluginIntoViteConfig(patched, pluginPackage, pluginName);
577
- patched = updatePluginComponentName(patched, pluginName, componentName);
578
- fs.writeFileSync(viteConfigPath, patched);
579
- }
580
- spinner.stop('Project created');
581
- // 询问是否立即安装
582
- const shouldInstall = await clack.confirm({
583
- message: 'Install dependencies and start dev server?',
584
- initialValue: true,
510
+ );
511
+ const { shouldOverwrite, componentName } = project;
512
+ if (shouldOverwrite) {
513
+ emptyDir(path.join(cwd, targetDir));
514
+ }
515
+ if (!packageManager) {
516
+ const pmChoice = await clack.select({
517
+ message: "Select a package manager:",
518
+ options: PACKAGE_MANAGERS.map((pm) => ({
519
+ value: pm,
520
+ label: pm
521
+ }))
585
522
  });
586
- if (clack.isCancel(shouldInstall)) {
587
- // 用户取消
523
+ if (clack.isCancel(pmChoice)) {
524
+ clack.cancel("Operation cancelled.");
525
+ process.exit(0);
588
526
  }
589
- if (shouldInstall) {
590
- try {
591
- const stats = await runWithLogger(packageManager, ['install'], root, packageManager);
592
- displayInstallSummary(stats);
593
- clack.log.info('Starting dev server...');
594
- const devArgs = packageManager === 'npm' ? ['run', 'dev'] : ['dev'];
595
- spawn(packageManager, devArgs, { cwd: root, stdio: 'inherit' });
596
- }
597
- catch (error) {
598
- clack.log.error('Installation failed');
599
- throw error;
600
- }
601
- }
602
- else {
603
- const pkgManager = packageManager || 'npm';
604
- const cdPath = targetDir !== '.' ? `cd ${targetDir}` : null;
605
- const installCmd = PM_CONFIGS[pkgManager].install;
606
- const devCmd = PM_CONFIGS[pkgManager].dev;
607
- const nextSteps = [cdPath, installCmd, devCmd].filter(Boolean).join('\n ');
608
- clack.note(nextSteps, 'Next steps');
609
- clack.outro('Done');
527
+ packageManager = pmChoice;
528
+ }
529
+ const framework = await clack.select({
530
+ message: "Select a framework:",
531
+ options: FRAMEWORKS.map((fw) => ({
532
+ value: fw.name,
533
+ label: fw.supported ? fw.color(fw.display) : `${fw.color(fw.display)} ${dim("(Coming soon)")}`,
534
+ hint: fw.supported ? void 0 : "Not yet supported"
535
+ })),
536
+ initialValue: "react"
537
+ });
538
+ if (clack.isCancel(framework)) {
539
+ clack.cancel("Operation cancelled.");
540
+ process.exit(0);
541
+ }
542
+ const selectedFramework = FRAMEWORKS.find((fw) => fw.name === framework);
543
+ if (!selectedFramework?.supported) {
544
+ clack.outro(yellow(`\u26A0\uFE0F ${selectedFramework?.display} support is coming soon!`));
545
+ clack.note(
546
+ `We're working hard to add support for ${selectedFramework?.display}.
547
+
548
+ For now, please use React or stay tuned for updates!`,
549
+ "Roadmap"
550
+ );
551
+ process.exit(0);
552
+ }
553
+ const variant = await clack.select({
554
+ message: "Select a variant:",
555
+ options: REACT_TEMPLATES.map((template2) => ({
556
+ value: template2.name,
557
+ label: template2.color(template2.display)
558
+ }))
559
+ });
560
+ if (clack.isCancel(variant)) {
561
+ clack.cancel("Operation cancelled.");
562
+ process.exit(0);
563
+ }
564
+ const template = variant;
565
+ const shouldUseSWC = await clack.confirm({
566
+ message: "Use SWC for faster transpilation? (Optional)",
567
+ initialValue: false
568
+ });
569
+ if (clack.isCancel(shouldUseSWC)) {
570
+ clack.cancel("Operation cancelled.");
571
+ process.exit(0);
572
+ }
573
+ const shouldUseReactCompiler = await clack.confirm({
574
+ message: "Use React Compiler? (Experimental)",
575
+ initialValue: false
576
+ });
577
+ if (clack.isCancel(shouldUseReactCompiler)) {
578
+ clack.cancel("Operation cancelled.");
579
+ process.exit(0);
580
+ }
581
+ const shouldUseRolldown = await clack.confirm({
582
+ message: "Use Rolldown for bundling? (Experimental)",
583
+ initialValue: false
584
+ });
585
+ if (clack.isCancel(shouldUseRolldown)) {
586
+ clack.cancel("Operation cancelled.");
587
+ process.exit(0);
588
+ }
589
+ const root = path.join(cwd, targetDir);
590
+ const spinner = clack.spinner();
591
+ spinner.start("Scaffolding project");
592
+ await cloneViteTemplate(template, root, packageManager, spinner);
593
+ const pkgPath = path.join(root, "package.json");
594
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
595
+ pkg.name = toValidPackageName(getProjectName());
596
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
597
+ const isTs = template.includes("ts");
598
+ if (shouldUseSWC) {
599
+ spinner.message("Setting up SWC...");
600
+ setupReactSWC(root, isTs);
601
+ }
602
+ if (shouldUseReactCompiler) {
603
+ spinner.message("Setting up React Compiler...");
604
+ setupReactCompiler(root, isTs);
605
+ }
606
+ spinner.message("Adding @dev-to/react-plugin");
607
+ const pluginPackage = "@dev-to/react-plugin";
608
+ const pluginName = "devToReactPlugin";
609
+ addDevDependency(root, pluginPackage, "latest");
610
+ const viteConfigPath = findViteConfigFile(root);
611
+ if (viteConfigPath) {
612
+ let patched = fs.readFileSync(viteConfigPath, "utf-8");
613
+ patched = injectPluginIntoViteConfig(patched, pluginPackage, pluginName);
614
+ patched = updatePluginComponentName(patched, pluginName, componentName);
615
+ fs.writeFileSync(viteConfigPath, patched);
616
+ }
617
+ spinner.stop("Project created");
618
+ const shouldInstall = await clack.confirm({
619
+ message: "Install dependencies and start dev server?",
620
+ initialValue: true
621
+ });
622
+ if (clack.isCancel(shouldInstall)) {
623
+ }
624
+ if (shouldInstall) {
625
+ try {
626
+ const stats = await runWithLogger(packageManager, ["install"], root, packageManager);
627
+ displayInstallSummary(stats);
628
+ clack.log.info("Starting dev server...");
629
+ const devArgs = packageManager === "npm" ? ["run", "dev"] : ["dev"];
630
+ spawn(packageManager, devArgs, { cwd: root, stdio: "inherit" });
631
+ } catch (error) {
632
+ clack.log.error("Installation failed");
633
+ throw error;
610
634
  }
635
+ } else {
636
+ const pkgManager = packageManager || "npm";
637
+ const cdPath = targetDir !== "." ? `cd ${targetDir}` : null;
638
+ const installCmd = PM_CONFIGS[pkgManager].install;
639
+ const devCmd = PM_CONFIGS[pkgManager].dev;
640
+ const nextSteps = [cdPath, installCmd, devCmd].filter(Boolean).join("\n ");
641
+ clack.note(nextSteps, "Next steps");
642
+ clack.outro("Done");
643
+ }
611
644
  }
612
645
  init().catch((e) => {
613
- clack.log.error(red(e.message || e));
614
- process.exit(1);
646
+ clack.log.error(red(e.message || e));
647
+ process.exit(1);
615
648
  });
616
- //# sourceMappingURL=index.js.map