yg-team-cli 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1673 -273
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1926 -488
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
4
|
var __esm = (fn, res) => function __init() {
|
|
4
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
6
|
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
6
11
|
|
|
7
12
|
// node_modules/tsup/assets/esm_shims.js
|
|
8
13
|
import path from "path";
|
|
@@ -177,6 +182,238 @@ var init_logger = __esm({
|
|
|
177
182
|
}
|
|
178
183
|
});
|
|
179
184
|
|
|
185
|
+
// src/lib/utils.ts
|
|
186
|
+
import fs from "fs-extra";
|
|
187
|
+
import path2 from "path";
|
|
188
|
+
import { glob } from "glob";
|
|
189
|
+
var FileUtils, StringUtils2, DateUtils, GitUtils, SpecUtils;
|
|
190
|
+
var init_utils = __esm({
|
|
191
|
+
"src/lib/utils.ts"() {
|
|
192
|
+
"use strict";
|
|
193
|
+
init_esm_shims();
|
|
194
|
+
FileUtils = class {
|
|
195
|
+
/**
|
|
196
|
+
* 确保目录存在
|
|
197
|
+
*/
|
|
198
|
+
static async ensureDir(dir) {
|
|
199
|
+
await fs.ensureDir(dir);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 读取文件内容
|
|
203
|
+
*/
|
|
204
|
+
static async read(file) {
|
|
205
|
+
return await fs.readFile(file, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 写入文件内容
|
|
209
|
+
*/
|
|
210
|
+
static async write(file, content) {
|
|
211
|
+
await fs.writeFile(file, content, "utf-8");
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 检查文件是否存在
|
|
215
|
+
*/
|
|
216
|
+
static async exists(file) {
|
|
217
|
+
return await fs.pathExists(file);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 复制文件
|
|
221
|
+
*/
|
|
222
|
+
static async copy(src, dest) {
|
|
223
|
+
await fs.copy(src, dest);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 删除文件或目录
|
|
227
|
+
*/
|
|
228
|
+
static async remove(file) {
|
|
229
|
+
await fs.remove(file);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 移动文件
|
|
233
|
+
*/
|
|
234
|
+
static async move(src, dest) {
|
|
235
|
+
await fs.move(src, dest);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 使用 glob 查找文件
|
|
239
|
+
*/
|
|
240
|
+
static async findFiles(pattern, cwd) {
|
|
241
|
+
return await glob(pattern, {
|
|
242
|
+
cwd,
|
|
243
|
+
absolute: false,
|
|
244
|
+
nodir: true
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 读取 JSON 文件
|
|
249
|
+
*/
|
|
250
|
+
static async readJson(file) {
|
|
251
|
+
return await fs.readJson(file);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 写入 JSON 文件
|
|
255
|
+
*/
|
|
256
|
+
static async writeJson(file, data) {
|
|
257
|
+
await fs.writeJson(file, data, { spaces: 2 });
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
StringUtils2 = class {
|
|
261
|
+
/**
|
|
262
|
+
* 转换为 kebab-case
|
|
263
|
+
*/
|
|
264
|
+
static toKebabCase(str) {
|
|
265
|
+
return str.toLowerCase().replace(/[\s_]+/g, "-").replace(/[^\w\-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 转换为 PascalCase
|
|
269
|
+
*/
|
|
270
|
+
static toPascalCase(str) {
|
|
271
|
+
return str.replace(/[-_\s](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 转换为 camelCase
|
|
275
|
+
*/
|
|
276
|
+
static toCamelCase(str) {
|
|
277
|
+
const pascal = this.toPascalCase(str);
|
|
278
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 转换为 snake_case
|
|
282
|
+
*/
|
|
283
|
+
static toSnakeCase(str) {
|
|
284
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).replace(/^_/, "").replace(/-+/g, "_").replace(/[\s]+/g, "_");
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* 首字母大写
|
|
288
|
+
*/
|
|
289
|
+
static capitalize(str) {
|
|
290
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 截断字符串
|
|
294
|
+
*/
|
|
295
|
+
static truncate(str, length, suffix = "...") {
|
|
296
|
+
if (str.length <= length) return str;
|
|
297
|
+
return str.slice(0, length - suffix.length) + suffix;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 生成 slug
|
|
301
|
+
*/
|
|
302
|
+
static slugify(str) {
|
|
303
|
+
return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
DateUtils = class _DateUtils {
|
|
307
|
+
/**
|
|
308
|
+
* 格式化日期
|
|
309
|
+
*/
|
|
310
|
+
static format(date = /* @__PURE__ */ new Date(), format = "YYYY-MM-DD HH:mm:ss") {
|
|
311
|
+
const year = date.getFullYear();
|
|
312
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
313
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
314
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
315
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
316
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
317
|
+
return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* 获取相对时间
|
|
321
|
+
*/
|
|
322
|
+
static relative(date) {
|
|
323
|
+
const now = /* @__PURE__ */ new Date();
|
|
324
|
+
const diff = now.getTime() - date.getTime();
|
|
325
|
+
const seconds = Math.floor(diff / 1e3);
|
|
326
|
+
const minutes = Math.floor(seconds / 60);
|
|
327
|
+
const hours = Math.floor(minutes / 60);
|
|
328
|
+
const days = Math.floor(hours / 24);
|
|
329
|
+
if (days > 7) {
|
|
330
|
+
return _DateUtils.format(date, "YYYY-MM-DD");
|
|
331
|
+
} else if (days > 0) {
|
|
332
|
+
return `${days} \u5929\u524D`;
|
|
333
|
+
} else if (hours > 0) {
|
|
334
|
+
return `${hours} \u5C0F\u65F6\u524D`;
|
|
335
|
+
} else if (minutes > 0) {
|
|
336
|
+
return `${minutes} \u5206\u949F\u524D`;
|
|
337
|
+
} else {
|
|
338
|
+
return "\u521A\u521A";
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
GitUtils = class {
|
|
343
|
+
/**
|
|
344
|
+
* 检查是否在 Git 仓库中
|
|
345
|
+
*/
|
|
346
|
+
static async isGitRepo(cwd = process.cwd()) {
|
|
347
|
+
const gitDir = path2.join(cwd, ".git");
|
|
348
|
+
return await FileUtils.exists(gitDir);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 获取当前分支名
|
|
352
|
+
*/
|
|
353
|
+
static async getCurrentBranch(cwd = process.cwd()) {
|
|
354
|
+
const { execa: execa5 } = await import("execa");
|
|
355
|
+
const { stdout } = await execa5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
356
|
+
cwd
|
|
357
|
+
});
|
|
358
|
+
return stdout.trim();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 获取当前 commit hash
|
|
362
|
+
*/
|
|
363
|
+
static async getCurrentCommit(cwd = process.cwd()) {
|
|
364
|
+
const { execa: execa5 } = await import("execa");
|
|
365
|
+
const { stdout } = await execa5("git", ["rev-parse", "HEAD"], { cwd });
|
|
366
|
+
return stdout.trim().slice(0, 7);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
SpecUtils = class {
|
|
370
|
+
/**
|
|
371
|
+
* 解析 spec 文件
|
|
372
|
+
*/
|
|
373
|
+
static async parseSpec(file) {
|
|
374
|
+
const content = await FileUtils.read(file);
|
|
375
|
+
const spec = {};
|
|
376
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
377
|
+
if (titleMatch) {
|
|
378
|
+
spec.title = titleMatch[1];
|
|
379
|
+
}
|
|
380
|
+
const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
|
|
381
|
+
if (nameMatch) {
|
|
382
|
+
spec.name = nameMatch[1];
|
|
383
|
+
}
|
|
384
|
+
const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
|
|
385
|
+
if (priorityMatch) {
|
|
386
|
+
spec.priority = priorityMatch[1];
|
|
387
|
+
}
|
|
388
|
+
const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
|
|
389
|
+
if (statusMatch) {
|
|
390
|
+
spec.status = statusMatch[1];
|
|
391
|
+
}
|
|
392
|
+
const depSection = content.match(/## 依赖关系\s+([\s\S]+?)##/m);
|
|
393
|
+
if (depSection) {
|
|
394
|
+
const depMatches = depSection[1].matchAll(/- \[([ x])\]\s*(.+)$/gm);
|
|
395
|
+
spec.dependencies = Array.from(depMatches).map((m) => ({
|
|
396
|
+
completed: m[1] === "x",
|
|
397
|
+
name: m[2]
|
|
398
|
+
}));
|
|
399
|
+
}
|
|
400
|
+
return spec;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 获取 spec 状态
|
|
404
|
+
*/
|
|
405
|
+
static async getSpecStatus(file) {
|
|
406
|
+
try {
|
|
407
|
+
const spec = await this.parseSpec(file);
|
|
408
|
+
return spec.status || "\u672A\u77E5";
|
|
409
|
+
} catch {
|
|
410
|
+
return "\u9519\u8BEF";
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
180
417
|
// src/lib/claude.ts
|
|
181
418
|
import { execa } from "execa";
|
|
182
419
|
var ClaudeAI, claudeAI;
|
|
@@ -185,6 +422,7 @@ var init_claude = __esm({
|
|
|
185
422
|
"use strict";
|
|
186
423
|
init_esm_shims();
|
|
187
424
|
init_logger();
|
|
425
|
+
init_utils();
|
|
188
426
|
ClaudeAI = class {
|
|
189
427
|
verbose;
|
|
190
428
|
constructor(verbose = false) {
|
|
@@ -218,25 +456,45 @@ var init_claude = __esm({
|
|
|
218
456
|
async prompt(promptText, options) {
|
|
219
457
|
const spinner = logger.startLoading("\u6B63\u5728\u8C03\u7528 Claude...");
|
|
220
458
|
try {
|
|
221
|
-
const args = [
|
|
459
|
+
const args = [];
|
|
460
|
+
const validContextFiles = [];
|
|
222
461
|
if (options?.contextFiles && options.contextFiles.length > 0) {
|
|
223
462
|
for (const file of options.contextFiles) {
|
|
224
|
-
|
|
463
|
+
const exists = await FileUtils.exists(file);
|
|
464
|
+
if (exists) {
|
|
465
|
+
validContextFiles.push(file);
|
|
466
|
+
} else {
|
|
467
|
+
logger.warn(`\u4E0A\u4E0B\u6587\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${file}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
for (const file of validContextFiles) {
|
|
471
|
+
args.push("--context", file);
|
|
225
472
|
}
|
|
226
473
|
}
|
|
227
|
-
|
|
474
|
+
args.push("--no-confirm", "-p", promptText);
|
|
475
|
+
const result = await execa("claude", args, {
|
|
228
476
|
stdio: this.verbose ? "inherit" : "pipe",
|
|
229
|
-
timeout: options?.timeout || 3e5
|
|
477
|
+
timeout: options?.timeout || 3e5,
|
|
230
478
|
// 默认 5 分钟
|
|
479
|
+
reject: false
|
|
480
|
+
// 不自动拒绝非零退出码
|
|
231
481
|
});
|
|
232
482
|
spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
|
|
233
|
-
|
|
483
|
+
if (result.exitCode !== 0 && !result.stdout) {
|
|
484
|
+
const stderr = result.stderr || "";
|
|
485
|
+
throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})${stderr ? `
|
|
486
|
+
${stderr}` : ""}`);
|
|
487
|
+
}
|
|
488
|
+
return result.stdout || "";
|
|
234
489
|
} catch (error) {
|
|
235
490
|
spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
|
|
236
491
|
if (error.killed && error.signal === "SIGTERM") {
|
|
237
492
|
throw new Error("Claude \u6267\u884C\u8D85\u65F6");
|
|
238
493
|
}
|
|
239
|
-
|
|
494
|
+
const stderr = error.stderr || "";
|
|
495
|
+
const exitCode = error.exitCode !== void 0 ? ` (\u9000\u51FA\u7801: ${error.exitCode})` : "";
|
|
496
|
+
throw new Error(`Claude \u8C03\u7528\u5931\u8D25: ${error.message}${exitCode}${stderr ? `
|
|
497
|
+
${stderr}` : ""}`);
|
|
240
498
|
}
|
|
241
499
|
}
|
|
242
500
|
/**
|
|
@@ -260,12 +518,17 @@ var init_claude = __esm({
|
|
|
260
518
|
const role = msg.role === "system" ? "\u7CFB\u7EDF\u6307\u4EE4" : `${msg.role === "user" ? "\u7528\u6237" : "\u52A9\u624B"}`;
|
|
261
519
|
return `[${role}]: ${msg.content}`;
|
|
262
520
|
}).join("\n\n");
|
|
263
|
-
const
|
|
521
|
+
const args = ["--no-confirm", "-p", fullPrompt];
|
|
522
|
+
const result = await execa("claude", args, {
|
|
264
523
|
stdio: this.verbose ? "inherit" : "pipe",
|
|
265
|
-
timeout: options?.timeout || 3e5
|
|
524
|
+
timeout: options?.timeout || 3e5,
|
|
525
|
+
reject: false
|
|
266
526
|
});
|
|
267
527
|
spinner.succeed("Claude \u54CD\u5E94\u5B8C\u6210");
|
|
268
|
-
|
|
528
|
+
if (result.exitCode !== 0 && !result.stdout) {
|
|
529
|
+
throw new Error(`Claude \u547D\u4EE4\u6267\u884C\u5931\u8D25 (\u9000\u51FA\u7801: ${result.exitCode})`);
|
|
530
|
+
}
|
|
531
|
+
return result.stdout || "";
|
|
269
532
|
} catch (error) {
|
|
270
533
|
spinner.fail("Claude \u8C03\u7528\u5931\u8D25");
|
|
271
534
|
throw error;
|
|
@@ -478,232 +741,556 @@ ${projectContext}
|
|
|
478
741
|
}
|
|
479
742
|
});
|
|
480
743
|
|
|
481
|
-
// src/lib/
|
|
482
|
-
import
|
|
483
|
-
import
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
744
|
+
// src/lib/template-version.ts
|
|
745
|
+
import { execa as execa2 } from "execa";
|
|
746
|
+
import path3 from "path";
|
|
747
|
+
function getConfigPath(projectPath) {
|
|
748
|
+
return path3.join(projectPath, ".team-cli", "template.json");
|
|
749
|
+
}
|
|
750
|
+
async function readTemplateConfig(projectPath) {
|
|
751
|
+
const configPath = getConfigPath(projectPath);
|
|
752
|
+
const exists = await FileUtils.exists(configPath);
|
|
753
|
+
if (!exists) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
const content = await FileUtils.read(configPath);
|
|
758
|
+
return JSON.parse(content);
|
|
759
|
+
} catch {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function saveTemplateConfig(projectPath, config) {
|
|
764
|
+
const configDir = path3.join(projectPath, ".team-cli");
|
|
765
|
+
await FileUtils.ensureDir(configDir);
|
|
766
|
+
const configPath = getConfigPath(projectPath);
|
|
767
|
+
const content = JSON.stringify(config, null, 2);
|
|
768
|
+
await FileUtils.write(configPath, content);
|
|
769
|
+
}
|
|
770
|
+
async function initTemplateConfig(projectPath) {
|
|
771
|
+
const config = await readTemplateConfig(projectPath);
|
|
772
|
+
if (config) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
await saveTemplateConfig(projectPath, DEFAULT_TEMPLATES);
|
|
776
|
+
}
|
|
777
|
+
async function getLatestCommit(repository) {
|
|
778
|
+
try {
|
|
779
|
+
const { stdout } = await execa2("git", ["ls-remote", repository, "HEAD"], {
|
|
780
|
+
stdio: "pipe"
|
|
781
|
+
});
|
|
782
|
+
const commit = stdout.split(" ")[0];
|
|
783
|
+
return commit;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 commit \u5931\u8D25: ${error}`);
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async function getLatestTag(repository) {
|
|
790
|
+
try {
|
|
791
|
+
const { stdout } = await execa2(
|
|
792
|
+
"git",
|
|
793
|
+
["ls-remote", "--tags", repository],
|
|
794
|
+
{
|
|
795
|
+
stdio: "pipe"
|
|
796
|
+
}
|
|
797
|
+
);
|
|
798
|
+
const tags = stdout.split("\n").filter((line) => line.includes("refs/tags/") && !line.includes("^{}")).map((line) => {
|
|
799
|
+
const [, ref] = line.split(" ");
|
|
800
|
+
return ref.replace("refs/tags/", "");
|
|
801
|
+
}).filter((tag) => /^v?\d+\.\d+\.\d+/.test(tag)).sort((a, b) => {
|
|
802
|
+
const va = a.replace(/^v/, "").split(".").map(Number);
|
|
803
|
+
const vb = b.replace(/^v/, "").split(".").map(Number);
|
|
804
|
+
for (let i = 0; i < 3; i++) {
|
|
805
|
+
if ((va[i] || 0) > (vb[i] || 0)) return 1;
|
|
806
|
+
if ((va[i] || 0) < (vb[i] || 0)) return -1;
|
|
807
|
+
}
|
|
808
|
+
return 0;
|
|
809
|
+
});
|
|
810
|
+
return tags[tags.length - 1] || "";
|
|
811
|
+
} catch (error) {
|
|
812
|
+
logger.debug(`\u83B7\u53D6 ${repository} \u6700\u65B0 tag \u5931\u8D25: ${error}`);
|
|
813
|
+
return "";
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function checkTemplateUpdate(projectPath, type) {
|
|
817
|
+
const config = await readTemplateConfig(projectPath);
|
|
818
|
+
if (!config || !config[type]) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
const repository = config[type].repository;
|
|
822
|
+
const currentCommit = config[type].commit;
|
|
823
|
+
const currentTag = config[type].tag;
|
|
824
|
+
const latestCommit = await getLatestCommit(repository);
|
|
825
|
+
const latestTag = await getLatestTag(repository);
|
|
826
|
+
if (!latestCommit) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
const needsUpdate = currentCommit && currentCommit !== latestCommit;
|
|
830
|
+
return {
|
|
831
|
+
type,
|
|
832
|
+
repository,
|
|
833
|
+
currentCommit,
|
|
834
|
+
currentTag,
|
|
835
|
+
latestCommit,
|
|
836
|
+
latestTag,
|
|
837
|
+
needsUpdate: needsUpdate || false
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
async function updateTemplateVersion(projectPath, type, commit, options) {
|
|
841
|
+
const config = await readTemplateConfig(projectPath);
|
|
842
|
+
if (!config) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
config[type].commit = commit;
|
|
846
|
+
if (options?.tag) {
|
|
847
|
+
config[type].tag = options.tag;
|
|
848
|
+
}
|
|
849
|
+
if (options?.branch) {
|
|
850
|
+
config[type].branch = options.branch;
|
|
851
|
+
}
|
|
852
|
+
config[type].lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
853
|
+
await saveTemplateConfig(projectPath, config);
|
|
854
|
+
}
|
|
855
|
+
var DEFAULT_TEMPLATES;
|
|
856
|
+
var init_template_version = __esm({
|
|
857
|
+
"src/lib/template-version.ts"() {
|
|
488
858
|
"use strict";
|
|
489
859
|
init_esm_shims();
|
|
490
|
-
|
|
860
|
+
init_utils();
|
|
861
|
+
init_logger();
|
|
862
|
+
DEFAULT_TEMPLATES = {
|
|
863
|
+
frontend: {
|
|
864
|
+
repository: "https://gitlab.yungu-inc.org/static/fe-tpl-ai"
|
|
865
|
+
},
|
|
866
|
+
backend: {
|
|
867
|
+
repository: "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git"
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// src/lib/user-config.ts
|
|
874
|
+
var user_config_exports = {};
|
|
875
|
+
__export(user_config_exports, {
|
|
876
|
+
UserConfigManager: () => UserConfigManager,
|
|
877
|
+
userConfigManager: () => userConfigManager
|
|
878
|
+
});
|
|
879
|
+
import path4 from "path";
|
|
880
|
+
import os from "os";
|
|
881
|
+
import crypto from "crypto";
|
|
882
|
+
var UserConfigManager, userConfigManager;
|
|
883
|
+
var init_user_config = __esm({
|
|
884
|
+
"src/lib/user-config.ts"() {
|
|
885
|
+
"use strict";
|
|
886
|
+
init_esm_shims();
|
|
887
|
+
init_utils();
|
|
888
|
+
init_logger();
|
|
889
|
+
UserConfigManager = class {
|
|
890
|
+
configPath;
|
|
891
|
+
constructor() {
|
|
892
|
+
const configDir = path4.join(os.homedir(), ".team-cli");
|
|
893
|
+
this.configPath = path4.join(configDir, "config.json");
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* 加载用户配置
|
|
897
|
+
*/
|
|
898
|
+
async load() {
|
|
899
|
+
try {
|
|
900
|
+
const exists = await FileUtils.exists(this.configPath);
|
|
901
|
+
if (!exists) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
const content = await FileUtils.read(this.configPath);
|
|
905
|
+
const config = JSON.parse(content);
|
|
906
|
+
if (config.gitlab?.accessToken) {
|
|
907
|
+
config.gitlab.accessToken = this.decrypt(config.gitlab.accessToken);
|
|
908
|
+
}
|
|
909
|
+
return config;
|
|
910
|
+
} catch (error) {
|
|
911
|
+
logger.debug(`\u52A0\u8F7D\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
491
915
|
/**
|
|
492
|
-
*
|
|
916
|
+
* 保存用户配置
|
|
493
917
|
*/
|
|
494
|
-
|
|
495
|
-
|
|
918
|
+
async save(config) {
|
|
919
|
+
try {
|
|
920
|
+
const configDir = path4.dirname(this.configPath);
|
|
921
|
+
await FileUtils.ensureDir(configDir);
|
|
922
|
+
const configToSave = { ...config };
|
|
923
|
+
if (configToSave.gitlab?.accessToken) {
|
|
924
|
+
configToSave.gitlab.accessToken = this.encrypt(configToSave.gitlab.accessToken);
|
|
925
|
+
}
|
|
926
|
+
const content = JSON.stringify(configToSave, null, 2);
|
|
927
|
+
await FileUtils.write(this.configPath, content);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
throw new Error(`\u4FDD\u5B58\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
|
|
930
|
+
}
|
|
496
931
|
}
|
|
497
932
|
/**
|
|
498
|
-
*
|
|
933
|
+
* 更新 GitLab Token
|
|
499
934
|
*/
|
|
500
|
-
|
|
501
|
-
|
|
935
|
+
async updateGitLabToken(token, baseUrl) {
|
|
936
|
+
const config = await this.load() || {
|
|
937
|
+
gitlab: {
|
|
938
|
+
accessToken: "",
|
|
939
|
+
baseUrl: "https://gitlab.com",
|
|
940
|
+
timeout: 3e4
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
config.gitlab.accessToken = token;
|
|
944
|
+
if (baseUrl) {
|
|
945
|
+
config.gitlab.baseUrl = baseUrl;
|
|
946
|
+
}
|
|
947
|
+
await this.save(config);
|
|
502
948
|
}
|
|
503
949
|
/**
|
|
504
|
-
*
|
|
950
|
+
* 获取 GitLab Token
|
|
505
951
|
*/
|
|
506
|
-
|
|
507
|
-
await
|
|
952
|
+
async getGitLabToken() {
|
|
953
|
+
const config = await this.load();
|
|
954
|
+
return config?.gitlab?.accessToken || null;
|
|
508
955
|
}
|
|
509
956
|
/**
|
|
510
|
-
*
|
|
957
|
+
* 获取 GitLab 配置
|
|
511
958
|
*/
|
|
512
|
-
|
|
513
|
-
|
|
959
|
+
async getGitLabConfig() {
|
|
960
|
+
const config = await this.load();
|
|
961
|
+
return config?.gitlab || null;
|
|
514
962
|
}
|
|
515
963
|
/**
|
|
516
|
-
*
|
|
964
|
+
* 检查是否已有配置
|
|
517
965
|
*/
|
|
518
|
-
|
|
519
|
-
await
|
|
966
|
+
async hasConfig() {
|
|
967
|
+
const config = await this.load();
|
|
968
|
+
return config !== null && config.gitlab?.accessToken !== void 0;
|
|
520
969
|
}
|
|
521
970
|
/**
|
|
522
|
-
*
|
|
971
|
+
* 删除配置
|
|
523
972
|
*/
|
|
524
|
-
|
|
525
|
-
|
|
973
|
+
async removeConfig() {
|
|
974
|
+
try {
|
|
975
|
+
const exists = await FileUtils.exists(this.configPath);
|
|
976
|
+
if (exists) {
|
|
977
|
+
await FileUtils.remove(this.configPath);
|
|
978
|
+
}
|
|
979
|
+
} catch (error) {
|
|
980
|
+
throw new Error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error}`);
|
|
981
|
+
}
|
|
526
982
|
}
|
|
527
983
|
/**
|
|
528
|
-
*
|
|
984
|
+
* 简单加密 (使用机器特定密钥)
|
|
529
985
|
*/
|
|
530
|
-
|
|
531
|
-
|
|
986
|
+
encrypt(text) {
|
|
987
|
+
const key = this.getMachineKey();
|
|
988
|
+
const iv = crypto.randomBytes(16);
|
|
989
|
+
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
990
|
+
let encrypted = cipher.update(text, "utf8", "hex");
|
|
991
|
+
encrypted += cipher.final("hex");
|
|
992
|
+
return iv.toString("hex") + ":" + encrypted;
|
|
532
993
|
}
|
|
533
994
|
/**
|
|
534
|
-
*
|
|
995
|
+
* 简单解密
|
|
535
996
|
*/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
997
|
+
decrypt(encryptedText) {
|
|
998
|
+
try {
|
|
999
|
+
const key = this.getMachineKey();
|
|
1000
|
+
const parts = encryptedText.split(":");
|
|
1001
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
1002
|
+
const encrypted = parts[1];
|
|
1003
|
+
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
|
1004
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
1005
|
+
decrypted += decipher.final("utf8");
|
|
1006
|
+
return decrypted;
|
|
1007
|
+
} catch {
|
|
1008
|
+
return encryptedText;
|
|
1009
|
+
}
|
|
542
1010
|
}
|
|
543
1011
|
/**
|
|
544
|
-
*
|
|
1012
|
+
* 获取机器特定密钥
|
|
545
1013
|
*/
|
|
546
|
-
|
|
547
|
-
|
|
1014
|
+
getMachineKey() {
|
|
1015
|
+
const hostname = os.hostname();
|
|
1016
|
+
const platform = os.platform();
|
|
1017
|
+
const arch = os.arch();
|
|
1018
|
+
const cpus = os.cpus();
|
|
1019
|
+
const machineInfo = `${hostname}-${platform}-${arch}-${cpus[0]?.model || "unknown"}`;
|
|
1020
|
+
return crypto.createHash("sha256").update(machineInfo).digest();
|
|
548
1021
|
}
|
|
549
1022
|
/**
|
|
550
|
-
*
|
|
1023
|
+
* 获取配置目录
|
|
551
1024
|
*/
|
|
552
|
-
|
|
553
|
-
|
|
1025
|
+
getConfigDir() {
|
|
1026
|
+
return path4.dirname(this.configPath);
|
|
554
1027
|
}
|
|
555
|
-
};
|
|
556
|
-
StringUtils2 = class {
|
|
557
1028
|
/**
|
|
558
|
-
*
|
|
1029
|
+
* 获取配置文件路径
|
|
559
1030
|
*/
|
|
560
|
-
|
|
561
|
-
return
|
|
1031
|
+
getConfigPath() {
|
|
1032
|
+
return this.configPath;
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
userConfigManager = new UserConfigManager();
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
// src/lib/gitlab-api.ts
|
|
1040
|
+
var gitlab_api_exports = {};
|
|
1041
|
+
__export(gitlab_api_exports, {
|
|
1042
|
+
GitLabAPI: () => GitLabAPI
|
|
1043
|
+
});
|
|
1044
|
+
var GitLabAPI;
|
|
1045
|
+
var init_gitlab_api = __esm({
|
|
1046
|
+
"src/lib/gitlab-api.ts"() {
|
|
1047
|
+
"use strict";
|
|
1048
|
+
init_esm_shims();
|
|
1049
|
+
GitLabAPI = class {
|
|
1050
|
+
config;
|
|
1051
|
+
defaultTimeout = 3e4;
|
|
1052
|
+
constructor(config) {
|
|
1053
|
+
this.config = {
|
|
1054
|
+
...config,
|
|
1055
|
+
timeout: config.timeout || this.defaultTimeout
|
|
1056
|
+
};
|
|
562
1057
|
}
|
|
563
1058
|
/**
|
|
564
|
-
*
|
|
1059
|
+
* 验证 Token 是否有效
|
|
565
1060
|
*/
|
|
566
|
-
|
|
567
|
-
|
|
1061
|
+
async authenticate() {
|
|
1062
|
+
try {
|
|
1063
|
+
const response = await this.request("/user");
|
|
1064
|
+
return response.ok;
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
568
1068
|
}
|
|
569
1069
|
/**
|
|
570
|
-
*
|
|
1070
|
+
* 列出项目的所有 Tags
|
|
571
1071
|
*/
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1072
|
+
async listTags(projectPath) {
|
|
1073
|
+
try {
|
|
1074
|
+
const encodedPath = this.encodeProjectPath(projectPath);
|
|
1075
|
+
const response = await this.request(`/projects/${encodedPath}/repository/tags?per_page=100`);
|
|
1076
|
+
if (!response.ok) {
|
|
1077
|
+
throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
|
|
1078
|
+
}
|
|
1079
|
+
const tags = await response.json();
|
|
1080
|
+
return tags.sort((a, b) => {
|
|
1081
|
+
return this.compareVersionStrings(b.name, a.name);
|
|
1082
|
+
});
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
throw new Error(`\u83B7\u53D6 Tags \u5931\u8D25: ${error}`);
|
|
1085
|
+
}
|
|
575
1086
|
}
|
|
576
1087
|
/**
|
|
577
|
-
*
|
|
1088
|
+
* 列出项目的所有 Branches
|
|
578
1089
|
*/
|
|
579
|
-
|
|
580
|
-
|
|
1090
|
+
async listBranches(projectPath) {
|
|
1091
|
+
try {
|
|
1092
|
+
const encodedPath = this.encodeProjectPath(projectPath);
|
|
1093
|
+
const response = await this.request(`/projects/${encodedPath}/repository/branches?per_page=100`);
|
|
1094
|
+
if (!response.ok) {
|
|
1095
|
+
throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
|
|
1096
|
+
}
|
|
1097
|
+
const branches = await response.json();
|
|
1098
|
+
return branches.sort((a, b) => {
|
|
1099
|
+
if (a.default) return -1;
|
|
1100
|
+
if (b.default) return 1;
|
|
1101
|
+
return a.name.localeCompare(b.name);
|
|
1102
|
+
});
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
throw new Error(`\u83B7\u53D6 Branches \u5931\u8D25: ${error}`);
|
|
1105
|
+
}
|
|
581
1106
|
}
|
|
582
1107
|
/**
|
|
583
|
-
*
|
|
1108
|
+
* 验证 Tag 是否存在
|
|
584
1109
|
*/
|
|
585
|
-
|
|
586
|
-
|
|
1110
|
+
async validateTag(projectPath, tag) {
|
|
1111
|
+
try {
|
|
1112
|
+
const tags = await this.listTags(projectPath);
|
|
1113
|
+
return tags.some((t) => t.name === tag);
|
|
1114
|
+
} catch {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
587
1117
|
}
|
|
588
1118
|
/**
|
|
589
|
-
*
|
|
1119
|
+
* 验证 Branch 是否存在
|
|
590
1120
|
*/
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1121
|
+
async validateBranch(projectPath, branch) {
|
|
1122
|
+
try {
|
|
1123
|
+
const branches = await this.listBranches(projectPath);
|
|
1124
|
+
return branches.some((b) => b.name === branch);
|
|
1125
|
+
} catch {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
594
1128
|
}
|
|
595
1129
|
/**
|
|
596
|
-
*
|
|
1130
|
+
* 获取项目信息
|
|
597
1131
|
*/
|
|
598
|
-
|
|
599
|
-
|
|
1132
|
+
async getProject(projectPath) {
|
|
1133
|
+
try {
|
|
1134
|
+
const encodedPath = this.encodeProjectPath(projectPath);
|
|
1135
|
+
const response = await this.request(`/projects/${encodedPath}`);
|
|
1136
|
+
if (!response.ok) {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
return await response.json();
|
|
1140
|
+
} catch {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
600
1143
|
}
|
|
601
|
-
};
|
|
602
|
-
DateUtils = class _DateUtils {
|
|
603
1144
|
/**
|
|
604
|
-
*
|
|
1145
|
+
* 获取指定 Tag 的 commit 信息
|
|
605
1146
|
*/
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1147
|
+
async getTagCommit(projectPath, tag) {
|
|
1148
|
+
try {
|
|
1149
|
+
const tags = await this.listTags(projectPath);
|
|
1150
|
+
const targetTag = tags.find((t) => t.name === tag);
|
|
1151
|
+
return targetTag?.commit.id || null;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
614
1155
|
}
|
|
615
1156
|
/**
|
|
616
|
-
*
|
|
1157
|
+
* 获取指定 Branch 的最新 commit 信息
|
|
617
1158
|
*/
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
return
|
|
629
|
-
}
|
|
630
|
-
return
|
|
631
|
-
} else if (minutes > 0) {
|
|
632
|
-
return `${minutes} \u5206\u949F\u524D`;
|
|
633
|
-
} else {
|
|
634
|
-
return "\u521A\u521A";
|
|
1159
|
+
async getBranchCommit(projectPath, branch) {
|
|
1160
|
+
try {
|
|
1161
|
+
const encodedPath = this.encodeProjectPath(projectPath);
|
|
1162
|
+
const response = await this.request(
|
|
1163
|
+
`/projects/${encodedPath}/repository/branches/${encodeURIComponent(branch)}`
|
|
1164
|
+
);
|
|
1165
|
+
if (!response.ok) {
|
|
1166
|
+
return null;
|
|
1167
|
+
}
|
|
1168
|
+
const branchInfo = await response.json();
|
|
1169
|
+
return branchInfo.commit.id;
|
|
1170
|
+
} catch {
|
|
1171
|
+
return null;
|
|
635
1172
|
}
|
|
636
1173
|
}
|
|
637
|
-
};
|
|
638
|
-
GitUtils = class {
|
|
639
1174
|
/**
|
|
640
|
-
*
|
|
1175
|
+
* 对比两个版本之间的差异
|
|
641
1176
|
*/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1177
|
+
async compareVersions(projectPath, from, to) {
|
|
1178
|
+
try {
|
|
1179
|
+
const encodedPath = this.encodeProjectPath(projectPath);
|
|
1180
|
+
const response = await this.request(
|
|
1181
|
+
`/projects/${encodedPath}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
|
|
1182
|
+
);
|
|
1183
|
+
if (!response.ok) {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
return await response.json();
|
|
1187
|
+
} catch {
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
645
1190
|
}
|
|
646
1191
|
/**
|
|
647
|
-
*
|
|
1192
|
+
* 比较两个版本号(用于排序)
|
|
1193
|
+
* 返回值: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
|
|
648
1194
|
*/
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1195
|
+
compareVersionStrings(v1, v2) {
|
|
1196
|
+
const version1 = v1.replace(/^v/, "");
|
|
1197
|
+
const version2 = v2.replace(/^v/, "");
|
|
1198
|
+
const parts1 = version1.split(".").map((p) => parseInt(p, 10) || 0);
|
|
1199
|
+
const parts2 = version2.split(".").map((p) => parseInt(p, 10) || 0);
|
|
1200
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
1201
|
+
const p1 = parts1[i] || 0;
|
|
1202
|
+
const p2 = parts2[i] || 0;
|
|
1203
|
+
if (p1 > p2) return 1;
|
|
1204
|
+
if (p1 < p2) return -1;
|
|
1205
|
+
}
|
|
1206
|
+
return 0;
|
|
655
1207
|
}
|
|
656
1208
|
/**
|
|
657
|
-
*
|
|
1209
|
+
* 获取最新的版本(优先 Tag,否则 latest commit)
|
|
658
1210
|
*/
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1211
|
+
async getLatestVersion(projectPath) {
|
|
1212
|
+
try {
|
|
1213
|
+
const tags = await this.listTags(projectPath);
|
|
1214
|
+
const branches = await this.listBranches(projectPath);
|
|
1215
|
+
if (tags.length > 0) {
|
|
1216
|
+
const latestTag = tags[0];
|
|
1217
|
+
return {
|
|
1218
|
+
type: "tag",
|
|
1219
|
+
name: latestTag.name,
|
|
1220
|
+
commit: latestTag.commit.id
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
const defaultBranch = branches.find((b) => b.default);
|
|
1224
|
+
if (defaultBranch) {
|
|
1225
|
+
return {
|
|
1226
|
+
type: "commit",
|
|
1227
|
+
name: defaultBranch.name,
|
|
1228
|
+
commit: defaultBranch.commit.id
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
return null;
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
663
1235
|
}
|
|
664
|
-
};
|
|
665
|
-
SpecUtils = class {
|
|
666
1236
|
/**
|
|
667
|
-
*
|
|
1237
|
+
* 解析项目路径
|
|
1238
|
+
* 从 Git URL 中提取项目路径
|
|
668
1239
|
*/
|
|
669
|
-
static
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const nameMatch = content.match(/\*\*功能名称\*\*:\s*(.+)$/m);
|
|
677
|
-
if (nameMatch) {
|
|
678
|
-
spec.name = nameMatch[1];
|
|
679
|
-
}
|
|
680
|
-
const priorityMatch = content.match(/\*\*优先级\*\*:\s*(P[0-2])/);
|
|
681
|
-
if (priorityMatch) {
|
|
682
|
-
spec.priority = priorityMatch[1];
|
|
683
|
-
}
|
|
684
|
-
const statusMatch = content.match(/\*\*状态\*\*:\s*(.+)$/m);
|
|
685
|
-
if (statusMatch) {
|
|
686
|
-
spec.status = statusMatch[1];
|
|
1240
|
+
static parseProjectPath(repository) {
|
|
1241
|
+
let path17 = repository;
|
|
1242
|
+
path17 = path17.replace(/^https?:\/\//, "");
|
|
1243
|
+
path17 = path17.replace(/^git@/, "");
|
|
1244
|
+
const parts = path17.split("/");
|
|
1245
|
+
if (parts.length > 1) {
|
|
1246
|
+
path17 = parts.slice(1).join("/");
|
|
687
1247
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1248
|
+
path17 = path17.replace(/\.git$/, "");
|
|
1249
|
+
return path17;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* 编码项目路径用于 API 请求
|
|
1253
|
+
*/
|
|
1254
|
+
encodeProjectPath(projectPath) {
|
|
1255
|
+
return encodeURIComponent(projectPath).replace(/%2F/g, "%2F");
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* 发送 HTTP 请求
|
|
1259
|
+
*/
|
|
1260
|
+
async request(endpoint, options = {}) {
|
|
1261
|
+
const url = `${this.config.baseUrl}/api/v4${endpoint}`;
|
|
1262
|
+
const headers = {
|
|
1263
|
+
"PRIVATE-TOKEN": this.config.accessToken,
|
|
1264
|
+
"Content-Type": "application/json",
|
|
1265
|
+
...options.headers
|
|
1266
|
+
};
|
|
1267
|
+
const controller = new AbortController();
|
|
1268
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
1269
|
+
try {
|
|
1270
|
+
const response = await fetch(url, {
|
|
1271
|
+
...options,
|
|
1272
|
+
headers,
|
|
1273
|
+
signal: controller.signal
|
|
1274
|
+
});
|
|
1275
|
+
return response;
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
if (error.name === "AbortError") {
|
|
1278
|
+
throw new Error("\u8BF7\u6C42\u8D85\u65F6");
|
|
1279
|
+
}
|
|
1280
|
+
throw error;
|
|
1281
|
+
} finally {
|
|
1282
|
+
clearTimeout(timeoutId);
|
|
695
1283
|
}
|
|
696
|
-
return spec;
|
|
697
1284
|
}
|
|
698
1285
|
/**
|
|
699
|
-
*
|
|
1286
|
+
* 获取默认分支
|
|
700
1287
|
*/
|
|
701
|
-
|
|
1288
|
+
async getDefaultBranch(projectPath) {
|
|
702
1289
|
try {
|
|
703
|
-
const
|
|
704
|
-
return
|
|
1290
|
+
const project = await this.getProject(projectPath);
|
|
1291
|
+
return project?.default_branch || null;
|
|
705
1292
|
} catch {
|
|
706
|
-
return
|
|
1293
|
+
return null;
|
|
707
1294
|
}
|
|
708
1295
|
}
|
|
709
1296
|
};
|
|
@@ -713,7 +1300,7 @@ var init_utils = __esm({
|
|
|
713
1300
|
// src/commands/init.ts
|
|
714
1301
|
import { Command } from "commander";
|
|
715
1302
|
import inquirer from "inquirer";
|
|
716
|
-
import
|
|
1303
|
+
import path5 from "path";
|
|
717
1304
|
import fs2 from "fs-extra";
|
|
718
1305
|
import { Listr } from "listr2";
|
|
719
1306
|
async function generateTechStack(projectPath) {
|
|
@@ -812,7 +1399,7 @@ docs/
|
|
|
812
1399
|
\u2514\u2500\u2500 sessions/ # \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
|
|
813
1400
|
\`\`\`
|
|
814
1401
|
`;
|
|
815
|
-
await FileUtils.write(
|
|
1402
|
+
await FileUtils.write(path5.join(projectPath, "TECH_STACK.md"), content);
|
|
816
1403
|
}
|
|
817
1404
|
async function generateConventions(projectPath) {
|
|
818
1405
|
const content = `# \u5F00\u53D1\u89C4\u8303
|
|
@@ -1115,7 +1702,7 @@ test('renders user name', () => {
|
|
|
1115
1702
|
});
|
|
1116
1703
|
\`\`\`
|
|
1117
1704
|
`;
|
|
1118
|
-
await FileUtils.write(
|
|
1705
|
+
await FileUtils.write(path5.join(projectPath, "CONVENTIONS.md"), content);
|
|
1119
1706
|
}
|
|
1120
1707
|
async function generateAIMemory(projectPath, projectName) {
|
|
1121
1708
|
const content = `# AI Memory - \u9879\u76EE\u72B6\u6001\u8BB0\u5F55
|
|
@@ -1126,6 +1713,15 @@ async function generateAIMemory(projectPath, projectName) {
|
|
|
1126
1713
|
- **\u5F53\u524D\u9636\u6BB5**: \u521D\u59CB\u5316
|
|
1127
1714
|
- **\u6700\u540E\u66F4\u65B0**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
1128
1715
|
|
|
1716
|
+
## \u6A21\u677F\u7248\u672C\u4FE1\u606F
|
|
1717
|
+
|
|
1718
|
+
> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u7BA1\u7406\uFF0C\u8BB0\u5F55\u4F7F\u7528\u7684\u524D\u540E\u7AEF\u6A21\u677F\u7248\u672C
|
|
1719
|
+
|
|
1720
|
+
| \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |
|
|
1721
|
+
|------|------|-----|--------|--------|----------|
|
|
1722
|
+
| \u524D\u7AEF | - | - | - | - | - |
|
|
1723
|
+
| \u540E\u7AEF | - | - | - | - | - |
|
|
1724
|
+
|
|
1129
1725
|
## \u529F\u80FD\u6E05\u5355 (Feature Inventory)
|
|
1130
1726
|
|
|
1131
1727
|
| \u529F\u80FD | Spec \u6587\u4EF6 | \u72B6\u6001 | \u8FDB\u5EA6 | \u5B8C\u6210\u65E5\u671F | \u5907\u6CE8 |
|
|
@@ -1165,7 +1761,7 @@ async function generateAIMemory(projectPath, projectName) {
|
|
|
1165
1761
|
| Bug ID | \u65E5\u671F | \u95EE\u9898\u63CF\u8FF0 | \u72B6\u6001 |
|
|
1166
1762
|
|--------|------|---------|------|
|
|
1167
1763
|
`;
|
|
1168
|
-
await FileUtils.write(
|
|
1764
|
+
await FileUtils.write(path5.join(projectPath, "AI_MEMORY.md"), content);
|
|
1169
1765
|
}
|
|
1170
1766
|
async function generateSpecTemplate(projectPath) {
|
|
1171
1767
|
const content = `# [\u529F\u80FD\u6807\u9898]
|
|
@@ -1229,35 +1825,78 @@ async function generateSpecTemplate(projectPath) {
|
|
|
1229
1825
|
----
|
|
1230
1826
|
*\u751F\u6210\u4E8E: {{TIMESTAMP}} by team-cli*
|
|
1231
1827
|
`;
|
|
1232
|
-
await FileUtils.write(
|
|
1828
|
+
await FileUtils.write(path5.join(projectPath, "docs/specs/template.md"), content);
|
|
1233
1829
|
}
|
|
1234
|
-
async function cloneBackendTemplate(projectPath) {
|
|
1830
|
+
async function cloneBackendTemplate(projectPath, versionOptions) {
|
|
1235
1831
|
const templateRepo = process.env.TEMPLATE_REPO || "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git";
|
|
1236
|
-
const backendPath =
|
|
1832
|
+
const backendPath = path5.join(projectPath, "backend");
|
|
1237
1833
|
try {
|
|
1238
1834
|
const { execaCommand } = await import("execa");
|
|
1239
|
-
const tempDir =
|
|
1240
|
-
|
|
1835
|
+
const tempDir = path5.join(projectPath, ".template-temp");
|
|
1836
|
+
if (versionOptions?.tag || versionOptions?.branch) {
|
|
1837
|
+
const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
|
|
1838
|
+
const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
|
|
1839
|
+
const config = await userConfigManager2.getGitLabConfig();
|
|
1840
|
+
if (config) {
|
|
1841
|
+
const gitlabAPI = new GitLabAPI2(config);
|
|
1842
|
+
const projectPathEncoded = GitLabAPI2.parseProjectPath(templateRepo);
|
|
1843
|
+
if (versionOptions.tag) {
|
|
1844
|
+
const isValid = await gitlabAPI.validateTag(projectPathEncoded, versionOptions.tag);
|
|
1845
|
+
if (!isValid) {
|
|
1846
|
+
logger.error(`\u540E\u7AEF\u6A21\u677F tag "${versionOptions.tag}" \u4E0D\u5B58\u5728`);
|
|
1847
|
+
process.exit(1);
|
|
1848
|
+
}
|
|
1849
|
+
logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F tag: ${versionOptions.tag}`);
|
|
1850
|
+
}
|
|
1851
|
+
if (versionOptions.branch) {
|
|
1852
|
+
const isValid = await gitlabAPI.validateBranch(projectPathEncoded, versionOptions.branch);
|
|
1853
|
+
if (!isValid) {
|
|
1854
|
+
logger.error(`\u540E\u7AEF\u6A21\u677F\u5206\u652F "${versionOptions.branch}" \u4E0D\u5B58\u5728`);
|
|
1855
|
+
process.exit(1);
|
|
1856
|
+
}
|
|
1857
|
+
logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F\u5206\u652F: ${versionOptions.branch}`);
|
|
1858
|
+
}
|
|
1859
|
+
} else {
|
|
1860
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const ref = versionOptions?.tag || versionOptions?.branch || "HEAD";
|
|
1864
|
+
await execaCommand(`git clone --depth=1 --branch ${ref} ${templateRepo} ${tempDir}`, {
|
|
1241
1865
|
stdio: "inherit",
|
|
1242
1866
|
timeout: 6e4
|
|
1243
1867
|
});
|
|
1868
|
+
const { execa: e } = await import("execa");
|
|
1869
|
+
const { stdout: commit } = await e("git", ["rev-parse", "HEAD"], {
|
|
1870
|
+
cwd: tempDir,
|
|
1871
|
+
stdio: "pipe"
|
|
1872
|
+
});
|
|
1873
|
+
const { stdout: tags } = await e("git", ["tag", "-l", "--sort=-v:refname"], {
|
|
1874
|
+
cwd: tempDir,
|
|
1875
|
+
stdio: "pipe"
|
|
1876
|
+
});
|
|
1877
|
+
const latestTag = tags.split("\n")[0] || void 0;
|
|
1244
1878
|
await fs2.copy(tempDir, backendPath, {
|
|
1245
1879
|
filter: (src) => !src.includes(".git")
|
|
1246
1880
|
});
|
|
1247
1881
|
await fs2.remove(tempDir);
|
|
1248
|
-
const gitDir =
|
|
1882
|
+
const gitDir = path5.join(backendPath, ".git");
|
|
1249
1883
|
if (await FileUtils.exists(gitDir)) {
|
|
1250
1884
|
await FileUtils.remove(gitDir);
|
|
1251
1885
|
}
|
|
1886
|
+
await initTemplateConfig(projectPath);
|
|
1887
|
+
await updateTemplateVersion(projectPath, "backend", commit.trim(), {
|
|
1888
|
+
tag: versionOptions?.tag || latestTag,
|
|
1889
|
+
branch: versionOptions?.branch
|
|
1890
|
+
});
|
|
1252
1891
|
} catch (error) {
|
|
1253
1892
|
logger.warn("\u514B\u9686\u540E\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
|
|
1254
|
-
await FileUtils.ensureDir(
|
|
1255
|
-
await FileUtils.ensureDir(
|
|
1256
|
-
await FileUtils.ensureDir(
|
|
1893
|
+
await FileUtils.ensureDir(path5.join(backendPath, "src/main/java/com/example"));
|
|
1894
|
+
await FileUtils.ensureDir(path5.join(backendPath, "src/main/resources"));
|
|
1895
|
+
await FileUtils.ensureDir(path5.join(backendPath, "src/test/java"));
|
|
1257
1896
|
}
|
|
1258
1897
|
}
|
|
1259
1898
|
async function generateFrontendScaffold(projectPath) {
|
|
1260
|
-
const frontendPath =
|
|
1899
|
+
const frontendPath = path5.join(projectPath, "frontend");
|
|
1261
1900
|
try {
|
|
1262
1901
|
const prompt = `Read TECH_STACK.md and CONVENTIONS.md.
|
|
1263
1902
|
Initialize a Next.js 14 frontend in ./frontend with:
|
|
@@ -1270,16 +1909,16 @@ Initialize a Next.js 14 frontend in ./frontend with:
|
|
|
1270
1909
|
Do not run any servers, just generate the folder structure and configuration files.`;
|
|
1271
1910
|
await claudeAI.prompt(prompt, {
|
|
1272
1911
|
contextFiles: [
|
|
1273
|
-
|
|
1274
|
-
|
|
1912
|
+
path5.join(projectPath, "TECH_STACK.md"),
|
|
1913
|
+
path5.join(projectPath, "CONVENTIONS.md")
|
|
1275
1914
|
]
|
|
1276
1915
|
});
|
|
1277
1916
|
} catch (error) {
|
|
1278
1917
|
logger.warn("Claude \u751F\u6210\u524D\u7AEF\u5931\u8D25\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
|
|
1279
|
-
await FileUtils.ensureDir(
|
|
1280
|
-
await FileUtils.ensureDir(
|
|
1281
|
-
await FileUtils.ensureDir(
|
|
1282
|
-
await FileUtils.ensureDir(
|
|
1918
|
+
await FileUtils.ensureDir(path5.join(frontendPath, "src/app"));
|
|
1919
|
+
await FileUtils.ensureDir(path5.join(frontendPath, "src/components"));
|
|
1920
|
+
await FileUtils.ensureDir(path5.join(frontendPath, "src/lib"));
|
|
1921
|
+
await FileUtils.ensureDir(path5.join(frontendPath, "public"));
|
|
1283
1922
|
}
|
|
1284
1923
|
}
|
|
1285
1924
|
async function generateDockerFiles(projectPath) {
|
|
@@ -1307,7 +1946,8 @@ var init_init = __esm({
|
|
|
1307
1946
|
init_logger();
|
|
1308
1947
|
init_claude();
|
|
1309
1948
|
init_utils();
|
|
1310
|
-
|
|
1949
|
+
init_template_version();
|
|
1950
|
+
initCommand = new Command("init").argument("[project-name]", "\u9879\u76EE\u540D\u79F0").description("\u521D\u59CB\u5316\u65B0\u9879\u76EE").option("-d, --dir <directory>", "\u9879\u76EE\u76EE\u5F55", ".").option("--no-docker", "\u4E0D\u751F\u6210 Docker \u914D\u7F6E").option("--no-git", "\u4E0D\u521D\u59CB\u5316 Git").option("--tag <tag>", "\u6307\u5B9A\u6A21\u677F\u6807\u7B7E (\u524D\u540E\u7AEF\u901A\u7528)").option("--backend-tag <tag>", "\u6307\u5B9A\u540E\u7AEF\u6A21\u677F\u6807\u7B7E").option("--frontend-tag <tag>", "\u6307\u5B9A\u524D\u7AEF\u6A21\u677F\u6807\u7B7E").option("--backend-branch <branch>", "\u6307\u5B9A\u540E\u7AEF\u6A21\u677F\u5206\u652F").option("--frontend-branch <branch>", "\u6307\u5B9A\u524D\u7AEF\u6A21\u677F\u5206\u652F").action(async (projectName, options) => {
|
|
1311
1951
|
try {
|
|
1312
1952
|
if (!projectName) {
|
|
1313
1953
|
const answers = await inquirer.prompt([
|
|
@@ -1338,7 +1978,7 @@ var init_init = __esm({
|
|
|
1338
1978
|
logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
|
|
1339
1979
|
process.exit(1);
|
|
1340
1980
|
}
|
|
1341
|
-
const projectPath =
|
|
1981
|
+
const projectPath = path5.resolve(options.dir, projectName);
|
|
1342
1982
|
if (await FileUtils.exists(projectPath)) {
|
|
1343
1983
|
logger.error(`\u76EE\u5F55\u5DF2\u5B58\u5728: ${projectPath}`);
|
|
1344
1984
|
logger.info("\u8BF7\u9009\u62E9\u5176\u4ED6\u9879\u76EE\u540D\u79F0\u6216\u5220\u9664\u73B0\u6709\u76EE\u5F55");
|
|
@@ -1357,11 +1997,11 @@ var init_init = __esm({
|
|
|
1357
1997
|
title: "\u521B\u5EFA\u9879\u76EE\u76EE\u5F55",
|
|
1358
1998
|
task: async () => {
|
|
1359
1999
|
await FileUtils.ensureDir(projectPath);
|
|
1360
|
-
await FileUtils.ensureDir(
|
|
1361
|
-
await FileUtils.ensureDir(
|
|
1362
|
-
await FileUtils.ensureDir(
|
|
1363
|
-
await FileUtils.ensureDir(
|
|
1364
|
-
await FileUtils.ensureDir(
|
|
2000
|
+
await FileUtils.ensureDir(path5.join(projectPath, "frontend"));
|
|
2001
|
+
await FileUtils.ensureDir(path5.join(projectPath, "backend"));
|
|
2002
|
+
await FileUtils.ensureDir(path5.join(projectPath, "docs/specs"));
|
|
2003
|
+
await FileUtils.ensureDir(path5.join(projectPath, "docs/api"));
|
|
2004
|
+
await FileUtils.ensureDir(path5.join(projectPath, "docs/sessions"));
|
|
1365
2005
|
}
|
|
1366
2006
|
},
|
|
1367
2007
|
{
|
|
@@ -1391,7 +2031,12 @@ var init_init = __esm({
|
|
|
1391
2031
|
{
|
|
1392
2032
|
title: "\u514B\u9686\u540E\u7AEF\u6A21\u677F",
|
|
1393
2033
|
task: async () => {
|
|
1394
|
-
|
|
2034
|
+
const backendTag = options.backendTag || options.tag;
|
|
2035
|
+
const backendBranch = options.backendBranch;
|
|
2036
|
+
await cloneBackendTemplate(projectPath, {
|
|
2037
|
+
tag: backendTag,
|
|
2038
|
+
branch: backendBranch
|
|
2039
|
+
});
|
|
1395
2040
|
}
|
|
1396
2041
|
},
|
|
1397
2042
|
{
|
|
@@ -1446,7 +2091,7 @@ var init_init = __esm({
|
|
|
1446
2091
|
// src/commands/breakdown.ts
|
|
1447
2092
|
import { Command as Command2 } from "commander";
|
|
1448
2093
|
import inquirer2 from "inquirer";
|
|
1449
|
-
import
|
|
2094
|
+
import path6 from "path";
|
|
1450
2095
|
import { Listr as Listr2 } from "listr2";
|
|
1451
2096
|
function buildBreakdownPrompt(specContent) {
|
|
1452
2097
|
return `Role: Senior Technical Lead and Agile Coach
|
|
@@ -1561,9 +2206,9 @@ var init_breakdown = __esm({
|
|
|
1561
2206
|
choices: ctx.specs
|
|
1562
2207
|
}
|
|
1563
2208
|
]);
|
|
1564
|
-
return
|
|
2209
|
+
return path6.join("docs/specs", selectedFile);
|
|
1565
2210
|
}
|
|
1566
|
-
const fullPath = specFile.startsWith("docs/specs/") ? specFile :
|
|
2211
|
+
const fullPath = specFile.startsWith("docs/specs/") ? specFile : path6.join("docs/specs", specFile);
|
|
1567
2212
|
const exists = await FileUtils.exists(fullPath);
|
|
1568
2213
|
if (!exists) {
|
|
1569
2214
|
throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
|
|
@@ -1633,7 +2278,7 @@ var init_breakdown = __esm({
|
|
|
1633
2278
|
// src/commands/dev.ts
|
|
1634
2279
|
import { Command as Command3 } from "commander";
|
|
1635
2280
|
import inquirer3 from "inquirer";
|
|
1636
|
-
import
|
|
2281
|
+
import path7 from "path";
|
|
1637
2282
|
async function selectSpec() {
|
|
1638
2283
|
logger.step("\u6B65\u9AA4 1/3: \u9009\u62E9 spec \u6587\u4EF6...");
|
|
1639
2284
|
logger.newLine();
|
|
@@ -1649,7 +2294,7 @@ async function selectSpec() {
|
|
|
1649
2294
|
}
|
|
1650
2295
|
const specs = [];
|
|
1651
2296
|
for (let i = 0; i < specFiles.length; i++) {
|
|
1652
|
-
const file =
|
|
2297
|
+
const file = path7.join(specDir, specFiles[i]);
|
|
1653
2298
|
const spec = await FileUtils.read(file);
|
|
1654
2299
|
const status = parseSpecStatus(spec);
|
|
1655
2300
|
const dependencies = parseDependencies(spec);
|
|
@@ -1963,8 +2608,8 @@ async function generateSessionLog(specFile, milestone, todo, taskDescription, re
|
|
|
1963
2608
|
const sessionDir = "docs/sessions";
|
|
1964
2609
|
await FileUtils.ensureDir(sessionDir);
|
|
1965
2610
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1966
|
-
const specName =
|
|
1967
|
-
const logFile =
|
|
2611
|
+
const specName = path7.basename(specFile, ".md");
|
|
2612
|
+
const logFile = path7.join(sessionDir, `${timestamp}_${specName}.md`);
|
|
1968
2613
|
const content = `# \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
|
|
1969
2614
|
|
|
1970
2615
|
**\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
|
|
@@ -2037,7 +2682,7 @@ var init_dev = __esm({
|
|
|
2037
2682
|
// src/commands/add-feature.ts
|
|
2038
2683
|
import { Command as Command4 } from "commander";
|
|
2039
2684
|
import inquirer4 from "inquirer";
|
|
2040
|
-
import
|
|
2685
|
+
import path8 from "path";
|
|
2041
2686
|
import { Listr as Listr3 } from "listr2";
|
|
2042
2687
|
async function addFeatureFromPrd(featureName, featureSlug, specFile) {
|
|
2043
2688
|
const { prdPath } = await inquirer4.prompt([
|
|
@@ -2066,7 +2711,7 @@ async function addFeatureFromPrd(featureName, featureSlug, specFile) {
|
|
|
2066
2711
|
const specs = files.filter((f) => !f.includes("template"));
|
|
2067
2712
|
ctx2.completedSpecs = [];
|
|
2068
2713
|
for (const file of specs) {
|
|
2069
|
-
const status = await SpecUtils.getSpecStatus(
|
|
2714
|
+
const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
|
|
2070
2715
|
if (status === "\u5DF2\u5B8C\u6210") {
|
|
2071
2716
|
ctx2.completedSpecs.push(file.replace(".md", ""));
|
|
2072
2717
|
}
|
|
@@ -2138,7 +2783,7 @@ async function addFeatureSimple(featureName, featureSlug, specFile) {
|
|
|
2138
2783
|
const specs = files.filter((f) => !f.includes("template"));
|
|
2139
2784
|
ctx2.completedSpecs = [];
|
|
2140
2785
|
for (const file of specs) {
|
|
2141
|
-
const status = await SpecUtils.getSpecStatus(
|
|
2786
|
+
const status = await SpecUtils.getSpecStatus(path8.join(specDir, file));
|
|
2142
2787
|
if (status === "\u5DF2\u5B8C\u6210") {
|
|
2143
2788
|
ctx2.completedSpecs.push(file.replace(".md", ""));
|
|
2144
2789
|
}
|
|
@@ -2235,7 +2880,7 @@ async function buildProjectContext() {
|
|
|
2235
2880
|
const files = await FileUtils.findFiles("*.md", "docs/specs");
|
|
2236
2881
|
const specs = files.filter((f) => !f.includes("template"));
|
|
2237
2882
|
for (const file of specs) {
|
|
2238
|
-
const status = await SpecUtils.getSpecStatus(
|
|
2883
|
+
const status = await SpecUtils.getSpecStatus(path8.join("docs/specs", file));
|
|
2239
2884
|
context.push(` - ${file.replace(".md", "")} [${status}]`);
|
|
2240
2885
|
}
|
|
2241
2886
|
}
|
|
@@ -2498,7 +3143,7 @@ var init_add_feature = __esm({
|
|
|
2498
3143
|
process.exit(1);
|
|
2499
3144
|
}
|
|
2500
3145
|
const featureSlug = StringUtils2.toKebabCase(featureName);
|
|
2501
|
-
const specFile =
|
|
3146
|
+
const specFile = path8.join("docs/specs", `${featureSlug}.md`);
|
|
2502
3147
|
const specExists = await FileUtils.exists(specFile);
|
|
2503
3148
|
if (specExists) {
|
|
2504
3149
|
logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
|
|
@@ -2535,7 +3180,7 @@ var init_add_feature = __esm({
|
|
|
2535
3180
|
|
|
2536
3181
|
// src/commands/split-prd.ts
|
|
2537
3182
|
import { Command as Command5 } from "commander";
|
|
2538
|
-
import
|
|
3183
|
+
import path9 from "path";
|
|
2539
3184
|
import { Listr as Listr4 } from "listr2";
|
|
2540
3185
|
function buildSplitPrdPrompt(prdContent, screenshots, demoRepos) {
|
|
2541
3186
|
let prompt = `Role: Senior Product Manager and Technical Architect
|
|
@@ -2681,7 +3326,7 @@ var init_split_prd = __esm({
|
|
|
2681
3326
|
ctx2.prdFiles = [];
|
|
2682
3327
|
for (const ext of supportedExtensions) {
|
|
2683
3328
|
const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
|
|
2684
|
-
ctx2.prdFiles.push(...files.map((f) =>
|
|
3329
|
+
ctx2.prdFiles.push(...files.map((f) => path9.join(prdFolder, f)));
|
|
2685
3330
|
}
|
|
2686
3331
|
if (ctx2.prdFiles.length === 0) {
|
|
2687
3332
|
throw new Error(
|
|
@@ -2699,7 +3344,7 @@ var init_split_prd = __esm({
|
|
|
2699
3344
|
{
|
|
2700
3345
|
title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
|
|
2701
3346
|
task: async (ctx2) => {
|
|
2702
|
-
const screenshotDir =
|
|
3347
|
+
const screenshotDir = path9.join(prdFolder, "screenshots");
|
|
2703
3348
|
const dirExists = await FileUtils.exists(screenshotDir);
|
|
2704
3349
|
if (!dirExists) {
|
|
2705
3350
|
logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
|
|
@@ -2710,7 +3355,7 @@ var init_split_prd = __esm({
|
|
|
2710
3355
|
ctx2.screenshots = [];
|
|
2711
3356
|
for (const ext of imageExtensions) {
|
|
2712
3357
|
const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
|
|
2713
|
-
ctx2.screenshots.push(...files.map((f) =>
|
|
3358
|
+
ctx2.screenshots.push(...files.map((f) => path9.join(screenshotDir, f)));
|
|
2714
3359
|
}
|
|
2715
3360
|
logger.success(`\u627E\u5230 ${ctx2.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
|
|
2716
3361
|
}
|
|
@@ -2721,8 +3366,8 @@ var init_split_prd = __esm({
|
|
|
2721
3366
|
const entries = await FileUtils.findFiles("*/", prdFolder);
|
|
2722
3367
|
ctx2.demoRepos = [];
|
|
2723
3368
|
for (const entry of entries) {
|
|
2724
|
-
const dirPath =
|
|
2725
|
-
const gitDir =
|
|
3369
|
+
const dirPath = path9.join(prdFolder, entry);
|
|
3370
|
+
const gitDir = path9.join(dirPath, ".git");
|
|
2726
3371
|
const hasGit = await FileUtils.exists(gitDir);
|
|
2727
3372
|
if (hasGit) {
|
|
2728
3373
|
ctx2.demoRepos.push(dirPath);
|
|
@@ -2786,7 +3431,7 @@ var init_split_prd = __esm({
|
|
|
2786
3431
|
// src/commands/bugfix.ts
|
|
2787
3432
|
import { Command as Command6 } from "commander";
|
|
2788
3433
|
import inquirer5 from "inquirer";
|
|
2789
|
-
import
|
|
3434
|
+
import path10 from "path";
|
|
2790
3435
|
import { Listr as Listr5 } from "listr2";
|
|
2791
3436
|
function generateBugId() {
|
|
2792
3437
|
const date = /* @__PURE__ */ new Date();
|
|
@@ -2813,7 +3458,7 @@ async function findRelatedSpec(description) {
|
|
|
2813
3458
|
}
|
|
2814
3459
|
const keywords = extractKeywords(description);
|
|
2815
3460
|
for (const file of specs) {
|
|
2816
|
-
const filePath =
|
|
3461
|
+
const filePath = path10.join(specDir, file);
|
|
2817
3462
|
const content = await FileUtils.read(filePath);
|
|
2818
3463
|
for (const keyword of keywords) {
|
|
2819
3464
|
if (content.toLowerCase().includes(keyword.toLowerCase())) {
|
|
@@ -2953,7 +3598,7 @@ var init_bugfix = __esm({
|
|
|
2953
3598
|
const relatedSpec = await findRelatedSpec(answers.description);
|
|
2954
3599
|
const bugfixDir = "docs/bugfixes";
|
|
2955
3600
|
await FileUtils.ensureDir(bugfixDir);
|
|
2956
|
-
const bugfixFile =
|
|
3601
|
+
const bugfixFile = path10.join(bugfixDir, `${timestamp}_${bugId}.md`);
|
|
2957
3602
|
const content = formatBugfixDocument({
|
|
2958
3603
|
id: bugId,
|
|
2959
3604
|
severity: answers.severity,
|
|
@@ -3019,8 +3664,8 @@ var init_bugfix = __esm({
|
|
|
3019
3664
|
{
|
|
3020
3665
|
title: "\u521B\u5EFA hotfix \u5206\u652F",
|
|
3021
3666
|
task: async () => {
|
|
3022
|
-
const { execa:
|
|
3023
|
-
await
|
|
3667
|
+
const { execa: execa5 } = await import("execa");
|
|
3668
|
+
await execa5("git", ["checkout", "-b", branchName], { stdio: "inherit" });
|
|
3024
3669
|
}
|
|
3025
3670
|
},
|
|
3026
3671
|
{
|
|
@@ -3029,7 +3674,7 @@ var init_bugfix = __esm({
|
|
|
3029
3674
|
const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
|
|
3030
3675
|
const hotfixDir = "docs/hotfixes";
|
|
3031
3676
|
await FileUtils.ensureDir(hotfixDir);
|
|
3032
|
-
const hotfixFile =
|
|
3677
|
+
const hotfixFile = path10.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
|
|
3033
3678
|
const content = formatHotfixDocument({
|
|
3034
3679
|
id: hotfixId,
|
|
3035
3680
|
description: answers.description,
|
|
@@ -3044,9 +3689,9 @@ var init_bugfix = __esm({
|
|
|
3044
3689
|
{
|
|
3045
3690
|
title: "\u521B\u5EFA\u521D\u59CB commit",
|
|
3046
3691
|
task: async () => {
|
|
3047
|
-
const { execa:
|
|
3048
|
-
await
|
|
3049
|
-
await
|
|
3692
|
+
const { execa: execa5 } = await import("execa");
|
|
3693
|
+
await execa5("git", ["add", "docs/hotfixes"], { stdio: "pipe" });
|
|
3694
|
+
await execa5(
|
|
3050
3695
|
"git",
|
|
3051
3696
|
[
|
|
3052
3697
|
"commit",
|
|
@@ -3084,7 +3729,7 @@ Temporary solution: ${answers.solution}`
|
|
|
3084
3729
|
|
|
3085
3730
|
// src/commands/lint.ts
|
|
3086
3731
|
import { Command as Command7 } from "commander";
|
|
3087
|
-
import { execa as
|
|
3732
|
+
import { execa as execa3 } from "execa";
|
|
3088
3733
|
var lintCommand;
|
|
3089
3734
|
var init_lint = __esm({
|
|
3090
3735
|
"src/commands/lint.ts"() {
|
|
@@ -3112,17 +3757,17 @@ var init_lint = __esm({
|
|
|
3112
3757
|
try {
|
|
3113
3758
|
logger.step("\u68C0\u67E5\u524D\u7AEF\u4EE3\u7801...");
|
|
3114
3759
|
if (options.fix) {
|
|
3115
|
-
await
|
|
3760
|
+
await execa3("npm", ["run", "lint", "--", "--fix"], {
|
|
3116
3761
|
cwd: "frontend",
|
|
3117
3762
|
stdio: "inherit"
|
|
3118
3763
|
});
|
|
3119
3764
|
} else {
|
|
3120
|
-
await
|
|
3765
|
+
await execa3("npm", ["run", "lint"], {
|
|
3121
3766
|
cwd: "frontend",
|
|
3122
3767
|
stdio: "inherit"
|
|
3123
3768
|
});
|
|
3124
3769
|
}
|
|
3125
|
-
await
|
|
3770
|
+
await execa3("npx", ["tsc", "--noEmit"], {
|
|
3126
3771
|
cwd: "frontend",
|
|
3127
3772
|
stdio: "pipe"
|
|
3128
3773
|
});
|
|
@@ -3145,23 +3790,23 @@ var init_lint = __esm({
|
|
|
3145
3790
|
logger.step("\u68C0\u67E5\u540E\u7AEF\u4EE3\u7801...");
|
|
3146
3791
|
if (hasGradle) {
|
|
3147
3792
|
if (options.fix) {
|
|
3148
|
-
await
|
|
3793
|
+
await execa3("./gradlew", ["spotlessApply"], {
|
|
3149
3794
|
cwd: "backend",
|
|
3150
3795
|
stdio: "inherit"
|
|
3151
3796
|
});
|
|
3152
3797
|
}
|
|
3153
|
-
await
|
|
3798
|
+
await execa3("./gradlew", ["checkstyleMain", "compileJava"], {
|
|
3154
3799
|
cwd: "backend",
|
|
3155
3800
|
stdio: "inherit"
|
|
3156
3801
|
});
|
|
3157
3802
|
} else if (hasMaven) {
|
|
3158
3803
|
if (options.fix) {
|
|
3159
|
-
await
|
|
3804
|
+
await execa3("./mvnw", ["spotless:apply"], {
|
|
3160
3805
|
cwd: "backend",
|
|
3161
3806
|
stdio: "inherit"
|
|
3162
3807
|
});
|
|
3163
3808
|
}
|
|
3164
|
-
await
|
|
3809
|
+
await execa3("./mvnw", ["checkstyle:check", "compile"], {
|
|
3165
3810
|
cwd: "backend",
|
|
3166
3811
|
stdio: "inherit"
|
|
3167
3812
|
});
|
|
@@ -3216,7 +3861,7 @@ var init_lint = __esm({
|
|
|
3216
3861
|
|
|
3217
3862
|
// src/commands/status.ts
|
|
3218
3863
|
import { Command as Command8 } from "commander";
|
|
3219
|
-
import
|
|
3864
|
+
import path11 from "path";
|
|
3220
3865
|
async function displayProjectInfo() {
|
|
3221
3866
|
logger.info("\u9879\u76EE\u4FE1\u606F:");
|
|
3222
3867
|
logger.newLine();
|
|
@@ -3252,7 +3897,7 @@ async function displayFeatureInventory() {
|
|
|
3252
3897
|
}
|
|
3253
3898
|
const inventory = [];
|
|
3254
3899
|
for (const file of specs) {
|
|
3255
|
-
const filePath =
|
|
3900
|
+
const filePath = path11.join(specDir, file);
|
|
3256
3901
|
const content = await FileUtils.read(filePath);
|
|
3257
3902
|
const status = parseSpecStatus2(content);
|
|
3258
3903
|
inventory.push({
|
|
@@ -3311,7 +3956,7 @@ async function displayRecentActivity() {
|
|
|
3311
3956
|
}
|
|
3312
3957
|
const sorted = files.sort().reverse().slice(0, 5);
|
|
3313
3958
|
for (const file of sorted) {
|
|
3314
|
-
const filePath =
|
|
3959
|
+
const filePath = path11.join(sessionDir, file);
|
|
3315
3960
|
const stat = await FileUtils.read(filePath);
|
|
3316
3961
|
const specMatch = stat.match(/\*\*Spec\*\*:\s*(.+)/);
|
|
3317
3962
|
const spec = specMatch ? specMatch[1].trim() : "\u672A\u77E5";
|
|
@@ -3381,13 +4026,13 @@ var init_status = __esm({
|
|
|
3381
4026
|
|
|
3382
4027
|
// src/commands/detect-deps.ts
|
|
3383
4028
|
import { Command as Command9 } from "commander";
|
|
3384
|
-
import
|
|
4029
|
+
import path12 from "path";
|
|
3385
4030
|
import inquirer6 from "inquirer";
|
|
3386
4031
|
async function detectDependencies(specFile) {
|
|
3387
4032
|
logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
|
|
3388
4033
|
const projectDir = ".";
|
|
3389
4034
|
const allDeps = /* @__PURE__ */ new Set();
|
|
3390
|
-
const backendDir =
|
|
4035
|
+
const backendDir = path12.join(projectDir, "backend");
|
|
3391
4036
|
const backendExists = await FileUtils.exists(backendDir);
|
|
3392
4037
|
if (backendExists) {
|
|
3393
4038
|
const apiDeps = await scanBackendApiCalls(backendDir);
|
|
@@ -3397,7 +4042,7 @@ async function detectDependencies(specFile) {
|
|
|
3397
4042
|
const serviceDeps = await scanBackendServiceRefs(backendDir);
|
|
3398
4043
|
serviceDeps.forEach((d) => allDeps.add(d));
|
|
3399
4044
|
}
|
|
3400
|
-
const frontendDir =
|
|
4045
|
+
const frontendDir = path12.join(projectDir, "frontend");
|
|
3401
4046
|
const frontendExists = await FileUtils.exists(frontendDir);
|
|
3402
4047
|
if (frontendExists) {
|
|
3403
4048
|
const frontendDeps = await scanFrontendApiCalls(frontendDir);
|
|
@@ -3438,11 +4083,11 @@ async function detectDependencies(specFile) {
|
|
|
3438
4083
|
}
|
|
3439
4084
|
async function scanBackendApiCalls(backendDir) {
|
|
3440
4085
|
const deps = [];
|
|
3441
|
-
const srcDir =
|
|
4086
|
+
const srcDir = path12.join(backendDir, "src");
|
|
3442
4087
|
try {
|
|
3443
4088
|
const javaFiles = await FileUtils.findFiles("*.java", srcDir);
|
|
3444
4089
|
for (const file of javaFiles) {
|
|
3445
|
-
const filePath =
|
|
4090
|
+
const filePath = path12.join(srcDir, file);
|
|
3446
4091
|
const content = await FileUtils.read(filePath);
|
|
3447
4092
|
const pathRegex = /"(\/api\/[^"]+)"/g;
|
|
3448
4093
|
let match;
|
|
@@ -3460,11 +4105,11 @@ async function scanBackendApiCalls(backendDir) {
|
|
|
3460
4105
|
}
|
|
3461
4106
|
async function scanBackendEntityRelations(backendDir) {
|
|
3462
4107
|
const deps = [];
|
|
3463
|
-
const srcDir =
|
|
4108
|
+
const srcDir = path12.join(backendDir, "src");
|
|
3464
4109
|
try {
|
|
3465
4110
|
const javaFiles = await FileUtils.findFiles("*.java", srcDir);
|
|
3466
4111
|
for (const file of javaFiles) {
|
|
3467
|
-
const filePath =
|
|
4112
|
+
const filePath = path12.join(srcDir, file);
|
|
3468
4113
|
const content = await FileUtils.read(filePath);
|
|
3469
4114
|
if (content.includes("@JoinColumn") || content.includes("@ManyToOne") || content.includes("@OneToMany")) {
|
|
3470
4115
|
const typeRegex = /type\s*=\s*(\w+)/g;
|
|
@@ -3480,11 +4125,11 @@ async function scanBackendEntityRelations(backendDir) {
|
|
|
3480
4125
|
}
|
|
3481
4126
|
async function scanBackendServiceRefs(backendDir) {
|
|
3482
4127
|
const deps = [];
|
|
3483
|
-
const srcDir =
|
|
4128
|
+
const srcDir = path12.join(backendDir, "src");
|
|
3484
4129
|
try {
|
|
3485
4130
|
const javaFiles = await FileUtils.findFiles("*.java", srcDir);
|
|
3486
4131
|
for (const file of javaFiles) {
|
|
3487
|
-
const filePath =
|
|
4132
|
+
const filePath = path12.join(srcDir, file);
|
|
3488
4133
|
const content = await FileUtils.read(filePath);
|
|
3489
4134
|
const serviceRegex = /private\s+(\w+)Service/g;
|
|
3490
4135
|
let match;
|
|
@@ -3500,11 +4145,11 @@ async function scanBackendServiceRefs(backendDir) {
|
|
|
3500
4145
|
}
|
|
3501
4146
|
async function scanFrontendApiCalls(frontendDir) {
|
|
3502
4147
|
const deps = [];
|
|
3503
|
-
const srcDir =
|
|
4148
|
+
const srcDir = path12.join(frontendDir, "src");
|
|
3504
4149
|
try {
|
|
3505
4150
|
const tsFiles = await FileUtils.findFiles("*.{ts,tsx,js,jsx}", srcDir);
|
|
3506
4151
|
for (const file of tsFiles) {
|
|
3507
|
-
const filePath =
|
|
4152
|
+
const filePath = path12.join(srcDir, file);
|
|
3508
4153
|
const content = await FileUtils.read(filePath);
|
|
3509
4154
|
const pathRegex = /"(\/api\/[^"]+)"/g;
|
|
3510
4155
|
let match;
|
|
@@ -3621,7 +4266,7 @@ var init_detect_deps = __esm({
|
|
|
3621
4266
|
process.exit(1);
|
|
3622
4267
|
}
|
|
3623
4268
|
for (const spec of specs) {
|
|
3624
|
-
const specPath =
|
|
4269
|
+
const specPath = path12.join(specsDir, spec);
|
|
3625
4270
|
logger.step(`\u5904\u7406: ${spec}`);
|
|
3626
4271
|
await detectDependencies(specPath);
|
|
3627
4272
|
logger.newLine();
|
|
@@ -3648,10 +4293,10 @@ var init_detect_deps = __esm({
|
|
|
3648
4293
|
|
|
3649
4294
|
// src/commands/sync-memory.ts
|
|
3650
4295
|
import { Command as Command10 } from "commander";
|
|
3651
|
-
import
|
|
4296
|
+
import path13 from "path";
|
|
3652
4297
|
async function syncFeatureInventory(aiMemoryFile, projectDir) {
|
|
3653
4298
|
logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
|
|
3654
|
-
const specsDir =
|
|
4299
|
+
const specsDir = path13.join(projectDir, "docs/specs");
|
|
3655
4300
|
const exists = await FileUtils.exists(specsDir);
|
|
3656
4301
|
if (!exists) {
|
|
3657
4302
|
return;
|
|
@@ -3666,7 +4311,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
|
|
|
3666
4311
|
for (const specFile of specs) {
|
|
3667
4312
|
const name = specFile.replace(".md", "");
|
|
3668
4313
|
const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
3669
|
-
const specPath =
|
|
4314
|
+
const specPath = path13.join(specsDir, specFile);
|
|
3670
4315
|
const content = await FileUtils.read(specPath);
|
|
3671
4316
|
const status = parseSpecStatus3(content);
|
|
3672
4317
|
const progress = getSpecProgress(content);
|
|
@@ -3684,7 +4329,7 @@ async function syncFeatureInventory(aiMemoryFile, projectDir) {
|
|
|
3684
4329
|
}
|
|
3685
4330
|
async function syncApiInventory(aiMemoryFile, projectDir) {
|
|
3686
4331
|
logger.step("\u540C\u6B65 API \u5217\u8868...");
|
|
3687
|
-
const backendDir =
|
|
4332
|
+
const backendDir = path13.join(projectDir, "backend");
|
|
3688
4333
|
const exists = await FileUtils.exists(backendDir);
|
|
3689
4334
|
if (!exists) {
|
|
3690
4335
|
return;
|
|
@@ -3694,13 +4339,13 @@ async function syncApiInventory(aiMemoryFile, projectDir) {
|
|
|
3694
4339
|
lines.push("");
|
|
3695
4340
|
lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Controller \u751F\u6210");
|
|
3696
4341
|
lines.push("");
|
|
3697
|
-
const srcDir =
|
|
4342
|
+
const srcDir = path13.join(backendDir, "src");
|
|
3698
4343
|
const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
|
|
3699
4344
|
if (controllers.length === 0) {
|
|
3700
4345
|
lines.push("\u6682\u65E0 API");
|
|
3701
4346
|
} else {
|
|
3702
4347
|
for (const controllerFile of controllers) {
|
|
3703
|
-
const controllerPath =
|
|
4348
|
+
const controllerPath = path13.join(srcDir, controllerFile);
|
|
3704
4349
|
const controllerName = controllerFile.replace(".java", "");
|
|
3705
4350
|
const module = controllerName.replace(/Controller$/, "").toLowerCase();
|
|
3706
4351
|
lines.push(`### ${module} \u6A21\u5757`);
|
|
@@ -3777,7 +4422,7 @@ function extractMethodComment(content, methodName) {
|
|
|
3777
4422
|
}
|
|
3778
4423
|
async function syncDataModels(aiMemoryFile, projectDir) {
|
|
3779
4424
|
logger.step("\u540C\u6B65\u6570\u636E\u6A21\u578B...");
|
|
3780
|
-
const backendDir =
|
|
4425
|
+
const backendDir = path13.join(projectDir, "backend");
|
|
3781
4426
|
const exists = await FileUtils.exists(backendDir);
|
|
3782
4427
|
if (!exists) {
|
|
3783
4428
|
return;
|
|
@@ -3787,7 +4432,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
|
|
|
3787
4432
|
lines.push("");
|
|
3788
4433
|
lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u626B\u63CF\u540E\u7AEF Entity \u751F\u6210");
|
|
3789
4434
|
lines.push("");
|
|
3790
|
-
const srcDir =
|
|
4435
|
+
const srcDir = path13.join(backendDir, "src");
|
|
3791
4436
|
const entities = await FileUtils.findFiles("*Entity.java", srcDir);
|
|
3792
4437
|
if (entities.length === 0) {
|
|
3793
4438
|
lines.push("\u6682\u65E0\u6570\u636E\u6A21\u578B");
|
|
@@ -3795,7 +4440,7 @@ async function syncDataModels(aiMemoryFile, projectDir) {
|
|
|
3795
4440
|
lines.push("| \u6A21\u578B | \u8BF4\u660E | \u5B57\u6BB5 | \u5173\u8054 |");
|
|
3796
4441
|
lines.push("|------|------|------|------|");
|
|
3797
4442
|
for (const entityFile of entities) {
|
|
3798
|
-
const entityPath =
|
|
4443
|
+
const entityPath = path13.join(srcDir, entityFile);
|
|
3799
4444
|
const entityName = entityFile.replace(".java", "").replace(/Entity$/, "");
|
|
3800
4445
|
const displayName = entityName.split(/(?=[A-Z])/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
3801
4446
|
const content = await FileUtils.read(entityPath);
|
|
@@ -3884,6 +4529,49 @@ function getSpecProgress(spec) {
|
|
|
3884
4529
|
}
|
|
3885
4530
|
return `${completedTodos}/${totalTodos}`;
|
|
3886
4531
|
}
|
|
4532
|
+
async function syncTemplateVersions(aiMemoryFile, projectDir) {
|
|
4533
|
+
logger.step("\u540C\u6B65\u6A21\u677F\u7248\u672C\u4FE1\u606F...");
|
|
4534
|
+
const config = await readTemplateConfig(projectDir);
|
|
4535
|
+
if (!config) {
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
4538
|
+
const lines = [];
|
|
4539
|
+
lines.push("## \u6A21\u677F\u7248\u672C\u4FE1\u606F");
|
|
4540
|
+
lines.push("");
|
|
4541
|
+
lines.push("> \u672C\u90E8\u5206\u7531 team-cli \u81EA\u52A8\u7BA1\u7406\uFF0C\u8BB0\u5F55\u4F7F\u7528\u7684\u524D\u540E\u7AEF\u6A21\u677F\u7248\u672C");
|
|
4542
|
+
lines.push("");
|
|
4543
|
+
lines.push("| \u7C7B\u578B | \u4ED3\u5E93 | Tag | Branch | Commit | \u66F4\u65B0\u65F6\u95F4 |");
|
|
4544
|
+
lines.push("|------|------|-----|--------|--------|----------|");
|
|
4545
|
+
if (config.frontend) {
|
|
4546
|
+
const repoName = extractRepoName(config.frontend.repository);
|
|
4547
|
+
const commit = config.frontend.commit?.substring(0, 8) || "-";
|
|
4548
|
+
const tag = config.frontend.tag || "-";
|
|
4549
|
+
const branch = config.frontend.branch || "-";
|
|
4550
|
+
const updateDate = config.frontend.lastUpdate ? new Date(config.frontend.lastUpdate).toLocaleDateString("zh-CN") : "-";
|
|
4551
|
+
lines.push(`| \u524D\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
|
|
4552
|
+
}
|
|
4553
|
+
if (config.backend) {
|
|
4554
|
+
const repoName = extractRepoName(config.backend.repository);
|
|
4555
|
+
const commit = config.backend.commit?.substring(0, 8) || "-";
|
|
4556
|
+
const tag = config.backend.tag || "-";
|
|
4557
|
+
const branch = config.backend.branch || "-";
|
|
4558
|
+
const updateDate = config.backend.lastUpdate ? new Date(config.backend.lastUpdate).toLocaleDateString("zh-CN") : "-";
|
|
4559
|
+
lines.push(`| \u540E\u7AEF | ${repoName} | ${tag} | ${branch} | ${commit} | ${updateDate} |`);
|
|
4560
|
+
}
|
|
4561
|
+
const newContent = lines.join("\n") + "\n";
|
|
4562
|
+
await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
|
|
4563
|
+
}
|
|
4564
|
+
function extractRepoName(repository) {
|
|
4565
|
+
let path17 = repository;
|
|
4566
|
+
path17 = path17.replace(/^https?:\/\//, "");
|
|
4567
|
+
path17 = path17.replace(/^git@/, "");
|
|
4568
|
+
const parts = path17.split("/");
|
|
4569
|
+
if (parts.length > 1) {
|
|
4570
|
+
path17 = parts.slice(1).join("/");
|
|
4571
|
+
}
|
|
4572
|
+
path17 = path17.replace(/\.git$/, "");
|
|
4573
|
+
return path17;
|
|
4574
|
+
}
|
|
3887
4575
|
var syncMemoryCommand;
|
|
3888
4576
|
var init_sync_memory = __esm({
|
|
3889
4577
|
"src/commands/sync-memory.ts"() {
|
|
@@ -3891,6 +4579,7 @@ var init_sync_memory = __esm({
|
|
|
3891
4579
|
init_esm_shims();
|
|
3892
4580
|
init_utils();
|
|
3893
4581
|
init_logger();
|
|
4582
|
+
init_template_version();
|
|
3894
4583
|
syncMemoryCommand = new Command10("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
|
|
3895
4584
|
try {
|
|
3896
4585
|
logger.header("\u540C\u6B65 AI_MEMORY.md");
|
|
@@ -3910,6 +4599,7 @@ var init_sync_memory = __esm({
|
|
|
3910
4599
|
await syncFeatureInventory(aiMemoryFile, ".");
|
|
3911
4600
|
await syncApiInventory(aiMemoryFile, ".");
|
|
3912
4601
|
await syncDataModels(aiMemoryFile, ".");
|
|
4602
|
+
await syncTemplateVersions(aiMemoryFile, ".");
|
|
3913
4603
|
await updateSyncTime(aiMemoryFile);
|
|
3914
4604
|
logger.success("AI_MEMORY.md \u5DF2\u540C\u6B65");
|
|
3915
4605
|
} catch (error) {
|
|
@@ -3925,11 +4615,11 @@ var init_sync_memory = __esm({
|
|
|
3925
4615
|
|
|
3926
4616
|
// src/commands/check-api.ts
|
|
3927
4617
|
import { Command as Command11 } from "commander";
|
|
3928
|
-
import
|
|
4618
|
+
import path14 from "path";
|
|
3929
4619
|
import inquirer7 from "inquirer";
|
|
3930
4620
|
import { Listr as Listr6 } from "listr2";
|
|
3931
4621
|
async function checkApiConflicts(projectDir) {
|
|
3932
|
-
const backendDir =
|
|
4622
|
+
const backendDir = path14.join(projectDir, "backend");
|
|
3933
4623
|
const exists = await FileUtils.exists(backendDir);
|
|
3934
4624
|
if (!exists) {
|
|
3935
4625
|
logger.info("\u672A\u627E\u5230\u540E\u7AEF\u9879\u76EE");
|
|
@@ -3938,10 +4628,10 @@ async function checkApiConflicts(projectDir) {
|
|
|
3938
4628
|
logger.step("\u626B\u63CF\u540E\u7AEF API...");
|
|
3939
4629
|
logger.newLine();
|
|
3940
4630
|
const apiMap = /* @__PURE__ */ new Map();
|
|
3941
|
-
const srcDir =
|
|
4631
|
+
const srcDir = path14.join(backendDir, "src");
|
|
3942
4632
|
const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
|
|
3943
4633
|
for (const controllerFile of controllers) {
|
|
3944
|
-
const controllerPath =
|
|
4634
|
+
const controllerPath = path14.join(srcDir, controllerFile);
|
|
3945
4635
|
const apis = await extractApisFromController(controllerPath);
|
|
3946
4636
|
for (const api of apis) {
|
|
3947
4637
|
const key = `${api.method}:${api.path}`;
|
|
@@ -3972,8 +4662,8 @@ async function checkApiConflicts(projectDir) {
|
|
|
3972
4662
|
}
|
|
3973
4663
|
}
|
|
3974
4664
|
async function detectApiChanges(projectDir) {
|
|
3975
|
-
const backendDir =
|
|
3976
|
-
const registryFile =
|
|
4665
|
+
const backendDir = path14.join(projectDir, "backend");
|
|
4666
|
+
const registryFile = path14.join(projectDir, "docs/api-registry.md");
|
|
3977
4667
|
const registryExists = await FileUtils.exists(registryFile);
|
|
3978
4668
|
if (!registryExists) {
|
|
3979
4669
|
logger.info("API Registry \u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u53D8\u66F4\u68C0\u6D4B");
|
|
@@ -3985,10 +4675,10 @@ async function detectApiChanges(projectDir) {
|
|
|
3985
4675
|
const registryContent = await FileUtils.read(registryFile);
|
|
3986
4676
|
const existingApis = extractApisFromRegistry(registryContent);
|
|
3987
4677
|
const currentApis = /* @__PURE__ */ new Map();
|
|
3988
|
-
const srcDir =
|
|
4678
|
+
const srcDir = path14.join(backendDir, "src");
|
|
3989
4679
|
const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
|
|
3990
4680
|
for (const controllerFile of controllers) {
|
|
3991
|
-
const controllerPath =
|
|
4681
|
+
const controllerPath = path14.join(srcDir, controllerFile);
|
|
3992
4682
|
const apis = await extractApisFromController(controllerPath);
|
|
3993
4683
|
for (const api of apis) {
|
|
3994
4684
|
const key = `${api.method}:${api.path}`;
|
|
@@ -4050,9 +4740,9 @@ async function detectApiChanges(projectDir) {
|
|
|
4050
4740
|
}
|
|
4051
4741
|
}
|
|
4052
4742
|
async function generateApiRegistry(projectDir) {
|
|
4053
|
-
const registryFile =
|
|
4743
|
+
const registryFile = path14.join(projectDir, "docs/api-registry.md");
|
|
4054
4744
|
logger.step("\u626B\u63CF\u5E76\u751F\u6210 API Registry...");
|
|
4055
|
-
await FileUtils.ensureDir(
|
|
4745
|
+
await FileUtils.ensureDir(path14.dirname(registryFile));
|
|
4056
4746
|
const header = `# API Registry
|
|
4057
4747
|
|
|
4058
4748
|
> \u672C\u6587\u4EF6\u8BB0\u5F55\u6240\u6709 API \u7684\u5B9A\u4E49\u3001\u7248\u672C\u548C\u53D8\u66F4\u5386\u53F2
|
|
@@ -4081,14 +4771,14 @@ async function generateApiRegistry(projectDir) {
|
|
|
4081
4771
|
*\u6700\u540E\u66F4\u65B0: ${DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss")}*
|
|
4082
4772
|
`;
|
|
4083
4773
|
let content = header;
|
|
4084
|
-
const backendDir =
|
|
4774
|
+
const backendDir = path14.join(projectDir, "backend");
|
|
4085
4775
|
const exists = await FileUtils.exists(backendDir);
|
|
4086
4776
|
if (exists) {
|
|
4087
|
-
const srcDir =
|
|
4777
|
+
const srcDir = path14.join(backendDir, "src");
|
|
4088
4778
|
const controllers = await FileUtils.findFiles("*Controller.java", srcDir);
|
|
4089
4779
|
const moduleMap = /* @__PURE__ */ new Map();
|
|
4090
4780
|
for (const controllerFile of controllers) {
|
|
4091
|
-
const controllerPath =
|
|
4781
|
+
const controllerPath = path14.join(srcDir, controllerFile);
|
|
4092
4782
|
const controllerName = controllerFile.replace(".java", "");
|
|
4093
4783
|
const module = controllerName.replace(/Controller$/, "").toLowerCase();
|
|
4094
4784
|
if (!moduleMap.has(module)) {
|
|
@@ -4172,10 +4862,10 @@ function extractApisFromRegistry(registryContent) {
|
|
|
4172
4862
|
let match;
|
|
4173
4863
|
while ((match = apiRegex.exec(registryContent)) !== null) {
|
|
4174
4864
|
const method = match[1];
|
|
4175
|
-
const
|
|
4865
|
+
const path17 = match[2].trim();
|
|
4176
4866
|
const description = match[3].trim();
|
|
4177
|
-
const key = `${method}:${
|
|
4178
|
-
apis.set(key, { method, path:
|
|
4867
|
+
const key = `${method}:${path17}`;
|
|
4868
|
+
apis.set(key, { method, path: path17, description });
|
|
4179
4869
|
}
|
|
4180
4870
|
return apis;
|
|
4181
4871
|
}
|
|
@@ -4263,7 +4953,7 @@ var init_check_api = __esm({
|
|
|
4263
4953
|
|
|
4264
4954
|
// src/commands/logs.ts
|
|
4265
4955
|
import { Command as Command12 } from "commander";
|
|
4266
|
-
import
|
|
4956
|
+
import path15 from "path";
|
|
4267
4957
|
import inquirer8 from "inquirer";
|
|
4268
4958
|
async function collectLogFiles(targetDir) {
|
|
4269
4959
|
const logs = [];
|
|
@@ -4271,7 +4961,7 @@ async function collectLogFiles(targetDir) {
|
|
|
4271
4961
|
const allFiles = await FileUtils.findFiles("*.md", targetDir);
|
|
4272
4962
|
const filtered = allFiles.filter((f) => f !== "index.md");
|
|
4273
4963
|
for (const file of filtered) {
|
|
4274
|
-
const filePath =
|
|
4964
|
+
const filePath = path15.join(targetDir, file);
|
|
4275
4965
|
const stat = await FileUtils.exists(filePath);
|
|
4276
4966
|
if (stat) {
|
|
4277
4967
|
logs.push(filePath);
|
|
@@ -4311,7 +5001,7 @@ var init_logs = __esm({
|
|
|
4311
5001
|
case "":
|
|
4312
5002
|
case "today": {
|
|
4313
5003
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
4314
|
-
targetDir =
|
|
5004
|
+
targetDir = path15.join(sessionsDir, today);
|
|
4315
5005
|
const todayExists = await FileUtils.exists(targetDir);
|
|
4316
5006
|
if (!todayExists) {
|
|
4317
5007
|
logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
|
|
@@ -4327,7 +5017,7 @@ var init_logs = __esm({
|
|
|
4327
5017
|
break;
|
|
4328
5018
|
}
|
|
4329
5019
|
default: {
|
|
4330
|
-
targetDir =
|
|
5020
|
+
targetDir = path15.join(sessionsDir, filter);
|
|
4331
5021
|
const dateExists = await FileUtils.exists(targetDir);
|
|
4332
5022
|
if (!dateExists) {
|
|
4333
5023
|
logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
|
|
@@ -4351,7 +5041,7 @@ var init_logs = __esm({
|
|
|
4351
5041
|
process.exit(0);
|
|
4352
5042
|
}
|
|
4353
5043
|
for (let i = 0; i < logs.length; i++) {
|
|
4354
|
-
const relPath =
|
|
5044
|
+
const relPath = path15.relative(sessionsDir, logs[i]);
|
|
4355
5045
|
logger.step(`${i + 1}) ${relPath}`);
|
|
4356
5046
|
}
|
|
4357
5047
|
logger.newLine();
|
|
@@ -4389,15 +5079,709 @@ var init_logs = __esm({
|
|
|
4389
5079
|
}
|
|
4390
5080
|
});
|
|
4391
5081
|
|
|
4392
|
-
// src/
|
|
4393
|
-
var index_exports = {};
|
|
5082
|
+
// src/commands/update.ts
|
|
4394
5083
|
import { Command as Command13 } from "commander";
|
|
5084
|
+
import path16 from "path";
|
|
5085
|
+
import { execa as execa4 } from "execa";
|
|
5086
|
+
import inquirer9 from "inquirer";
|
|
5087
|
+
import fs3 from "fs-extra";
|
|
5088
|
+
async function performUpdate(projectPath, updates) {
|
|
5089
|
+
logger.newLine();
|
|
5090
|
+
logger.info("\u5F00\u59CB\u66F4\u65B0\u6A21\u677F...");
|
|
5091
|
+
for (const update of updates) {
|
|
5092
|
+
const { type, info, updateOptions } = update;
|
|
5093
|
+
const targetDir = type === "frontend" ? "frontend" : "backend";
|
|
5094
|
+
const targetPath = path16.join(projectPath, targetDir);
|
|
5095
|
+
logger.newLine();
|
|
5096
|
+
logger.step(`\u66F4\u65B0 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
|
|
5097
|
+
if (updateOptions?.tag || updateOptions?.branch) {
|
|
5098
|
+
const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
|
|
5099
|
+
const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
|
|
5100
|
+
const config = await userConfigManager2.getGitLabConfig();
|
|
5101
|
+
if (config) {
|
|
5102
|
+
const gitlabAPI = new GitLabAPI2(config);
|
|
5103
|
+
const projectPathEncoded = GitLabAPI2.parseProjectPath(info.repository);
|
|
5104
|
+
if (updateOptions.tag) {
|
|
5105
|
+
const isValid = await gitlabAPI.validateTag(projectPathEncoded, updateOptions.tag);
|
|
5106
|
+
if (!isValid) {
|
|
5107
|
+
logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag "${updateOptions.tag}" \u4E0D\u5B58\u5728`);
|
|
5108
|
+
continue;
|
|
5109
|
+
}
|
|
5110
|
+
logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F tag: ${updateOptions.tag}`);
|
|
5111
|
+
}
|
|
5112
|
+
if (updateOptions.branch) {
|
|
5113
|
+
const isValid = await gitlabAPI.validateBranch(projectPathEncoded, updateOptions.branch);
|
|
5114
|
+
if (!isValid) {
|
|
5115
|
+
logger.error(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F "${updateOptions.branch}" \u4E0D\u5B58\u5728`);
|
|
5116
|
+
continue;
|
|
5117
|
+
}
|
|
5118
|
+
logger.info(`\u4F7F\u7528 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5206\u652F: ${updateOptions.branch}`);
|
|
5119
|
+
}
|
|
5120
|
+
} else {
|
|
5121
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
const ref = updateOptions?.tag || updateOptions?.branch || "HEAD";
|
|
5125
|
+
const backupDir = path16.join(projectPath, `.backup-${Date.now()}`);
|
|
5126
|
+
await fs3.copy(targetPath, path16.join(backupDir, targetDir));
|
|
5127
|
+
logger.info(`\u5DF2\u521B\u5EFA\u5907\u4EFD: ${backupDir}`);
|
|
5128
|
+
if (updateOptions?.dryRun) {
|
|
5129
|
+
logger.info("[Dry Run] \u5C06\u4F1A\u66F4\u65B0\u5230\u4EE5\u4E0B\u7248\u672C:");
|
|
5130
|
+
logger.info(` Ref: ${ref}`);
|
|
5131
|
+
logger.info(` \u4ED3\u5E93: ${info.repository}`);
|
|
5132
|
+
logger.info("\u5907\u4EFD\u5DF2\u521B\u5EFA\uFF0C\u8DF3\u8FC7\u5B9E\u9645\u66F4\u65B0");
|
|
5133
|
+
continue;
|
|
5134
|
+
}
|
|
5135
|
+
try {
|
|
5136
|
+
const tempDir = path16.join(projectPath, `.template-update-${Date.now()}`);
|
|
5137
|
+
await execa4("git", ["clone", "--depth=1", "--branch", ref, info.repository, tempDir], {
|
|
5138
|
+
stdio: "pipe"
|
|
5139
|
+
});
|
|
5140
|
+
const { stdout: commit } = await execa4("git", ["rev-parse", "HEAD"], {
|
|
5141
|
+
cwd: tempDir,
|
|
5142
|
+
stdio: "pipe"
|
|
5143
|
+
});
|
|
5144
|
+
const { stdout: tags } = await execa4("git", ["tag", "-l", "--sort=-v:refname"], {
|
|
5145
|
+
cwd: tempDir,
|
|
5146
|
+
stdio: "pipe"
|
|
5147
|
+
});
|
|
5148
|
+
const latestTag = tags.split("\n")[0] || void 0;
|
|
5149
|
+
const keepFiles = [".env.local", "package-lock.json", "node_modules"];
|
|
5150
|
+
const currentFiles = await FileUtils.findFiles("*", targetPath);
|
|
5151
|
+
for (const file of currentFiles) {
|
|
5152
|
+
if (!keepFiles.includes(file)) {
|
|
5153
|
+
const filePath = path16.join(targetPath, file);
|
|
5154
|
+
try {
|
|
5155
|
+
await fs3.remove(filePath);
|
|
5156
|
+
} catch {
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
await fs3.copy(tempDir, targetPath, {
|
|
5161
|
+
filter: (src) => !src.includes(".git")
|
|
5162
|
+
});
|
|
5163
|
+
await fs3.remove(tempDir);
|
|
5164
|
+
await updateTemplateVersion(projectPath, type, commit.trim(), {
|
|
5165
|
+
tag: updateOptions?.tag || latestTag,
|
|
5166
|
+
branch: updateOptions?.branch
|
|
5167
|
+
});
|
|
5168
|
+
logger.success(`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!`);
|
|
5169
|
+
logger.info(`\u65B0\u7248\u672C: ${updateOptions?.tag || updateOptions?.branch || latestTag || commit.substring(0, 8)}`);
|
|
5170
|
+
logger.info(`\u5907\u4EFD\u4F4D\u7F6E: ${backupDir}`);
|
|
5171
|
+
} catch (error) {
|
|
5172
|
+
logger.error(`\u66F4\u65B0\u5931\u8D25: ${error.message}`);
|
|
5173
|
+
logger.info("\u6B63\u5728\u6062\u590D\u5907\u4EFD...");
|
|
5174
|
+
await fs3.remove(targetPath);
|
|
5175
|
+
await fs3.copy(path16.join(backupDir, targetDir), targetPath);
|
|
5176
|
+
await fs3.remove(backupDir);
|
|
5177
|
+
logger.info("\u5DF2\u6062\u590D\u5230\u66F4\u65B0\u524D\u7684\u72B6\u6001");
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
logger.newLine();
|
|
5181
|
+
logger.header("\u6A21\u677F\u66F4\u65B0\u5B8C\u6210!");
|
|
5182
|
+
logger.newLine();
|
|
5183
|
+
logger.info("\u4E0B\u4E00\u6B65:");
|
|
5184
|
+
logger.step("1. \u68C0\u67E5\u66F4\u65B0\u540E\u7684\u4EE3\u7801");
|
|
5185
|
+
logger.step("2. \u8FD0\u884C npm install \u5B89\u88C5\u65B0\u4F9D\u8D56");
|
|
5186
|
+
logger.step("3. \u6D4B\u8BD5\u5E94\u7528\u662F\u5426\u6B63\u5E38\u8FD0\u884C");
|
|
5187
|
+
logger.step("4. \u63D0\u4EA4\u4EE3\u7801\u5230 Git");
|
|
5188
|
+
logger.newLine();
|
|
5189
|
+
}
|
|
5190
|
+
var updateCommand;
|
|
5191
|
+
var init_update = __esm({
|
|
5192
|
+
"src/commands/update.ts"() {
|
|
5193
|
+
"use strict";
|
|
5194
|
+
init_esm_shims();
|
|
5195
|
+
init_template_version();
|
|
5196
|
+
init_logger();
|
|
5197
|
+
init_utils();
|
|
5198
|
+
updateCommand = new Command13("update").description("\u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C").option("-f, --frontend", "\u68C0\u67E5\u524D\u7AEF\u6A21\u677F\u66F4\u65B0").option("-b, --backend", "\u68C0\u67E5\u540E\u7AEF\u6A21\u677F\u66F4\u65B0").option("-a, --all", "\u68C0\u67E5\u6240\u6709\u6A21\u677F (\u9ED8\u8BA4)").option("-t, --tag <tag>", "\u66F4\u65B0\u5230\u6307\u5B9A\u6807\u7B7E").option("-B, --branch <branch>", "\u66F4\u65B0\u5230\u6307\u5B9A\u5206\u652F").option("--dry-run", "\u9884\u89C8\u66F4\u65B0\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
|
|
5199
|
+
try {
|
|
5200
|
+
logger.header("\u6A21\u677F\u7248\u672C\u68C0\u67E5");
|
|
5201
|
+
logger.newLine();
|
|
5202
|
+
const hasConfig = await FileUtils.exists("TECH_STACK.md");
|
|
5203
|
+
if (!hasConfig) {
|
|
5204
|
+
logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
|
|
5205
|
+
logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
|
|
5206
|
+
process.exit(1);
|
|
5207
|
+
}
|
|
5208
|
+
const projectPath = ".";
|
|
5209
|
+
const checkAll = !options.frontend && !options.backend;
|
|
5210
|
+
const checkFrontend = options.frontend || checkAll;
|
|
5211
|
+
const checkBackend = options.backend || checkAll;
|
|
5212
|
+
const forceUpdate = options.tag || options.branch;
|
|
5213
|
+
const updates = [];
|
|
5214
|
+
const updateOptions = {
|
|
5215
|
+
tag: options.tag,
|
|
5216
|
+
branch: options.branch,
|
|
5217
|
+
dryRun: options.dryRun
|
|
5218
|
+
};
|
|
5219
|
+
if (checkFrontend) {
|
|
5220
|
+
logger.step("\u68C0\u67E5\u524D\u7AEF\u6A21\u677F...");
|
|
5221
|
+
const frontendInfo = await checkTemplateUpdate(projectPath, "frontend");
|
|
5222
|
+
if (frontendInfo) {
|
|
5223
|
+
if (frontendInfo.needsUpdate || forceUpdate) {
|
|
5224
|
+
const version = updateOptions.tag || updateOptions.branch || frontendInfo.latestTag || frontendInfo.latestCommit?.substring(0, 8);
|
|
5225
|
+
logger.success(`\u524D\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
|
|
5226
|
+
updates.push({ type: "frontend", info: frontendInfo, updateOptions });
|
|
5227
|
+
} else {
|
|
5228
|
+
logger.info("\u524D\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
5229
|
+
}
|
|
5230
|
+
} else {
|
|
5231
|
+
logger.info("\u524D\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
|
|
5232
|
+
}
|
|
5233
|
+
logger.newLine();
|
|
5234
|
+
}
|
|
5235
|
+
if (checkBackend) {
|
|
5236
|
+
logger.step("\u68C0\u67E5\u540E\u7AEF\u6A21\u677F...");
|
|
5237
|
+
const backendInfo = await checkTemplateUpdate(projectPath, "backend");
|
|
5238
|
+
if (backendInfo) {
|
|
5239
|
+
if (backendInfo.needsUpdate || forceUpdate) {
|
|
5240
|
+
const version = updateOptions.tag || updateOptions.branch || backendInfo.latestTag || backendInfo.latestCommit?.substring(0, 8);
|
|
5241
|
+
logger.success(`\u540E\u7AEF\u6A21\u677F${forceUpdate ? "\u5C06\u66F4\u65B0" : "\u6709\u66F4\u65B0"} (${version})`);
|
|
5242
|
+
updates.push({ type: "backend", info: backendInfo, updateOptions });
|
|
5243
|
+
} else {
|
|
5244
|
+
logger.info("\u540E\u7AEF\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
5245
|
+
}
|
|
5246
|
+
} else {
|
|
5247
|
+
logger.info("\u540E\u7AEF\u6A21\u677F\u672A\u914D\u7F6E\u6216\u65E0\u6CD5\u68C0\u67E5");
|
|
5248
|
+
}
|
|
5249
|
+
logger.newLine();
|
|
5250
|
+
}
|
|
5251
|
+
if (updates.length === 0) {
|
|
5252
|
+
logger.success("\u6240\u6709\u6A21\u677F\u5DF2\u662F\u6700\u65B0\u7248\u672C!");
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
5255
|
+
if (options.dryRun) {
|
|
5256
|
+
logger.header(`[Dry Run] \u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F`);
|
|
5257
|
+
} else {
|
|
5258
|
+
logger.header(`\u53D1\u73B0 ${updates.length} \u4E2A\u6A21\u677F\u66F4\u65B0`);
|
|
5259
|
+
}
|
|
5260
|
+
logger.newLine();
|
|
5261
|
+
for (const update of updates) {
|
|
5262
|
+
const version = update.updateOptions?.tag || update.updateOptions?.branch || update.info.latestTag || update.info.latestCommit?.substring(0, 8);
|
|
5263
|
+
logger.step(`${update.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${version}`);
|
|
5264
|
+
}
|
|
5265
|
+
logger.newLine();
|
|
5266
|
+
if (options.dryRun) {
|
|
5267
|
+
logger.info("Dry run \u6A21\u5F0F\uFF0C\u4E0D\u6267\u884C\u5B9E\u9645\u66F4\u65B0");
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
5270
|
+
const answers = await inquirer9.prompt([
|
|
5271
|
+
{
|
|
5272
|
+
type: "confirm",
|
|
5273
|
+
name: "shouldUpdate",
|
|
5274
|
+
message: "\u662F\u5426\u66F4\u65B0\u6A21\u677F?",
|
|
5275
|
+
default: false
|
|
5276
|
+
}
|
|
5277
|
+
]);
|
|
5278
|
+
if (answers.shouldUpdate) {
|
|
5279
|
+
await performUpdate(projectPath, updates);
|
|
5280
|
+
} else {
|
|
5281
|
+
logger.info("\u5DF2\u53D6\u6D88\u66F4\u65B0");
|
|
5282
|
+
}
|
|
5283
|
+
} catch (error) {
|
|
5284
|
+
logger.error(`\u66F4\u65B0\u68C0\u67E5\u5931\u8D25: ${error.message}`);
|
|
5285
|
+
if (process.env.DEBUG) {
|
|
5286
|
+
console.error(error);
|
|
5287
|
+
}
|
|
5288
|
+
process.exit(1);
|
|
5289
|
+
}
|
|
5290
|
+
});
|
|
5291
|
+
}
|
|
5292
|
+
});
|
|
5293
|
+
|
|
5294
|
+
// src/commands/config.ts
|
|
5295
|
+
import { Command as Command14 } from "commander";
|
|
5296
|
+
import inquirer10 from "inquirer";
|
|
4395
5297
|
import chalk2 from "chalk";
|
|
5298
|
+
var configCommand, setTokenCommand, showConfigCommand, removeConfigCommand, validateTokenCommand;
|
|
5299
|
+
var init_config = __esm({
|
|
5300
|
+
"src/commands/config.ts"() {
|
|
5301
|
+
"use strict";
|
|
5302
|
+
init_esm_shims();
|
|
5303
|
+
init_user_config();
|
|
5304
|
+
init_gitlab_api();
|
|
5305
|
+
init_logger();
|
|
5306
|
+
configCommand = new Command14("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
|
|
5307
|
+
setTokenCommand = new Command14("set-token").description("\u8BBE\u7F6E GitLab Access Token").option("-t, --token <token>", "Access Token").option("-u, --url <url>", "GitLab Base URL", "https://gitlab.com").action(async (options) => {
|
|
5308
|
+
try {
|
|
5309
|
+
logger.header("GitLab Access Token \u914D\u7F6E");
|
|
5310
|
+
logger.newLine();
|
|
5311
|
+
let { token, url } = options;
|
|
5312
|
+
if (!token) {
|
|
5313
|
+
const answers = await inquirer10.prompt([
|
|
5314
|
+
{
|
|
5315
|
+
type: "password",
|
|
5316
|
+
name: "token",
|
|
5317
|
+
message: "\u8BF7\u8F93\u5165 GitLab Access Token:",
|
|
5318
|
+
mask: "*",
|
|
5319
|
+
validate: (input) => {
|
|
5320
|
+
if (!input || input.trim().length === 0) {
|
|
5321
|
+
return "Token \u4E0D\u80FD\u4E3A\u7A7A";
|
|
5322
|
+
}
|
|
5323
|
+
return true;
|
|
5324
|
+
}
|
|
5325
|
+
},
|
|
5326
|
+
{
|
|
5327
|
+
type: "input",
|
|
5328
|
+
name: "url",
|
|
5329
|
+
message: "\u8BF7\u8F93\u5165 GitLab Base URL:",
|
|
5330
|
+
default: "https://gitlab.com",
|
|
5331
|
+
validate: (input) => {
|
|
5332
|
+
try {
|
|
5333
|
+
new URL(input);
|
|
5334
|
+
return true;
|
|
5335
|
+
} catch {
|
|
5336
|
+
return "\u8BF7\u8F93\u5165\u6709\u6548\u7684 URL";
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
]);
|
|
5341
|
+
token = answers.token;
|
|
5342
|
+
url = answers.url;
|
|
5343
|
+
}
|
|
5344
|
+
try {
|
|
5345
|
+
new URL(url);
|
|
5346
|
+
} catch {
|
|
5347
|
+
logger.error("\u65E0\u6548\u7684 GitLab URL");
|
|
5348
|
+
process.exit(1);
|
|
5349
|
+
}
|
|
5350
|
+
url = url.replace(/\/+$/, "");
|
|
5351
|
+
logger.info("\u9A8C\u8BC1 Token...");
|
|
5352
|
+
const gitlabAPI = new GitLabAPI({ accessToken: token, baseUrl: url });
|
|
5353
|
+
const isValid = await gitlabAPI.authenticate();
|
|
5354
|
+
if (!isValid) {
|
|
5355
|
+
logger.error("Token \u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5:");
|
|
5356
|
+
logger.info("1. Token \u662F\u5426\u6B63\u786E");
|
|
5357
|
+
logger.info("2. Token \u662F\u5426\u6709 api \u6743\u9650");
|
|
5358
|
+
logger.info("3. GitLab URL \u662F\u5426\u6B63\u786E");
|
|
5359
|
+
process.exit(1);
|
|
5360
|
+
}
|
|
5361
|
+
logger.success("Token \u9A8C\u8BC1\u6210\u529F!");
|
|
5362
|
+
await userConfigManager.updateGitLabToken(token, url);
|
|
5363
|
+
logger.success("\u914D\u7F6E\u5DF2\u4FDD\u5B58!");
|
|
5364
|
+
logger.newLine();
|
|
5365
|
+
logger.info(`GitLab URL: ${chalk2.cyan(url)}`);
|
|
5366
|
+
logger.info(`\u914D\u7F6E\u6587\u4EF6: ${chalk2.gray(userConfigManager.getConfigPath())}`);
|
|
5367
|
+
} catch (error) {
|
|
5368
|
+
logger.error(`\u914D\u7F6E\u5931\u8D25: ${error.message}`);
|
|
5369
|
+
if (process.env.DEBUG) {
|
|
5370
|
+
console.error(error);
|
|
5371
|
+
}
|
|
5372
|
+
process.exit(1);
|
|
5373
|
+
}
|
|
5374
|
+
});
|
|
5375
|
+
showConfigCommand = new Command14("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
|
|
5376
|
+
try {
|
|
5377
|
+
logger.header("GitLab \u914D\u7F6E");
|
|
5378
|
+
logger.newLine();
|
|
5379
|
+
const hasConfig = await userConfigManager.hasConfig();
|
|
5380
|
+
if (!hasConfig) {
|
|
5381
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
|
|
5382
|
+
logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
|
|
5383
|
+
} else {
|
|
5384
|
+
const config = await userConfigManager.load();
|
|
5385
|
+
if (config?.gitlab) {
|
|
5386
|
+
const token = config.gitlab.accessToken;
|
|
5387
|
+
const maskedToken = token ? `${token.substring(0, 8)}${"*".repeat(Math.min(16, token.length - 8))}` : "";
|
|
5388
|
+
logger.info(`GitLab URL: ${chalk2.cyan(config.gitlab.baseUrl)}`);
|
|
5389
|
+
logger.info(`Access Token: ${chalk2.yellow(maskedToken)}`);
|
|
5390
|
+
logger.info(`Timeout: ${chalk2.gray(config.gitlab.timeout || 3e4)}ms`);
|
|
5391
|
+
if (config.preferences) {
|
|
5392
|
+
logger.newLine();
|
|
5393
|
+
logger.info("\u504F\u597D\u8BBE\u7F6E:");
|
|
5394
|
+
if (config.preferences.defaultBackendBranch) {
|
|
5395
|
+
logger.info(` \u9ED8\u8BA4\u540E\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultBackendBranch)}`);
|
|
5396
|
+
}
|
|
5397
|
+
if (config.preferences.defaultFrontendBranch) {
|
|
5398
|
+
logger.info(` \u9ED8\u8BA4\u524D\u7AEF\u5206\u652F: ${chalk2.cyan(config.preferences.defaultFrontendBranch)}`);
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
logger.newLine();
|
|
5402
|
+
logger.info("\u914D\u7F6E\u6587\u4EF6\u4F4D\u7F6E:");
|
|
5403
|
+
logger.info(` ${chalk2.gray(userConfigManager.getConfigPath())}`);
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
} catch (error) {
|
|
5407
|
+
logger.error(`\u8BFB\u53D6\u914D\u7F6E\u5931\u8D25: ${error.message}`);
|
|
5408
|
+
if (process.env.DEBUG) {
|
|
5409
|
+
console.error(error);
|
|
5410
|
+
}
|
|
5411
|
+
process.exit(1);
|
|
5412
|
+
}
|
|
5413
|
+
});
|
|
5414
|
+
removeConfigCommand = new Command14("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
|
|
5415
|
+
try {
|
|
5416
|
+
const hasConfig = await userConfigManager.hasConfig();
|
|
5417
|
+
if (!hasConfig) {
|
|
5418
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
|
|
5419
|
+
return;
|
|
5420
|
+
}
|
|
5421
|
+
const answers = await inquirer10.prompt([
|
|
5422
|
+
{
|
|
5423
|
+
type: "confirm",
|
|
5424
|
+
name: "confirm",
|
|
5425
|
+
message: "\u786E\u5B9A\u8981\u5220\u9664 GitLab \u914D\u7F6E\u5417?",
|
|
5426
|
+
default: false
|
|
5427
|
+
}
|
|
5428
|
+
]);
|
|
5429
|
+
if (!answers.confirm) {
|
|
5430
|
+
logger.info("\u5DF2\u53D6\u6D88");
|
|
5431
|
+
return;
|
|
5432
|
+
}
|
|
5433
|
+
await userConfigManager.removeConfig();
|
|
5434
|
+
logger.success("\u914D\u7F6E\u5DF2\u5220\u9664");
|
|
5435
|
+
} catch (error) {
|
|
5436
|
+
logger.error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error.message}`);
|
|
5437
|
+
if (process.env.DEBUG) {
|
|
5438
|
+
console.error(error);
|
|
5439
|
+
}
|
|
5440
|
+
process.exit(1);
|
|
5441
|
+
}
|
|
5442
|
+
});
|
|
5443
|
+
validateTokenCommand = new Command14("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
|
|
5444
|
+
try {
|
|
5445
|
+
logger.header("\u9A8C\u8BC1 GitLab Token");
|
|
5446
|
+
logger.newLine();
|
|
5447
|
+
const config = await userConfigManager.getGitLabConfig();
|
|
5448
|
+
if (!config) {
|
|
5449
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
|
|
5450
|
+
logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
|
|
5451
|
+
process.exit(1);
|
|
5452
|
+
}
|
|
5453
|
+
logger.info("\u6B63\u5728\u9A8C\u8BC1...");
|
|
5454
|
+
const gitlabAPI = new GitLabAPI(config);
|
|
5455
|
+
const isValid = await gitlabAPI.authenticate();
|
|
5456
|
+
if (isValid) {
|
|
5457
|
+
logger.success("Token \u6709\u6548!");
|
|
5458
|
+
logger.newLine();
|
|
5459
|
+
logger.info(`GitLab URL: ${chalk2.cyan(config.baseUrl)}`);
|
|
5460
|
+
} else {
|
|
5461
|
+
logger.error("Token \u9A8C\u8BC1\u5931\u8D25");
|
|
5462
|
+
logger.info("\u8BF7\u68C0\u67E5 Token \u662F\u5426\u6B63\u786E\u6216\u5DF2\u8FC7\u671F");
|
|
5463
|
+
process.exit(1);
|
|
5464
|
+
}
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
logger.error(`\u9A8C\u8BC1\u5931\u8D25: ${error.message}`);
|
|
5467
|
+
if (process.env.DEBUG) {
|
|
5468
|
+
console.error(error);
|
|
5469
|
+
}
|
|
5470
|
+
process.exit(1);
|
|
5471
|
+
}
|
|
5472
|
+
});
|
|
5473
|
+
}
|
|
5474
|
+
});
|
|
5475
|
+
|
|
5476
|
+
// src/commands/diff.ts
|
|
5477
|
+
import { Command as Command15 } from "commander";
|
|
5478
|
+
import chalk3 from "chalk";
|
|
5479
|
+
async function compareTemplate(projectPath, type, localConfig, remoteTag, remoteBranch, gitlabConfig) {
|
|
5480
|
+
try {
|
|
5481
|
+
logger.step(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
|
|
5482
|
+
const repository = localConfig.repository;
|
|
5483
|
+
const localCommit = localConfig.commit;
|
|
5484
|
+
const localTag = localConfig.tag;
|
|
5485
|
+
const localBranch = localConfig.branch;
|
|
5486
|
+
let remoteCommit = null;
|
|
5487
|
+
let remoteTagName;
|
|
5488
|
+
let remoteBranchName;
|
|
5489
|
+
if (gitlabConfig) {
|
|
5490
|
+
const gitlabAPI = new GitLabAPI(gitlabConfig);
|
|
5491
|
+
const projectPathEncoded = GitLabAPI.parseProjectPath(repository);
|
|
5492
|
+
if (remoteTag) {
|
|
5493
|
+
remoteCommit = await gitlabAPI.getTagCommit(projectPathEncoded, remoteTag);
|
|
5494
|
+
remoteTagName = remoteTag;
|
|
5495
|
+
} else if (remoteBranch) {
|
|
5496
|
+
remoteCommit = await gitlabAPI.getBranchCommit(projectPathEncoded, remoteBranch);
|
|
5497
|
+
remoteBranchName = remoteBranch;
|
|
5498
|
+
} else {
|
|
5499
|
+
const latest = await gitlabAPI.getLatestVersion(projectPathEncoded);
|
|
5500
|
+
if (latest) {
|
|
5501
|
+
remoteCommit = latest.commit;
|
|
5502
|
+
if (latest.type === "tag") {
|
|
5503
|
+
remoteTagName = latest.name;
|
|
5504
|
+
} else {
|
|
5505
|
+
remoteBranchName = latest.name;
|
|
5506
|
+
}
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
if (remoteCommit && localCommit && remoteCommit !== localCommit) {
|
|
5510
|
+
const from = localTag || localBranch || localCommit;
|
|
5511
|
+
const to = remoteTagName || remoteBranchName || remoteCommit;
|
|
5512
|
+
const diff = await gitlabAPI.compareVersions(projectPathEncoded, from, to);
|
|
5513
|
+
return {
|
|
5514
|
+
type,
|
|
5515
|
+
local: {
|
|
5516
|
+
commit: localCommit,
|
|
5517
|
+
tag: localTag,
|
|
5518
|
+
branch: localBranch
|
|
5519
|
+
},
|
|
5520
|
+
remote: {
|
|
5521
|
+
commit: remoteCommit,
|
|
5522
|
+
tag: remoteTagName,
|
|
5523
|
+
branch: remoteBranchName
|
|
5524
|
+
},
|
|
5525
|
+
diff: diff || void 0
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
}
|
|
5529
|
+
if (!remoteCommit) {
|
|
5530
|
+
const { execa: execa5 } = await import("execa");
|
|
5531
|
+
const ref = remoteTag || remoteBranch || "HEAD";
|
|
5532
|
+
const { stdout } = await execa5("git", ["ls-remote", repository, ref], {
|
|
5533
|
+
stdio: "pipe"
|
|
5534
|
+
});
|
|
5535
|
+
remoteCommit = stdout.split(" ")[0];
|
|
5536
|
+
remoteTagName = remoteTag;
|
|
5537
|
+
remoteBranchName = remoteBranch;
|
|
5538
|
+
}
|
|
5539
|
+
const needsUpdate = localCommit !== remoteCommit;
|
|
5540
|
+
logger.info(
|
|
5541
|
+
`${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F: ${needsUpdate ? chalk3.yellow("\u6709\u66F4\u65B0") : chalk3.green("\u5DF2\u662F\u6700\u65B0")}`
|
|
5542
|
+
);
|
|
5543
|
+
return {
|
|
5544
|
+
type,
|
|
5545
|
+
local: {
|
|
5546
|
+
commit: localCommit,
|
|
5547
|
+
tag: localTag,
|
|
5548
|
+
branch: localBranch
|
|
5549
|
+
},
|
|
5550
|
+
remote: {
|
|
5551
|
+
commit: remoteCommit || void 0,
|
|
5552
|
+
tag: remoteTagName,
|
|
5553
|
+
branch: remoteBranchName
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
} catch (error) {
|
|
5557
|
+
logger.error(`\u5BF9\u6BD4${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F\u5931\u8D25: ${error}`);
|
|
5558
|
+
return null;
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
function outputTable(results) {
|
|
5562
|
+
logger.newLine();
|
|
5563
|
+
for (const result of results) {
|
|
5564
|
+
const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
|
|
5565
|
+
console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
|
|
5566
|
+
console.log("");
|
|
5567
|
+
const t = new Table({
|
|
5568
|
+
head: [chalk3.cyan("\u9879\u76EE"), chalk3.cyan("\u7248\u672C")],
|
|
5569
|
+
colWidths: [20, 50]
|
|
5570
|
+
});
|
|
5571
|
+
const localVersion = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "-";
|
|
5572
|
+
const localInfo = `Commit: ${result.local.commit?.substring(0, 8) || "-"}${result.local.tag ? `
|
|
5573
|
+
Tag: ${result.local.tag}` : ""}${result.local.branch ? `
|
|
5574
|
+
Branch: ${result.local.branch}` : ""}`;
|
|
5575
|
+
t.push(["\u672C\u5730", localInfo]);
|
|
5576
|
+
const remoteVersion = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "-";
|
|
5577
|
+
const remoteInfo = `Commit: ${result.remote.commit?.substring(0, 8) || "-"}${result.remote.tag ? `
|
|
5578
|
+
Tag: ${result.remote.tag}` : ""}${result.remote.branch ? `
|
|
5579
|
+
Branch: ${result.remote.branch}` : ""}`;
|
|
5580
|
+
t.push(["\u8FDC\u7A0B", remoteInfo]);
|
|
5581
|
+
console.log(t.toString());
|
|
5582
|
+
if (result.diff && result.diff.commits.length > 0) {
|
|
5583
|
+
console.log("");
|
|
5584
|
+
console.log(chalk3.bold("\u65B0\u589E\u63D0\u4EA4:"));
|
|
5585
|
+
const commitsTable = new Table({
|
|
5586
|
+
head: [chalk3.cyan("Commit"), chalk3.cyan("\u4F5C\u8005"), chalk3.cyan("\u65F6\u95F4"), chalk3.cyan("\u63CF\u8FF0")],
|
|
5587
|
+
colWidths: [10, 15, 20, 50],
|
|
5588
|
+
wordWrap: true
|
|
5589
|
+
});
|
|
5590
|
+
for (const commit of result.diff.commits.slice(0, 10)) {
|
|
5591
|
+
commitsTable.push([
|
|
5592
|
+
commit.short_id,
|
|
5593
|
+
commit.author_name,
|
|
5594
|
+
new Date(commit.created_at).toLocaleDateString("zh-CN"),
|
|
5595
|
+
commit.title
|
|
5596
|
+
]);
|
|
5597
|
+
}
|
|
5598
|
+
console.log(commitsTable.toString());
|
|
5599
|
+
if (result.diff.commits.length > 10) {
|
|
5600
|
+
console.log(chalk3.gray(`... \u8FD8\u6709 ${result.diff.commits.length - 10} \u4E2A\u63D0\u4EA4`));
|
|
5601
|
+
}
|
|
5602
|
+
const files = result.diff.diffs;
|
|
5603
|
+
const added = files.filter((f) => f.new_file).length;
|
|
5604
|
+
const deleted = files.filter((f) => f.deleted_file).length;
|
|
5605
|
+
const modified = files.length - added - deleted;
|
|
5606
|
+
console.log("");
|
|
5607
|
+
console.log(chalk3.bold("\u6587\u4EF6\u53D8\u66F4:"));
|
|
5608
|
+
console.log(` ${chalk3.green("+")} \u65B0\u589E: ${added}`);
|
|
5609
|
+
console.log(` ${chalk3.red("-")} \u5220\u9664: ${deleted}`);
|
|
5610
|
+
console.log(` ${chalk3.yellow("~")} \u4FEE\u6539: ${modified}`);
|
|
5611
|
+
}
|
|
5612
|
+
console.log("");
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
function outputJson(results) {
|
|
5616
|
+
console.log(JSON.stringify(results, null, 2));
|
|
5617
|
+
}
|
|
5618
|
+
function outputDiff(results) {
|
|
5619
|
+
logger.newLine();
|
|
5620
|
+
for (const result of results) {
|
|
5621
|
+
const typeName = result.type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF";
|
|
5622
|
+
console.log(chalk3.bold(`${typeName}\u6A21\u677F:`));
|
|
5623
|
+
console.log("");
|
|
5624
|
+
const from = result.local.tag || result.local.branch || result.local.commit?.substring(0, 8) || "unknown";
|
|
5625
|
+
const to = result.remote.tag || result.remote.branch || result.remote.commit?.substring(0, 8) || "unknown";
|
|
5626
|
+
console.log(`\u672C\u5730\u7248\u672C: ${chalk3.cyan(from)}`);
|
|
5627
|
+
console.log(`\u8FDC\u7A0B\u7248\u672C: ${chalk3.cyan(to)}`);
|
|
5628
|
+
if (result.diff && result.diff.commits.length > 0) {
|
|
5629
|
+
console.log("");
|
|
5630
|
+
console.log(chalk3.bold("\u63D0\u4EA4\u5386\u53F2:"));
|
|
5631
|
+
for (const commit of result.diff.commits) {
|
|
5632
|
+
console.log("");
|
|
5633
|
+
console.log(chalk3.yellow(`commit ${commit.id}`));
|
|
5634
|
+
console.log(`Author: ${commit.author_name} <${commit.author_email}>`);
|
|
5635
|
+
console.log(`Date: ${new Date(commit.created_at).toLocaleString("zh-CN")}`);
|
|
5636
|
+
console.log("");
|
|
5637
|
+
console.log(` ${commit.title}`);
|
|
5638
|
+
if (commit.message) {
|
|
5639
|
+
console.log(` ${commit.message.split("\n").join("\n ")}`);
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
console.log("");
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
var diffCommand, Table;
|
|
5647
|
+
var init_diff = __esm({
|
|
5648
|
+
"src/commands/diff.ts"() {
|
|
5649
|
+
"use strict";
|
|
5650
|
+
init_esm_shims();
|
|
5651
|
+
init_template_version();
|
|
5652
|
+
init_logger();
|
|
5653
|
+
init_utils();
|
|
5654
|
+
init_user_config();
|
|
5655
|
+
init_gitlab_api();
|
|
5656
|
+
diffCommand = new Command15("diff").description("\u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02").option("-f, --frontend", "\u5BF9\u6BD4\u524D\u7AEF\u6A21\u677F").option("-b, --backend", "\u5BF9\u6BD4\u540E\u7AEF\u6A21\u677F").option("-t, --tag <tag>", "\u6307\u5B9A\u8FDC\u7A0B\u6807\u7B7E").option("-B, --branch <branch>", "\u6307\u5B9A\u8FDC\u7A0B\u5206\u652F").option("-o, --output <format>", "\u8F93\u51FA\u683C\u5F0F (table|json|diff)", "table").action(async (options) => {
|
|
5657
|
+
try {
|
|
5658
|
+
logger.header("\u6A21\u677F\u7248\u672C\u5BF9\u6BD4");
|
|
5659
|
+
logger.newLine();
|
|
5660
|
+
const hasConfig = await FileUtils.exists("TECH_STACK.md");
|
|
5661
|
+
if (!hasConfig) {
|
|
5662
|
+
logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
|
|
5663
|
+
logger.info("\u8BF7\u5148\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
|
|
5664
|
+
process.exit(1);
|
|
5665
|
+
}
|
|
5666
|
+
const projectPath = ".";
|
|
5667
|
+
const checkAll = !options.frontend && !options.backend;
|
|
5668
|
+
const checkFrontend = options.frontend || checkAll;
|
|
5669
|
+
const checkBackend = options.backend || checkAll;
|
|
5670
|
+
const config = await readTemplateConfig(projectPath);
|
|
5671
|
+
if (!config) {
|
|
5672
|
+
logger.error("\u672A\u627E\u5230\u6A21\u677F\u914D\u7F6E");
|
|
5673
|
+
process.exit(1);
|
|
5674
|
+
}
|
|
5675
|
+
const gitlabConfig = await userConfigManager.getGitLabConfig();
|
|
5676
|
+
if (!gitlabConfig) {
|
|
5677
|
+
logger.warn("\u672A\u914D\u7F6E GitLab Token");
|
|
5678
|
+
logger.info("\u8BF7\u4F7F\u7528 'team-cli config set-token' \u8FDB\u884C\u914D\u7F6E");
|
|
5679
|
+
logger.info("\u5C06\u4F7F\u7528\u57FA\u672C git \u547D\u4EE4\u8FDB\u884C\u5BF9\u6BD4");
|
|
5680
|
+
}
|
|
5681
|
+
const results = [];
|
|
5682
|
+
if (checkFrontend && config.frontend) {
|
|
5683
|
+
const result = await compareTemplate(
|
|
5684
|
+
projectPath,
|
|
5685
|
+
"frontend",
|
|
5686
|
+
config.frontend,
|
|
5687
|
+
options.tag,
|
|
5688
|
+
options.branch,
|
|
5689
|
+
gitlabConfig || void 0
|
|
5690
|
+
);
|
|
5691
|
+
if (result) {
|
|
5692
|
+
results.push(result);
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
if (checkBackend && config.backend) {
|
|
5696
|
+
const result = await compareTemplate(
|
|
5697
|
+
projectPath,
|
|
5698
|
+
"backend",
|
|
5699
|
+
config.backend,
|
|
5700
|
+
options.tag,
|
|
5701
|
+
options.branch,
|
|
5702
|
+
gitlabConfig || void 0
|
|
5703
|
+
);
|
|
5704
|
+
if (result) {
|
|
5705
|
+
results.push(result);
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
if (results.length === 0) {
|
|
5709
|
+
logger.info("\u6CA1\u6709\u53EF\u5BF9\u6BD4\u7684\u6A21\u677F");
|
|
5710
|
+
return;
|
|
5711
|
+
}
|
|
5712
|
+
switch (options.output) {
|
|
5713
|
+
case "json":
|
|
5714
|
+
outputJson(results);
|
|
5715
|
+
break;
|
|
5716
|
+
case "diff":
|
|
5717
|
+
outputDiff(results);
|
|
5718
|
+
break;
|
|
5719
|
+
case "table":
|
|
5720
|
+
default:
|
|
5721
|
+
outputTable(results);
|
|
5722
|
+
break;
|
|
5723
|
+
}
|
|
5724
|
+
} catch (error) {
|
|
5725
|
+
logger.error(`\u5BF9\u6BD4\u5931\u8D25: ${error.message}`);
|
|
5726
|
+
if (process.env.DEBUG) {
|
|
5727
|
+
console.error(error);
|
|
5728
|
+
}
|
|
5729
|
+
process.exit(1);
|
|
5730
|
+
}
|
|
5731
|
+
});
|
|
5732
|
+
Table = class {
|
|
5733
|
+
options;
|
|
5734
|
+
rows = [];
|
|
5735
|
+
constructor(options) {
|
|
5736
|
+
this.options = options;
|
|
5737
|
+
}
|
|
5738
|
+
push(row) {
|
|
5739
|
+
this.rows.push(row);
|
|
5740
|
+
}
|
|
5741
|
+
toString() {
|
|
5742
|
+
if (this.rows.length === 0) return "";
|
|
5743
|
+
let result = "";
|
|
5744
|
+
const colWidths = this.options.colWidths || [];
|
|
5745
|
+
if (this.options.head) {
|
|
5746
|
+
result += "| ";
|
|
5747
|
+
for (let i = 0; i < this.options.head.length; i++) {
|
|
5748
|
+
const width = colWidths[i] || 20;
|
|
5749
|
+
const cell = this.options.head[i] || "";
|
|
5750
|
+
result += cell.padEnd(width) + " | ";
|
|
5751
|
+
}
|
|
5752
|
+
result += "\n";
|
|
5753
|
+
result += "|";
|
|
5754
|
+
for (let i = 0; i < this.options.head.length; i++) {
|
|
5755
|
+
const width = colWidths[i] || 20;
|
|
5756
|
+
result += "-".repeat(width + 2) + "|";
|
|
5757
|
+
}
|
|
5758
|
+
result += "\n";
|
|
5759
|
+
}
|
|
5760
|
+
for (const row of this.rows) {
|
|
5761
|
+
result += "| ";
|
|
5762
|
+
for (let i = 0; i < row.length; i++) {
|
|
5763
|
+
const width = colWidths[i] || 20;
|
|
5764
|
+
const cell = String(row[i] || "");
|
|
5765
|
+
const displayCell = this.options.wordWrap && cell.length > width ? cell.substring(0, width - 3) + "..." : cell;
|
|
5766
|
+
result += displayCell.padEnd(width) + " | ";
|
|
5767
|
+
}
|
|
5768
|
+
result += "\n";
|
|
5769
|
+
}
|
|
5770
|
+
return result;
|
|
5771
|
+
}
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
});
|
|
5775
|
+
|
|
5776
|
+
// src/index.ts
|
|
5777
|
+
var index_exports = {};
|
|
5778
|
+
import { Command as Command16 } from "commander";
|
|
5779
|
+
import chalk4 from "chalk";
|
|
4396
5780
|
function showHelp() {
|
|
4397
5781
|
console.log("");
|
|
4398
5782
|
logger.header("team-cli - AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6");
|
|
4399
5783
|
console.log("");
|
|
4400
|
-
console.log(
|
|
5784
|
+
console.log(chalk4.bold("\u4F7F\u7528\u65B9\u6CD5:"));
|
|
4401
5785
|
console.log(" team-cli init [project-name] \u521D\u59CB\u5316\u65B0\u9879\u76EE");
|
|
4402
5786
|
console.log(" team-cli split-prd <prd-folder> \u5C06 PRD \u62C6\u5206\u6210\u591A\u4E2A specs");
|
|
4403
5787
|
console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
|
|
@@ -4411,21 +5795,24 @@ function showHelp() {
|
|
|
4411
5795
|
console.log(" team-cli status \u67E5\u770B\u9879\u76EE\u72B6\u6001");
|
|
4412
5796
|
console.log(" team-cli lint \u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF+\u540E\u7AEF)");
|
|
4413
5797
|
console.log(" team-cli logs [date] \u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7");
|
|
5798
|
+
console.log(" team-cli update \u68C0\u67E5\u5E76\u66F4\u65B0\u6A21\u677F\u7248\u672C");
|
|
5799
|
+
console.log(" team-cli config \u7BA1\u7406 GitLab \u914D\u7F6E");
|
|
5800
|
+
console.log(" team-cli diff \u5BF9\u6BD4\u672C\u5730\u4E0E\u8FDC\u7A0B\u6A21\u677F\u5DEE\u5F02");
|
|
4414
5801
|
console.log(" team-cli --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
|
|
4415
5802
|
console.log("");
|
|
4416
|
-
console.log(
|
|
5803
|
+
console.log(chalk4.bold("\u793A\u4F8B:"));
|
|
4417
5804
|
console.log(" team-cli init my-project");
|
|
4418
5805
|
console.log(" cd my-project");
|
|
4419
5806
|
console.log(" team-cli add-feature payment-system");
|
|
4420
5807
|
console.log(" team-cli breakdown docs/specs/xxx.md");
|
|
4421
5808
|
console.log(" team-cli dev");
|
|
4422
5809
|
console.log("");
|
|
4423
|
-
console.log(
|
|
5810
|
+
console.log(chalk4.bold("\u5F00\u53D1\u6D41\u7A0B:"));
|
|
4424
5811
|
console.log(" 1. PRD \u2192 specs (split-prd)");
|
|
4425
5812
|
console.log(" 2. spec \u2192 milestones + todos (breakdown)");
|
|
4426
5813
|
console.log(" 3. \u9009\u62E9 milestone/todo \u2192 \u5B9E\u73B0 (dev)");
|
|
4427
5814
|
console.log("");
|
|
4428
|
-
console.log(
|
|
5815
|
+
console.log(chalk4.bold("\u8FED\u4EE3\u6D41\u7A0B:"));
|
|
4429
5816
|
console.log(" team-cli add-feature <name> # \u6DFB\u52A0\u65B0\u529F\u80FD");
|
|
4430
5817
|
console.log(" team-cli detect-deps [spec] # \u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
|
|
4431
5818
|
console.log(" team-cli sync-memory # \u540C\u6B65 AI_MEMORY");
|
|
@@ -4434,7 +5821,14 @@ function showHelp() {
|
|
|
4434
5821
|
console.log(" team-cli hotfix # \u7D27\u6025\u4FEE\u590D");
|
|
4435
5822
|
console.log(" team-cli status # \u67E5\u770B\u9879\u76EE\u72B6\u6001");
|
|
4436
5823
|
console.log("");
|
|
4437
|
-
console.log(
|
|
5824
|
+
console.log(chalk4.bold("\u6A21\u677F\u7BA1\u7406:"));
|
|
5825
|
+
console.log(" team-cli config set-token # \u8BBE\u7F6E GitLab Access Token");
|
|
5826
|
+
console.log(" team-cli config show # \u663E\u793A\u5F53\u524D\u914D\u7F6E");
|
|
5827
|
+
console.log(" team-cli init --tag v1.0.0 # \u4F7F\u7528\u6307\u5B9A tag \u521D\u59CB\u5316");
|
|
5828
|
+
console.log(" team-cli update --tag v1.1.0 # \u66F4\u65B0\u5230\u6307\u5B9A\u7248\u672C");
|
|
5829
|
+
console.log(" team-cli diff # \u5BF9\u6BD4\u6A21\u677F\u5DEE\u5F02");
|
|
5830
|
+
console.log("");
|
|
5831
|
+
console.log(chalk4.gray("\u66F4\u591A\u4FE1\u606F: https://github.com/yungu/team-cli"));
|
|
4438
5832
|
console.log("");
|
|
4439
5833
|
}
|
|
4440
5834
|
var program;
|
|
@@ -4456,7 +5850,10 @@ var init_index = __esm({
|
|
|
4456
5850
|
init_sync_memory();
|
|
4457
5851
|
init_check_api();
|
|
4458
5852
|
init_logs();
|
|
4459
|
-
|
|
5853
|
+
init_update();
|
|
5854
|
+
init_config();
|
|
5855
|
+
init_diff();
|
|
5856
|
+
program = new Command16();
|
|
4460
5857
|
program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version("2.0.0");
|
|
4461
5858
|
program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
|
|
4462
5859
|
program.addCommand(initCommand);
|
|
@@ -4472,6 +5869,9 @@ var init_index = __esm({
|
|
|
4472
5869
|
program.addCommand(syncMemoryCommand);
|
|
4473
5870
|
program.addCommand(checkApiCommand);
|
|
4474
5871
|
program.addCommand(logsCommand);
|
|
5872
|
+
program.addCommand(updateCommand);
|
|
5873
|
+
program.addCommand(configCommand);
|
|
5874
|
+
program.addCommand(diffCommand);
|
|
4475
5875
|
program.action(() => {
|
|
4476
5876
|
showHelp();
|
|
4477
5877
|
});
|