@lambertkeith/spec-go 0.2.4 → 0.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.
@@ -6,29 +6,138 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
8
 
9
- // src/cli.ts
9
+ // src/cli/cli.ts
10
10
  import path7 from "path";
11
11
  import fs5 from "fs";
12
12
  import { Command } from "commander";
13
- import pc6 from "picocolors";
13
+ import pc5 from "picocolors";
14
14
 
15
15
  // package.json
16
- var version = "0.2.4";
16
+ var version = "0.3.0";
17
17
 
18
- // src/prompts.ts
19
- import path2 from "path";
20
- import fs from "fs";
18
+ // src/cli/prompts.ts
19
+ import path5 from "path";
20
+ import fs3 from "fs";
21
21
  import { input, select, confirm, Separator } from "@inquirer/prompts";
22
22
  import pc from "picocolors";
23
23
 
24
- // src/utils.ts
24
+ // src/core/types.ts
25
+ var silentLogger = {
26
+ info: () => {
27
+ },
28
+ success: () => {
29
+ },
30
+ warn: () => {
31
+ },
32
+ error: () => {
33
+ },
34
+ dim: () => {
35
+ }
36
+ };
37
+
38
+ // src/core/config.ts
39
+ import fs from "fs";
25
40
  import path from "path";
41
+ import os from "os";
42
+ var CONFIG_PATH = path.join(os.homedir(), ".spec-go.json");
43
+ var DEFAULT_CONFIG = {
44
+ defaults: {
45
+ github: false,
46
+ public: false,
47
+ template: ""
48
+ },
49
+ github: {
50
+ token: "",
51
+ defaultOrg: ""
52
+ }
53
+ };
54
+ function ensureConfigExists() {
55
+ if (!fs.existsSync(CONFIG_PATH)) {
56
+ saveConfig(DEFAULT_CONFIG);
57
+ }
58
+ }
59
+ function loadConfig() {
60
+ try {
61
+ if (fs.existsSync(CONFIG_PATH)) {
62
+ const content = fs.readFileSync(CONFIG_PATH, "utf-8");
63
+ return JSON.parse(content);
64
+ }
65
+ } catch {
66
+ }
67
+ return {};
68
+ }
69
+ function saveConfig(config) {
70
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
71
+ fs.chmodSync(CONFIG_PATH, 384);
72
+ }
73
+ function getConfigPath() {
74
+ return CONFIG_PATH;
75
+ }
76
+
77
+ // src/core/debug.ts
78
+ var debugEnabled = false;
79
+ function setDebugEnabled(enabled) {
80
+ debugEnabled = enabled;
81
+ }
82
+ function isDebugEnabled() {
83
+ return debugEnabled;
84
+ }
85
+ function debug(category, message, data) {
86
+ if (!debugEnabled) return;
87
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
88
+ const prefix = `[DEBUG ${timestamp}] [${category}]`;
89
+ if (data !== void 0) {
90
+ const dataStr = formatData(data);
91
+ console.error(`${prefix} ${message}`, dataStr);
92
+ } else {
93
+ console.error(`${prefix} ${message}`);
94
+ }
95
+ }
96
+ function formatData(data) {
97
+ try {
98
+ const str = JSON.stringify(data, null, 2);
99
+ if (str.length > 500) {
100
+ return str.slice(0, 500) + "...(truncated)";
101
+ }
102
+ return str;
103
+ } catch {
104
+ return String(data);
105
+ }
106
+ }
107
+
108
+ // src/core/exit-codes.ts
109
+ var ExitCodes = {
110
+ /** 成功完成 */
111
+ SUCCESS: 0,
112
+ /** 用户错误:参数错误、输入无效 */
113
+ USER_ERROR: 1,
114
+ /** 外部错误:网络、GitHub API、包管理器 */
115
+ EXTERNAL_ERROR: 2,
116
+ /** 未知内部错误 */
117
+ INTERNAL_ERROR: 10,
118
+ /** 模板相关错误 */
119
+ TEMPLATE_ERROR: 11,
120
+ /** 文件系统错误 */
121
+ FILE_SYSTEM_ERROR: 12,
122
+ /** Git 操作错误 */
123
+ GIT_ERROR: 13
124
+ };
125
+ var CliError = class extends Error {
126
+ constructor(message, exitCode = ExitCodes.INTERNAL_ERROR) {
127
+ super(message);
128
+ this.exitCode = exitCode;
129
+ this.name = "CliError";
130
+ }
131
+ };
132
+
133
+ // src/core/utils.ts
134
+ import path2 from "path";
26
135
  import { fileURLToPath } from "url";
27
136
  import { spawn } from "child_process";
28
137
  var __filename = fileURLToPath(import.meta.url);
29
- var __dirname = path.dirname(__filename);
138
+ var __dirname = path2.dirname(__filename);
30
139
  function getTemplatesDir() {
31
- return path.resolve(__dirname, "..", "templates");
140
+ return path2.resolve(__dirname, "..", "..", "templates");
32
141
  }
33
142
  function isValidPackageName(name) {
34
143
  return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
@@ -45,6 +154,7 @@ function isEmpty(dirPath) {
45
154
  return files.length === 0 || files.length === 1 && files[0] === ".git";
46
155
  }
47
156
  function execAsync(command, args, options = {}) {
157
+ debug("exec", `\u6267\u884C\u547D\u4EE4: ${command} ${args.join(" ")}`, { cwd: options.cwd });
48
158
  return new Promise((resolve) => {
49
159
  const proc = spawn(command, args, {
50
160
  cwd: options.cwd,
@@ -64,141 +174,41 @@ function execAsync(command, args, options = {}) {
64
174
  });
65
175
  }
66
176
  proc.on("close", (code) => {
177
+ debug("exec", `\u547D\u4EE4\u5B8C\u6210\uFF0C\u9000\u51FA\u7801: ${code ?? 1}`);
67
178
  resolve({ code: code ?? 1, stdout, stderr });
68
179
  });
69
- proc.on("error", () => {
180
+ proc.on("error", (err) => {
181
+ debug("exec", `\u547D\u4EE4\u9519\u8BEF: ${err.message}`);
70
182
  resolve({ code: 1, stdout, stderr });
71
183
  });
72
184
  });
73
185
  }
74
186
 
75
- // src/prompts.ts
76
- async function runPrompts(argProjectName, options, userConfig = {}) {
77
- const defaults = userConfig.defaults ?? {};
78
- const templatesDir = getTemplatesDir();
79
- const registryPath = path2.join(templatesDir, "template.config.json");
80
- const registry = JSON.parse(
81
- fs.readFileSync(registryPath, "utf-8")
82
- );
83
- let projectName = argProjectName;
84
- if (!projectName) {
85
- projectName = await input({
86
- message: "\u9879\u76EE\u540D\u79F0:",
87
- default: "my-project",
88
- validate: (value) => {
89
- if (!value.trim()) {
90
- return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
91
- }
92
- if (!isValidPackageName(toValidPackageName(value))) {
93
- return "\u65E0\u6548\u7684\u9879\u76EE\u540D\u79F0";
94
- }
95
- return true;
96
- }
97
- });
98
- }
99
- const validName = toValidPackageName(projectName);
100
- const targetDir = path2.resolve(process.cwd(), validName);
101
- if (fs.existsSync(targetDir) && !isEmpty(targetDir)) {
102
- const overwrite = await confirm({
103
- message: `\u76EE\u6807\u76EE\u5F55 "${validName}" \u975E\u7A7A\uFF0C\u662F\u5426\u7EE7\u7EED? (\u5C06\u8986\u76D6\u5DF2\u6709\u6587\u4EF6)`,
104
- default: false
105
- });
106
- if (!overwrite) {
107
- const error = new Error("PROMPT_CANCELLED");
108
- throw error;
109
- }
110
- }
111
- let template = options.template;
112
- if (!template) {
113
- const defaultTemplate = defaults.template;
114
- const singleTemplates = registry.templates.filter((t) => t.category !== "fullstack");
115
- const fullstackTemplates = registry.templates.filter((t) => t.category === "fullstack");
116
- const templateChoices = [];
117
- templateChoices.push(new Separator("\u2500\u2500 \u5355\u4F53\u9879\u76EE \u2500\u2500"));
118
- for (const t of singleTemplates) {
119
- templateChoices.push({
120
- name: `${t.displayName} ${pc.dim(`- ${t.description}`)}`,
121
- value: t.name
122
- });
123
- }
124
- if (fullstackTemplates.length > 0) {
125
- templateChoices.push(new Separator("\u2500\u2500 \u524D\u540E\u7AEF\u5206\u79BB \u2500\u2500"));
126
- for (const t of fullstackTemplates) {
127
- templateChoices.push({
128
- name: `${t.displayName} ${pc.dim(`- ${t.description}`)}`,
129
- value: t.name
130
- });
131
- }
132
- }
133
- template = await select({
134
- message: "\u9009\u62E9\u6A21\u677F:",
135
- choices: templateChoices,
136
- default: defaultTemplate && registry.templates.some((t) => t.name === defaultTemplate) ? defaultTemplate : void 0
137
- });
138
- } else {
139
- const found = registry.templates.find((t) => t.name === template);
140
- if (!found) {
141
- console.log(pc.red(`\u9519\u8BEF: \u672A\u627E\u5230\u6A21\u677F "${template}"`));
142
- console.log(pc.dim(`\u53EF\u7528\u6A21\u677F: ${registry.templates.map((t) => t.name).join(", ")}`));
143
- process.exit(1);
144
- }
145
- }
146
- let initGit2 = options.git !== false;
147
- if (options.git === void 0) {
148
- initGit2 = await confirm({
149
- message: "\u521D\u59CB\u5316 Git \u4ED3\u5E93?",
150
- default: true
151
- });
152
- }
153
- let createGithub = options.github ?? false;
154
- if (initGit2 && options.github === void 0) {
155
- createGithub = await confirm({
156
- message: "\u5728 GitHub \u521B\u5EFA\u8FDC\u7A0B\u4ED3\u5E93?",
157
- default: defaults.github ?? false
158
- });
159
- }
160
- let isPublic = options.public ?? false;
161
- if (createGithub && options.public === void 0) {
162
- const publicDefault = defaults.public ?? false;
163
- isPublic = await select({
164
- message: "\u4ED3\u5E93\u53EF\u89C1\u6027:",
165
- choices: [
166
- { name: "private (\u79C1\u6709)", value: false },
167
- { name: "public (\u516C\u5F00)", value: true }
168
- ],
169
- default: publicDefault
170
- });
171
- }
172
- return {
173
- projectName: validName,
174
- template,
175
- targetDir,
176
- initGit: initGit2,
177
- createGithub,
178
- isPublic,
179
- noInstall: options.install === false
180
- };
181
- }
182
-
183
- // src/scaffold.ts
187
+ // src/core/scaffold.ts
184
188
  import path3 from "path";
185
189
  import fs2 from "fs-extra";
186
190
  async function scaffoldProject(options) {
187
191
  const templatesDir = getTemplatesDir();
188
192
  const registryPath = path3.join(templatesDir, "template.config.json");
189
193
  const registry = await fs2.readJson(registryPath);
194
+ debug("scaffold", `\u5F00\u59CB\u811A\u624B\u67B6\u9879\u76EE: ${options.projectName}`);
195
+ debug("scaffold", `\u6A21\u677F: ${options.template}`);
196
+ debug("scaffold", `\u76EE\u6807\u76EE\u5F55: ${options.targetDir}`);
190
197
  const templateEntry = registry.templates.find((t) => t.name === options.template);
191
198
  if (!templateEntry) {
192
199
  throw new Error(`\u672A\u627E\u5230\u6A21\u677F: ${options.template}`);
193
200
  }
194
201
  const templateDir = path3.join(templatesDir, templateEntry.dir);
195
202
  const templateConfigPath = path3.join(templateDir, "_template.json");
203
+ debug("scaffold", `\u6A21\u677F\u76EE\u5F55: ${templateDir}`);
196
204
  let templateConfig = null;
197
205
  if (await fs2.pathExists(templateConfigPath)) {
198
206
  templateConfig = await fs2.readJson(templateConfigPath);
207
+ debug("scaffold", "\u6A21\u677F\u914D\u7F6E", templateConfig);
199
208
  }
200
209
  await fs2.ensureDir(options.targetDir);
201
210
  const files = await fs2.readdir(templateDir);
211
+ debug("scaffold", `\u6A21\u677F\u6587\u4EF6\u6570\u91CF: ${files.length}`);
202
212
  for (const file of files) {
203
213
  if (file === "_template.json") {
204
214
  continue;
@@ -213,12 +223,14 @@ async function scaffoldProject(options) {
213
223
  }
214
224
  const destPath = path3.join(options.targetDir, destFileName);
215
225
  const stat = await fs2.stat(srcPath);
226
+ debug("scaffold", `\u590D\u5236: ${file} -> ${destFileName}`);
216
227
  if (stat.isDirectory()) {
217
228
  await copyDirectory(srcPath, destPath, options.projectName, templateConfig);
218
229
  } else {
219
230
  await copyFile(srcPath, destPath, options.projectName, templateConfig);
220
231
  }
221
232
  }
233
+ debug("scaffold", "\u811A\u624B\u67B6\u5B8C\u6210");
222
234
  return templateConfig;
223
235
  }
224
236
  async function copyDirectory(srcDir, destDir, projectName, config) {
@@ -275,52 +287,11 @@ async function copyFile(srcPath, destPath, projectName, config) {
275
287
  await fs2.writeFile(destPath, content, "utf-8");
276
288
  }
277
289
 
278
- // src/git.ts
279
- import pc2 from "picocolors";
280
-
281
- // src/config.ts
282
- import fs3 from "fs";
283
- import path4 from "path";
284
- import os from "os";
285
- var CONFIG_PATH = path4.join(os.homedir(), ".spec-go.json");
286
- var DEFAULT_CONFIG = {
287
- defaults: {
288
- github: false,
289
- public: false,
290
- template: ""
291
- },
292
- github: {
293
- token: "",
294
- defaultOrg: ""
295
- }
296
- };
297
- function ensureConfigExists() {
298
- if (!fs3.existsSync(CONFIG_PATH)) {
299
- saveConfig(DEFAULT_CONFIG);
300
- }
301
- }
302
- function loadConfig() {
303
- try {
304
- if (fs3.existsSync(CONFIG_PATH)) {
305
- const content = fs3.readFileSync(CONFIG_PATH, "utf-8");
306
- return JSON.parse(content);
307
- }
308
- } catch {
309
- }
310
- return {};
311
- }
312
- function saveConfig(config) {
313
- fs3.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
314
- fs3.chmodSync(CONFIG_PATH, 384);
315
- }
316
- function getConfigPath() {
317
- return CONFIG_PATH;
318
- }
319
-
320
- // src/github-api.ts
290
+ // src/core/github-api.ts
321
291
  var GITHUB_API_BASE = "https://api.github.com";
322
292
  async function githubFetch(endpoint, token, options = {}) {
323
293
  const url = `${GITHUB_API_BASE}${endpoint}`;
294
+ debug("github-api", `\u8BF7\u6C42: ${options.method || "GET"} ${endpoint}`);
324
295
  try {
325
296
  const response = await fetch(url, {
326
297
  ...options,
@@ -332,9 +303,11 @@ async function githubFetch(endpoint, token, options = {}) {
332
303
  ...options.headers
333
304
  }
334
305
  });
306
+ debug("github-api", `\u54CD\u5E94\u72B6\u6001: ${response.status}`);
335
307
  if (response.status === 403) {
336
308
  const rateLimitRemaining = response.headers.get("x-ratelimit-remaining");
337
309
  const rateLimitReset = response.headers.get("x-ratelimit-reset");
310
+ debug("github-api", `Rate Limit \u4FE1\u606F`, { rateLimitRemaining, rateLimitReset });
338
311
  if (rateLimitRemaining === "0" && rateLimitReset) {
339
312
  const resetDate = new Date(parseInt(rateLimitReset) * 1e3);
340
313
  return {
@@ -346,6 +319,7 @@ async function githubFetch(endpoint, token, options = {}) {
346
319
  }
347
320
  }
348
321
  if (response.status === 401) {
322
+ debug("github-api", "Token \u9A8C\u8BC1\u5931\u8D25");
349
323
  return {
350
324
  success: false,
351
325
  error: "Token \u65E0\u6548\u6216\u5DF2\u8FC7\u671F",
@@ -354,6 +328,7 @@ async function githubFetch(endpoint, token, options = {}) {
354
328
  }
355
329
  if (!response.ok) {
356
330
  const errorData = await response.json();
331
+ debug("github-api", "\u8BF7\u6C42\u5931\u8D25", errorData);
357
332
  return {
358
333
  success: false,
359
334
  error: errorData.message || `\u8BF7\u6C42\u5931\u8D25: ${response.status}`,
@@ -361,8 +336,10 @@ async function githubFetch(endpoint, token, options = {}) {
361
336
  };
362
337
  }
363
338
  const data = await response.json();
339
+ debug("github-api", "\u8BF7\u6C42\u6210\u529F");
364
340
  return { success: true, data };
365
341
  } catch (err) {
342
+ debug("github-api", "\u7F51\u7EDC\u8BF7\u6C42\u5F02\u5E38", { error: err instanceof Error ? err.message : String(err) });
366
343
  return {
367
344
  success: false,
368
345
  error: err instanceof Error ? err.message : "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25"
@@ -386,32 +363,35 @@ async function createRepository(token, options) {
386
363
  });
387
364
  }
388
365
 
389
- // src/git.ts
366
+ // src/core/git.ts
390
367
  async function initGit(targetDir) {
368
+ debug("git", `\u521D\u59CB\u5316 Git \u4ED3\u5E93: ${targetDir}`);
391
369
  await execAsync("git", ["init"], { cwd: targetDir });
392
370
  await execAsync("git", ["add", "-A"], { cwd: targetDir });
393
371
  await execAsync("git", ["commit", "-m", "Initial commit"], { cwd: targetDir });
372
+ debug("git", "Git \u521D\u59CB\u5316\u5B8C\u6210");
394
373
  }
395
- async function createGithubRepo(repoName, targetDir, isPublic) {
374
+ async function createGithubRepo(options) {
375
+ const { repoName, targetDir, isPublic, organization, logger = silentLogger } = options;
376
+ debug("git", `\u521B\u5EFA GitHub \u4ED3\u5E93: ${repoName}, \u516C\u5F00: ${isPublic}`);
396
377
  const config = loadConfig();
397
378
  if (!config.github?.token) {
398
- console.log(pc2.yellow("\u26A0 \u672A\u914D\u7F6E GitHub Token\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u521B\u5EFA"));
399
- console.log(pc2.dim(` \u8FD0\u884C ${pc2.cyan("spec-go config --setup-github")} \u914D\u7F6E Token`));
400
- console.log(pc2.dim(` \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${getConfigPath()}`));
379
+ logger.warn("\u26A0 \u672A\u914D\u7F6E GitHub Token\uFF0C\u8DF3\u8FC7\u4ED3\u5E93\u521B\u5EFA");
380
+ logger.dim(` \u8FD0\u884C spec-go config --setup-github \u914D\u7F6E Token`);
381
+ logger.dim(` \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${getConfigPath()}`);
401
382
  return null;
402
383
  }
403
384
  const token = config.github.token;
404
- const defaultOrg = config.github.defaultOrg;
385
+ const defaultOrg = organization || config.github.defaultOrg;
405
386
  const validateResult = await validateToken(token);
406
387
  if (!validateResult.success) {
407
- console.log(pc2.yellow(`\u26A0 GitHub Token \u65E0\u6548: ${validateResult.error}`));
388
+ logger.warn(`\u26A0 GitHub Token \u65E0\u6548: ${validateResult.error}`);
408
389
  if (validateResult.rateLimitReset) {
409
- console.log(pc2.dim(` Rate limit \u5C06\u5728 ${validateResult.rateLimitReset.toLocaleString()} \u91CD\u7F6E`));
390
+ logger.dim(` Rate limit \u5C06\u5728 ${validateResult.rateLimitReset.toLocaleString()} \u91CD\u7F6E`);
410
391
  }
411
- console.log(pc2.dim(` \u8FD0\u884C ${pc2.cyan("spec-go config --setup-github")} \u91CD\u65B0\u914D\u7F6E`));
392
+ logger.dim(` \u8FD0\u884C spec-go config --setup-github \u91CD\u65B0\u914D\u7F6E`);
412
393
  return null;
413
394
  }
414
- const username = validateResult.data.login;
415
395
  const createResult = await createRepository(token, {
416
396
  name: repoName,
417
397
  isPrivate: !isPublic,
@@ -419,12 +399,12 @@ async function createGithubRepo(repoName, targetDir, isPublic) {
419
399
  });
420
400
  if (!createResult.success) {
421
401
  if (createResult.statusCode === 422) {
422
- console.log(pc2.yellow(`\u26A0 \u4ED3\u5E93 "${repoName}" \u5DF2\u5B58\u5728\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6\u540D\u79F0`));
402
+ logger.warn(`\u26A0 \u4ED3\u5E93 "${repoName}" \u5DF2\u5B58\u5728\uFF0C\u8BF7\u9009\u62E9\u5176\u4ED6\u540D\u79F0`);
423
403
  } else if (createResult.rateLimitReset) {
424
- console.log(pc2.yellow(`\u26A0 API \u8BF7\u6C42\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650`));
425
- console.log(pc2.dim(` \u5C06\u5728 ${createResult.rateLimitReset.toLocaleString()} \u91CD\u7F6E`));
404
+ logger.warn(`\u26A0 API \u8BF7\u6C42\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650`);
405
+ logger.dim(` \u5C06\u5728 ${createResult.rateLimitReset.toLocaleString()} \u91CD\u7F6E`);
426
406
  } else {
427
- console.log(pc2.yellow(`\u26A0 \u521B\u5EFA\u4ED3\u5E93\u5931\u8D25: ${createResult.error}`));
407
+ logger.warn(`\u26A0 \u521B\u5EFA\u4ED3\u5E93\u5931\u8D25: ${createResult.error}`);
428
408
  }
429
409
  return null;
430
410
  }
@@ -453,165 +433,178 @@ async function createGithubRepo(repoName, targetDir, isPublic) {
453
433
  { cwd: targetDir }
454
434
  );
455
435
  if (pushMaster.code !== 0) {
456
- console.log(pc2.yellow(`\u26A0 \u63A8\u9001\u5931\u8D25: ${pushResult.stderr || pushMaster.stderr}`));
457
- console.log(pc2.dim(` \u4ED3\u5E93\u5DF2\u521B\u5EFA: ${repo.html_url}`));
458
- console.log(pc2.dim(" \u8BF7\u624B\u52A8\u63A8\u9001\u4EE3\u7801"));
436
+ logger.warn(`\u26A0 \u63A8\u9001\u5931\u8D25: ${pushResult.stderr || pushMaster.stderr}`);
437
+ logger.dim(` \u4ED3\u5E93\u5DF2\u521B\u5EFA: ${repo.html_url}`);
438
+ logger.dim(" \u8BF7\u624B\u52A8\u63A8\u9001\u4EE3\u7801");
459
439
  return repo.html_url;
460
440
  }
461
441
  }
462
442
  return repo.html_url;
463
443
  }
464
444
 
465
- // src/post-init.ts
466
- import path5 from "path";
467
- import pc3 from "picocolors";
468
- async function runPostInit(targetDir, commands) {
445
+ // src/core/post-init.ts
446
+ import path4 from "path";
447
+ async function runPostInit(targetDir, commands, options = {}) {
448
+ const { logger = silentLogger } = options;
469
449
  for (const cmd of commands) {
470
- console.log(pc3.dim(` ${cmd.description}...`));
450
+ logger.dim(` ${cmd.description}...`);
471
451
  const [command, ...args] = cmd.command.split(" ");
472
452
  const result = await execAsync(command, args, {
473
453
  cwd: targetDir,
474
454
  stdio: "pipe"
475
455
  });
476
456
  if (result.code !== 0) {
477
- console.log(pc3.yellow(`\u26A0 ${cmd.description} \u5931\u8D25`));
457
+ logger.warn(`\u26A0 ${cmd.description} \u5931\u8D25`);
478
458
  if (result.stderr) {
479
- console.log(pc3.dim(result.stderr.slice(0, 200)));
459
+ logger.dim(result.stderr.slice(0, 200));
480
460
  }
481
461
  }
482
462
  }
483
463
  }
484
- async function runWorkspacePostInit(targetDir, config) {
464
+ async function runWorkspacePostInit(targetDir, config, options = {}) {
465
+ const { logger = silentLogger } = options;
485
466
  if (config.postInit && config.postInit.length > 0) {
486
- await runPostInit(targetDir, config.postInit);
467
+ await runPostInit(targetDir, config.postInit, options);
487
468
  }
488
469
  if (config.workspaces) {
489
470
  for (const ws of config.workspaces) {
490
471
  if (ws.postInit && ws.postInit.length > 0) {
491
- const wsDir = path5.join(targetDir, ws.name);
492
- console.log(pc3.dim(` [${ws.name}]`));
493
- await runPostInit(wsDir, ws.postInit);
472
+ const wsDir = path4.join(targetDir, ws.name);
473
+ logger.dim(` [${ws.name}]`);
474
+ await runPostInit(wsDir, ws.postInit, options);
494
475
  }
495
476
  }
496
477
  }
497
478
  }
498
479
 
499
- // src/update-check.ts
500
- import fs4 from "fs";
501
- import path6 from "path";
502
- import os2 from "os";
503
- import { execSync } from "child_process";
504
- import pc4 from "picocolors";
505
- var CACHE_PATH = path6.join(os2.homedir(), ".spec-go-update-check");
506
- var CACHE_TTL = 24 * 60 * 60 * 1e3;
507
- var PACKAGE_NAME = "@lambertkeith/spec-go";
508
- function readCache() {
509
- try {
510
- if (fs4.existsSync(CACHE_PATH)) {
511
- const content = fs4.readFileSync(CACHE_PATH, "utf-8");
512
- return JSON.parse(content);
480
+ // src/cli/prompts.ts
481
+ var DEFAULT_TEMPLATE = "node-ts";
482
+ async function runPrompts(argProjectName, options, userConfig = {}, interactive = true) {
483
+ const defaults = userConfig.defaults ?? {};
484
+ const templatesDir = getTemplatesDir();
485
+ const registryPath = path5.join(templatesDir, "template.config.json");
486
+ const registry = JSON.parse(
487
+ fs3.readFileSync(registryPath, "utf-8")
488
+ );
489
+ debug("prompts", `\u4EA4\u4E92\u6A21\u5F0F: ${interactive}`);
490
+ debug("prompts", `CLI \u53C2\u6570`, { argProjectName, options });
491
+ let projectName = argProjectName;
492
+ if (!projectName) {
493
+ if (!interactive) {
494
+ console.log(pc.red("\u9519\u8BEF: \u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u5FC5\u987B\u63D0\u4F9B\u9879\u76EE\u540D\u79F0"));
495
+ throw new CliError("\u975E\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u5FC5\u987B\u63D0\u4F9B\u9879\u76EE\u540D\u79F0", ExitCodes.USER_ERROR);
513
496
  }
514
- } catch {
515
- }
516
- return null;
517
- }
518
- function writeCache(cache) {
519
- try {
520
- fs4.writeFileSync(CACHE_PATH, JSON.stringify(cache), "utf-8");
521
- } catch {
497
+ projectName = await input({
498
+ message: "\u9879\u76EE\u540D\u79F0:",
499
+ default: "my-project",
500
+ validate: (value) => {
501
+ if (!value.trim()) {
502
+ return "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A";
503
+ }
504
+ if (!isValidPackageName(toValidPackageName(value))) {
505
+ return "\u65E0\u6548\u7684\u9879\u76EE\u540D\u79F0";
506
+ }
507
+ return true;
508
+ }
509
+ });
522
510
  }
523
- }
524
- async function fetchLatestVersion() {
525
- try {
526
- const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
527
- if (!response.ok) {
528
- return null;
511
+ const validName = toValidPackageName(projectName);
512
+ const targetDir = path5.resolve(process.cwd(), validName);
513
+ if (fs3.existsSync(targetDir) && !isEmpty(targetDir)) {
514
+ if (!interactive) {
515
+ console.log(pc.yellow(`\u8B66\u544A: \u76EE\u6807\u76EE\u5F55 "${validName}" \u975E\u7A7A\uFF0C\u5C06\u8986\u76D6\u5DF2\u6709\u6587\u4EF6`));
516
+ } else {
517
+ const overwrite = await confirm({
518
+ message: `\u76EE\u6807\u76EE\u5F55 "${validName}" \u975E\u7A7A\uFF0C\u662F\u5426\u7EE7\u7EED? (\u5C06\u8986\u76D6\u5DF2\u6709\u6587\u4EF6)`,
519
+ default: false
520
+ });
521
+ if (!overwrite) {
522
+ const error = new Error("PROMPT_CANCELLED");
523
+ throw error;
524
+ }
529
525
  }
530
- const data = await response.json();
531
- return data.version;
532
- } catch {
533
- return null;
534
- }
535
- }
536
- function compareVersions(current, latest) {
537
- const currentParts = current.split(".").map(Number);
538
- const latestParts = latest.split(".").map(Number);
539
- for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
540
- const c = currentParts[i] || 0;
541
- const l = latestParts[i] || 0;
542
- if (c < l) return -1;
543
- if (c > l) return 1;
544
526
  }
545
- return 0;
546
- }
547
- async function checkForUpdates(currentVersion) {
548
- const cache = readCache();
549
- const now = Date.now();
550
- let latestVersion = null;
551
- if (cache && now - cache.lastCheck < CACHE_TTL) {
552
- latestVersion = cache.latestVersion;
553
- } else {
554
- latestVersion = await fetchLatestVersion();
555
- if (latestVersion) {
556
- writeCache({ lastCheck: now, latestVersion });
527
+ let template = options.template;
528
+ if (!template) {
529
+ if (!interactive) {
530
+ template = defaults.template || DEFAULT_TEMPLATE;
531
+ console.log(pc.dim(`\u4F7F\u7528\u9ED8\u8BA4\u6A21\u677F: ${template}`));
532
+ } else {
533
+ const defaultTemplate = defaults.template;
534
+ const singleTemplates = registry.templates.filter((t) => t.category !== "fullstack");
535
+ const fullstackTemplates = registry.templates.filter((t) => t.category === "fullstack");
536
+ const templateChoices = [];
537
+ templateChoices.push(new Separator("\u2500\u2500 \u5355\u4F53\u9879\u76EE \u2500\u2500"));
538
+ for (const t of singleTemplates) {
539
+ templateChoices.push({
540
+ name: `${t.displayName} ${pc.dim(`- ${t.description}`)}`,
541
+ value: t.name
542
+ });
543
+ }
544
+ if (fullstackTemplates.length > 0) {
545
+ templateChoices.push(new Separator("\u2500\u2500 \u524D\u540E\u7AEF\u5206\u79BB \u2500\u2500"));
546
+ for (const t of fullstackTemplates) {
547
+ templateChoices.push({
548
+ name: `${t.displayName} ${pc.dim(`- ${t.description}`)}`,
549
+ value: t.name
550
+ });
551
+ }
552
+ }
553
+ template = await select({
554
+ message: "\u9009\u62E9\u6A21\u677F:",
555
+ choices: templateChoices,
556
+ default: defaultTemplate && registry.templates.some((t) => t.name === defaultTemplate) ? defaultTemplate : void 0
557
+ });
557
558
  }
558
559
  }
559
- if (latestVersion && compareVersions(currentVersion, latestVersion) < 0) {
560
- console.log();
561
- console.log(
562
- pc4.yellow(` \u26A0 \u53D1\u73B0\u65B0\u7248\u672C ${pc4.bold(latestVersion)}\uFF0C\u5F53\u524D ${currentVersion}`)
563
- );
564
- console.log(pc4.cyan(` \u8FD0\u884C ${pc4.bold(`npm update -g ${PACKAGE_NAME}`)} \u66F4\u65B0`));
565
- console.log();
566
- }
567
- }
568
- function detectPackageManager() {
569
- const execPath = process.argv[1] || "";
570
- if (execPath.includes("pnpm")) return "pnpm";
571
- if (execPath.includes("yarn")) return "yarn";
572
- return "npm";
573
- }
574
- function getUpdateCommand(pm) {
575
- switch (pm) {
576
- case "pnpm":
577
- return `pnpm update -g ${PACKAGE_NAME}`;
578
- case "yarn":
579
- return `yarn global upgrade ${PACKAGE_NAME}`;
580
- default:
581
- return `npm update -g ${PACKAGE_NAME}`;
582
- }
583
- }
584
- async function runUpdate(currentVersion, checkOnly) {
585
- const latestVersion = await fetchLatestVersion();
586
- if (!latestVersion) {
587
- console.log(pc4.red("\u2717 \u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F"));
588
- process.exit(1);
560
+ const found = registry.templates.find((t) => t.name === template);
561
+ if (!found) {
562
+ console.log(pc.red(`\u9519\u8BEF: \u672A\u627E\u5230\u6A21\u677F "${template}"`));
563
+ console.log(pc.dim(`\u53EF\u7528\u6A21\u677F: ${registry.templates.map((t) => t.name).join(", ")}`));
564
+ throw new CliError(`\u672A\u627E\u5230\u6A21\u677F "${template}"`, ExitCodes.USER_ERROR);
589
565
  }
590
- const comparison = compareVersions(currentVersion, latestVersion);
591
- if (comparison >= 0) {
592
- console.log(pc4.green(`\u2714 \u5DF2\u662F\u6700\u65B0\u7248\u672C (${currentVersion})`));
593
- return;
566
+ let initGit2 = options.git !== false;
567
+ if (options.git === void 0 && interactive) {
568
+ initGit2 = await confirm({
569
+ message: "\u521D\u59CB\u5316 Git \u4ED3\u5E93?",
570
+ default: true
571
+ });
594
572
  }
595
- console.log(pc4.cyan(`\u53D1\u73B0\u65B0\u7248\u672C: ${currentVersion} \u2192 ${latestVersion}`));
596
- if (checkOnly) {
597
- console.log(pc4.dim(`\u8FD0\u884C spec-go update \u66F4\u65B0`));
598
- return;
573
+ let createGithub = options.github ?? false;
574
+ if (initGit2 && options.github === void 0 && interactive) {
575
+ createGithub = await confirm({
576
+ message: "\u5728 GitHub \u521B\u5EFA\u8FDC\u7A0B\u4ED3\u5E93?",
577
+ default: defaults.github ?? false
578
+ });
599
579
  }
600
- const pm = detectPackageManager();
601
- const updateCmd = getUpdateCommand(pm);
602
- console.log(pc4.dim(`\u6B63\u5728\u6267\u884C: ${updateCmd}`));
603
- try {
604
- execSync(updateCmd, { stdio: "inherit" });
605
- console.log(pc4.green(`\u2714 \u66F4\u65B0\u5B8C\u6210`));
606
- } catch {
607
- console.log(pc4.red("\u2717 \u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\u66F4\u65B0\u547D\u4EE4"));
608
- process.exit(1);
580
+ let isPublic = options.public ?? false;
581
+ if (createGithub && options.public === void 0 && interactive) {
582
+ const publicDefault = defaults.public ?? false;
583
+ isPublic = await select({
584
+ message: "\u4ED3\u5E93\u53EF\u89C1\u6027:",
585
+ choices: [
586
+ { name: "private (\u79C1\u6709)", value: false },
587
+ { name: "public (\u516C\u5F00)", value: true }
588
+ ],
589
+ default: publicDefault
590
+ });
609
591
  }
592
+ const result = {
593
+ projectName: validName,
594
+ template,
595
+ targetDir,
596
+ initGit: initGit2,
597
+ createGithub,
598
+ isPublic,
599
+ noInstall: options.install === false
600
+ };
601
+ debug("prompts", "\u6700\u7EC8\u9879\u76EE\u9009\u9879", result);
602
+ return result;
610
603
  }
611
604
 
612
- // src/github-setup.ts
605
+ // src/cli/github-setup.ts
613
606
  import { input as input2, confirm as confirm2 } from "@inquirer/prompts";
614
- import pc5 from "picocolors";
607
+ import pc2 from "picocolors";
615
608
  var TOKEN_HELP_URL = "https://github.com/settings/tokens/new?scopes=repo&description=spec-go";
616
609
  function isUserCancelled(err) {
617
610
  const error = err;
@@ -619,17 +612,17 @@ function isUserCancelled(err) {
619
612
  }
620
613
  async function runGitHubSetup() {
621
614
  console.log();
622
- console.log(pc5.cyan("\u914D\u7F6E GitHub Token"));
615
+ console.log(pc2.cyan("\u914D\u7F6E GitHub Token"));
623
616
  console.log();
624
617
  console.log("\u9700\u8981\u4E00\u4E2A\u5177\u6709 repo \u6743\u9650\u7684 Personal Access Token (Classic) \u6765\u521B\u5EFA\u4ED3\u5E93\u3002");
625
618
  console.log();
626
- console.log(pc5.dim("\u83B7\u53D6\u6B65\u9AA4:"));
627
- console.log(pc5.dim("1. \u8BBF\u95EE GitHub Settings > Developer settings > Personal access tokens"));
628
- console.log(pc5.dim('2. \u70B9\u51FB "Generate new token (classic)"'));
629
- console.log(pc5.dim('3. \u52FE\u9009 "repo" \u6743\u9650'));
630
- console.log(pc5.dim("4. \u751F\u6210\u5E76\u590D\u5236 Token"));
619
+ console.log(pc2.dim("\u83B7\u53D6\u6B65\u9AA4:"));
620
+ console.log(pc2.dim("1. \u8BBF\u95EE GitHub Settings > Developer settings > Personal access tokens"));
621
+ console.log(pc2.dim('2. \u70B9\u51FB "Generate new token (classic)"'));
622
+ console.log(pc2.dim('3. \u52FE\u9009 "repo" \u6743\u9650'));
623
+ console.log(pc2.dim("4. \u751F\u6210\u5E76\u590D\u5236 Token"));
631
624
  console.log();
632
- console.log(pc5.dim(`\u5FEB\u6377\u94FE\u63A5: ${TOKEN_HELP_URL}`));
625
+ console.log(pc2.dim(`\u5FEB\u6377\u94FE\u63A5: ${TOKEN_HELP_URL}`));
633
626
  console.log();
634
627
  try {
635
628
  const token = await input2({
@@ -645,17 +638,17 @@ async function runGitHubSetup() {
645
638
  }
646
639
  });
647
640
  console.log();
648
- console.log(pc5.dim("\u6B63\u5728\u9A8C\u8BC1 Token..."));
641
+ console.log(pc2.dim("\u6B63\u5728\u9A8C\u8BC1 Token..."));
649
642
  const result = await validateToken(token.trim());
650
643
  if (!result.success) {
651
- console.log(pc5.red(`\u2717 Token \u9A8C\u8BC1\u5931\u8D25: ${result.error}`));
644
+ console.log(pc2.red(`\u2717 Token \u9A8C\u8BC1\u5931\u8D25: ${result.error}`));
652
645
  if (result.rateLimitReset) {
653
- console.log(pc5.dim(` Rate limit \u5C06\u5728 ${result.rateLimitReset.toLocaleString()} \u91CD\u7F6E`));
646
+ console.log(pc2.dim(` Rate limit \u5C06\u5728 ${result.rateLimitReset.toLocaleString()} \u91CD\u7F6E`));
654
647
  }
655
648
  return false;
656
649
  }
657
650
  const user = result.data;
658
- console.log(pc5.green(`\u2714 Token \u6709\u6548\uFF0C\u5DF2\u767B\u5F55\u4E3A: ${user.login}${user.name ? ` (${user.name})` : ""}`));
651
+ console.log(pc2.green(`\u2714 Token \u6709\u6548\uFF0C\u5DF2\u767B\u5F55\u4E3A: ${user.login}${user.name ? ` (${user.name})` : ""}`));
659
652
  console.log();
660
653
  const setOrg = await confirm2({
661
654
  message: "\u662F\u5426\u8BBE\u7F6E\u9ED8\u8BA4\u7EC4\u7EC7? (\u5426\u5219\u4ED3\u5E93\u5C06\u521B\u5EFA\u5728\u4E2A\u4EBA\u8D26\u6237\u4E0B)",
@@ -681,12 +674,12 @@ async function runGitHubSetup() {
681
674
  };
682
675
  saveConfig(config);
683
676
  console.log();
684
- console.log(pc5.green("\u2714 GitHub \u914D\u7F6E\u5DF2\u4FDD\u5B58"));
685
- console.log(pc5.dim(" \u914D\u7F6E\u6587\u4EF6\u5DF2\u8BBE\u7F6E\u4E3A\u4EC5\u5F53\u524D\u7528\u6237\u53EF\u8BFB"));
677
+ console.log(pc2.green("\u2714 GitHub \u914D\u7F6E\u5DF2\u4FDD\u5B58"));
678
+ console.log(pc2.dim(" \u914D\u7F6E\u6587\u4EF6\u5DF2\u8BBE\u7F6E\u4E3A\u4EC5\u5F53\u524D\u7528\u6237\u53EF\u8BFB"));
686
679
  return true;
687
680
  } catch (err) {
688
681
  if (isUserCancelled(err)) {
689
- console.log(pc5.yellow("\n\u64CD\u4F5C\u5DF2\u53D6\u6D88"));
682
+ console.log(pc2.yellow("\n\u64CD\u4F5C\u5DF2\u53D6\u6D88"));
690
683
  return false;
691
684
  }
692
685
  throw err;
@@ -695,10 +688,10 @@ async function runGitHubSetup() {
695
688
  function showConfig() {
696
689
  const config = loadConfig();
697
690
  console.log();
698
- console.log(pc5.cyan("\u5F53\u524D\u914D\u7F6E:"));
691
+ console.log(pc2.cyan("\u5F53\u524D\u914D\u7F6E:"));
699
692
  console.log();
700
693
  if (config.defaults) {
701
- console.log(pc5.dim("defaults:"));
694
+ console.log(pc2.dim("defaults:"));
702
695
  if (config.defaults.github !== void 0) {
703
696
  console.log(` github: ${config.defaults.github}`);
704
697
  }
@@ -710,7 +703,7 @@ function showConfig() {
710
703
  }
711
704
  }
712
705
  if (config.github) {
713
- console.log(pc5.dim("github:"));
706
+ console.log(pc2.dim("github:"));
714
707
  if (config.github.token) {
715
708
  const masked = config.github.token.slice(0, 8) + "..." + config.github.token.slice(-4);
716
709
  console.log(` token: ${masked}`);
@@ -720,51 +713,204 @@ function showConfig() {
720
713
  }
721
714
  }
722
715
  if (!config.defaults && !config.github) {
723
- console.log(pc5.dim("(\u65E0\u914D\u7F6E)"));
716
+ console.log(pc2.dim("(\u65E0\u914D\u7F6E)"));
724
717
  }
725
718
  console.log();
726
719
  }
727
720
 
728
- // src/cli.ts
721
+ // src/cli/update-check.ts
722
+ import fs4 from "fs";
723
+ import path6 from "path";
724
+ import os2 from "os";
725
+ import { execSync } from "child_process";
726
+ import pc3 from "picocolors";
727
+ var CACHE_PATH = path6.join(os2.homedir(), ".spec-go-update-check");
728
+ var CACHE_TTL = 24 * 60 * 60 * 1e3;
729
+ var PACKAGE_NAME = "@lambertkeith/spec-go";
730
+ function readCache() {
731
+ try {
732
+ if (fs4.existsSync(CACHE_PATH)) {
733
+ const content = fs4.readFileSync(CACHE_PATH, "utf-8");
734
+ return JSON.parse(content);
735
+ }
736
+ } catch {
737
+ }
738
+ return null;
739
+ }
740
+ function writeCache(cache) {
741
+ try {
742
+ fs4.writeFileSync(CACHE_PATH, JSON.stringify(cache), "utf-8");
743
+ } catch {
744
+ }
745
+ }
746
+ async function fetchLatestVersion() {
747
+ try {
748
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`);
749
+ if (!response.ok) {
750
+ return null;
751
+ }
752
+ const data = await response.json();
753
+ return data.version;
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
758
+ function compareVersions(current, latest) {
759
+ const currentParts = current.split(".").map(Number);
760
+ const latestParts = latest.split(".").map(Number);
761
+ for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
762
+ const c = currentParts[i] || 0;
763
+ const l = latestParts[i] || 0;
764
+ if (c < l) return -1;
765
+ if (c > l) return 1;
766
+ }
767
+ return 0;
768
+ }
769
+ async function checkForUpdates(currentVersion) {
770
+ const cache = readCache();
771
+ const now = Date.now();
772
+ let latestVersion = null;
773
+ if (cache && now - cache.lastCheck < CACHE_TTL) {
774
+ latestVersion = cache.latestVersion;
775
+ } else {
776
+ latestVersion = await fetchLatestVersion();
777
+ if (latestVersion) {
778
+ writeCache({ lastCheck: now, latestVersion });
779
+ }
780
+ }
781
+ if (latestVersion && compareVersions(currentVersion, latestVersion) < 0) {
782
+ console.log();
783
+ console.log(
784
+ pc3.yellow(` \u26A0 \u53D1\u73B0\u65B0\u7248\u672C ${pc3.bold(latestVersion)}\uFF0C\u5F53\u524D ${currentVersion}`)
785
+ );
786
+ console.log(pc3.cyan(` \u8FD0\u884C ${pc3.bold(`npm update -g ${PACKAGE_NAME}`)} \u66F4\u65B0`));
787
+ console.log();
788
+ }
789
+ }
790
+ function detectPackageManager() {
791
+ const execPath = process.argv[1] || "";
792
+ if (execPath.includes("pnpm")) return "pnpm";
793
+ if (execPath.includes("yarn")) return "yarn";
794
+ return "npm";
795
+ }
796
+ function getUpdateCommand(pm) {
797
+ switch (pm) {
798
+ case "pnpm":
799
+ return `pnpm update -g ${PACKAGE_NAME}`;
800
+ case "yarn":
801
+ return `yarn global upgrade ${PACKAGE_NAME}`;
802
+ default:
803
+ return `npm update -g ${PACKAGE_NAME}`;
804
+ }
805
+ }
806
+ async function runUpdate(currentVersion, checkOnly) {
807
+ const latestVersion = await fetchLatestVersion();
808
+ if (!latestVersion) {
809
+ console.log(pc3.red("\u2717 \u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F"));
810
+ process.exit(ExitCodes.EXTERNAL_ERROR);
811
+ }
812
+ const comparison = compareVersions(currentVersion, latestVersion);
813
+ if (comparison >= 0) {
814
+ console.log(pc3.green(`\u2714 \u5DF2\u662F\u6700\u65B0\u7248\u672C (${currentVersion})`));
815
+ return;
816
+ }
817
+ console.log(pc3.cyan(`\u53D1\u73B0\u65B0\u7248\u672C: ${currentVersion} \u2192 ${latestVersion}`));
818
+ if (checkOnly) {
819
+ console.log(pc3.dim(`\u8FD0\u884C spec-go update \u66F4\u65B0`));
820
+ return;
821
+ }
822
+ const pm = detectPackageManager();
823
+ const updateCmd = getUpdateCommand(pm);
824
+ console.log(pc3.dim(`\u6B63\u5728\u6267\u884C: ${updateCmd}`));
825
+ try {
826
+ execSync(updateCmd, { stdio: "inherit" });
827
+ console.log(pc3.green(`\u2714 \u66F4\u65B0\u5B8C\u6210`));
828
+ } catch {
829
+ console.log(pc3.red("\u2717 \u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u6267\u884C\u66F4\u65B0\u547D\u4EE4"));
830
+ process.exit(ExitCodes.EXTERNAL_ERROR);
831
+ }
832
+ }
833
+
834
+ // src/cli/console-logger.ts
835
+ import pc4 from "picocolors";
836
+ var consoleLogger = {
837
+ info(message) {
838
+ console.log(message);
839
+ },
840
+ success(message) {
841
+ console.log(pc4.green(message));
842
+ },
843
+ warn(message) {
844
+ console.log(pc4.yellow(message));
845
+ },
846
+ error(message) {
847
+ console.log(pc4.red(message));
848
+ },
849
+ dim(message) {
850
+ console.log(pc4.dim(message));
851
+ }
852
+ };
853
+
854
+ // src/cli/cli.ts
855
+ function isInteractive(options) {
856
+ if (options.yes) return false;
857
+ return process.stdin.isTTY === true;
858
+ }
729
859
  async function createCli() {
730
860
  const program = new Command();
731
- program.name("spec-go").description("CLI \u811A\u624B\u67B6\u5DE5\u5177\uFF0C\u5FEB\u901F\u521B\u5EFA\u9879\u76EE\u5E76\u53EF\u9009\u521D\u59CB\u5316 GitHub \u4ED3\u5E93").version(version).argument("[project-name]", "\u9879\u76EE\u540D\u79F0").option("-t, --template <template>", "\u4F7F\u7528\u6307\u5B9A\u6A21\u677F").option("--github", "\u5728 GitHub \u521B\u5EFA\u8FDC\u7A0B\u4ED3\u5E93").option("--public", "\u521B\u5EFA\u516C\u5F00\u4ED3\u5E93 (\u9ED8\u8BA4\u79C1\u6709)").option("--no-git", "\u8DF3\u8FC7 Git \u521D\u59CB\u5316").option("--no-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").action(async (projectName, options) => {
861
+ program.name("spec-go").description("CLI \u811A\u624B\u67B6\u5DE5\u5177\uFF0C\u5FEB\u901F\u521B\u5EFA\u9879\u76EE\u5E76\u53EF\u9009\u521D\u59CB\u5316 GitHub \u4ED3\u5E93").version(version).argument("[project-name]", "\u9879\u76EE\u540D\u79F0").option("-t, --template <template>", "\u4F7F\u7528\u6307\u5B9A\u6A21\u677F").option("--github", "\u5728 GitHub \u521B\u5EFA\u8FDC\u7A0B\u4ED3\u5E93").option("--public", "\u521B\u5EFA\u516C\u5F00\u4ED3\u5E93 (\u9ED8\u8BA4\u79C1\u6709)").option("--no-git", "\u8DF3\u8FC7 Git \u521D\u59CB\u5316").option("--no-install", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("-y, --yes", "\u8DF3\u8FC7\u6240\u6709\u786E\u8BA4\u63D0\u793A\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF08\u975E\u4EA4\u4E92\u6A21\u5F0F\uFF09").option("--debug", "\u542F\u7528\u8C03\u8BD5\u8F93\u51FA").action(async (projectName, options) => {
862
+ if (options.debug) {
863
+ setDebugEnabled(true);
864
+ }
732
865
  console.log();
733
- console.log(` ${pc6.cyan("spec-go")} ${pc6.dim(`v${version}`)}`);
866
+ console.log(` ${pc5.cyan("spec-go")} ${pc5.dim(`v${version}`)}`);
734
867
  console.log();
735
868
  checkForUpdates(version).catch(() => {
736
869
  });
737
870
  const userConfig = loadConfig();
871
+ const interactive = isInteractive(options);
738
872
  try {
739
- const projectOptions = await runPrompts(projectName, options, userConfig);
873
+ const projectOptions = await runPrompts(projectName, options, userConfig, interactive);
740
874
  await executeProject(projectOptions);
741
875
  } catch (err) {
742
876
  const error = err;
743
877
  if (error.message === "PROMPT_CANCELLED" || error.name === "ExitPromptError" || error.message.includes("User force closed")) {
744
- console.log(pc6.yellow("\n\u64CD\u4F5C\u5DF2\u53D6\u6D88"));
745
- process.exit(0);
878
+ console.log(pc5.yellow("\n\u64CD\u4F5C\u5DF2\u53D6\u6D88"));
879
+ process.exit(ExitCodes.SUCCESS);
746
880
  }
747
881
  throw err;
748
882
  }
749
883
  });
750
- program.command("list").description("\u5217\u51FA\u6240\u6709\u53EF\u7528\u6A21\u677F").action(() => {
884
+ program.command("list").description("\u5217\u51FA\u6240\u6709\u53EF\u7528\u6A21\u677F").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
751
885
  const templatesDir = getTemplatesDir();
752
886
  const registryPath = path7.join(templatesDir, "template.config.json");
753
887
  const registry = JSON.parse(
754
888
  fs5.readFileSync(registryPath, "utf-8")
755
889
  );
890
+ if (options.json) {
891
+ const output = {
892
+ templates: registry.templates.map((t) => ({
893
+ name: t.name,
894
+ displayName: t.displayName,
895
+ description: t.description,
896
+ category: t.category || "single"
897
+ }))
898
+ };
899
+ console.log(JSON.stringify(output, null, 2));
900
+ return;
901
+ }
756
902
  const singleTemplates = registry.templates.filter((t) => t.category !== "fullstack");
757
903
  const fullstackTemplates = registry.templates.filter((t) => t.category === "fullstack");
758
904
  console.log();
759
- console.log(pc6.cyan(" \u5355\u4F53\u9879\u76EE:"));
905
+ console.log(pc5.cyan(" \u5355\u4F53\u9879\u76EE:"));
760
906
  for (const t of singleTemplates) {
761
- console.log(` ${pc6.green(t.name.padEnd(24))} ${pc6.dim(t.displayName)}`);
907
+ console.log(` ${pc5.green(t.name.padEnd(24))} ${pc5.dim(t.displayName)}`);
762
908
  }
763
909
  if (fullstackTemplates.length > 0) {
764
910
  console.log();
765
- console.log(pc6.cyan(" \u524D\u540E\u7AEF\u5206\u79BB:"));
911
+ console.log(pc5.cyan(" \u524D\u540E\u7AEF\u5206\u79BB:"));
766
912
  for (const t of fullstackTemplates) {
767
- console.log(` ${pc6.green(t.name.padEnd(24))} ${pc6.dim(t.displayName)}`);
913
+ console.log(` ${pc5.green(t.name.padEnd(24))} ${pc5.dim(t.displayName)}`);
768
914
  }
769
915
  }
770
916
  console.log();
@@ -772,18 +918,18 @@ async function createCli() {
772
918
  program.command("config").description("\u7BA1\u7406\u914D\u7F6E").option("--setup-github", "\u914D\u7F6E GitHub Token").option("--show", "\u663E\u793A\u5F53\u524D\u914D\u7F6E").option("--path", "\u663E\u793A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
773
919
  if (options.setupGithub) {
774
920
  const success = await runGitHubSetup();
775
- process.exit(success ? 0 : 1);
921
+ process.exit(success ? ExitCodes.SUCCESS : ExitCodes.USER_ERROR);
776
922
  } else if (options.show) {
777
923
  showConfig();
778
924
  } else if (options.path) {
779
925
  console.log(getConfigPath());
780
926
  } else {
781
927
  console.log();
782
- console.log(pc6.cyan("\u914D\u7F6E\u7BA1\u7406\u547D\u4EE4:"));
928
+ console.log(pc5.cyan("\u914D\u7F6E\u7BA1\u7406\u547D\u4EE4:"));
783
929
  console.log();
784
- console.log(` ${pc6.dim("spec-go config --setup-github")} \u914D\u7F6E GitHub Token`);
785
- console.log(` ${pc6.dim("spec-go config --show")} \u663E\u793A\u5F53\u524D\u914D\u7F6E`);
786
- console.log(` ${pc6.dim("spec-go config --path")} \u663E\u793A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84`);
930
+ console.log(` ${pc5.dim("spec-go config --setup-github")} \u914D\u7F6E GitHub Token`);
931
+ console.log(` ${pc5.dim("spec-go config --show")} \u663E\u793A\u5F53\u524D\u914D\u7F6E`);
932
+ console.log(` ${pc5.dim("spec-go config --path")} \u663E\u793A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84`);
787
933
  console.log();
788
934
  }
789
935
  });
@@ -794,28 +940,29 @@ async function createCli() {
794
940
  }
795
941
  async function executeProject(options) {
796
942
  const templateConfig = await scaffoldProject(options);
797
- console.log(pc6.green("\u2714 \u9879\u76EE\u6587\u4EF6\u5DF2\u751F\u6210"));
943
+ console.log(pc5.green("\u2714 \u9879\u76EE\u6587\u4EF6\u5DF2\u751F\u6210"));
798
944
  if (!options.noInstall && templateConfig) {
799
945
  if (templateConfig.type === "workspace") {
800
- await runWorkspacePostInit(options.targetDir, templateConfig);
801
- console.log(pc6.green("\u2714 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210"));
946
+ await runWorkspacePostInit(options.targetDir, templateConfig, { logger: consoleLogger });
947
+ console.log(pc5.green("\u2714 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210"));
802
948
  } else if (templateConfig.postInit) {
803
- await runPostInit(options.targetDir, templateConfig.postInit);
804
- console.log(pc6.green("\u2714 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210"));
949
+ await runPostInit(options.targetDir, templateConfig.postInit, { logger: consoleLogger });
950
+ console.log(pc5.green("\u2714 \u4F9D\u8D56\u5B89\u88C5\u5B8C\u6210"));
805
951
  }
806
952
  }
807
953
  if (options.initGit) {
808
954
  await initGit(options.targetDir);
809
- console.log(pc6.green("\u2714 Git \u4ED3\u5E93\u5DF2\u521D\u59CB\u5316"));
955
+ console.log(pc5.green("\u2714 Git \u4ED3\u5E93\u5DF2\u521D\u59CB\u5316"));
810
956
  }
811
957
  if (options.createGithub) {
812
- const repoUrl = await createGithubRepo(
813
- options.projectName,
814
- options.targetDir,
815
- options.isPublic
816
- );
958
+ const repoUrl = await createGithubRepo({
959
+ repoName: options.projectName,
960
+ targetDir: options.targetDir,
961
+ isPublic: options.isPublic,
962
+ logger: consoleLogger
963
+ });
817
964
  if (repoUrl) {
818
- console.log(pc6.green(`\u2714 GitHub \u4ED3\u5E93\u5DF2\u521B\u5EFA: ${pc6.cyan(repoUrl)}`));
965
+ console.log(pc5.green(`\u2714 GitHub \u4ED3\u5E93\u5DF2\u521B\u5EFA: ${pc5.cyan(repoUrl)}`));
819
966
  }
820
967
  }
821
968
  console.log();
@@ -823,28 +970,34 @@ async function executeProject(options) {
823
970
  const cdCmd = options.targetDir !== process.cwd() ? `cd ${options.projectName} && ` : "";
824
971
  if (templateConfig?.type === "workspace") {
825
972
  if (pm === "pnpm") {
826
- console.log(pc6.dim(` ${cdCmd}pnpm dev`));
973
+ console.log(pc5.dim(` ${cdCmd}pnpm dev`));
827
974
  } else {
828
- console.log(pc6.dim(` ${cdCmd}make dev`));
975
+ console.log(pc5.dim(` ${cdCmd}make dev`));
829
976
  }
830
977
  } else if (pm === "maven") {
831
- console.log(pc6.dim(` ${cdCmd}./mvnw spring-boot:run`));
978
+ console.log(pc5.dim(` ${cdCmd}./mvnw spring-boot:run`));
832
979
  } else if (pm === "gradle") {
833
- console.log(pc6.dim(` ${cdCmd}./gradlew bootRun`));
980
+ console.log(pc5.dim(` ${cdCmd}./gradlew bootRun`));
834
981
  } else {
835
- console.log(pc6.dim(` ${cdCmd}${pm} dev`));
982
+ console.log(pc5.dim(` ${cdCmd}${pm} dev`));
836
983
  }
837
984
  console.log();
838
985
  }
839
986
 
840
- // src/index.ts
987
+ // src/cli/index.ts
841
988
  async function main() {
842
989
  ensureConfigExists();
843
990
  const program = await createCli();
844
991
  await program.parseAsync(process.argv);
845
992
  }
846
993
  main().catch((err) => {
994
+ if (err instanceof CliError) {
995
+ if (isDebugEnabled()) {
996
+ console.error(err);
997
+ }
998
+ process.exit(err.exitCode);
999
+ }
847
1000
  console.error(err);
848
- process.exit(1);
1001
+ process.exit(ExitCodes.INTERNAL_ERROR);
849
1002
  });
850
1003
  //# sourceMappingURL=index.js.map