@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.
- package/README.md +60 -0
- package/dist/{index.js → cli/index.js} +500 -347
- package/dist/cli/index.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +591 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +8 -4
- package/dist/index.js.map +0 -1
- /package/dist/{index.d.ts → cli/index.d.ts} +0 -0
|
@@ -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
|
|
13
|
+
import pc5 from "picocolors";
|
|
14
14
|
|
|
15
15
|
// package.json
|
|
16
|
-
var version = "0.
|
|
16
|
+
var version = "0.3.0";
|
|
17
17
|
|
|
18
|
-
// src/prompts.ts
|
|
19
|
-
import
|
|
20
|
-
import
|
|
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/
|
|
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 =
|
|
138
|
+
var __dirname = path2.dirname(__filename);
|
|
30
139
|
function getTemplatesDir() {
|
|
31
|
-
return
|
|
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/
|
|
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/
|
|
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(
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
388
|
+
logger.warn(`\u26A0 GitHub Token \u65E0\u6548: ${validateResult.error}`);
|
|
408
389
|
if (validateResult.rateLimitReset) {
|
|
409
|
-
|
|
390
|
+
logger.dim(` Rate limit \u5C06\u5728 ${validateResult.rateLimitReset.toLocaleString()} \u91CD\u7F6E`);
|
|
410
391
|
}
|
|
411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
+
logger.warn(`\u26A0 ${cmd.description} \u5931\u8D25`);
|
|
478
458
|
if (result.stderr) {
|
|
479
|
-
|
|
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 =
|
|
492
|
-
|
|
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/
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
console.log(
|
|
562
|
-
|
|
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
|
-
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
596
|
-
if (
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
|
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(
|
|
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(
|
|
627
|
-
console.log(
|
|
628
|
-
console.log(
|
|
629
|
-
console.log(
|
|
630
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
644
|
+
console.log(pc2.red(`\u2717 Token \u9A8C\u8BC1\u5931\u8D25: ${result.error}`));
|
|
652
645
|
if (result.rateLimitReset) {
|
|
653
|
-
console.log(
|
|
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(
|
|
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(
|
|
685
|
-
console.log(
|
|
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(
|
|
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(
|
|
691
|
+
console.log(pc2.cyan("\u5F53\u524D\u914D\u7F6E:"));
|
|
699
692
|
console.log();
|
|
700
693
|
if (config.defaults) {
|
|
701
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
745
|
-
process.exit(
|
|
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(
|
|
905
|
+
console.log(pc5.cyan(" \u5355\u4F53\u9879\u76EE:"));
|
|
760
906
|
for (const t of singleTemplates) {
|
|
761
|
-
console.log(` ${
|
|
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(
|
|
911
|
+
console.log(pc5.cyan(" \u524D\u540E\u7AEF\u5206\u79BB:"));
|
|
766
912
|
for (const t of fullstackTemplates) {
|
|
767
|
-
console.log(` ${
|
|
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 ?
|
|
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(
|
|
928
|
+
console.log(pc5.cyan("\u914D\u7F6E\u7BA1\u7406\u547D\u4EE4:"));
|
|
783
929
|
console.log();
|
|
784
|
-
console.log(` ${
|
|
785
|
-
console.log(` ${
|
|
786
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
973
|
+
console.log(pc5.dim(` ${cdCmd}pnpm dev`));
|
|
827
974
|
} else {
|
|
828
|
-
console.log(
|
|
975
|
+
console.log(pc5.dim(` ${cdCmd}make dev`));
|
|
829
976
|
}
|
|
830
977
|
} else if (pm === "maven") {
|
|
831
|
-
console.log(
|
|
978
|
+
console.log(pc5.dim(` ${cdCmd}./mvnw spring-boot:run`));
|
|
832
979
|
} else if (pm === "gradle") {
|
|
833
|
-
console.log(
|
|
980
|
+
console.log(pc5.dim(` ${cdCmd}./gradlew bootRun`));
|
|
834
981
|
} else {
|
|
835
|
-
console.log(
|
|
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(
|
|
1001
|
+
process.exit(ExitCodes.INTERNAL_ERROR);
|
|
849
1002
|
});
|
|
850
1003
|
//# sourceMappingURL=index.js.map
|