cf-yoyo 1.0.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/.eslintrc.json +28 -0
- package/.github/workflows/ci.yml +96 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +55 -0
- package/README.md +138 -0
- package/__tests__/cli-e2e.test.ts +145 -0
- package/__tests__/config.test.ts +268 -0
- package/__tests__/filesystem.test.ts +453 -0
- package/__tests__/logger.test.ts +274 -0
- package/__tests__/template-engine.test.ts +450 -0
- package/__tests__/types.test.ts +25 -0
- package/deep_todos.md +766 -0
- package/dist/cli/commands/create.d.ts +26 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +308 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/git.d.ts +10 -0
- package/dist/cli/commands/git.d.ts.map +1 -0
- package/dist/cli/commands/git.js +887 -0
- package/dist/cli/commands/git.js.map +1 -0
- package/dist/cli/commands/list.d.ts +10 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +90 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +62 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/config.d.ts +35 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +260 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/filesystem.d.ts +84 -0
- package/dist/core/filesystem.d.ts.map +1 -0
- package/dist/core/filesystem.js +417 -0
- package/dist/core/filesystem.js.map +1 -0
- package/dist/core/git-token.d.ts +81 -0
- package/dist/core/git-token.d.ts.map +1 -0
- package/dist/core/git-token.js +244 -0
- package/dist/core/git-token.js.map +1 -0
- package/dist/core/git.d.ts +70 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +367 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/prompt.d.ts +28 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +253 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/template-engine.d.ts +52 -0
- package/dist/core/template-engine.d.ts.map +1 -0
- package/dist/core/template-engine.js +308 -0
- package/dist/core/template-engine.js.map +1 -0
- package/dist/core/template-manager.d.ts +54 -0
- package/dist/core/template-manager.d.ts.map +1 -0
- package/dist/core/template-manager.js +330 -0
- package/dist/core/template-manager.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +244 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +51 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +68 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/logger.js.map +1 -0
- package/memory.md +241 -0
- package/need-debug.md +395 -0
- package/package.json +42 -0
- package/src/cli/commands/create.ts +326 -0
- package/src/cli/commands/git.ts +1001 -0
- package/src/cli/commands/list.ts +97 -0
- package/src/cli/index.ts +71 -0
- package/src/core/config.ts +262 -0
- package/src/core/filesystem.ts +408 -0
- package/src/core/git-token.ts +248 -0
- package/src/core/git.ts +384 -0
- package/src/core/prompt.ts +345 -0
- package/src/core/template-engine.ts +324 -0
- package/src/core/template-manager.ts +338 -0
- package/src/index.ts +19 -0
- package/src/types/index.ts +259 -0
- package/src/utils/logger.ts +150 -0
- package/templates/pages/basic/README.md.mustache +63 -0
- package/templates/pages/basic/package.json.mustache +23 -0
- package/templates/pages/basic/public/css/style.css +199 -0
- package/templates/pages/basic/public/index.html.mustache +72 -0
- package/templates/pages/basic/public/js/main.js +103 -0
- package/templates/pages/basic/template.json +38 -0
- package/templates/pages/basic/tsconfig.json +21 -0
- package/templates/pages/basic/wrangler.toml.mustache +14 -0
- package/templates/pages/basic-js/README.md.mustache +62 -0
- package/templates/pages/basic-js/package.json.mustache +25 -0
- package/templates/pages/basic-js/public/css/style.css +212 -0
- package/templates/pages/basic-js/public/index.html.mustache +53 -0
- package/templates/pages/basic-js/public/js/main.js +134 -0
- package/templates/pages/basic-js/template.json +35 -0
- package/templates/pages/basic-js/wrangler.toml.mustache +14 -0
- package/templates/pages/react/README.md.mustache +97 -0
- package/templates/pages/react/index.html.mustache +14 -0
- package/templates/pages/react/package.json.mustache +34 -0
- package/templates/pages/react/src/App.css +168 -0
- package/templates/pages/react/src/App.tsx.mustache +62 -0
- package/templates/pages/react/src/index.css +53 -0
- package/templates/pages/react/src/main.tsx.mustache +10 -0
- package/templates/pages/react/src/vite-env.d.ts +1 -0
- package/templates/pages/react/template.json +54 -0
- package/templates/pages/react/tsconfig.json +21 -0
- package/templates/pages/react/tsconfig.node.json +10 -0
- package/templates/pages/react/vite.config.ts +16 -0
- package/templates/worker/basic/README.md.mustache +56 -0
- package/templates/worker/basic/package.json.mustache +29 -0
- package/templates/worker/basic/src/index.ts.mustache +125 -0
- package/templates/worker/basic/template.json +30 -0
- package/templates/worker/basic/tsconfig.json +24 -0
- package/templates/worker/basic/wrangler.toml.mustache +33 -0
- package/templates/worker/basic-js/README.md.mustache +55 -0
- package/templates/worker/basic-js/package.json.mustache +25 -0
- package/templates/worker/basic-js/src/index.js.mustache +146 -0
- package/templates/worker/basic-js/template.json +27 -0
- package/templates/worker/basic-js/wrangler.toml.mustache +33 -0
- package/templates/worker/hono/README.md.mustache +79 -0
- package/templates/worker/hono/package.json.mustache +33 -0
- package/templates/worker/hono/src/index.ts.mustache +64 -0
- package/templates/worker/hono/src/routes/index.ts.mustache +165 -0
- package/templates/worker/hono/template.json +34 -0
- package/templates/worker/hono/tsconfig.json +24 -0
- package/templates/worker/hono/wrangler.toml.mustache +36 -0
- package/templates/worker/hono-js/README.md.mustache +67 -0
- package/templates/worker/hono-js/package.json.mustache +29 -0
- package/templates/worker/hono-js/src/index.js.mustache +55 -0
- package/templates/worker/hono-js/src/routes/index.js.mustache +127 -0
- package/templates/worker/hono-js/template.json +31 -0
- package/templates/worker/hono-js/wrangler.toml.mustache +36 -0
- package/thoughts/ledgers/CONTINUITY_ses_287e.md +74 -0
- package/thoughts/ledgers/CONTINUITY_ses_28b5.md +85 -0
- package/tsconfig.json +30 -0
- package/vitest.config.ts +20 -0
- package//351/240/205/347/233/256/350/241/250.md +140 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* create 命令實現
|
|
3
|
+
* 負責執行專案創建流程
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { runPrompts } from '../../core/prompt';
|
|
9
|
+
import { parseConfig, parseFromOptions } from '../../core/config';
|
|
10
|
+
import { directoryExists, isEmptyDir, deleteDirectory } from '../../core/filesystem';
|
|
11
|
+
import { loadTemplate, validateTemplate } from '../../core/template-manager';
|
|
12
|
+
import {
|
|
13
|
+
generateDefaultContext,
|
|
14
|
+
renderTemplateToFiles,
|
|
15
|
+
validateContext,
|
|
16
|
+
} from '../../core/template-engine';
|
|
17
|
+
import { gitInit, createGitignore } from '../../core/git';
|
|
18
|
+
import { logger } from '../../utils/logger';
|
|
19
|
+
import { ProjectConfig, Language } from '../../types';
|
|
20
|
+
import { execa } from 'execa';
|
|
21
|
+
import { existsSync } from 'fs';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 創建命令的選項接口
|
|
26
|
+
*/
|
|
27
|
+
interface CreateOptions {
|
|
28
|
+
template?: string;
|
|
29
|
+
git?: boolean;
|
|
30
|
+
install?: boolean;
|
|
31
|
+
type?: string;
|
|
32
|
+
language?: string;
|
|
33
|
+
verbose?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 處理目錄已存在的情況
|
|
38
|
+
* 包含二次確認機制,確保用戶不會誤刪重要資料
|
|
39
|
+
*/
|
|
40
|
+
async function handleExistingDirectory(targetDir: string): Promise<boolean> {
|
|
41
|
+
const isEmpty = isEmptyDir(targetDir);
|
|
42
|
+
|
|
43
|
+
if (isEmpty) {
|
|
44
|
+
// 空目錄也需要確認(安全措施)
|
|
45
|
+
logger.warn(`目錄 "${targetDir}" 已存在但為空`);
|
|
46
|
+
const { confirmEmpty } = await import('inquirer').then((mod) =>
|
|
47
|
+
mod.default.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'confirm',
|
|
50
|
+
name: 'confirmEmpty',
|
|
51
|
+
message: '是否移除現有的空目錄?',
|
|
52
|
+
default: true,
|
|
53
|
+
},
|
|
54
|
+
])
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (confirmEmpty) {
|
|
58
|
+
deleteDirectory(targetDir);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 目錄不為空,需要二次確認
|
|
65
|
+
logger.warn(`目錄 "${targetDir}" 已存在且包含檔案`);
|
|
66
|
+
logger.warn('⚠️ 警告:此操作將永久刪除目錄中的所有檔案!');
|
|
67
|
+
|
|
68
|
+
// 第一次確認
|
|
69
|
+
const { confirm1 } = await import('inquirer').then((mod) =>
|
|
70
|
+
mod.default.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'confirm',
|
|
73
|
+
name: 'confirm1',
|
|
74
|
+
message: '確定要覆蓋現有目錄嗎?(第一步確認)',
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
])
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (!confirm1) {
|
|
81
|
+
logger.info('已取消操作');
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 第二次確認(輸入目錄名稱確認)
|
|
86
|
+
const dirName = targetDir.split(/[/\\]/).pop() || targetDir;
|
|
87
|
+
const { confirm2 } = await import('inquirer').then((mod) =>
|
|
88
|
+
mod.default.prompt([
|
|
89
|
+
{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'confirm2',
|
|
92
|
+
message: `請輸入目錄名稱 "${dirName}" 以確認刪除:`,
|
|
93
|
+
validate: (input: string) => {
|
|
94
|
+
if (input === dirName) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return `輸入不正確,請輸入 "${dirName}" 以確認`;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
])
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// 確認輸入正確(validate 已確保)
|
|
104
|
+
logger.info('正在刪除現有目錄...');
|
|
105
|
+
deleteDirectory(targetDir);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 執行專案創建流程
|
|
111
|
+
*/
|
|
112
|
+
export async function createProject(
|
|
113
|
+
initialName?: string,
|
|
114
|
+
options: CreateOptions = {}
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const spinner = ora();
|
|
117
|
+
let config: ProjectConfig;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
logger.empty();
|
|
121
|
+
logger.icon('🚀', '開始建立 Cloudflare 專案');
|
|
122
|
+
logger.empty();
|
|
123
|
+
|
|
124
|
+
// 1. 執行互動式問答(如果沒有提供足夠的命令行選項)
|
|
125
|
+
const hasAllOptions = !!(initialName && options.type && options.template);
|
|
126
|
+
|
|
127
|
+
const userInput = hasAllOptions
|
|
128
|
+
? parseFromOptions(initialName as string, {
|
|
129
|
+
template: options.template as string,
|
|
130
|
+
...(options.git !== undefined && { git: options.git }),
|
|
131
|
+
...(options.install !== undefined && { install: options.install }),
|
|
132
|
+
type: options.type as string,
|
|
133
|
+
...(options.language !== undefined && { language: options.language }),
|
|
134
|
+
})
|
|
135
|
+
: await runPrompts(initialName);
|
|
136
|
+
|
|
137
|
+
// 2. 解析配置
|
|
138
|
+
config = parseConfig(userInput);
|
|
139
|
+
|
|
140
|
+
logger.empty();
|
|
141
|
+
logger.icon('📋', '專案配置');
|
|
142
|
+
logger.divider();
|
|
143
|
+
logger.icon('📁', `專案名稱:${config.projectName}`);
|
|
144
|
+
logger.icon(
|
|
145
|
+
'🔷',
|
|
146
|
+
`程式語言:${config.language === Language.TYPESCRIPT ? 'TypeScript' : 'JavaScript'}`
|
|
147
|
+
);
|
|
148
|
+
logger.icon('🏷️', `專案類型:${config.projectType}`);
|
|
149
|
+
logger.icon('📝', `使用模板:${config.template}`);
|
|
150
|
+
logger.icon('🔧', `初始化 Git: ${config.initGit ? '是' : '否'}`);
|
|
151
|
+
logger.icon('📦', `安裝依賴:${config.installDeps ? '是' : '否'}`);
|
|
152
|
+
logger.divider();
|
|
153
|
+
logger.empty();
|
|
154
|
+
|
|
155
|
+
// 3. 檢查目標目錄
|
|
156
|
+
const targetExists = directoryExists(config.targetDir);
|
|
157
|
+
|
|
158
|
+
if (targetExists) {
|
|
159
|
+
const shouldContinue = await handleExistingDirectory(config.targetDir);
|
|
160
|
+
|
|
161
|
+
if (!shouldContinue) {
|
|
162
|
+
logger.info('已取消操作');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 4. 載入並驗證模板
|
|
168
|
+
spinner.start('正在載入模板...');
|
|
169
|
+
const templateConfig = loadTemplate(config.projectType, config.template, config.language);
|
|
170
|
+
|
|
171
|
+
if (!templateConfig) {
|
|
172
|
+
spinner.fail('模板載入失敗');
|
|
173
|
+
throw new Error(`無法載入模板:${config.projectType}/${config.template}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const validation = validateTemplate(config.projectType, config.template, config.language);
|
|
177
|
+
|
|
178
|
+
if (!validation.valid) {
|
|
179
|
+
spinner.fail('模板驗證失敗');
|
|
180
|
+
throw new Error(validation.error);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
spinner.succeed(`模板「${templateConfig.name}」已載入`);
|
|
184
|
+
|
|
185
|
+
// 5. 生成渲染上下文
|
|
186
|
+
spinner.start('正在準備渲染上下文...');
|
|
187
|
+
const context = generateDefaultContext({
|
|
188
|
+
projectName: config.projectName,
|
|
189
|
+
projectType: config.projectType,
|
|
190
|
+
template: config.template,
|
|
191
|
+
language: config.language,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 驗證必需變數
|
|
195
|
+
const contextValidation = validateContext(
|
|
196
|
+
context,
|
|
197
|
+
templateConfig.requiredVars || ['projectName', 'projectType', 'template']
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (!contextValidation.valid) {
|
|
201
|
+
spinner.fail('上下文驗證失敗');
|
|
202
|
+
throw new Error(`缺少必需的模板變數:${contextValidation.missing.join(', ')}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
spinner.succeed('渲染上下文已準備');
|
|
206
|
+
|
|
207
|
+
// 6. 渲染模板
|
|
208
|
+
spinner.start('正在渲染模板檔案...');
|
|
209
|
+
const renderResult = await renderTemplateToFiles(templateConfig, config.targetDir, context);
|
|
210
|
+
|
|
211
|
+
if (!renderResult.success) {
|
|
212
|
+
spinner.fail('模板渲染失敗');
|
|
213
|
+
for (const error of renderResult.errors) {
|
|
214
|
+
logger.error(` - ${error.file}: ${error.error}`);
|
|
215
|
+
}
|
|
216
|
+
throw new Error('模板渲染過程中發生錯誤');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
spinner.succeed(`已生成 ${renderResult.files.length} 個檔案`);
|
|
220
|
+
|
|
221
|
+
// 7. Git 初始化
|
|
222
|
+
if (config.initGit) {
|
|
223
|
+
spinner.start('正在初始化 Git 倉庫...');
|
|
224
|
+
try {
|
|
225
|
+
const gitResult = await gitInit(config.targetDir);
|
|
226
|
+
if (gitResult.success) {
|
|
227
|
+
// 建立 .gitignore
|
|
228
|
+
createGitignore(config.targetDir);
|
|
229
|
+
spinner.succeed('Git 倉庫已初始化');
|
|
230
|
+
} else {
|
|
231
|
+
spinner.warn(gitResult.message);
|
|
232
|
+
}
|
|
233
|
+
} catch (error: unknown) {
|
|
234
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
235
|
+
spinner.warn(`Git 初始化失敗:${errorMessage}`);
|
|
236
|
+
logger.info(' 您可以稍後手動執行 "git init"');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 8. 依賴安裝
|
|
241
|
+
if (config.installDeps) {
|
|
242
|
+
spinner.start('正在安裝依賴...');
|
|
243
|
+
try {
|
|
244
|
+
// 檢查是否存在 package.json
|
|
245
|
+
const packageJsonPath = join(config.targetDir, 'package.json');
|
|
246
|
+
if (!existsSync(packageJsonPath)) {
|
|
247
|
+
spinner.warn('未找到 package.json,跳過依賴安裝');
|
|
248
|
+
} else {
|
|
249
|
+
await execa('npm', ['install'], {
|
|
250
|
+
cwd: config.targetDir,
|
|
251
|
+
timeout: 300000, // 5 分鐘超時
|
|
252
|
+
});
|
|
253
|
+
spinner.succeed('依賴已安裝');
|
|
254
|
+
}
|
|
255
|
+
} catch (error: unknown) {
|
|
256
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
257
|
+
spinner.warn(`依賴安裝失敗:${errorMessage}`);
|
|
258
|
+
logger.info(' 您可以稍後手動執行 "npm install"');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 9. 顯示完成信息與下一步指引
|
|
263
|
+
logger.empty();
|
|
264
|
+
logger.success('✅ 專案建立完成!');
|
|
265
|
+
logger.empty();
|
|
266
|
+
logger.divider();
|
|
267
|
+
logger.empty();
|
|
268
|
+
logger.icon('📂', `專案位置:${config.targetDir}`);
|
|
269
|
+
logger.empty();
|
|
270
|
+
logger.icon('📝', '已生成檔案:');
|
|
271
|
+
for (const file of renderResult.files) {
|
|
272
|
+
const relativePath = file.replace(config.targetDir + '\\', '');
|
|
273
|
+
logger.info(` ✓ ${relativePath}`);
|
|
274
|
+
}
|
|
275
|
+
logger.empty();
|
|
276
|
+
logger.icon('💡', '下一步操作:');
|
|
277
|
+
logger.info(` 1. cd ${config.projectName}`);
|
|
278
|
+
if (!config.installDeps) {
|
|
279
|
+
logger.info(' 2. npm install');
|
|
280
|
+
logger.info(' 3. npm run dev');
|
|
281
|
+
} else {
|
|
282
|
+
logger.info(' 2. npm run dev');
|
|
283
|
+
}
|
|
284
|
+
logger.empty();
|
|
285
|
+
} catch (error: unknown) {
|
|
286
|
+
if (spinner.isSpinning) {
|
|
287
|
+
spinner.fail('發生意外錯誤');
|
|
288
|
+
}
|
|
289
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
290
|
+
logger.error(`錯誤:${errorMessage}`);
|
|
291
|
+
if (options.verbose && error instanceof Error && error.stack) {
|
|
292
|
+
logger.error(error.stack);
|
|
293
|
+
}
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 建立 create 命令
|
|
300
|
+
*/
|
|
301
|
+
export function createCommand(program: Command): void {
|
|
302
|
+
program
|
|
303
|
+
.command('create')
|
|
304
|
+
.description('建立新的 Cloudflare 專案')
|
|
305
|
+
.argument('[project-name]', '專案名稱')
|
|
306
|
+
.option('-t, --template <template>', '使用指定模板', 'basic')
|
|
307
|
+
.option('--type <type>', '專案類型 (worker, pages, d1, kv, r2)')
|
|
308
|
+
.option('-l, --language <language>', '程式語言 (typescript, javascript)', 'typescript')
|
|
309
|
+
.option('--git', '是否初始化 Git 倉庫')
|
|
310
|
+
.option('--no-git', '不初始化 Git 倉庫')
|
|
311
|
+
.option('--install', '是否安裝依賴')
|
|
312
|
+
.option('--no-install', '不安裝依賴')
|
|
313
|
+
.option('-v, --verbose', '顯示詳細日誌')
|
|
314
|
+
.action(async (projectName: string | undefined, options: CreateOptions) => {
|
|
315
|
+
try {
|
|
316
|
+
await createProject(projectName, options);
|
|
317
|
+
} catch (error: unknown) {
|
|
318
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
319
|
+
logger.error(`建立專案時發生錯誤:${errorMessage}`);
|
|
320
|
+
if (options.verbose && error instanceof Error && error.stack) {
|
|
321
|
+
logger.error(error.stack);
|
|
322
|
+
}
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|