yg-team-cli 2.6.3 → 2.6.4

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 CHANGED
@@ -1003,13 +1003,6 @@ async function saveTemplateConfig(projectPath, config) {
1003
1003
  const content = JSON.stringify(config, null, 2);
1004
1004
  await FileUtils.write(configPath, content);
1005
1005
  }
1006
- async function initTemplateConfig(projectPath) {
1007
- const config = await readTemplateConfig(projectPath);
1008
- if (config) {
1009
- return;
1010
- }
1011
- await saveTemplateConfig(projectPath, DEFAULT_TEMPLATES);
1012
- }
1013
1006
  async function getLatestCommit(repository) {
1014
1007
  try {
1015
1008
  const { stdout } = await execa2("git", ["ls-remote", repository, "HEAD"], {
@@ -1380,455 +1373,222 @@ var init_module_registry = __esm({
1380
1373
  }
1381
1374
  });
1382
1375
 
1383
- // src/lib/user-config.ts
1384
- var user_config_exports = {};
1385
- __export(user_config_exports, {
1386
- UserConfigManager: () => UserConfigManager,
1387
- userConfigManager: () => userConfigManager
1388
- });
1376
+ // src/commands/init.ts
1377
+ import { Command } from "commander";
1378
+ import inquirer from "inquirer";
1389
1379
  import path6 from "path";
1380
+ import fs3 from "fs-extra";
1390
1381
  import os2 from "os";
1391
- import crypto from "crypto";
1392
- var UserConfigManager, userConfigManager;
1393
- var init_user_config = __esm({
1394
- "src/lib/user-config.ts"() {
1395
- "use strict";
1396
- init_esm_shims();
1397
- init_utils();
1398
- init_logger();
1399
- UserConfigManager = class {
1400
- configPath;
1401
- constructor() {
1402
- const configDir = path6.join(os2.homedir(), ".team-cli");
1403
- this.configPath = path6.join(configDir, "config.json");
1404
- }
1405
- /**
1406
- * 加载用户配置
1407
- */
1408
- async load() {
1409
- try {
1410
- const exists = await FileUtils.exists(this.configPath);
1411
- if (!exists) {
1412
- return null;
1413
- }
1414
- const content = await FileUtils.read(this.configPath);
1415
- const config = JSON.parse(content);
1416
- if (config.gitlab?.accessToken) {
1417
- config.gitlab.accessToken = this.decrypt(config.gitlab.accessToken);
1382
+ import { Listr } from "listr2";
1383
+ async function collectProjectConfig(defaultProjectName) {
1384
+ let projectName = defaultProjectName;
1385
+ if (!projectName) {
1386
+ const nameAnswer = await inquirer.prompt([
1387
+ {
1388
+ type: "input",
1389
+ name: "projectName",
1390
+ message: "\u8BF7\u8F93\u5165\u9879\u76EE\u76EE\u5F55\u540D\u79F0:",
1391
+ default: "my-project",
1392
+ validate: (input) => {
1393
+ if (!/^[a-z0-9-]+$/.test(input)) {
1394
+ return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
1418
1395
  }
1419
- return config;
1420
- } catch (error) {
1421
- logger.debug(`\u52A0\u8F7D\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
1422
- return null;
1396
+ return true;
1423
1397
  }
1424
1398
  }
1425
- /**
1426
- * 保存用户配置
1427
- */
1428
- async save(config) {
1429
- try {
1430
- const configDir = path6.dirname(this.configPath);
1431
- await FileUtils.ensureDir(configDir);
1432
- const configToSave = JSON.parse(JSON.stringify(config));
1433
- if (configToSave.gitlab?.accessToken) {
1434
- configToSave.gitlab.accessToken = this.encrypt(configToSave.gitlab.accessToken);
1399
+ ]);
1400
+ projectName = nameAnswer.projectName;
1401
+ }
1402
+ const typeAnswer = await inquirer.prompt([
1403
+ {
1404
+ type: "list",
1405
+ name: "projectType",
1406
+ message: "\u8BF7\u9009\u62E9\u9879\u76EE\u7C7B\u578B:",
1407
+ choices: [
1408
+ { name: "\u5355\u6A21\u5757\u9879\u76EE", value: "single" },
1409
+ { name: "\u591A\u6A21\u5757\u9879\u76EE (Gradle)", value: "multi" }
1410
+ ],
1411
+ default: "single"
1412
+ }
1413
+ ]);
1414
+ let rootProjectName = projectName;
1415
+ if (typeAnswer.projectType === "multi") {
1416
+ const rootAnswer = await inquirer.prompt([
1417
+ {
1418
+ type: "input",
1419
+ name: "rootProjectName",
1420
+ message: "\u8BF7\u8F93\u5165\u6839\u9879\u76EE\u540D (settings.gradle rootProject.name):",
1421
+ default: projectName,
1422
+ validate: (input) => {
1423
+ if (!/^[a-z0-9-]+$/.test(input)) {
1424
+ return "\u6839\u9879\u76EE\u540D\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
1435
1425
  }
1436
- const content = JSON.stringify(configToSave, null, 2);
1437
- await FileUtils.write(this.configPath, content);
1438
- } catch (error) {
1439
- throw new Error(`\u4FDD\u5B58\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
1426
+ return true;
1440
1427
  }
1441
1428
  }
1442
- /**
1443
- * 更新 GitLab Token
1444
- */
1445
- async updateGitLabToken(token, baseUrl) {
1446
- const config = await this.load() || {
1447
- gitlab: {
1448
- accessToken: "",
1449
- baseUrl: "https://gitlab.com",
1450
- timeout: 3e4
1451
- }
1452
- };
1453
- config.gitlab.accessToken = token;
1454
- if (baseUrl) {
1455
- config.gitlab.baseUrl = baseUrl;
1429
+ ]);
1430
+ rootProjectName = rootAnswer.rootProjectName;
1431
+ }
1432
+ const groupAnswer = await inquirer.prompt([
1433
+ {
1434
+ type: "input",
1435
+ name: "groupId",
1436
+ message: "\u8BF7\u8F93\u5165 Group ID (\u5305\u8DEF\u5F84\u524D\u7F00):",
1437
+ default: "org.yungu",
1438
+ validate: (input) => {
1439
+ if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/.test(input)) {
1440
+ return "Group ID \u683C\u5F0F\u4E0D\u6B63\u786E (\u5982: com.example \u6216 org.yungu)";
1456
1441
  }
1457
- await this.save(config);
1458
- }
1459
- /**
1460
- * 获取 GitLab Token
1461
- */
1462
- async getGitLabToken() {
1463
- const config = await this.load();
1464
- return config?.gitlab?.accessToken || null;
1465
- }
1466
- /**
1467
- * 获取 GitLab 配置
1468
- */
1469
- async getGitLabConfig() {
1470
- const config = await this.load();
1471
- return config?.gitlab || null;
1442
+ return true;
1472
1443
  }
1473
- /**
1474
- * 检查是否已有配置
1475
- */
1476
- async hasConfig() {
1477
- const config = await this.load();
1478
- return config !== null && config.gitlab?.accessToken !== void 0;
1444
+ }
1445
+ ]);
1446
+ const groupId = groupAnswer.groupId;
1447
+ const modules = [];
1448
+ let addMoreModules = true;
1449
+ let moduleIndex = 1;
1450
+ while (addMoreModules) {
1451
+ let defaultModuleName = `module-${moduleIndex}`;
1452
+ if (moduleIndex === 1) {
1453
+ if (typeAnswer.projectType === "single") {
1454
+ defaultModuleName = "app";
1455
+ } else {
1456
+ defaultModuleName = projectName;
1479
1457
  }
1480
- /**
1481
- * 删除配置
1482
- */
1483
- async removeConfig() {
1484
- try {
1485
- const exists = await FileUtils.exists(this.configPath);
1486
- if (exists) {
1487
- await FileUtils.remove(this.configPath);
1458
+ }
1459
+ const moduleNameAnswer = await inquirer.prompt([
1460
+ {
1461
+ type: "input",
1462
+ name: "moduleName",
1463
+ message: `\u8BF7\u8F93\u5165\u7B2C ${moduleIndex} \u4E2A\u5B50\u6A21\u5757\u540D\u79F0 (\u76EE\u5F55\u540D):`,
1464
+ default: defaultModuleName,
1465
+ validate: (input) => {
1466
+ if (!/^[a-z][a-z0-9-]*$/.test(input)) {
1467
+ return "\u6A21\u5757\u540D\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
1488
1468
  }
1489
- } catch (error) {
1490
- throw new Error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error}`);
1469
+ return true;
1491
1470
  }
1492
1471
  }
1493
- /**
1494
- * 简单加密 (使用机器特定密钥)
1495
- */
1496
- encrypt(text) {
1497
- const key = this.getMachineKey();
1498
- const iv = crypto.randomBytes(16);
1499
- const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
1500
- let encrypted = cipher.update(text, "utf8", "hex");
1501
- encrypted += cipher.final("hex");
1502
- return iv.toString("hex") + ":" + encrypted;
1503
- }
1504
- /**
1505
- * 简单解密
1506
- */
1507
- decrypt(encryptedText) {
1508
- try {
1509
- const key = this.getMachineKey();
1510
- const parts = encryptedText.split(":");
1511
- const iv = Buffer.from(parts[0], "hex");
1512
- const encrypted = parts[1];
1513
- const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
1514
- let decrypted = decipher.update(encrypted, "hex", "utf8");
1515
- decrypted += decipher.final("utf8");
1516
- return decrypted;
1517
- } catch {
1518
- return encryptedText;
1472
+ ]);
1473
+ const moduleName = moduleNameAnswer.moduleName;
1474
+ let moduleType = "service";
1475
+ if (typeAnswer.projectType === "multi") {
1476
+ const typeSelectAnswer = await inquirer.prompt([
1477
+ {
1478
+ type: "list",
1479
+ name: "moduleType",
1480
+ message: `\u6A21\u5757 "${moduleName}" \u7C7B\u578B:`,
1481
+ choices: [
1482
+ { name: "API \u670D\u52A1\u6A21\u5757 (\u72EC\u7ACB\u90E8\u7F72)", value: "service" },
1483
+ { name: "\u516C\u5171\u6A21\u5757 (\u88AB\u5176\u4ED6\u6A21\u5757\u4F9D\u8D56)", value: "common" },
1484
+ { name: "API \u63A5\u53E3\u6A21\u5757 (\u4EC5\u63A5\u53E3\u5B9A\u4E49)", value: "api" }
1485
+ ],
1486
+ default: "service"
1519
1487
  }
1520
- }
1521
- /**
1522
- * 获取机器特定密钥
1523
- */
1524
- getMachineKey() {
1525
- const hostname = os2.hostname();
1526
- const platform = os2.platform();
1527
- const arch = os2.arch();
1528
- const cpus = os2.cpus();
1529
- const machineInfo = `${hostname}-${platform}-${arch}-${cpus[0]?.model || "unknown"}`;
1530
- return crypto.createHash("sha256").update(machineInfo).digest();
1531
- }
1532
- /**
1533
- * 获取配置目录
1534
- */
1535
- getConfigDir() {
1536
- return path6.dirname(this.configPath);
1537
- }
1538
- /**
1539
- * 获取配置文件路径
1540
- */
1541
- getConfigPath() {
1542
- return this.configPath;
1543
- }
1544
- };
1545
- userConfigManager = new UserConfigManager();
1488
+ ]);
1489
+ moduleType = typeSelectAnswer.moduleType;
1490
+ }
1491
+ const modulePackage = `${groupId}.${moduleName}`;
1492
+ const applicationClass = `${toPascalCase(moduleName)}Application`;
1493
+ modules.push({
1494
+ name: moduleName,
1495
+ type: moduleType,
1496
+ packagePath: modulePackage,
1497
+ applicationClass
1498
+ });
1499
+ if (typeAnswer.projectType === "multi") {
1500
+ const moreAnswer = await inquirer.prompt([
1501
+ {
1502
+ type: "confirm",
1503
+ name: "addMore",
1504
+ message: "\u662F\u5426\u8FD8\u9700\u8981\u6DFB\u52A0\u66F4\u591A\u6A21\u5757?",
1505
+ default: false
1506
+ }
1507
+ ]);
1508
+ addMoreModules = moreAnswer.addMore;
1509
+ } else {
1510
+ addMoreModules = false;
1511
+ }
1512
+ moduleIndex++;
1546
1513
  }
1547
- });
1514
+ const javaAnswer = await inquirer.prompt([
1515
+ {
1516
+ type: "list",
1517
+ name: "javaVersion",
1518
+ message: "\u8BF7\u9009\u62E9 Java \u7248\u672C:",
1519
+ choices: [
1520
+ { name: "Java 8 (1.8)", value: "1.8" },
1521
+ { name: "Java 11 (LTS)", value: "11" },
1522
+ { name: "Java 17 (LTS)", value: "17" },
1523
+ { name: "Java 21 (LTS)", value: "21" }
1524
+ ],
1525
+ default: "17"
1526
+ }
1527
+ ]);
1528
+ const buildAnswer = await inquirer.prompt([
1529
+ {
1530
+ type: "list",
1531
+ name: "buildTool",
1532
+ message: "\u8BF7\u9009\u62E9\u6784\u5EFA\u5DE5\u5177:",
1533
+ choices: [
1534
+ { name: "Gradle", value: "gradle" },
1535
+ { name: "Maven", value: "maven" }
1536
+ ],
1537
+ default: "gradle"
1538
+ }
1539
+ ]);
1540
+ const frontendAnswer = await inquirer.prompt([
1541
+ {
1542
+ type: "confirm",
1543
+ name: "includeFrontend",
1544
+ message: "\u662F\u5426\u9700\u8981\u5305\u542B\u524D\u7AEF\u9879\u76EE?",
1545
+ default: true
1546
+ }
1547
+ ]);
1548
+ return {
1549
+ projectName,
1550
+ projectType: typeAnswer.projectType,
1551
+ rootProjectName,
1552
+ groupId,
1553
+ modules,
1554
+ javaVersion: javaAnswer.javaVersion,
1555
+ buildTool: buildAnswer.buildTool,
1556
+ includeFrontend: frontendAnswer.includeFrontend
1557
+ };
1558
+ }
1559
+ function toPascalCase(str) {
1560
+ return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1561
+ }
1562
+ async function generateTechStack(projectPath, config) {
1563
+ const backendStructure = config.modules.map((m) => {
1564
+ const packagePath = `${config.groupId}.${m.name}`;
1565
+ return `\u251C\u2500\u2500 ${m.name}/
1566
+ \u2502 \u2514\u2500\u2500 src/main/java/${packagePath.replace(/\./g, "/")}/
1567
+ \u2502 \u251C\u2500\u2500 controller/ # API \u63A7\u5236\u5668
1568
+ \u2502 \u251C\u2500\u2500 service/ # \u4E1A\u52A1\u903B\u8F91
1569
+ \u2502 \u251C\u2500\u2500 mapper/ # \u6570\u636E\u8BBF\u95EE
1570
+ \u2502 \u251C\u2500\u2500 entity/ # \u6570\u636E\u6A21\u578B
1571
+ \u2502 \u251C\u2500\u2500 dto/ # \u6570\u636E\u4F20\u8F93\u5BF9\u8C61
1572
+ \u2502 \u2514\u2500\u2500 config/ # \u914D\u7F6E\u7C7B`;
1573
+ }).join("\n");
1574
+ const content = `# \u6280\u672F\u6808
1548
1575
 
1549
- // src/lib/gitlab-api.ts
1550
- var gitlab_api_exports = {};
1551
- __export(gitlab_api_exports, {
1552
- GitLabAPI: () => GitLabAPI
1553
- });
1554
- var GitLabAPI;
1555
- var init_gitlab_api = __esm({
1556
- "src/lib/gitlab-api.ts"() {
1557
- "use strict";
1558
- init_esm_shims();
1559
- GitLabAPI = class {
1560
- config;
1561
- defaultTimeout = 3e4;
1562
- constructor(config) {
1563
- this.config = {
1564
- ...config,
1565
- timeout: config.timeout || this.defaultTimeout
1566
- };
1567
- }
1568
- /**
1569
- * 验证 Token 是否有效
1570
- */
1571
- async authenticate() {
1572
- try {
1573
- const response = await this.request("/user");
1574
- return response.ok;
1575
- } catch (error) {
1576
- return false;
1577
- }
1578
- }
1579
- /**
1580
- * 列出项目的所有 Tags
1581
- */
1582
- async listTags(projectPath) {
1583
- try {
1584
- const encodedPath = this.encodeProjectPath(projectPath);
1585
- const response = await this.request(`/projects/${encodedPath}/repository/tags?per_page=100`);
1586
- if (!response.ok) {
1587
- throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
1588
- }
1589
- const tags = await response.json();
1590
- return tags.sort((a, b) => {
1591
- return this.compareVersionStrings(b.name, a.name);
1592
- });
1593
- } catch (error) {
1594
- throw new Error(`\u83B7\u53D6 Tags \u5931\u8D25: ${error}`);
1595
- }
1596
- }
1597
- /**
1598
- * 列出项目的所有 Branches
1599
- */
1600
- async listBranches(projectPath) {
1601
- try {
1602
- const encodedPath = this.encodeProjectPath(projectPath);
1603
- const response = await this.request(`/projects/${encodedPath}/repository/branches?per_page=100`);
1604
- if (!response.ok) {
1605
- throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
1606
- }
1607
- const branches = await response.json();
1608
- return branches.sort((a, b) => {
1609
- if (a.default) return -1;
1610
- if (b.default) return 1;
1611
- return a.name.localeCompare(b.name);
1612
- });
1613
- } catch (error) {
1614
- throw new Error(`\u83B7\u53D6 Branches \u5931\u8D25: ${error}`);
1615
- }
1616
- }
1617
- /**
1618
- * 验证 Tag 是否存在
1619
- */
1620
- async validateTag(projectPath, tag) {
1621
- try {
1622
- const tags = await this.listTags(projectPath);
1623
- return tags.some((t) => t.name === tag);
1624
- } catch {
1625
- return false;
1626
- }
1627
- }
1628
- /**
1629
- * 验证 Branch 是否存在
1630
- */
1631
- async validateBranch(projectPath, branch) {
1632
- try {
1633
- const branches = await this.listBranches(projectPath);
1634
- return branches.some((b) => b.name === branch);
1635
- } catch {
1636
- return false;
1637
- }
1638
- }
1639
- /**
1640
- * 获取项目信息
1641
- */
1642
- async getProject(projectPath) {
1643
- try {
1644
- const encodedPath = this.encodeProjectPath(projectPath);
1645
- const response = await this.request(`/projects/${encodedPath}`);
1646
- if (!response.ok) {
1647
- return null;
1648
- }
1649
- return await response.json();
1650
- } catch {
1651
- return null;
1652
- }
1653
- }
1654
- /**
1655
- * 获取指定 Tag 的 commit 信息
1656
- */
1657
- async getTagCommit(projectPath, tag) {
1658
- try {
1659
- const tags = await this.listTags(projectPath);
1660
- const targetTag = tags.find((t) => t.name === tag);
1661
- return targetTag?.commit.id || null;
1662
- } catch {
1663
- return null;
1664
- }
1665
- }
1666
- /**
1667
- * 获取指定 Branch 的最新 commit 信息
1668
- */
1669
- async getBranchCommit(projectPath, branch) {
1670
- try {
1671
- const encodedPath = this.encodeProjectPath(projectPath);
1672
- const response = await this.request(
1673
- `/projects/${encodedPath}/repository/branches/${encodeURIComponent(branch)}`
1674
- );
1675
- if (!response.ok) {
1676
- return null;
1677
- }
1678
- const branchInfo = await response.json();
1679
- return branchInfo.commit.id;
1680
- } catch {
1681
- return null;
1682
- }
1683
- }
1684
- /**
1685
- * 对比两个版本之间的差异
1686
- */
1687
- async compareVersions(projectPath, from, to) {
1688
- try {
1689
- const encodedPath = this.encodeProjectPath(projectPath);
1690
- const response = await this.request(
1691
- `/projects/${encodedPath}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
1692
- );
1693
- if (!response.ok) {
1694
- return null;
1695
- }
1696
- return await response.json();
1697
- } catch {
1698
- return null;
1699
- }
1700
- }
1701
- /**
1702
- * 比较两个版本号(用于排序)
1703
- * 返回值: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
1704
- */
1705
- compareVersionStrings(v1, v2) {
1706
- const version1 = v1.replace(/^v/, "");
1707
- const version2 = v2.replace(/^v/, "");
1708
- const parts1 = version1.split(".").map((p) => parseInt(p, 10) || 0);
1709
- const parts2 = version2.split(".").map((p) => parseInt(p, 10) || 0);
1710
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1711
- const p1 = parts1[i] || 0;
1712
- const p2 = parts2[i] || 0;
1713
- if (p1 > p2) return 1;
1714
- if (p1 < p2) return -1;
1715
- }
1716
- return 0;
1717
- }
1718
- /**
1719
- * 获取最新的版本(优先 Tag,否则 latest commit)
1720
- */
1721
- async getLatestVersion(projectPath) {
1722
- try {
1723
- const tags = await this.listTags(projectPath);
1724
- const branches = await this.listBranches(projectPath);
1725
- if (tags.length > 0) {
1726
- const latestTag = tags[0];
1727
- return {
1728
- type: "tag",
1729
- name: latestTag.name,
1730
- commit: latestTag.commit.id
1731
- };
1732
- }
1733
- const defaultBranch = branches.find((b) => b.default);
1734
- if (defaultBranch) {
1735
- return {
1736
- type: "commit",
1737
- name: defaultBranch.name,
1738
- commit: defaultBranch.commit.id
1739
- };
1740
- }
1741
- return null;
1742
- } catch {
1743
- return null;
1744
- }
1745
- }
1746
- /**
1747
- * 解析项目路径
1748
- * 从 Git URL 中提取项目路径
1749
- */
1750
- static parseProjectPath(repository) {
1751
- let path21 = repository;
1752
- if (path21.startsWith("git@")) {
1753
- path21 = path21.replace(/^git@/, "");
1754
- const colonIndex = path21.indexOf(":");
1755
- if (colonIndex !== -1) {
1756
- path21 = path21.substring(colonIndex + 1);
1757
- }
1758
- } else {
1759
- path21 = path21.replace(/^https?:\/\//, "");
1760
- const parts = path21.split("/");
1761
- if (parts.length > 1) {
1762
- path21 = parts.slice(1).join("/");
1763
- }
1764
- }
1765
- path21 = path21.replace(/\.git$/, "");
1766
- return path21;
1767
- }
1768
- /**
1769
- * 编码项目路径用于 API 请求
1770
- */
1771
- encodeProjectPath(projectPath) {
1772
- return encodeURIComponent(projectPath).replace(/%2F/g, "%2F");
1773
- }
1774
- /**
1775
- * 发送 HTTP 请求
1776
- */
1777
- async request(endpoint, options = {}) {
1778
- const url = `${this.config.baseUrl}/api/v4${endpoint}`;
1779
- const headers = {
1780
- "PRIVATE-TOKEN": this.config.accessToken,
1781
- "Content-Type": "application/json",
1782
- ...options.headers
1783
- };
1784
- const controller = new AbortController();
1785
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
1786
- try {
1787
- const response = await fetch(url, {
1788
- ...options,
1789
- headers,
1790
- signal: controller.signal
1791
- });
1792
- return response;
1793
- } catch (error) {
1794
- if (error.name === "AbortError") {
1795
- throw new Error("\u8BF7\u6C42\u8D85\u65F6");
1796
- }
1797
- throw error;
1798
- } finally {
1799
- clearTimeout(timeoutId);
1800
- }
1801
- }
1802
- /**
1803
- * 获取默认分支
1804
- */
1805
- async getDefaultBranch(projectPath) {
1806
- try {
1807
- const project = await this.getProject(projectPath);
1808
- return project?.default_branch || null;
1809
- } catch {
1810
- return null;
1811
- }
1812
- }
1813
- };
1814
- }
1815
- });
1576
+ ## \u9879\u76EE\u914D\u7F6E
1816
1577
 
1817
- // src/commands/init.ts
1818
- import { Command } from "commander";
1819
- import inquirer from "inquirer";
1820
- import path7 from "path";
1821
- import fs3 from "fs-extra";
1822
- import os3 from "os";
1823
- import { Listr } from "listr2";
1824
- async function generateTechStack(projectPath) {
1825
- const content = `# \u6280\u672F\u6808
1578
+ | \u914D\u7F6E\u9879 | \u503C |
1579
+ |-------|------|
1580
+ | \u9879\u76EE\u540D\u79F0 | ${config.projectName} |
1581
+ | Group ID | ${config.groupId} |
1582
+ | \u6839\u9879\u76EE\u540D | ${config.rootProjectName} |
1583
+ | \u6A21\u5757 | ${config.modules.map((m) => m.name).join(", ")} |
1584
+ | Java \u7248\u672C | ${config.javaVersion} |
1585
+ | \u6784\u5EFA\u5DE5\u5177 | ${config.buildTool === "gradle" ? "Gradle" : "Maven"} |
1826
1586
 
1827
1587
  ## \u540E\u7AEF\u6280\u672F\u6808
1828
1588
 
1829
1589
  | \u7EC4\u4EF6 | \u6280\u672F\u9009\u578B | \u7248\u672C | \u8BF4\u660E |
1830
1590
  |------|---------|------|------|
1831
- | \u8BED\u8A00 | Java | 17 | LTS \u7248\u672C |
1591
+ | \u8BED\u8A00 | Java | ${config.javaVersion} | LTS \u7248\u672C |
1832
1592
  | \u6846\u67B6 | Spring Boot | 3.2 | \u73B0\u4EE3\u5316 Java \u6846\u67B6 |
1833
1593
  | \u6784\u5EFA\u5DE5\u5177 | Gradle | 8.x | \u5FEB\u901F\u3001\u7075\u6D3B\u7684\u6784\u5EFA\u5DE5\u5177 |
1834
1594
  | ORM | MyBatis Plus | 3.5 | \u589E\u5F3A MyBatis\uFF0C\u7B80\u5316 CRUD |
@@ -1836,7 +1596,7 @@ async function generateTechStack(projectPath) {
1836
1596
  | \u7F13\u5B58 | Redis | 7.x | \u7F13\u5B58\u548C\u4F1A\u8BDD\u5B58\u50A8 |
1837
1597
  | \u6587\u6863 | SpringDoc OpenAPI | 2.3 | API \u6587\u6863\u81EA\u52A8\u751F\u6210 |
1838
1598
 
1839
- ## \u524D\u7AEF\u6280\u672F\u6808
1599
+ ${config.includeFrontend ? `## \u524D\u7AEF\u6280\u672F\u6808
1840
1600
 
1841
1601
  | \u7EC4\u4EF6 | \u6280\u672F\u9009\u578B | \u7248\u672C | \u8BF4\u660E |
1842
1602
  |------|---------|------|------|
@@ -1847,7 +1607,7 @@ async function generateTechStack(projectPath) {
1847
1607
  | \u56FE\u6807 | Lucide React | latest | \u4E00\u81F4\u6027\u56FE\u6807\u5E93 |
1848
1608
  | \u72B6\u6001\u7BA1\u7406 | React Context + Hooks | - | \u8F7B\u91CF\u7EA7\u72B6\u6001\u7BA1\u7406 |
1849
1609
  | \u8868\u5355 | React Hook Form | 7.x | \u9AD8\u6027\u80FD\u8868\u5355 |
1850
- | \u6570\u636E\u9A8C\u8BC1 | Zod | 3.x | TypeScript \u4F18\u5148\u7684\u9A8C\u8BC1\u5E93 |
1610
+ | \u6570\u636E\u9A8C\u8BC1 | Zod | 3.x | TypeScript \u4F18\u5148\u7684\u9A8C\u8BC1\u5E93 |` : ""}
1851
1611
 
1852
1612
  ## \u5F00\u53D1\u5DE5\u5177
1853
1613
 
@@ -1870,6 +1630,7 @@ async function generateTechStack(projectPath) {
1870
1630
  - API \u8DEF\u5F84\u4F7F\u7528 kebab-case
1871
1631
  - \u7C7B\u540D\u4F7F\u7528 PascalCase
1872
1632
  - \u65B9\u6CD5\u540D\u4F7F\u7528 camelCase
1633
+ - \u5305\u8DEF\u5F84: \`${config.groupId}.<module-name>\`
1873
1634
 
1874
1635
  ### \u524D\u7AEF\u89C4\u8303
1875
1636
 
@@ -1890,26 +1651,19 @@ async function generateTechStack(projectPath) {
1890
1651
 
1891
1652
  \`\`\`
1892
1653
  backend/
1893
- \u251C\u2500\u2500 src/main/java/com/example/demo/
1894
- \u2502 \u251C\u2500\u2500 controller/ # API \u63A7\u5236\u5668
1895
- \u2502 \u251C\u2500\u2500 service/ # \u4E1A\u52A1\u903B\u8F91
1896
- \u2502 \u251C\u2500\u2500 mapper/ # \u6570\u636E\u8BBF\u95EE
1897
- \u2502 \u251C\u2500\u2500 entity/ # \u6570\u636E\u6A21\u578B
1898
- \u2502 \u251C\u2500\u2500 dto/ # \u6570\u636E\u4F20\u8F93\u5BF9\u8C61
1899
- \u2502 \u251C\u2500\u2500 config/ # \u914D\u7F6E\u7C7B
1900
- \u2502 \u2514\u2500\u2500 util/ # \u5DE5\u5177\u7C7B
1654
+ ${backendStructure}
1901
1655
  \u251C\u2500\u2500 src/main/resources/
1902
1656
  \u2502 \u251C\u2500\u2500 mapper/ # MyBatis XML
1903
1657
  \u2502 \u2514\u2500\u2500 application.yml # \u914D\u7F6E\u6587\u4EF6
1904
1658
  \u2514\u2500\u2500 src/test/ # \u6D4B\u8BD5\u4EE3\u7801
1905
1659
 
1906
- frontend/
1660
+ ${config.includeFrontend ? `frontend/
1907
1661
  \u251C\u2500\u2500 src/
1908
1662
  \u2502 \u251C\u2500\u2500 app/ # Next.js App Router
1909
1663
  \u2502 \u251C\u2500\u2500 components/ # React \u7EC4\u4EF6
1910
1664
  \u2502 \u251C\u2500\u2500 lib/ # \u5DE5\u5177\u5E93
1911
1665
  \u2502 \u2514\u2500\u2500 types/ # TypeScript \u7C7B\u578B
1912
- \u2514\u2500\u2500 public/ # \u9759\u6001\u8D44\u6E90
1666
+ \u2514\u2500\u2500 public/ # \u9759\u6001\u8D44\u6E90` : ""}
1913
1667
 
1914
1668
  docs/
1915
1669
  \u251C\u2500\u2500 prd-docs/ # PRD \u9700\u6C42\u6587\u6863
@@ -1918,20 +1672,28 @@ docs/
1918
1672
  \u2514\u2500\u2500 sessions/ # \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
1919
1673
  \`\`\`
1920
1674
  `;
1921
- await FileUtils.write(path7.join(projectPath, "TECH_STACK.md"), content);
1675
+ await FileUtils.write(path6.join(projectPath, "TECH_STACK.md"), content);
1922
1676
  }
1923
- async function generateConventions(projectPath) {
1677
+ async function generateConventions(projectPath, config) {
1924
1678
  const content = `# \u5F00\u53D1\u89C4\u8303
1925
1679
 
1680
+ ## \u5305\u8DEF\u5F84\u89C4\u8303
1681
+
1682
+ \u6240\u6709\u540E\u7AEF\u4EE3\u7801\u7684\u5305\u8DEF\u5F84\u9075\u5FAA: \`${config.groupId}.<module-name>\`
1683
+
1684
+ \u793A\u4F8B:
1685
+ ${config.modules.map((m) => `- \`${m.packagePath}\``).join("\n")}
1686
+
1687
+
1926
1688
  ## \u540E\u7AEF\u5F00\u53D1\u89C4\u8303
1927
1689
 
1928
1690
  ### 1. \u5206\u5C42\u67B6\u6784
1929
1691
 
1930
- \`\`\`
1692
+ \`
1931
1693
  Controller \u2192 Service \u2192 Mapper \u2192 Database
1932
1694
  \u2193 \u2193
1933
1695
  DTO Entity
1934
- \`\`\`
1696
+ \`
1935
1697
 
1936
1698
  **\u804C\u8D23\u5212\u5206:**
1937
1699
  - **Controller**: \u63A5\u6536 HTTP \u8BF7\u6C42\uFF0C\u53C2\u6570\u6821\u9A8C\uFF0C\u8C03\u7528 Service
@@ -1954,13 +1716,13 @@ Controller \u2192 Service \u2192 Mapper \u2192 Database
1954
1716
 
1955
1717
  **RESTful \u98CE\u683C:**
1956
1718
 
1957
- \`\`\`
1719
+ \`
1958
1720
  GET /api/users # \u5217\u8868
1959
1721
  GET /api/users/{id} # \u8BE6\u60C5
1960
1722
  POST /api/users # \u521B\u5EFA
1961
1723
  PUT /api/users/{id} # \u66F4\u65B0
1962
1724
  DELETE /api/users/{id} # \u5220\u9664
1963
- \`\`\`
1725
+ \`
1964
1726
 
1965
1727
  **\u7EDF\u4E00\u54CD\u5E94\u683C\u5F0F:**
1966
1728
 
@@ -2221,7 +1983,7 @@ test('renders user name', () => {
2221
1983
  });
2222
1984
  \`\`\`
2223
1985
  `;
2224
- await FileUtils.write(path7.join(projectPath, "CONVENTIONS.md"), content);
1986
+ await FileUtils.write(path6.join(projectPath, "CONVENTIONS.md"), content);
2225
1987
  }
2226
1988
  async function generateAIMemory(projectPath, projectName) {
2227
1989
  const content = `# AI Memory - \u9879\u76EE\u72B6\u6001\u8BB0\u5F55
@@ -2280,7 +2042,7 @@ async function generateAIMemory(projectPath, projectName) {
2280
2042
  | Bug ID | \u65E5\u671F | \u95EE\u9898\u63CF\u8FF0 | \u72B6\u6001 |
2281
2043
  |--------|------|---------|------|
2282
2044
  `;
2283
- await FileUtils.write(path7.join(projectPath, "AI_MEMORY.md"), content);
2045
+ await FileUtils.write(path6.join(projectPath, "AI_MEMORY.md"), content);
2284
2046
  }
2285
2047
  async function generateSpecTemplate(projectPath) {
2286
2048
  const content = `# [\u529F\u80FD\u6807\u9898]
@@ -2344,82 +2106,10 @@ async function generateSpecTemplate(projectPath) {
2344
2106
  ----
2345
2107
  *\u751F\u6210\u4E8E: {{TIMESTAMP}} by team-cli*
2346
2108
  `;
2347
- await FileUtils.write(path7.join(projectPath, "docs/specs/template.md"), content);
2348
- }
2349
- async function cloneBackendTemplate(projectPath, versionOptions) {
2350
- const templateRepo = process.env.TEMPLATE_REPO || "git@gitlab.yungu-inc.org:yungu-app/java-scaffold-template.git";
2351
- const backendPath = path7.join(projectPath, "backend");
2352
- try {
2353
- const { execa: e } = await import("execa");
2354
- const tempDir = path7.join(projectPath, ".template-temp");
2355
- if (versionOptions?.tag || versionOptions?.branch) {
2356
- const { userConfigManager: userConfigManager2 } = await Promise.resolve().then(() => (init_user_config(), user_config_exports));
2357
- const { GitLabAPI: GitLabAPI2 } = await Promise.resolve().then(() => (init_gitlab_api(), gitlab_api_exports));
2358
- const config = await userConfigManager2.getGitLabConfig();
2359
- if (config) {
2360
- const gitlabAPI = new GitLabAPI2(config);
2361
- const projectPathEncoded = GitLabAPI2.parseProjectPath(templateRepo);
2362
- if (versionOptions.tag) {
2363
- const isValid = await gitlabAPI.validateTag(projectPathEncoded, versionOptions.tag);
2364
- if (!isValid) {
2365
- logger.error(`\u540E\u7AEF\u6A21\u677F tag "${versionOptions.tag}" \u4E0D\u5B58\u5728`);
2366
- process.exit(1);
2367
- }
2368
- logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F tag: ${versionOptions.tag}`);
2369
- }
2370
- if (versionOptions.branch) {
2371
- const isValid = await gitlabAPI.validateBranch(projectPathEncoded, versionOptions.branch);
2372
- if (!isValid) {
2373
- logger.error(`\u540E\u7AEF\u6A21\u677F\u5206\u652F "${versionOptions.branch}" \u4E0D\u5B58\u5728`);
2374
- process.exit(1);
2375
- }
2376
- logger.info(`\u4F7F\u7528\u540E\u7AEF\u6A21\u677F\u5206\u652F: ${versionOptions.branch}`);
2377
- }
2378
- } else {
2379
- logger.warn("\u672A\u914D\u7F6E GitLab Token\uFF0C\u8DF3\u8FC7\u7248\u672C\u9A8C\u8BC1");
2380
- }
2381
- }
2382
- logger.info(`\u6B63\u5728\u514B\u9686\u540E\u7AEF\u6A21\u677F (${versionOptions?.tag || versionOptions?.branch || "latest"})...`);
2383
- const cloneArgs = ["clone", "--depth=1"];
2384
- if (versionOptions?.tag || versionOptions?.branch) {
2385
- cloneArgs.push("--branch", versionOptions.tag || versionOptions.branch);
2386
- }
2387
- cloneArgs.push(templateRepo, tempDir);
2388
- await e("git", cloneArgs, {
2389
- stdio: "inherit",
2390
- timeout: 6e4
2391
- });
2392
- const { stdout: commit } = await e("git", ["rev-parse", "HEAD"], {
2393
- cwd: tempDir,
2394
- stdio: "pipe"
2395
- });
2396
- const { stdout: tags } = await e("git", ["tag", "-l", "--sort=-v:refname"], {
2397
- cwd: tempDir,
2398
- stdio: "pipe"
2399
- });
2400
- const latestTag = tags.split("\n")[0] || void 0;
2401
- await fs3.copy(tempDir, backendPath, {
2402
- filter: (src) => !src.includes(".git")
2403
- });
2404
- await fs3.remove(tempDir);
2405
- const gitDir = path7.join(backendPath, ".git");
2406
- if (await FileUtils.exists(gitDir)) {
2407
- await FileUtils.remove(gitDir);
2408
- }
2409
- await initTemplateConfig(projectPath);
2410
- await updateTemplateVersion(projectPath, "backend", commit.trim(), {
2411
- tag: versionOptions?.tag || latestTag,
2412
- branch: versionOptions?.branch
2413
- });
2414
- } catch (error) {
2415
- logger.warn("\u514B\u9686\u540E\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
2416
- await FileUtils.ensureDir(path7.join(backendPath, "src/main/java/com/example"));
2417
- await FileUtils.ensureDir(path7.join(backendPath, "src/main/resources"));
2418
- await FileUtils.ensureDir(path7.join(backendPath, "src/test/java"));
2419
- }
2109
+ await FileUtils.write(path6.join(projectPath, "docs/specs/template.md"), content);
2420
2110
  }
2421
2111
  async function cloneFrontendTemplate(projectPath, versionOptions = {}) {
2422
- const frontendPath = path7.join(projectPath, "frontend");
2112
+ const frontendPath = path6.join(projectPath, "frontend");
2423
2113
  const templates = getDefaultTemplates();
2424
2114
  const repository = templates.frontend.repository;
2425
2115
  try {
@@ -2430,7 +2120,7 @@ async function cloneFrontendTemplate(projectPath, versionOptions = {}) {
2430
2120
  }
2431
2121
  const targetVersion = versionOptions.branch || versionOptions.tag || latestTag;
2432
2122
  logger.info(`\u6B63\u5728\u514B\u9686\u524D\u7AEF\u6A21\u677F (${targetVersion || "HEAD"})...`);
2433
- const tempDir = path7.join(os3.tmpdir(), `team-cli-fe-${Date.now()}`);
2123
+ const tempDir = path6.join(os2.tmpdir(), `team-cli-fe-${Date.now()}`);
2434
2124
  await FileUtils.ensureDir(tempDir);
2435
2125
  const cloneArgs = ["clone", "--depth", "1"];
2436
2126
  if (targetVersion) {
@@ -2450,21 +2140,210 @@ async function cloneFrontendTemplate(projectPath, versionOptions = {}) {
2450
2140
  logger.success("\u524D\u7AEF\u6A21\u677F\u514B\u9686\u5B8C\u6210");
2451
2141
  } catch (error) {
2452
2142
  logger.warn("\u514B\u9686\u524D\u7AEF\u6A21\u677F\u5931\u8D25\uFF0C\u5C06\u521B\u5EFA\u57FA\u7840\u7ED3\u6784");
2453
- await FileUtils.ensureDir(path7.join(frontendPath, "src/app"));
2454
- await FileUtils.ensureDir(path7.join(frontendPath, "src/components"));
2143
+ await FileUtils.ensureDir(path6.join(frontendPath, "src/app"));
2144
+ await FileUtils.ensureDir(path6.join(frontendPath, "src/components"));
2455
2145
  }
2456
2146
  }
2457
- async function generateDockerFiles(projectPath, projectName) {
2458
- const backendPath = path7.join(projectPath, "backend");
2459
- if (!await FileUtils.exists(backendPath)) {
2460
- logger.warn("\u672A\u627E\u5230 backend \u76EE\u5F55\uFF0C\u8DF3\u8FC7 Docker \u914D\u7F6E\u751F\u6210");
2461
- return;
2147
+ async function createBackendFromConfig(projectPath, config) {
2148
+ const backendPath = path6.join(projectPath, "backend");
2149
+ logger.info(`\u6B63\u5728\u521B\u5EFA\u540E\u7AEF\u591A\u6A21\u5757\u7ED3\u6784...`);
2150
+ await FileUtils.ensureDir(backendPath);
2151
+ await createSettingsGradle(backendPath, config);
2152
+ await createRootBuildGradle(backendPath, config);
2153
+ for (const module of config.modules) {
2154
+ await createModuleFromConfig(backendPath, module, config);
2155
+ }
2156
+ if (config.modules.length > 0) {
2157
+ await createCommonModule(backendPath, config);
2158
+ }
2159
+ logger.success("\u540E\u7AEF\u7ED3\u6784\u521B\u5EFA\u5B8C\u6210");
2160
+ }
2161
+ async function createSettingsGradle(backendPath, config) {
2162
+ const lines = [];
2163
+ lines.push(`rootProject.name = '${config.rootProjectName}'`);
2164
+ lines.push("");
2165
+ const allModules = ["common", ...config.modules.map((m) => m.name)].sort();
2166
+ for (const module of allModules) {
2167
+ lines.push(`include '${module}'`);
2462
2168
  }
2463
- const dockerfile = `FROM openjdk:17-jdk-slim
2464
- WORKDIR /app
2465
- COPY build/libs/*.jar app.jar
2466
- EXPOSE 8080
2467
- ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
2169
+ await FileUtils.write(path6.join(backendPath, "settings.gradle"), lines.join("\n"));
2170
+ logger.info("settings.gradle \u5DF2\u521B\u5EFA");
2171
+ }
2172
+ async function createRootBuildGradle(backendPath, config) {
2173
+ const content = `plugins {
2174
+ id 'java' version '8' apply false
2175
+ }
2176
+
2177
+ allprojects {
2178
+ group = '${config.groupId}'
2179
+ version = '0.0.1-SNAPSHOT'
2180
+
2181
+ repositories {
2182
+ maven { url 'https://maven.aliyun.com/repository/public/' }
2183
+ }
2184
+ }
2185
+
2186
+ subprojects {
2187
+ apply plugin: 'java'
2188
+ apply plugin: 'org.springframework.boot'
2189
+ apply plugin: 'io.spring.dependency-management'
2190
+
2191
+ sourceCompatibility = '${config.javaVersion}'
2192
+ targetCompatibility = '${config.javaVersion}'
2193
+
2194
+ dependencies {
2195
+ implementation 'org.springframework.boot:spring-boot-starter-web'
2196
+ implementation 'org.springframework.boot:spring-boot-starter-logging'
2197
+ compileOnly "org.projectlombok:lombok"
2198
+ annotationProcessor "org.projectlombok:lombok"
2199
+ testCompileOnly "org.projectlombok:lombok"
2200
+ testAnnotationProcessor "org.projectlombok:lombok"
2201
+ }
2202
+ }
2203
+ `;
2204
+ await FileUtils.write(path6.join(backendPath, "build.gradle"), content);
2205
+ logger.info("build.gradle (root) \u5DF2\u521B\u5EFA");
2206
+ }
2207
+ async function createModuleFromConfig(backendPath, module, config) {
2208
+ const modulePath = path6.join(backendPath, module.name);
2209
+ const packagePath = module.packagePath.split(".");
2210
+ logger.info(`\u521B\u5EFA\u6A21\u5757: ${module.name} (${module.type})...`);
2211
+ const baseDirs = [
2212
+ "src/main/java",
2213
+ "src/main/resources",
2214
+ "src/test/java"
2215
+ ];
2216
+ for (const dir of baseDirs) {
2217
+ await FileUtils.ensureDir(path6.join(modulePath, dir));
2218
+ }
2219
+ let subDirs = [];
2220
+ if (module.type === "service") {
2221
+ subDirs = ["controller", "service", "mapper", "entity", "dto", "config"];
2222
+ } else if (module.type === "api") {
2223
+ subDirs = ["model", "constant", "enums", "exception"];
2224
+ } else {
2225
+ subDirs = ["util", "common", "config", "manager"];
2226
+ }
2227
+ let javaPath = path6.join(modulePath, "src", "main", "java");
2228
+ for (const subDir of subDirs) {
2229
+ await FileUtils.ensureDir(path6.join(javaPath, ...packagePath, subDir));
2230
+ }
2231
+ await createApplicationClass(
2232
+ path6.join(javaPath, ...packagePath, "config", `${module.applicationClass}.java`),
2233
+ module.packagePath,
2234
+ module.applicationClass
2235
+ );
2236
+ await createModuleBuildGradle(modulePath, module, config);
2237
+ logger.success(`\u6A21\u5757 ${module.name} \u521B\u5EFA\u5B8C\u6210`);
2238
+ }
2239
+ async function createApplicationClass(filePath, packagePath, className) {
2240
+ const content = `package ${packagePath}.config;
2241
+
2242
+ import lombok.extern.slf4j.Slf4j;
2243
+ import org.springframework.boot.SpringApplication;
2244
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
2245
+ import org.springframework.context.ApplicationContext;
2246
+ import org.springframework.context.annotation.ComponentScan;
2247
+
2248
+ @SpringBootApplication
2249
+ @ComponentScan("${packagePath}")
2250
+ @Slf4j
2251
+ public class ${className} {
2252
+
2253
+ public static void main(String[] args) {
2254
+ ApplicationContext context = SpringApplication.run(${className}.class, args);
2255
+ log.info("${className} \u542F\u52A8\u6210\u529F!");
2256
+ }
2257
+ }
2258
+ `;
2259
+ await FileUtils.ensureDir(path6.dirname(filePath));
2260
+ await FileUtils.write(filePath, content);
2261
+ }
2262
+ async function createModuleBuildGradle(modulePath, module, config) {
2263
+ let applyPlugins = `plugins {
2264
+ id 'java-library'
2265
+ id 'org.springframework.boot'
2266
+ id 'io.spring.dependency-management'
2267
+ }
2268
+
2269
+ group = '${config.groupId}'
2270
+ version = '0.0.1-SNAPSHOT'
2271
+
2272
+ java {
2273
+ sourceCompatibility = '${config.javaVersion}'
2274
+ targetCompatibility = '${config.javaVersion}'
2275
+ }
2276
+
2277
+ repositories {
2278
+ maven { url 'https://maven.aliyun.com/repository/public/' }
2279
+ }
2280
+ `;
2281
+ let dependencies = "";
2282
+ if (module.type === "service") {
2283
+ dependencies = `
2284
+ dependencies {
2285
+ implementation project(":common")
2286
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
2287
+ implementation 'com.alibaba:druid-spring-boot-starter'
2288
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
2289
+ compileOnly "org.projectlombok:lombok"
2290
+ annotationProcessor "org.projectlombok:lombok"
2291
+ testCompileOnly "org.projectlombok:lombok"
2292
+ testAnnotationProcessor "org.projectlombok:lombok"
2293
+ }
2294
+ `;
2295
+ } else if (module.type === "api") {
2296
+ dependencies = `
2297
+ dependencies {
2298
+ compileOnly 'org.springframework.boot:spring-boot-starter'
2299
+ compileOnly "org.projectlombok:lombok"
2300
+ annotationProcessor "org.projectlombok:lombok"
2301
+ }
2302
+ `;
2303
+ } else {
2304
+ dependencies = `
2305
+ dependencies {
2306
+ implementation 'org.springframework.boot:spring-boot-starter'
2307
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
2308
+ implementation 'org.apache.commons:commons-lang3:3.12.0'
2309
+ compileOnly "org.projectlombok:lombok"
2310
+ annotationProcessor "org.projectlombok:lombok"
2311
+ }
2312
+ `;
2313
+ }
2314
+ await FileUtils.write(path6.join(modulePath, "build.gradle"), applyPlugins + dependencies);
2315
+ }
2316
+ async function createCommonModule(backendPath, config) {
2317
+ const modulePath = path6.join(backendPath, "common");
2318
+ const packagePath = config.groupId.split(".");
2319
+ const subDirs = ["util", "common", "config", "manager", "constant", "enums", "exception"];
2320
+ logger.info("\u521B\u5EFA\u516C\u5171\u6A21\u5757: common...");
2321
+ for (const dir of ["src/main/java", "src/main/resources", "src/test/java"]) {
2322
+ await FileUtils.ensureDir(path6.join(modulePath, dir));
2323
+ }
2324
+ let javaPath = path6.join(modulePath, "src", "main", "java");
2325
+ for (const subDir of subDirs) {
2326
+ await FileUtils.ensureDir(path6.join(javaPath, ...packagePath, subDir));
2327
+ }
2328
+ await createModuleBuildGradle(modulePath, {
2329
+ name: "common",
2330
+ type: "common",
2331
+ packagePath: `${config.groupId}.common`,
2332
+ applicationClass: ""
2333
+ }, config);
2334
+ logger.success("\u516C\u5171\u6A21\u5757 common \u521B\u5EFA\u5B8C\u6210");
2335
+ }
2336
+ async function generateDockerFiles(projectPath, projectName) {
2337
+ const backendPath = path6.join(projectPath, "backend");
2338
+ if (!await FileUtils.exists(backendPath)) {
2339
+ logger.warn("\u672A\u627E\u5230 backend \u76EE\u5F55\uFF0C\u8DF3\u8FC7 Docker \u914D\u7F6E\u751F\u6210");
2340
+ return;
2341
+ }
2342
+ const dockerfile = `FROM openjdk:17-jdk-slim
2343
+ WORKDIR /app
2344
+ COPY build/libs/*.jar app.jar
2345
+ EXPOSE 8080
2346
+ ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
2468
2347
  `;
2469
2348
  const deploySh = `#!/bin/bash
2470
2349
  # yg-team-cli \u81EA\u52A8\u751F\u6210\u7684\u90E8\u7F72\u811A\u672C
@@ -2493,9 +2372,9 @@ docker push \${REGISTRY}/\${PROJECT_NAME}:latest
2493
2372
 
2494
2373
  echo "\u2705 \u90E8\u7F72\u5B8C\u6210! \u955C\u50CF\u5730\u5740: \${REGISTRY}/\${IMAGE_TAG}"
2495
2374
  `;
2496
- await fs3.writeFile(path7.join(backendPath, "Dockerfile"), dockerfile);
2497
- await fs3.writeFile(path7.join(backendPath, "deploy.sh"), deploySh);
2498
- await fs3.chmod(path7.join(backendPath, "deploy.sh"), 493);
2375
+ await fs3.writeFile(path6.join(backendPath, "Dockerfile"), dockerfile);
2376
+ await fs3.writeFile(path6.join(backendPath, "deploy.sh"), deploySh);
2377
+ await fs3.chmod(path6.join(backendPath, "deploy.sh"), 493);
2499
2378
  }
2500
2379
  async function initGit(projectPath, projectName) {
2501
2380
  try {
@@ -2524,36 +2403,21 @@ var init_init = __esm({
2524
2403
  init_module_registry();
2525
2404
  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) => {
2526
2405
  try {
2527
- if (!projectName) {
2528
- const answers = await inquirer.prompt([
2529
- {
2530
- type: "input",
2531
- name: "projectName",
2532
- message: "\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0:",
2533
- default: "my-project",
2534
- validate: (input) => {
2535
- if (!/^[a-z0-9-]+$/.test(input)) {
2536
- return "\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
2537
- }
2538
- return true;
2539
- }
2540
- }
2541
- ]);
2542
- projectName = answers.projectName;
2543
- }
2544
- if (!StringUtils.validateProjectName(projectName)) {
2545
- logger.error("\u9879\u76EE\u540D\u79F0\u53EA\u80FD\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26");
2546
- process.exit(1);
2547
- }
2406
+ const config = await collectProjectConfig(projectName);
2548
2407
  logger.header("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6 - \u9879\u76EE\u521D\u59CB\u5316");
2549
2408
  logger.newLine();
2409
+ logger.info(`\u9879\u76EE\u7C7B\u578B: ${config.projectType === "multi" ? "\u591A\u6A21\u5757\u9879\u76EE" : "\u5355\u6A21\u5757\u9879\u76EE"}`);
2410
+ logger.info(`Group ID: ${config.groupId}`);
2411
+ logger.info(`\u6A21\u5757: ${config.modules.map((m) => m.name).join(", ")}`);
2412
+ logger.info(`Java \u7248\u672C: ${config.javaVersion}`);
2413
+ logger.newLine();
2550
2414
  const hasClaude = await claudeAI.checkInstalled();
2551
2415
  if (!hasClaude) {
2552
2416
  logger.error("\u672A\u68C0\u6D4B\u5230 Claude CLI");
2553
2417
  logger.info("\u8BF7\u5B89\u88C5 Claude CLI: npm install -g @anthropic-ai/claude-code");
2554
2418
  process.exit(1);
2555
2419
  }
2556
- const projectPath = path7.resolve(options.dir, projectName);
2420
+ const projectPath = path6.resolve(options.dir, config.projectName);
2557
2421
  const availableModules = ModuleManager.getAvailableModules();
2558
2422
  const { selectedModules } = await inquirer.prompt([
2559
2423
  {
@@ -2577,48 +2441,45 @@ var init_init = __esm({
2577
2441
  title: "\u521B\u5EFA\u9879\u76EE\u76EE\u5F55\u7ED3\u6784",
2578
2442
  task: async () => {
2579
2443
  await FileUtils.ensureDir(projectPath);
2580
- await FileUtils.ensureDir(path7.join(projectPath, "docs/specs"));
2581
- await FileUtils.ensureDir(path7.join(projectPath, "docs/prd-docs"));
2582
- await FileUtils.ensureDir(path7.join(projectPath, "docs/api"));
2583
- await FileUtils.ensureDir(path7.join(projectPath, "docs/sessions"));
2444
+ await FileUtils.ensureDir(path6.join(projectPath, "docs/specs"));
2445
+ await FileUtils.ensureDir(path6.join(projectPath, "docs/prd-docs"));
2446
+ await FileUtils.ensureDir(path6.join(projectPath, "docs/api"));
2447
+ await FileUtils.ensureDir(path6.join(projectPath, "docs/sessions"));
2584
2448
  }
2585
2449
  },
2586
2450
  {
2587
2451
  title: "\u751F\u6210\u6280\u672F\u6587\u6863 (Tech Stack, Conventions, Memory)",
2588
2452
  task: async () => {
2589
- await generateTechStack(projectPath);
2590
- await generateConventions(projectPath);
2591
- await generateAIMemory(projectPath, projectName);
2453
+ await generateTechStack(projectPath, config);
2454
+ await generateConventions(projectPath, config);
2455
+ await generateAIMemory(projectPath, config.projectName);
2592
2456
  await generateSpecTemplate(projectPath);
2593
2457
  }
2594
2458
  },
2595
2459
  {
2596
- title: "\u514B\u9686\u540E\u7AEF\u6A21\u677F",
2460
+ title: "\u521B\u5EFA\u540E\u7AEF\u7ED3\u6784",
2597
2461
  task: async () => {
2598
- const backendTag = options.backendTag || options.tag;
2599
- const backendBranch = options.backendBranch;
2600
- await cloneBackendTemplate(projectPath, {
2601
- tag: backendTag,
2602
- branch: backendBranch
2603
- });
2462
+ await createBackendFromConfig(projectPath, config);
2604
2463
  }
2605
2464
  },
2606
- {
2607
- title: "\u514B\u9686\u524D\u7AEF\u6A21\u677F",
2608
- task: async () => {
2609
- const frontendTag = options.frontendTag || options.tag;
2610
- const frontendBranch = options.frontendBranch;
2611
- await cloneFrontendTemplate(projectPath, {
2612
- tag: frontendTag,
2613
- branch: frontendBranch
2614
- });
2465
+ ...config.includeFrontend ? [
2466
+ {
2467
+ title: "\u514B\u9686\u524D\u7AEF\u6A21\u677F",
2468
+ task: async () => {
2469
+ const frontendTag = options.frontendTag || options.tag;
2470
+ const frontendBranch = options.frontendBranch;
2471
+ await cloneFrontendTemplate(projectPath, {
2472
+ tag: frontendTag,
2473
+ branch: frontendBranch
2474
+ });
2475
+ }
2615
2476
  }
2616
- },
2477
+ ] : [],
2617
2478
  {
2618
2479
  title: "\u6CE8\u5165\u9009\u5B9A\u7684\u901A\u7528\u6A21\u5757",
2619
2480
  task: async (ctx) => {
2620
2481
  if (selectedModules.length === 0) return;
2621
- const templatesDir = path7.resolve(FileUtils.getDirName(import.meta.url), "../templates");
2482
+ const templatesDir = path6.resolve(FileUtils.getDirName(import.meta.url), "../templates");
2622
2483
  ctx.addedFiles = [];
2623
2484
  for (const moduleId of selectedModules) {
2624
2485
  const files = await ModuleManager.injectModule(projectPath, moduleId, templatesDir);
@@ -2630,7 +2491,7 @@ var init_init = __esm({
2630
2491
  {
2631
2492
  title: "\u751F\u6210 Docker \u90E8\u7F72\u914D\u7F6E",
2632
2493
  task: async () => {
2633
- await generateDockerFiles(projectPath, projectName);
2494
+ await generateDockerFiles(projectPath, config.projectName);
2634
2495
  }
2635
2496
  }
2636
2497
  ] : []
@@ -2725,7 +2586,7 @@ var init_init = __esm({
2725
2586
  // src/commands/breakdown.ts
2726
2587
  import { Command as Command2 } from "commander";
2727
2588
  import inquirer2 from "inquirer";
2728
- import path8 from "path";
2589
+ import path7 from "path";
2729
2590
  import { Listr as Listr2 } from "listr2";
2730
2591
  function mergeMilestones(original, milestones) {
2731
2592
  const milestoneHeader = "## \u91CC\u7A0B\u7891 (Milestones)";
@@ -2892,10 +2753,10 @@ var init_breakdown = __esm({
2892
2753
  choices: ctx.specs
2893
2754
  }
2894
2755
  ]);
2895
- ctx.selectedFile = path8.join("docs/specs", selectedFile);
2756
+ ctx.selectedFile = path7.join("docs/specs", selectedFile);
2896
2757
  return;
2897
2758
  }
2898
- const fullPath = specFile.startsWith("docs/specs/") ? specFile : path8.join("docs/specs", specFile);
2759
+ const fullPath = specFile.startsWith("docs/specs/") ? specFile : path7.join("docs/specs", specFile);
2899
2760
  const exists = await FileUtils.exists(fullPath);
2900
2761
  if (!exists) {
2901
2762
  throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${specFile}`);
@@ -2966,7 +2827,7 @@ var init_breakdown = __esm({
2966
2827
  // src/commands/dev.ts
2967
2828
  import { Command as Command3 } from "commander";
2968
2829
  import inquirer3 from "inquirer";
2969
- import path9 from "path";
2830
+ import path8 from "path";
2970
2831
  async function selectSpec() {
2971
2832
  logger.step("\u6B65\u9AA4 1/3: \u9009\u62E9 spec \u6587\u4EF6...");
2972
2833
  logger.newLine();
@@ -2982,7 +2843,7 @@ async function selectSpec() {
2982
2843
  }
2983
2844
  const specs = [];
2984
2845
  for (let i = 0; i < specFiles.length; i++) {
2985
- const file = path9.join(specDir, specFiles[i]);
2846
+ const file = path8.join(specDir, specFiles[i]);
2986
2847
  const spec = await FileUtils.read(file);
2987
2848
  const status = SpecUtils.parseSpecStatus(spec);
2988
2849
  const dependencies = parseDependencies(spec);
@@ -3329,8 +3190,8 @@ async function generateSessionLog(specFile, milestone, todo, taskDescription, re
3329
3190
  const sessionDir = "docs/sessions";
3330
3191
  await FileUtils.ensureDir(sessionDir);
3331
3192
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3332
- const specName = path9.basename(specFile, ".md");
3333
- const logFile = path9.join(sessionDir, `${timestamp}_${specName}.md`);
3193
+ const specName = path8.basename(specFile, ".md");
3194
+ const logFile = path8.join(sessionDir, `${timestamp}_${specName}.md`);
3334
3195
  const content = `# \u5F00\u53D1\u4F1A\u8BDD\u8BB0\u5F55
3335
3196
 
3336
3197
  **\u65F6\u95F4**: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}
@@ -3485,7 +3346,7 @@ var init_dev = __esm({
3485
3346
  // src/commands/add-feature.ts
3486
3347
  import { Command as Command4 } from "commander";
3487
3348
  import inquirer4 from "inquirer";
3488
- import path10 from "path";
3349
+ import path9 from "path";
3489
3350
  import { Listr as Listr3 } from "listr2";
3490
3351
  async function addFeatureFromPrd(featureName, _featureSlug, prdOutputFile) {
3491
3352
  const { prdPath } = await inquirer4.prompt([
@@ -3514,7 +3375,7 @@ async function addFeatureFromPrd(featureName, _featureSlug, prdOutputFile) {
3514
3375
  const specs = files.filter((f) => !f.includes("template"));
3515
3376
  ctx.completedSpecs = [];
3516
3377
  for (const file of specs) {
3517
- const status = await SpecUtils.getSpecStatus(path10.join(specDir, file));
3378
+ const status = await SpecUtils.getSpecStatus(path9.join(specDir, file));
3518
3379
  if (status === "\u5DF2\u5B8C\u6210") {
3519
3380
  ctx.completedSpecs.push(file.replace(".md", ""));
3520
3381
  }
@@ -3575,7 +3436,7 @@ async function addFeatureSimple(featureName, _featureSlug, prdOutputFile) {
3575
3436
  const specs = files.filter((f) => !f.includes("template"));
3576
3437
  ctx.completedSpecs = [];
3577
3438
  for (const file of specs) {
3578
- const status = await SpecUtils.getSpecStatus(path10.join(specDir, file));
3439
+ const status = await SpecUtils.getSpecStatus(path9.join(specDir, file));
3579
3440
  if (status === "\u5DF2\u5B8C\u6210") {
3580
3441
  ctx.completedSpecs.push(file.replace(".md", ""));
3581
3442
  }
@@ -3661,7 +3522,7 @@ async function buildProjectContext() {
3661
3522
  const files = await FileUtils.findFiles("*.md", "docs/specs");
3662
3523
  const specs = files.filter((f) => !f.includes("template"));
3663
3524
  for (const file of specs) {
3664
- const status = await SpecUtils.getSpecStatus(path10.join("docs/specs", file));
3525
+ const status = await SpecUtils.getSpecStatus(path9.join("docs/specs", file));
3665
3526
  context.push(` - ${file.replace(".md", "")} [${status}]`);
3666
3527
  }
3667
3528
  }
@@ -3913,7 +3774,7 @@ var init_add_feature = __esm({
3913
3774
  process.exit(1);
3914
3775
  }
3915
3776
  const featureSlug = StringUtils.toKebabCase(featureName);
3916
- const prdFile = path10.join("docs/prd-docs", `${featureSlug}.md`);
3777
+ const prdFile = path9.join("docs/prd-docs", `${featureSlug}.md`);
3917
3778
  const prdExists = await FileUtils.exists(prdFile);
3918
3779
  if (prdExists) {
3919
3780
  logger.error(`PRD \u6587\u4EF6\u5DF2\u5B58\u5728: ${prdFile}`);
@@ -3950,12 +3811,12 @@ var init_add_feature = __esm({
3950
3811
 
3951
3812
  // src/commands/split-prd.ts
3952
3813
  import { Command as Command5 } from "commander";
3953
- import path11 from "path";
3814
+ import path10 from "path";
3954
3815
  import { Listr as Listr4 } from "listr2";
3955
3816
  async function processSinglePrd(prdFile) {
3956
- const baseName = path11.basename(prdFile, path11.extname(prdFile));
3817
+ const baseName = path10.basename(prdFile, path10.extname(prdFile));
3957
3818
  const featureSlug = StringUtils.toKebabCase(baseName);
3958
- const specFile = path11.join("docs/specs", `${featureSlug}.md`);
3819
+ const specFile = path10.join("docs/specs", `${featureSlug}.md`);
3959
3820
  const specExists = await FileUtils.exists(specFile);
3960
3821
  if (specExists) {
3961
3822
  logger.error(`Spec \u6587\u4EF6\u5DF2\u5B58\u5728: ${specFile}`);
@@ -4019,7 +3880,7 @@ async function processMultiplePrds(prdFolder) {
4019
3880
  ctx.prdFiles = [];
4020
3881
  for (const ext of supportedExtensions) {
4021
3882
  const files = await FileUtils.findFiles(`*.${ext}`, prdFolder);
4022
- ctx.prdFiles.push(...files.map((f) => path11.join(prdFolder, f)));
3883
+ ctx.prdFiles.push(...files.map((f) => path10.join(prdFolder, f)));
4023
3884
  }
4024
3885
  if (ctx.prdFiles.length === 0) {
4025
3886
  throw new Error(
@@ -4037,7 +3898,7 @@ async function processMultiplePrds(prdFolder) {
4037
3898
  {
4038
3899
  title: "\u626B\u63CF\u622A\u56FE\u6587\u4EF6",
4039
3900
  task: async (ctx) => {
4040
- const screenshotDir = path11.join(prdFolder, "screenshots");
3901
+ const screenshotDir = path10.join(prdFolder, "screenshots");
4041
3902
  const dirExists = await FileUtils.exists(screenshotDir);
4042
3903
  if (!dirExists) {
4043
3904
  logger.info("\u672A\u627E\u5230 screenshots \u76EE\u5F55\uFF0C\u8DF3\u8FC7\u622A\u56FE");
@@ -4048,7 +3909,7 @@ async function processMultiplePrds(prdFolder) {
4048
3909
  ctx.screenshots = [];
4049
3910
  for (const ext of imageExtensions) {
4050
3911
  const files = await FileUtils.findFiles(`*.${ext}`, screenshotDir);
4051
- ctx.screenshots.push(...files.map((f) => path11.join(screenshotDir, f)));
3912
+ ctx.screenshots.push(...files.map((f) => path10.join(screenshotDir, f)));
4052
3913
  }
4053
3914
  logger.success(`\u627E\u5230 ${ctx.screenshots.length} \u4E2A\u622A\u56FE\u6587\u4EF6`);
4054
3915
  }
@@ -4059,8 +3920,8 @@ async function processMultiplePrds(prdFolder) {
4059
3920
  const entries = await FileUtils.findFiles("*/", prdFolder);
4060
3921
  ctx.demoRepos = [];
4061
3922
  for (const entry of entries) {
4062
- const dirPath = path11.join(prdFolder, entry);
4063
- const gitDir = path11.join(dirPath, ".git");
3923
+ const dirPath = path10.join(prdFolder, entry);
3924
+ const gitDir = path10.join(dirPath, ".git");
4064
3925
  const hasGit = await FileUtils.exists(gitDir);
4065
3926
  if (hasGit) {
4066
3927
  ctx.demoRepos.push(dirPath);
@@ -4388,7 +4249,7 @@ var init_split_prd = __esm({
4388
4249
  // src/commands/bugfix.ts
4389
4250
  import { Command as Command6 } from "commander";
4390
4251
  import inquirer5 from "inquirer";
4391
- import path12 from "path";
4252
+ import path11 from "path";
4392
4253
  import { Listr as Listr5 } from "listr2";
4393
4254
  function generateBugId() {
4394
4255
  const date = /* @__PURE__ */ new Date();
@@ -4415,7 +4276,7 @@ async function findRelatedSpec(description) {
4415
4276
  }
4416
4277
  const keywords = extractKeywords(description);
4417
4278
  for (const file of specs) {
4418
- const filePath = path12.join(specDir, file);
4279
+ const filePath = path11.join(specDir, file);
4419
4280
  const content = await FileUtils.read(filePath);
4420
4281
  for (const keyword of keywords) {
4421
4282
  if (content.toLowerCase().includes(keyword.toLowerCase())) {
@@ -4555,7 +4416,7 @@ var init_bugfix = __esm({
4555
4416
  const relatedSpec = await findRelatedSpec(answers.description);
4556
4417
  const bugfixDir = "docs/bugfixes";
4557
4418
  await FileUtils.ensureDir(bugfixDir);
4558
- const bugfixFile = path12.join(bugfixDir, `${timestamp}_${bugId}.md`);
4419
+ const bugfixFile = path11.join(bugfixDir, `${timestamp}_${bugId}.md`);
4559
4420
  const content = formatBugfixDocument({
4560
4421
  id: bugId,
4561
4422
  severity: answers.severity,
@@ -4631,7 +4492,7 @@ var init_bugfix = __esm({
4631
4492
  const timestamp = DateUtils.format(/* @__PURE__ */ new Date(), "YYYY-MM-DD HH:mm:ss");
4632
4493
  const hotfixDir = "docs/hotfixes";
4633
4494
  await FileUtils.ensureDir(hotfixDir);
4634
- const hotfixFile = path12.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
4495
+ const hotfixFile = path11.join(hotfixDir, `${timestamp}_${hotfixId}.md`);
4635
4496
  const content = formatHotfixDocument({
4636
4497
  id: hotfixId,
4637
4498
  description: answers.description,
@@ -4687,7 +4548,7 @@ Temporary solution: ${answers.solution}`
4687
4548
  // src/commands/accept.ts
4688
4549
  import { Command as Command7 } from "commander";
4689
4550
  import inquirer6 from "inquirer";
4690
- import path13 from "path";
4551
+ import path12 from "path";
4691
4552
  async function selectSpec2(defaultSpec) {
4692
4553
  logger.step("\u6B65\u9AA4 1/4: \u9009\u62E9 spec \u6587\u4EF6...");
4693
4554
  logger.newLine();
@@ -4702,7 +4563,7 @@ async function selectSpec2(defaultSpec) {
4702
4563
  throw new Error("\u672A\u627E\u5230 spec \u6587\u4EF6");
4703
4564
  }
4704
4565
  if (defaultSpec) {
4705
- const fullPath = defaultSpec.startsWith("docs/specs/") ? defaultSpec : path13.join(specDir, defaultSpec);
4566
+ const fullPath = defaultSpec.startsWith("docs/specs/") ? defaultSpec : path12.join(specDir, defaultSpec);
4706
4567
  const exists2 = await FileUtils.exists(fullPath);
4707
4568
  if (!exists2) {
4708
4569
  throw new Error(`Spec \u6587\u4EF6\u4E0D\u5B58\u5728: ${defaultSpec}`);
@@ -4712,7 +4573,7 @@ async function selectSpec2(defaultSpec) {
4712
4573
  }
4713
4574
  const specs = [];
4714
4575
  for (const file of specFiles) {
4715
- const fullPath = path13.join(specDir, file);
4576
+ const fullPath = path12.join(specDir, file);
4716
4577
  const content = await FileUtils.read(fullPath);
4717
4578
  const status = SpecUtils.parseSpecStatus(content);
4718
4579
  specs.push({ file: fullPath, name: file, status });
@@ -5003,9 +4864,9 @@ async function generateAcceptanceReport(result) {
5003
4864
  const reportDir = "docs/acceptance-reports";
5004
4865
  await FileUtils.ensureDir(reportDir);
5005
4866
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5006
- const specName = path13.basename(result.specFile, ".md");
4867
+ const specName = path12.basename(result.specFile, ".md");
5007
4868
  const milestoneSafe = result.milestone.replace(/[^a-zA-Z0-9]/g, "-");
5008
- const reportFile = path13.join(reportDir, `${timestamp}_${specName}_${milestoneSafe}.md`);
4869
+ const reportFile = path12.join(reportDir, `${timestamp}_${specName}_${milestoneSafe}.md`);
5009
4870
  const report = generateMarkdownReport(result);
5010
4871
  await FileUtils.write(reportFile, report);
5011
4872
  logger.success(`\u9A8C\u6536\u62A5\u544A\u5DF2\u751F\u6210: ${reportFile}`);
@@ -5125,7 +4986,7 @@ async function handleIssues(result) {
5125
4986
  const bugfixDir = "docs/bugfixes";
5126
4987
  await FileUtils.ensureDir(bugfixDir);
5127
4988
  for (const issue of result.issues) {
5128
- const bugfixFile = path13.join(
4989
+ const bugfixFile = path12.join(
5129
4990
  bugfixDir,
5130
4991
  `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}_${issue.id}.md`
5131
4992
  );
@@ -5273,8 +5134,383 @@ var init_accept = __esm({
5273
5134
  }
5274
5135
  });
5275
5136
 
5276
- // src/commands/lint.ts
5137
+ // src/commands/add-module.ts
5277
5138
  import { Command as Command8 } from "commander";
5139
+ import inquirer7 from "inquirer";
5140
+ import path13 from "path";
5141
+ import fs4 from "fs-extra";
5142
+ async function scanExistingModules(projectPath) {
5143
+ const modules = [];
5144
+ const settingsGradle = path13.join(projectPath, "settings.gradle");
5145
+ if (await FileUtils.exists(settingsGradle)) {
5146
+ const content = await FileUtils.read(settingsGradle);
5147
+ const includeMatches = content.match(/include\s+'([^']+)'/g);
5148
+ if (includeMatches) {
5149
+ for (const match of includeMatches) {
5150
+ const moduleName = match.replace(/include\s+'/, "").replace(/'/, "");
5151
+ modules.push(moduleName);
5152
+ }
5153
+ }
5154
+ }
5155
+ const backendPath = path13.join(projectPath, "backend");
5156
+ if (await FileUtils.exists(backendPath)) {
5157
+ const entries = await fs4.readdir(backendPath, { withFileTypes: true });
5158
+ for (const entry of entries) {
5159
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "src") {
5160
+ if (!modules.includes(entry.name)) {
5161
+ modules.push(entry.name);
5162
+ }
5163
+ }
5164
+ }
5165
+ }
5166
+ return modules;
5167
+ }
5168
+ async function detectGroupId(projectPath) {
5169
+ const buildGradle = path13.join(projectPath, "backend", "build.gradle");
5170
+ if (await FileUtils.exists(buildGradle)) {
5171
+ const content = await FileUtils.read(buildGradle);
5172
+ const groupMatch = content.match(/group\s*=\s*['"]([^'"]+)['"]/);
5173
+ if (groupMatch) {
5174
+ return groupMatch[1];
5175
+ }
5176
+ }
5177
+ const rootBuildGradle = path13.join(projectPath, "build.gradle");
5178
+ if (await FileUtils.exists(rootBuildGradle)) {
5179
+ const content = await FileUtils.read(rootBuildGradle);
5180
+ const groupMatch = content.match(/group\s*=\s*['"]([^'"]+)['"]/);
5181
+ if (groupMatch) {
5182
+ return groupMatch[1];
5183
+ }
5184
+ }
5185
+ const backendPath = path13.join(projectPath, "backend");
5186
+ if (await FileUtils.exists(backendPath)) {
5187
+ const entries = await fs4.readdir(backendPath, { withFileTypes: true });
5188
+ for (const entry of entries) {
5189
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
5190
+ const srcPath = path13.join(backendPath, entry.name, "src", "main", "java");
5191
+ if (await FileUtils.exists(srcPath)) {
5192
+ const javaDirs = await fs4.readdir(srcPath);
5193
+ if (javaDirs.length > 0) {
5194
+ return javaDirs[0];
5195
+ }
5196
+ }
5197
+ }
5198
+ }
5199
+ }
5200
+ return "org.yungu";
5201
+ }
5202
+ async function detectRootProjectName(projectPath) {
5203
+ const settingsGradle = path13.join(projectPath, "settings.gradle");
5204
+ if (await FileUtils.exists(settingsGradle)) {
5205
+ const content = await FileUtils.read(settingsGradle);
5206
+ const rootMatch = content.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
5207
+ if (rootMatch) {
5208
+ return rootMatch[1];
5209
+ }
5210
+ }
5211
+ return path13.basename(projectPath);
5212
+ }
5213
+ async function createModuleStructure(projectPath, module, groupId) {
5214
+ const modulePath = path13.join(projectPath, "backend", module.name);
5215
+ logger.info(`\u6B63\u5728\u521B\u5EFA\u6A21\u5757: ${module.name}...`);
5216
+ const baseDirs = [
5217
+ "src/main/java",
5218
+ "src/main/resources",
5219
+ "src/test/java"
5220
+ ];
5221
+ for (const dir of baseDirs) {
5222
+ await FileUtils.ensureDir(path13.join(modulePath, dir));
5223
+ }
5224
+ const packageDirs = module.packagePath.split(".");
5225
+ let javaPath = path13.join(modulePath, "src", "main", "java");
5226
+ if (module.type === "service") {
5227
+ for (const subDir of ["controller", "service", "mapper", "entity", "dto", "config"]) {
5228
+ await FileUtils.ensureDir(path13.join(javaPath, ...packageDirs, subDir));
5229
+ }
5230
+ await createApplicationClass2(
5231
+ path13.join(javaPath, ...packageDirs, "config", `${module.applicationClass}.java`),
5232
+ module.packagePath,
5233
+ module.applicationClass
5234
+ );
5235
+ } else if (module.type === "api") {
5236
+ for (const subDir of ["model", "constant", "enums", "exception"]) {
5237
+ await FileUtils.ensureDir(path13.join(javaPath, ...packageDirs, subDir));
5238
+ }
5239
+ } else {
5240
+ for (const subDir of ["util", "common", "config", "manager"]) {
5241
+ await FileUtils.ensureDir(path13.join(javaPath, ...packageDirs, subDir));
5242
+ }
5243
+ }
5244
+ await createModuleBuildGradle2(
5245
+ path13.join(modulePath, "build.gradle"),
5246
+ module,
5247
+ groupId
5248
+ );
5249
+ const gitignore = `# Gradle
5250
+ .gradle/
5251
+ build/
5252
+
5253
+ # IDE
5254
+ .idea/
5255
+ *.iml
5256
+ *.ipr
5257
+ *.iws
5258
+ .vscode/
5259
+
5260
+ # OS
5261
+ .DS_Store
5262
+ Thumbs.db
5263
+
5264
+ # Logs
5265
+ logs/
5266
+ *.log
5267
+
5268
+ # Application
5269
+ application-local.yml
5270
+ application-dev.yml
5271
+ `;
5272
+ await FileUtils.write(path13.join(modulePath, ".gitignore"), gitignore);
5273
+ logger.success(`\u6A21\u5757 ${module.name} \u521B\u5EFA\u5B8C\u6210`);
5274
+ }
5275
+ async function createApplicationClass2(filePath, packagePath, className) {
5276
+ const content = `package ${packagePath}.config;
5277
+
5278
+ import lombok.extern.slf4j.Slf4j;
5279
+ import org.springframework.boot.SpringApplication;
5280
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5281
+ import org.springframework.context.ApplicationContext;
5282
+ import org.springframework.context.annotation.ComponentScan;
5283
+
5284
+ @SpringBootApplication
5285
+ @ComponentScan("${packagePath}")
5286
+ @Slf4j
5287
+ public class ${className} {
5288
+
5289
+ public static void main(String[] args) {
5290
+ ApplicationContext context = SpringApplication.run(${className}.class, args);
5291
+ log.info("${className} \u542F\u52A8\u6210\u529F!");
5292
+ }
5293
+ }
5294
+ `;
5295
+ await FileUtils.write(filePath, content);
5296
+ }
5297
+ async function createModuleBuildGradle2(filePath, module, groupId) {
5298
+ let dependencies = "";
5299
+ if (module.type === "service") {
5300
+ dependencies = `
5301
+ dependencies {
5302
+ implementation project(":common")
5303
+ implementation 'org.springframework.boot:spring-boot-starter-web'
5304
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
5305
+ implementation 'com.alibaba:druid-spring-boot-starter'
5306
+ compileOnly "org.projectlombok:lombok"
5307
+ annotationProcessor "org.projectlombok:lombok"
5308
+ }
5309
+ `;
5310
+ } else if (module.type === "api") {
5311
+ dependencies = `
5312
+ dependencies {
5313
+ compileOnly 'org.springframework.boot:spring-boot-starter'
5314
+ compileOnly "org.projectlombok:lombok"
5315
+ annotationProcessor "org.projectlombok:lombok"
5316
+ }
5317
+ `;
5318
+ } else {
5319
+ dependencies = `
5320
+ dependencies {
5321
+ implementation 'org.springframework.boot:spring-boot-starter'
5322
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter'
5323
+ compileOnly "org.projectlombok:lombok"
5324
+ annotationProcessor "org.projectlombok:lombok"
5325
+ }
5326
+ `;
5327
+ }
5328
+ const content = `plugins {
5329
+ id 'java-library'
5330
+ id 'org.springframework.boot' version '3.2.0'
5331
+ id 'io.spring.dependency-management' version '1.1.4'
5332
+ }
5333
+
5334
+ group = '${groupId}'
5335
+ version = '0.0.1-SNAPSHOT'
5336
+
5337
+ java {
5338
+ sourceCompatibility = '17'
5339
+ }
5340
+
5341
+ repositories {
5342
+ maven { url 'https://maven.aliyun.com/repository/public/' }
5343
+ }
5344
+
5345
+ ${dependencies}
5346
+ `;
5347
+ await FileUtils.write(filePath, content);
5348
+ }
5349
+ async function updateSettingsGradle(projectPath, rootProjectName, newModules) {
5350
+ const settingsPath = path13.join(projectPath, "backend", "settings.gradle");
5351
+ let content = "";
5352
+ if (await FileUtils.exists(settingsPath)) {
5353
+ content = await FileUtils.read(settingsPath);
5354
+ } else {
5355
+ content = `rootProject.name = '${rootProjectName}'
5356
+ `;
5357
+ }
5358
+ const existingModules = /* @__PURE__ */ new Set();
5359
+ const moduleMatches = content.matchAll(/include\s+'([^']+)'/g);
5360
+ for (const match of moduleMatches) {
5361
+ existingModules.add(match[1]);
5362
+ }
5363
+ for (const module of newModules) {
5364
+ if (!existingModules.has(module.name)) {
5365
+ existingModules.add(module.name);
5366
+ }
5367
+ }
5368
+ const lines = [];
5369
+ lines.push(`rootProject.name = '${rootProjectName}'`);
5370
+ lines.push("");
5371
+ const allModules = Array.from(existingModules).sort();
5372
+ for (const module of allModules) {
5373
+ lines.push(`include '${module}'`);
5374
+ }
5375
+ await FileUtils.write(settingsPath, lines.join("\n"));
5376
+ logger.success("settings.gradle \u5DF2\u66F4\u65B0");
5377
+ }
5378
+ function toPascalCase2(str) {
5379
+ return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
5380
+ }
5381
+ var addModuleCommand;
5382
+ var init_add_module = __esm({
5383
+ "src/commands/add-module.ts"() {
5384
+ "use strict";
5385
+ init_esm_shims();
5386
+ init_utils();
5387
+ init_logger();
5388
+ addModuleCommand = new Command8("add-module").description("\u4E3A\u5DF2\u6709\u9879\u76EE\u65B0\u589E\u6A21\u5757 (\u591A\u6A21\u5757 Gradle \u9879\u76EE)").action(async () => {
5389
+ try {
5390
+ logger.header("\u65B0\u589E\u6A21\u5757");
5391
+ logger.newLine();
5392
+ const projectPath = process.cwd();
5393
+ const hasTechStack = await FileUtils.exists("TECH_STACK.md");
5394
+ if (!hasTechStack) {
5395
+ logger.error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u4E00\u4E2A\u6709\u6548\u7684 team-cli \u9879\u76EE");
5396
+ logger.info("\u8BF7\u5207\u6362\u5230\u9879\u76EE\u6839\u76EE\u5F55\u540E\u518D\u6267\u884C\u6B64\u547D\u4EE4");
5397
+ process.exit(1);
5398
+ }
5399
+ const existingModules = await scanExistingModules(projectPath);
5400
+ if (existingModules.length > 0) {
5401
+ logger.success(`\u68C0\u6D4B\u5230\u73B0\u6709\u6A21\u5757: [${existingModules.join(", ")}]`);
5402
+ } else {
5403
+ logger.info("\u672A\u68C0\u6D4B\u5230\u73B0\u6709\u6A21\u5757");
5404
+ }
5405
+ logger.newLine();
5406
+ const groupId = await detectGroupId(projectPath);
5407
+ logger.info(`\u68C0\u6D4B\u5230 Group ID: ${groupId}`);
5408
+ logger.newLine();
5409
+ const rootProjectName = await detectRootProjectName(projectPath);
5410
+ logger.info(`\u68C0\u6D4B\u5230\u6839\u9879\u76EE\u540D: ${rootProjectName}`);
5411
+ logger.newLine();
5412
+ const newModules = [];
5413
+ let addMore = true;
5414
+ let moduleIndex = 1;
5415
+ while (addMore) {
5416
+ const moduleNameAnswer = await inquirer7.prompt([
5417
+ {
5418
+ type: "input",
5419
+ name: "moduleName",
5420
+ message: `\u8BF7\u8F93\u5165\u7B2C ${moduleIndex} \u4E2A\u65B0\u6A21\u5757\u540D\u79F0 (\u76EE\u5F55\u540D):`,
5421
+ validate: (input) => {
5422
+ if (!/^[a-z][a-z0-9-]*$/.test(input)) {
5423
+ return "\u6A21\u5757\u540D\u53EA\u80FD\u4EE5\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u5305\u542B\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26";
5424
+ }
5425
+ if (existingModules.includes(input)) {
5426
+ return "\u8BE5\u6A21\u5757\u5DF2\u5B58\u5728";
5427
+ }
5428
+ return true;
5429
+ }
5430
+ }
5431
+ ]);
5432
+ const moduleName = moduleNameAnswer.moduleName;
5433
+ const typeAnswer = await inquirer7.prompt([
5434
+ {
5435
+ type: "list",
5436
+ name: "moduleType",
5437
+ message: `\u6A21\u5757 "${moduleName}" \u7C7B\u578B:`,
5438
+ choices: [
5439
+ { name: "API \u670D\u52A1\u6A21\u5757 (\u72EC\u7ACB\u90E8\u7F72)", value: "service" },
5440
+ { name: "\u516C\u5171\u6A21\u5757 (\u88AB\u5176\u4ED6\u6A21\u5757\u4F9D\u8D56)", value: "common" },
5441
+ { name: "API \u63A5\u53E3\u6A21\u5757 (\u4EC5\u63A5\u53E3\u5B9A\u4E49)", value: "api" }
5442
+ ],
5443
+ default: "service"
5444
+ }
5445
+ ]);
5446
+ const defaultPackage = `${groupId}.${moduleName}`;
5447
+ const packageAnswer = await inquirer7.prompt([
5448
+ {
5449
+ type: "input",
5450
+ name: "packagePath",
5451
+ message: "\u6A21\u5757\u5305\u8DEF\u5F84:",
5452
+ default: defaultPackage,
5453
+ validate: (input) => {
5454
+ if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/.test(input)) {
5455
+ return "\u5305\u8DEF\u5F84\u683C\u5F0F\u4E0D\u6B63\u786E";
5456
+ }
5457
+ return true;
5458
+ }
5459
+ }
5460
+ ]);
5461
+ const pascalName = toPascalCase2(moduleName);
5462
+ const classAnswer = await inquirer7.prompt([
5463
+ {
5464
+ type: "input",
5465
+ name: "applicationClass",
5466
+ message: "\u542F\u52A8\u7C7B\u540D:",
5467
+ default: `${pascalName}Application`
5468
+ }
5469
+ ]);
5470
+ newModules.push({
5471
+ name: moduleName,
5472
+ type: typeAnswer.moduleType,
5473
+ packagePath: packageAnswer.packagePath,
5474
+ applicationClass: classAnswer.applicationClass
5475
+ });
5476
+ const moreAnswer = await inquirer7.prompt([
5477
+ {
5478
+ type: "confirm",
5479
+ name: "addMore",
5480
+ message: "\u662F\u5426\u8FD8\u9700\u8981\u6DFB\u52A0\u66F4\u591A\u6A21\u5757?",
5481
+ default: false
5482
+ }
5483
+ ]);
5484
+ addMore = moreAnswer.addMore;
5485
+ moduleIndex++;
5486
+ }
5487
+ logger.newLine();
5488
+ logger.info(`\u5C06\u65B0\u589E\u6A21\u5757: ${newModules.map((m) => m.name).join(", ")}`);
5489
+ logger.newLine();
5490
+ for (const module of newModules) {
5491
+ await createModuleStructure(projectPath, module, groupId);
5492
+ }
5493
+ await updateSettingsGradle(projectPath, rootProjectName, newModules);
5494
+ logger.newLine();
5495
+ logger.success("\u6A21\u5757\u521B\u5EFA\u5B8C\u6210!");
5496
+ logger.newLine();
5497
+ logger.info("\u4E0B\u4E00\u6B65:");
5498
+ logger.step("1. \u8FD0\u884C `team-cli breakdown docs/specs/xxx.md` \u62C6\u5206\u9700\u6C42");
5499
+ logger.step("2. \u8FD0\u884C `team-cli dev` \u5F00\u59CB\u5F00\u53D1");
5500
+ logger.newLine();
5501
+ } catch (error) {
5502
+ logger.error(`\u65B0\u589E\u6A21\u5757\u5931\u8D25: ${error.message}`);
5503
+ if (process.env.DEBUG) {
5504
+ console.error(error);
5505
+ }
5506
+ process.exit(1);
5507
+ }
5508
+ });
5509
+ }
5510
+ });
5511
+
5512
+ // src/commands/lint.ts
5513
+ import { Command as Command9 } from "commander";
5278
5514
  import { execa as execa3 } from "execa";
5279
5515
  var lintCommand;
5280
5516
  var init_lint = __esm({
@@ -5283,7 +5519,7 @@ var init_lint = __esm({
5283
5519
  init_esm_shims();
5284
5520
  init_utils();
5285
5521
  init_logger();
5286
- lintCommand = new Command8("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").option("--no-type-check", "\u8DF3\u8FC7 TypeScript \u7C7B\u578B\u68C0\u67E5").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
5522
+ lintCommand = new Command9("lint").option("--fix", "\u81EA\u52A8\u4FEE\u590D\u95EE\u9898").option("--no-type-check", "\u8DF3\u8FC7 TypeScript \u7C7B\u578B\u68C0\u67E5").description("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5 (\u524D\u7AEF + \u540E\u7AEF)").action(async (options) => {
5287
5523
  try {
5288
5524
  logger.header("\u4EE3\u7801\u8D28\u91CF\u68C0\u67E5");
5289
5525
  logger.newLine();
@@ -5417,7 +5653,7 @@ var init_lint = __esm({
5417
5653
  });
5418
5654
 
5419
5655
  // src/commands/status.ts
5420
- import { Command as Command9 } from "commander";
5656
+ import { Command as Command10 } from "commander";
5421
5657
  import path14 from "path";
5422
5658
  async function displayProjectInfo() {
5423
5659
  logger.info("\u9879\u76EE\u4FE1\u606F:");
@@ -5535,7 +5771,7 @@ var init_status = __esm({
5535
5771
  init_esm_shims();
5536
5772
  init_utils();
5537
5773
  init_logger();
5538
- statusCommand = new Command9("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
5774
+ statusCommand = new Command10("status").description("\u67E5\u770B\u9879\u76EE\u72B6\u6001").action(async () => {
5539
5775
  try {
5540
5776
  logger.header("\u9879\u76EE\u72B6\u6001");
5541
5777
  logger.newLine();
@@ -5561,9 +5797,9 @@ var init_status = __esm({
5561
5797
  });
5562
5798
 
5563
5799
  // src/commands/detect-deps.ts
5564
- import { Command as Command10 } from "commander";
5800
+ import { Command as Command11 } from "commander";
5565
5801
  import path15 from "path";
5566
- import inquirer7 from "inquirer";
5802
+ import inquirer8 from "inquirer";
5567
5803
  async function detectDependencies(specFile) {
5568
5804
  logger.step("\u81EA\u52A8\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB...");
5569
5805
  const projectDir = ".";
@@ -5604,7 +5840,7 @@ async function detectDependencies(specFile) {
5604
5840
  logger.step(`- ${spec}`);
5605
5841
  }
5606
5842
  logger.newLine();
5607
- const answers = await inquirer7.prompt([
5843
+ const answers = await inquirer8.prompt([
5608
5844
  {
5609
5845
  type: "confirm",
5610
5846
  name: "autoUpdate",
@@ -5776,7 +6012,7 @@ var init_detect_deps = __esm({
5776
6012
  init_esm_shims();
5777
6013
  init_utils();
5778
6014
  init_logger();
5779
- detectDepsCommand = new Command10("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
6015
+ detectDepsCommand = new Command11("detect-deps").argument("[spec-file]", "Spec \u6587\u4EF6\u8DEF\u5F84").description("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB").action(async (specFile) => {
5780
6016
  try {
5781
6017
  logger.header("\u68C0\u6D4B\u4F9D\u8D56\u5173\u7CFB");
5782
6018
  logger.newLine();
@@ -5828,7 +6064,7 @@ var init_detect_deps = __esm({
5828
6064
  });
5829
6065
 
5830
6066
  // src/commands/sync-memory.ts
5831
- import { Command as Command11 } from "commander";
6067
+ import { Command as Command12 } from "commander";
5832
6068
  import path16 from "path";
5833
6069
  async function syncFeatureInventory(aiMemoryFile, projectDir) {
5834
6070
  logger.step("\u540C\u6B65\u529F\u80FD\u6E05\u5355...");
@@ -6079,15 +6315,15 @@ async function syncTemplateVersions(aiMemoryFile, projectDir) {
6079
6315
  await replaceOrInsertSection(aiMemoryFile, "## \u6A21\u677F\u7248\u672C\u4FE1\u606F", newContent);
6080
6316
  }
6081
6317
  function extractRepoName(repository) {
6082
- let path21 = repository;
6083
- path21 = path21.replace(/^https?:\/\//, "");
6084
- path21 = path21.replace(/^git@/, "");
6085
- const parts = path21.split("/");
6318
+ let path22 = repository;
6319
+ path22 = path22.replace(/^https?:\/\//, "");
6320
+ path22 = path22.replace(/^git@/, "");
6321
+ const parts = path22.split("/");
6086
6322
  if (parts.length > 1) {
6087
- path21 = parts.slice(1).join("/");
6323
+ path22 = parts.slice(1).join("/");
6088
6324
  }
6089
- path21 = path21.replace(/\.git$/, "");
6090
- return path21;
6325
+ path22 = path22.replace(/\.git$/, "");
6326
+ return path22;
6091
6327
  }
6092
6328
  var syncMemoryCommand;
6093
6329
  var init_sync_memory = __esm({
@@ -6097,7 +6333,7 @@ var init_sync_memory = __esm({
6097
6333
  init_utils();
6098
6334
  init_logger();
6099
6335
  init_template_version();
6100
- syncMemoryCommand = new Command11("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
6336
+ syncMemoryCommand = new Command12("sync-memory").description("\u540C\u6B65 AI_MEMORY.md").action(async () => {
6101
6337
  try {
6102
6338
  logger.header("\u540C\u6B65 AI_MEMORY.md");
6103
6339
  logger.newLine();
@@ -6131,9 +6367,9 @@ var init_sync_memory = __esm({
6131
6367
  });
6132
6368
 
6133
6369
  // src/commands/check-api.ts
6134
- import { Command as Command12 } from "commander";
6370
+ import { Command as Command13 } from "commander";
6135
6371
  import path17 from "path";
6136
- import inquirer8 from "inquirer";
6372
+ import inquirer9 from "inquirer";
6137
6373
  import { Listr as Listr6 } from "listr2";
6138
6374
  async function checkApiConflicts(projectDir) {
6139
6375
  const backendDir = path17.join(projectDir, "backend");
@@ -6379,10 +6615,10 @@ function extractApisFromRegistry(registryContent) {
6379
6615
  let match;
6380
6616
  while ((match = apiRegex.exec(registryContent)) !== null) {
6381
6617
  const method = match[1];
6382
- const path21 = match[2].trim();
6618
+ const path22 = match[2].trim();
6383
6619
  const description = match[3].trim();
6384
- const key = `${method}:${path21}`;
6385
- apis.set(key, { method, path: path21, description });
6620
+ const key = `${method}:${path22}`;
6621
+ apis.set(key, { method, path: path22, description });
6386
6622
  }
6387
6623
  return apis;
6388
6624
  }
@@ -6405,7 +6641,7 @@ var init_check_api = __esm({
6405
6641
  init_esm_shims();
6406
6642
  init_utils();
6407
6643
  init_logger();
6408
- checkApiCommand = new Command12("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
6644
+ checkApiCommand = new Command13("check-api").description("API \u68C0\u67E5\uFF08\u51B2\u7A81/\u53D8\u66F4/Registry\uFF09").action(async () => {
6409
6645
  try {
6410
6646
  logger.header("API \u68C0\u67E5");
6411
6647
  logger.newLine();
@@ -6415,7 +6651,7 @@ var init_check_api = __esm({
6415
6651
  logger.info("\u8BF7\u5148\u8FD0\u884C 'team-cli init <project-name>' \u6216\u5207\u6362\u5230\u9879\u76EE\u76EE\u5F55");
6416
6652
  process.exit(1);
6417
6653
  }
6418
- const answers = await inquirer8.prompt([
6654
+ const answers = await inquirer9.prompt([
6419
6655
  {
6420
6656
  type: "list",
6421
6657
  name: "checkType",
@@ -6469,9 +6705,9 @@ var init_check_api = __esm({
6469
6705
  });
6470
6706
 
6471
6707
  // src/commands/logs.ts
6472
- import { Command as Command13 } from "commander";
6708
+ import { Command as Command14 } from "commander";
6473
6709
  import path18 from "path";
6474
- import inquirer9 from "inquirer";
6710
+ import inquirer10 from "inquirer";
6475
6711
  async function collectLogFiles(targetDir) {
6476
6712
  const logs = [];
6477
6713
  try {
@@ -6495,7 +6731,7 @@ var init_logs = __esm({
6495
6731
  init_esm_shims();
6496
6732
  init_utils();
6497
6733
  init_logger();
6498
- logsCommand = new Command13("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
6734
+ logsCommand = new Command14("logs").argument("[filter]", "\u8FC7\u6EE4\u5668 (today, --all, \u6216\u65E5\u671F YYYY-MM-DD)").description("\u67E5\u770B\u4F1A\u8BDD\u65E5\u5FD7").action(async (filter = "today") => {
6499
6735
  try {
6500
6736
  logger.header("\u4F1A\u8BDD\u65E5\u5FD7");
6501
6737
  logger.newLine();
@@ -6512,103 +6748,537 @@ var init_logs = __esm({
6512
6748
  logger.info("\u8FD0\u884C 'team-cli dev' \u540E\u4F1A\u81EA\u52A8\u751F\u6210\u65E5\u5FD7");
6513
6749
  process.exit(0);
6514
6750
  }
6515
- let targetDir = "";
6516
- let displayTitle = "";
6517
- switch (filter) {
6518
- case "":
6519
- case "today": {
6520
- const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6521
- targetDir = path18.join(sessionsDir, today);
6522
- const todayExists = await FileUtils.exists(targetDir);
6523
- if (!todayExists) {
6524
- logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
6525
- process.exit(0);
6526
- }
6527
- displayTitle = "\u663E\u793A\u4ECA\u65E5\u4F1A\u8BDD\u65E5\u5FD7:";
6528
- break;
6751
+ let targetDir = "";
6752
+ let displayTitle = "";
6753
+ switch (filter) {
6754
+ case "":
6755
+ case "today": {
6756
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6757
+ targetDir = path18.join(sessionsDir, today);
6758
+ const todayExists = await FileUtils.exists(targetDir);
6759
+ if (!todayExists) {
6760
+ logger.info("\u4ECA\u65E5\u6682\u65E0\u4F1A\u8BDD\u65E5\u5FD7");
6761
+ process.exit(0);
6762
+ }
6763
+ displayTitle = "\u663E\u793A\u4ECA\u65E5\u4F1A\u8BDD\u65E5\u5FD7:";
6764
+ break;
6765
+ }
6766
+ case "--all":
6767
+ case "-a": {
6768
+ targetDir = sessionsDir;
6769
+ displayTitle = "\u663E\u793A\u6240\u6709\u4F1A\u8BDD\u65E5\u5FD7:";
6770
+ break;
6771
+ }
6772
+ default: {
6773
+ targetDir = path18.join(sessionsDir, filter);
6774
+ const dateExists = await FileUtils.exists(targetDir);
6775
+ if (!dateExists) {
6776
+ logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
6777
+ logger.info("\u53EF\u7528\u65E5\u671F:");
6778
+ const entries = await FileUtils.findFiles("*/", sessionsDir);
6779
+ const dates = entries.slice(0, 10);
6780
+ for (const date of dates) {
6781
+ logger.info(` ${date.replace("/", "")}`);
6782
+ }
6783
+ process.exit(1);
6784
+ }
6785
+ displayTitle = `\u663E\u793A ${filter} \u7684\u4F1A\u8BDD\u65E5\u5FD7:`;
6786
+ break;
6787
+ }
6788
+ }
6789
+ logger.info(displayTitle);
6790
+ logger.newLine();
6791
+ const logs = await collectLogFiles(targetDir);
6792
+ if (logs.length === 0) {
6793
+ logger.info("\u65E0\u65E5\u5FD7\u6587\u4EF6");
6794
+ process.exit(0);
6795
+ }
6796
+ for (let i = 0; i < logs.length; i++) {
6797
+ const relPath = path18.relative(sessionsDir, logs[i]);
6798
+ logger.step(`${i + 1}) ${relPath}`);
6799
+ }
6800
+ logger.newLine();
6801
+ const answers = await inquirer10.prompt([
6802
+ {
6803
+ type: "input",
6804
+ name: "selection",
6805
+ message: "\u8F93\u5165\u7F16\u53F7\u67E5\u770B\u8BE6\u60C5 (\u6216 Enter \u9000\u51FA):",
6806
+ default: ""
6807
+ }
6808
+ ]);
6809
+ const selection = answers.selection.trim();
6810
+ if (selection === "") {
6811
+ process.exit(0);
6812
+ }
6813
+ const selectionNum = parseInt(selection, 10);
6814
+ if (isNaN(selectionNum) || selectionNum < 1 || selectionNum > logs.length) {
6815
+ logger.error("\u65E0\u6548\u7684\u9009\u62E9");
6816
+ process.exit(1);
6817
+ }
6818
+ const selectedLog = logs[selectionNum - 1];
6819
+ logger.newLine();
6820
+ logger.header("\u65E5\u5FD7\u8BE6\u60C5");
6821
+ logger.newLine();
6822
+ const content = await FileUtils.read(selectedLog);
6823
+ console.log(content);
6824
+ } catch (error) {
6825
+ logger.error(`\u67E5\u770B\u65E5\u5FD7\u5931\u8D25: ${error.message}`);
6826
+ if (process.env.DEBUG) {
6827
+ console.error(error);
6828
+ }
6829
+ process.exit(1);
6830
+ }
6831
+ });
6832
+ }
6833
+ });
6834
+
6835
+ // src/lib/user-config.ts
6836
+ var user_config_exports = {};
6837
+ __export(user_config_exports, {
6838
+ UserConfigManager: () => UserConfigManager,
6839
+ userConfigManager: () => userConfigManager
6840
+ });
6841
+ import path19 from "path";
6842
+ import os3 from "os";
6843
+ import crypto from "crypto";
6844
+ var UserConfigManager, userConfigManager;
6845
+ var init_user_config = __esm({
6846
+ "src/lib/user-config.ts"() {
6847
+ "use strict";
6848
+ init_esm_shims();
6849
+ init_utils();
6850
+ init_logger();
6851
+ UserConfigManager = class {
6852
+ configPath;
6853
+ constructor() {
6854
+ const configDir = path19.join(os3.homedir(), ".team-cli");
6855
+ this.configPath = path19.join(configDir, "config.json");
6856
+ }
6857
+ /**
6858
+ * 加载用户配置
6859
+ */
6860
+ async load() {
6861
+ try {
6862
+ const exists = await FileUtils.exists(this.configPath);
6863
+ if (!exists) {
6864
+ return null;
6865
+ }
6866
+ const content = await FileUtils.read(this.configPath);
6867
+ const config = JSON.parse(content);
6868
+ if (config.gitlab?.accessToken) {
6869
+ config.gitlab.accessToken = this.decrypt(config.gitlab.accessToken);
6870
+ }
6871
+ return config;
6872
+ } catch (error) {
6873
+ logger.debug(`\u52A0\u8F7D\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
6874
+ return null;
6875
+ }
6876
+ }
6877
+ /**
6878
+ * 保存用户配置
6879
+ */
6880
+ async save(config) {
6881
+ try {
6882
+ const configDir = path19.dirname(this.configPath);
6883
+ await FileUtils.ensureDir(configDir);
6884
+ const configToSave = JSON.parse(JSON.stringify(config));
6885
+ if (configToSave.gitlab?.accessToken) {
6886
+ configToSave.gitlab.accessToken = this.encrypt(configToSave.gitlab.accessToken);
6887
+ }
6888
+ const content = JSON.stringify(configToSave, null, 2);
6889
+ await FileUtils.write(this.configPath, content);
6890
+ } catch (error) {
6891
+ throw new Error(`\u4FDD\u5B58\u7528\u6237\u914D\u7F6E\u5931\u8D25: ${error}`);
6892
+ }
6893
+ }
6894
+ /**
6895
+ * 更新 GitLab Token
6896
+ */
6897
+ async updateGitLabToken(token, baseUrl) {
6898
+ const config = await this.load() || {
6899
+ gitlab: {
6900
+ accessToken: "",
6901
+ baseUrl: "https://gitlab.com",
6902
+ timeout: 3e4
6903
+ }
6904
+ };
6905
+ config.gitlab.accessToken = token;
6906
+ if (baseUrl) {
6907
+ config.gitlab.baseUrl = baseUrl;
6908
+ }
6909
+ await this.save(config);
6910
+ }
6911
+ /**
6912
+ * 获取 GitLab Token
6913
+ */
6914
+ async getGitLabToken() {
6915
+ const config = await this.load();
6916
+ return config?.gitlab?.accessToken || null;
6917
+ }
6918
+ /**
6919
+ * 获取 GitLab 配置
6920
+ */
6921
+ async getGitLabConfig() {
6922
+ const config = await this.load();
6923
+ return config?.gitlab || null;
6924
+ }
6925
+ /**
6926
+ * 检查是否已有配置
6927
+ */
6928
+ async hasConfig() {
6929
+ const config = await this.load();
6930
+ return config !== null && config.gitlab?.accessToken !== void 0;
6931
+ }
6932
+ /**
6933
+ * 删除配置
6934
+ */
6935
+ async removeConfig() {
6936
+ try {
6937
+ const exists = await FileUtils.exists(this.configPath);
6938
+ if (exists) {
6939
+ await FileUtils.remove(this.configPath);
6940
+ }
6941
+ } catch (error) {
6942
+ throw new Error(`\u5220\u9664\u914D\u7F6E\u5931\u8D25: ${error}`);
6943
+ }
6944
+ }
6945
+ /**
6946
+ * 简单加密 (使用机器特定密钥)
6947
+ */
6948
+ encrypt(text) {
6949
+ const key = this.getMachineKey();
6950
+ const iv = crypto.randomBytes(16);
6951
+ const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
6952
+ let encrypted = cipher.update(text, "utf8", "hex");
6953
+ encrypted += cipher.final("hex");
6954
+ return iv.toString("hex") + ":" + encrypted;
6955
+ }
6956
+ /**
6957
+ * 简单解密
6958
+ */
6959
+ decrypt(encryptedText) {
6960
+ try {
6961
+ const key = this.getMachineKey();
6962
+ const parts = encryptedText.split(":");
6963
+ const iv = Buffer.from(parts[0], "hex");
6964
+ const encrypted = parts[1];
6965
+ const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
6966
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
6967
+ decrypted += decipher.final("utf8");
6968
+ return decrypted;
6969
+ } catch {
6970
+ return encryptedText;
6971
+ }
6972
+ }
6973
+ /**
6974
+ * 获取机器特定密钥
6975
+ */
6976
+ getMachineKey() {
6977
+ const hostname = os3.hostname();
6978
+ const platform = os3.platform();
6979
+ const arch = os3.arch();
6980
+ const cpus = os3.cpus();
6981
+ const machineInfo = `${hostname}-${platform}-${arch}-${cpus[0]?.model || "unknown"}`;
6982
+ return crypto.createHash("sha256").update(machineInfo).digest();
6983
+ }
6984
+ /**
6985
+ * 获取配置目录
6986
+ */
6987
+ getConfigDir() {
6988
+ return path19.dirname(this.configPath);
6989
+ }
6990
+ /**
6991
+ * 获取配置文件路径
6992
+ */
6993
+ getConfigPath() {
6994
+ return this.configPath;
6995
+ }
6996
+ };
6997
+ userConfigManager = new UserConfigManager();
6998
+ }
6999
+ });
7000
+
7001
+ // src/lib/gitlab-api.ts
7002
+ var gitlab_api_exports = {};
7003
+ __export(gitlab_api_exports, {
7004
+ GitLabAPI: () => GitLabAPI
7005
+ });
7006
+ var GitLabAPI;
7007
+ var init_gitlab_api = __esm({
7008
+ "src/lib/gitlab-api.ts"() {
7009
+ "use strict";
7010
+ init_esm_shims();
7011
+ GitLabAPI = class {
7012
+ config;
7013
+ defaultTimeout = 3e4;
7014
+ constructor(config) {
7015
+ this.config = {
7016
+ ...config,
7017
+ timeout: config.timeout || this.defaultTimeout
7018
+ };
7019
+ }
7020
+ /**
7021
+ * 验证 Token 是否有效
7022
+ */
7023
+ async authenticate() {
7024
+ try {
7025
+ const response = await this.request("/user");
7026
+ return response.ok;
7027
+ } catch (error) {
7028
+ return false;
7029
+ }
7030
+ }
7031
+ /**
7032
+ * 列出项目的所有 Tags
7033
+ */
7034
+ async listTags(projectPath) {
7035
+ try {
7036
+ const encodedPath = this.encodeProjectPath(projectPath);
7037
+ const response = await this.request(`/projects/${encodedPath}/repository/tags?per_page=100`);
7038
+ if (!response.ok) {
7039
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
7040
+ }
7041
+ const tags = await response.json();
7042
+ return tags.sort((a, b) => {
7043
+ return this.compareVersionStrings(b.name, a.name);
7044
+ });
7045
+ } catch (error) {
7046
+ throw new Error(`\u83B7\u53D6 Tags \u5931\u8D25: ${error}`);
7047
+ }
7048
+ }
7049
+ /**
7050
+ * 列出项目的所有 Branches
7051
+ */
7052
+ async listBranches(projectPath) {
7053
+ try {
7054
+ const encodedPath = this.encodeProjectPath(projectPath);
7055
+ const response = await this.request(`/projects/${encodedPath}/repository/branches?per_page=100`);
7056
+ if (!response.ok) {
7057
+ throw new Error(`GitLab API \u8BF7\u6C42\u5931\u8D25: ${response.status}`);
7058
+ }
7059
+ const branches = await response.json();
7060
+ return branches.sort((a, b) => {
7061
+ if (a.default) return -1;
7062
+ if (b.default) return 1;
7063
+ return a.name.localeCompare(b.name);
7064
+ });
7065
+ } catch (error) {
7066
+ throw new Error(`\u83B7\u53D6 Branches \u5931\u8D25: ${error}`);
7067
+ }
7068
+ }
7069
+ /**
7070
+ * 验证 Tag 是否存在
7071
+ */
7072
+ async validateTag(projectPath, tag) {
7073
+ try {
7074
+ const tags = await this.listTags(projectPath);
7075
+ return tags.some((t) => t.name === tag);
7076
+ } catch {
7077
+ return false;
7078
+ }
7079
+ }
7080
+ /**
7081
+ * 验证 Branch 是否存在
7082
+ */
7083
+ async validateBranch(projectPath, branch) {
7084
+ try {
7085
+ const branches = await this.listBranches(projectPath);
7086
+ return branches.some((b) => b.name === branch);
7087
+ } catch {
7088
+ return false;
7089
+ }
7090
+ }
7091
+ /**
7092
+ * 获取项目信息
7093
+ */
7094
+ async getProject(projectPath) {
7095
+ try {
7096
+ const encodedPath = this.encodeProjectPath(projectPath);
7097
+ const response = await this.request(`/projects/${encodedPath}`);
7098
+ if (!response.ok) {
7099
+ return null;
6529
7100
  }
6530
- case "--all":
6531
- case "-a": {
6532
- targetDir = sessionsDir;
6533
- displayTitle = "\u663E\u793A\u6240\u6709\u4F1A\u8BDD\u65E5\u5FD7:";
6534
- break;
7101
+ return await response.json();
7102
+ } catch {
7103
+ return null;
7104
+ }
7105
+ }
7106
+ /**
7107
+ * 获取指定 Tag 的 commit 信息
7108
+ */
7109
+ async getTagCommit(projectPath, tag) {
7110
+ try {
7111
+ const tags = await this.listTags(projectPath);
7112
+ const targetTag = tags.find((t) => t.name === tag);
7113
+ return targetTag?.commit.id || null;
7114
+ } catch {
7115
+ return null;
7116
+ }
7117
+ }
7118
+ /**
7119
+ * 获取指定 Branch 的最新 commit 信息
7120
+ */
7121
+ async getBranchCommit(projectPath, branch) {
7122
+ try {
7123
+ const encodedPath = this.encodeProjectPath(projectPath);
7124
+ const response = await this.request(
7125
+ `/projects/${encodedPath}/repository/branches/${encodeURIComponent(branch)}`
7126
+ );
7127
+ if (!response.ok) {
7128
+ return null;
6535
7129
  }
6536
- default: {
6537
- targetDir = path18.join(sessionsDir, filter);
6538
- const dateExists = await FileUtils.exists(targetDir);
6539
- if (!dateExists) {
6540
- logger.error(`\u672A\u627E\u5230\u65E5\u671F '${filter}' \u7684\u65E5\u5FD7`);
6541
- logger.info("\u53EF\u7528\u65E5\u671F:");
6542
- const entries = await FileUtils.findFiles("*/", sessionsDir);
6543
- const dates = entries.slice(0, 10);
6544
- for (const date of dates) {
6545
- logger.info(` ${date.replace("/", "")}`);
6546
- }
6547
- process.exit(1);
6548
- }
6549
- displayTitle = `\u663E\u793A ${filter} \u7684\u4F1A\u8BDD\u65E5\u5FD7:`;
6550
- break;
7130
+ const branchInfo = await response.json();
7131
+ return branchInfo.commit.id;
7132
+ } catch {
7133
+ return null;
7134
+ }
7135
+ }
7136
+ /**
7137
+ * 对比两个版本之间的差异
7138
+ */
7139
+ async compareVersions(projectPath, from, to) {
7140
+ try {
7141
+ const encodedPath = this.encodeProjectPath(projectPath);
7142
+ const response = await this.request(
7143
+ `/projects/${encodedPath}/repository/compare?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`
7144
+ );
7145
+ if (!response.ok) {
7146
+ return null;
6551
7147
  }
7148
+ return await response.json();
7149
+ } catch {
7150
+ return null;
6552
7151
  }
6553
- logger.info(displayTitle);
6554
- logger.newLine();
6555
- const logs = await collectLogFiles(targetDir);
6556
- if (logs.length === 0) {
6557
- logger.info("\u65E0\u65E5\u5FD7\u6587\u4EF6");
6558
- process.exit(0);
7152
+ }
7153
+ /**
7154
+ * 比较两个版本号(用于排序)
7155
+ * 返回值: -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2)
7156
+ */
7157
+ compareVersionStrings(v1, v2) {
7158
+ const version1 = v1.replace(/^v/, "");
7159
+ const version2 = v2.replace(/^v/, "");
7160
+ const parts1 = version1.split(".").map((p) => parseInt(p, 10) || 0);
7161
+ const parts2 = version2.split(".").map((p) => parseInt(p, 10) || 0);
7162
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
7163
+ const p1 = parts1[i] || 0;
7164
+ const p2 = parts2[i] || 0;
7165
+ if (p1 > p2) return 1;
7166
+ if (p1 < p2) return -1;
6559
7167
  }
6560
- for (let i = 0; i < logs.length; i++) {
6561
- const relPath = path18.relative(sessionsDir, logs[i]);
6562
- logger.step(`${i + 1}) ${relPath}`);
7168
+ return 0;
7169
+ }
7170
+ /**
7171
+ * 获取最新的版本(优先 Tag,否则 latest commit)
7172
+ */
7173
+ async getLatestVersion(projectPath) {
7174
+ try {
7175
+ const tags = await this.listTags(projectPath);
7176
+ const branches = await this.listBranches(projectPath);
7177
+ if (tags.length > 0) {
7178
+ const latestTag = tags[0];
7179
+ return {
7180
+ type: "tag",
7181
+ name: latestTag.name,
7182
+ commit: latestTag.commit.id
7183
+ };
7184
+ }
7185
+ const defaultBranch = branches.find((b) => b.default);
7186
+ if (defaultBranch) {
7187
+ return {
7188
+ type: "commit",
7189
+ name: defaultBranch.name,
7190
+ commit: defaultBranch.commit.id
7191
+ };
7192
+ }
7193
+ return null;
7194
+ } catch {
7195
+ return null;
6563
7196
  }
6564
- logger.newLine();
6565
- const answers = await inquirer9.prompt([
6566
- {
6567
- type: "input",
6568
- name: "selection",
6569
- message: "\u8F93\u5165\u7F16\u53F7\u67E5\u770B\u8BE6\u60C5 (\u6216 Enter \u9000\u51FA):",
6570
- default: ""
7197
+ }
7198
+ /**
7199
+ * 解析项目路径
7200
+ * 从 Git URL 中提取项目路径
7201
+ */
7202
+ static parseProjectPath(repository) {
7203
+ let path22 = repository;
7204
+ if (path22.startsWith("git@")) {
7205
+ path22 = path22.replace(/^git@/, "");
7206
+ const colonIndex = path22.indexOf(":");
7207
+ if (colonIndex !== -1) {
7208
+ path22 = path22.substring(colonIndex + 1);
7209
+ }
7210
+ } else {
7211
+ path22 = path22.replace(/^https?:\/\//, "");
7212
+ const parts = path22.split("/");
7213
+ if (parts.length > 1) {
7214
+ path22 = parts.slice(1).join("/");
6571
7215
  }
6572
- ]);
6573
- const selection = answers.selection.trim();
6574
- if (selection === "") {
6575
- process.exit(0);
6576
7216
  }
6577
- const selectionNum = parseInt(selection, 10);
6578
- if (isNaN(selectionNum) || selectionNum < 1 || selectionNum > logs.length) {
6579
- logger.error("\u65E0\u6548\u7684\u9009\u62E9");
6580
- process.exit(1);
7217
+ path22 = path22.replace(/\.git$/, "");
7218
+ return path22;
7219
+ }
7220
+ /**
7221
+ * 编码项目路径用于 API 请求
7222
+ */
7223
+ encodeProjectPath(projectPath) {
7224
+ return encodeURIComponent(projectPath).replace(/%2F/g, "%2F");
7225
+ }
7226
+ /**
7227
+ * 发送 HTTP 请求
7228
+ */
7229
+ async request(endpoint, options = {}) {
7230
+ const url = `${this.config.baseUrl}/api/v4${endpoint}`;
7231
+ const headers = {
7232
+ "PRIVATE-TOKEN": this.config.accessToken,
7233
+ "Content-Type": "application/json",
7234
+ ...options.headers
7235
+ };
7236
+ const controller = new AbortController();
7237
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
7238
+ try {
7239
+ const response = await fetch(url, {
7240
+ ...options,
7241
+ headers,
7242
+ signal: controller.signal
7243
+ });
7244
+ return response;
7245
+ } catch (error) {
7246
+ if (error.name === "AbortError") {
7247
+ throw new Error("\u8BF7\u6C42\u8D85\u65F6");
7248
+ }
7249
+ throw error;
7250
+ } finally {
7251
+ clearTimeout(timeoutId);
6581
7252
  }
6582
- const selectedLog = logs[selectionNum - 1];
6583
- logger.newLine();
6584
- logger.header("\u65E5\u5FD7\u8BE6\u60C5");
6585
- logger.newLine();
6586
- const content = await FileUtils.read(selectedLog);
6587
- console.log(content);
6588
- } catch (error) {
6589
- logger.error(`\u67E5\u770B\u65E5\u5FD7\u5931\u8D25: ${error.message}`);
6590
- if (process.env.DEBUG) {
6591
- console.error(error);
7253
+ }
7254
+ /**
7255
+ * 获取默认分支
7256
+ */
7257
+ async getDefaultBranch(projectPath) {
7258
+ try {
7259
+ const project = await this.getProject(projectPath);
7260
+ return project?.default_branch || null;
7261
+ } catch {
7262
+ return null;
6592
7263
  }
6593
- process.exit(1);
6594
7264
  }
6595
- });
7265
+ };
6596
7266
  }
6597
7267
  });
6598
7268
 
6599
7269
  // src/commands/update.ts
6600
- import { Command as Command14 } from "commander";
6601
- import path19 from "path";
7270
+ import { Command as Command15 } from "commander";
7271
+ import path20 from "path";
6602
7272
  import { execa as execa4 } from "execa";
6603
- import inquirer10 from "inquirer";
6604
- import fs4 from "fs-extra";
7273
+ import inquirer11 from "inquirer";
7274
+ import fs5 from "fs-extra";
6605
7275
  async function performUpdate(projectPath, updates) {
6606
7276
  logger.newLine();
6607
7277
  logger.info("\u5F00\u59CB\u66F4\u65B0\u6A21\u677F...");
6608
7278
  for (const update of updates) {
6609
7279
  const { type, info, updateOptions } = update;
6610
7280
  const targetDir = type === "frontend" ? "frontend" : "backend";
6611
- const targetPath = path19.join(projectPath, targetDir);
7281
+ const targetPath = path20.join(projectPath, targetDir);
6612
7282
  logger.newLine();
6613
7283
  logger.step(`\u66F4\u65B0 ${type === "frontend" ? "\u524D\u7AEF" : "\u540E\u7AEF"}\u6A21\u677F...`);
6614
7284
  if (updateOptions?.tag || updateOptions?.branch) {
@@ -6639,8 +7309,8 @@ async function performUpdate(projectPath, updates) {
6639
7309
  }
6640
7310
  }
6641
7311
  const ref = updateOptions?.tag || updateOptions?.branch || "HEAD";
6642
- const backupDir = path19.join(projectPath, `.backup-${Date.now()}`);
6643
- await fs4.copy(targetPath, path19.join(backupDir, targetDir));
7312
+ const backupDir = path20.join(projectPath, `.backup-${Date.now()}`);
7313
+ await fs5.copy(targetPath, path20.join(backupDir, targetDir));
6644
7314
  logger.info(`\u5DF2\u521B\u5EFA\u5907\u4EFD: ${backupDir}`);
6645
7315
  if (updateOptions?.dryRun) {
6646
7316
  logger.info("[Dry Run] \u5C06\u4F1A\u66F4\u65B0\u5230\u4EE5\u4E0B\u7248\u672C:");
@@ -6650,7 +7320,7 @@ async function performUpdate(projectPath, updates) {
6650
7320
  continue;
6651
7321
  }
6652
7322
  try {
6653
- const tempDir = path19.join(projectPath, `.template-update-${Date.now()}`);
7323
+ const tempDir = path20.join(projectPath, `.template-update-${Date.now()}`);
6654
7324
  await execa4("git", ["clone", "--depth=1", "--branch", ref, info.repository, tempDir], {
6655
7325
  stdio: "pipe"
6656
7326
  });
@@ -6667,17 +7337,17 @@ async function performUpdate(projectPath, updates) {
6667
7337
  const currentFiles = await FileUtils.findFiles("*", targetPath);
6668
7338
  for (const file of currentFiles) {
6669
7339
  if (!keepFiles.includes(file)) {
6670
- const filePath = path19.join(targetPath, file);
7340
+ const filePath = path20.join(targetPath, file);
6671
7341
  try {
6672
- await fs4.remove(filePath);
7342
+ await fs5.remove(filePath);
6673
7343
  } catch {
6674
7344
  }
6675
7345
  }
6676
7346
  }
6677
- await fs4.copy(tempDir, targetPath, {
7347
+ await fs5.copy(tempDir, targetPath, {
6678
7348
  filter: (src) => !src.includes(".git")
6679
7349
  });
6680
- await fs4.remove(tempDir);
7350
+ await fs5.remove(tempDir);
6681
7351
  await updateTemplateVersion(projectPath, type, commit.trim(), {
6682
7352
  tag: updateOptions?.tag || latestTag,
6683
7353
  branch: updateOptions?.branch
@@ -6688,9 +7358,9 @@ async function performUpdate(projectPath, updates) {
6688
7358
  } catch (error) {
6689
7359
  logger.error(`\u66F4\u65B0\u5931\u8D25: ${error.message}`);
6690
7360
  logger.info("\u6B63\u5728\u6062\u590D\u5907\u4EFD...");
6691
- await fs4.remove(targetPath);
6692
- await fs4.copy(path19.join(backupDir, targetDir), targetPath);
6693
- await fs4.remove(backupDir);
7361
+ await fs5.remove(targetPath);
7362
+ await fs5.copy(path20.join(backupDir, targetDir), targetPath);
7363
+ await fs5.remove(backupDir);
6694
7364
  logger.info("\u5DF2\u6062\u590D\u5230\u66F4\u65B0\u524D\u7684\u72B6\u6001");
6695
7365
  }
6696
7366
  }
@@ -6712,7 +7382,7 @@ var init_update = __esm({
6712
7382
  init_template_version();
6713
7383
  init_logger();
6714
7384
  init_utils();
6715
- updateCommand = new Command14("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) => {
7385
+ updateCommand = new Command15("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) => {
6716
7386
  try {
6717
7387
  logger.header("\u6A21\u677F\u7248\u672C\u68C0\u67E5");
6718
7388
  logger.newLine();
@@ -6784,7 +7454,7 @@ var init_update = __esm({
6784
7454
  logger.info("Dry run \u6A21\u5F0F\uFF0C\u4E0D\u6267\u884C\u5B9E\u9645\u66F4\u65B0");
6785
7455
  return;
6786
7456
  }
6787
- const answers = await inquirer10.prompt([
7457
+ const answers = await inquirer11.prompt([
6788
7458
  {
6789
7459
  type: "confirm",
6790
7460
  name: "shouldUpdate",
@@ -6809,8 +7479,8 @@ var init_update = __esm({
6809
7479
  });
6810
7480
 
6811
7481
  // src/commands/config.ts
6812
- import { Command as Command15 } from "commander";
6813
- import inquirer11 from "inquirer";
7482
+ import { Command as Command16 } from "commander";
7483
+ import inquirer12 from "inquirer";
6814
7484
  import chalk2 from "chalk";
6815
7485
  var setTokenCommand, showConfigCommand, removeConfigCommand, validateTokenCommand, configCommand;
6816
7486
  var init_config = __esm({
@@ -6820,13 +7490,13 @@ var init_config = __esm({
6820
7490
  init_user_config();
6821
7491
  init_gitlab_api();
6822
7492
  init_logger();
6823
- setTokenCommand = new Command15("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) => {
7493
+ setTokenCommand = new Command16("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) => {
6824
7494
  try {
6825
7495
  logger.header("GitLab Access Token \u914D\u7F6E");
6826
7496
  logger.newLine();
6827
7497
  let { token, url } = options;
6828
7498
  if (!token) {
6829
- const answers = await inquirer11.prompt([
7499
+ const answers = await inquirer12.prompt([
6830
7500
  {
6831
7501
  type: "password",
6832
7502
  name: "token",
@@ -6888,7 +7558,7 @@ var init_config = __esm({
6888
7558
  process.exit(1);
6889
7559
  }
6890
7560
  });
6891
- showConfigCommand = new Command15("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
7561
+ showConfigCommand = new Command16("show").description("\u663E\u793A\u5F53\u524D\u914D\u7F6E").action(async () => {
6892
7562
  try {
6893
7563
  logger.header("GitLab \u914D\u7F6E");
6894
7564
  logger.newLine();
@@ -6927,14 +7597,14 @@ var init_config = __esm({
6927
7597
  process.exit(1);
6928
7598
  }
6929
7599
  });
6930
- removeConfigCommand = new Command15("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
7600
+ removeConfigCommand = new Command16("remove").alias("rm").description("\u5220\u9664 GitLab \u914D\u7F6E").action(async () => {
6931
7601
  try {
6932
7602
  const hasConfig = await userConfigManager.hasConfig();
6933
7603
  if (!hasConfig) {
6934
7604
  logger.warn("\u672A\u914D\u7F6E GitLab Access Token");
6935
7605
  return;
6936
7606
  }
6937
- const answers = await inquirer11.prompt([
7607
+ const answers = await inquirer12.prompt([
6938
7608
  {
6939
7609
  type: "confirm",
6940
7610
  name: "confirm",
@@ -6956,7 +7626,7 @@ var init_config = __esm({
6956
7626
  process.exit(1);
6957
7627
  }
6958
7628
  });
6959
- validateTokenCommand = new Command15("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
7629
+ validateTokenCommand = new Command16("validate").alias("test").description("\u9A8C\u8BC1\u5F53\u524D Token \u662F\u5426\u6709\u6548").action(async () => {
6960
7630
  try {
6961
7631
  logger.header("\u9A8C\u8BC1 GitLab Token");
6962
7632
  logger.newLine();
@@ -6986,12 +7656,12 @@ var init_config = __esm({
6986
7656
  process.exit(1);
6987
7657
  }
6988
7658
  });
6989
- configCommand = new Command15("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
7659
+ configCommand = new Command16("config").description("\u7BA1\u7406 GitLab \u914D\u7F6E").addCommand(setTokenCommand).addCommand(showConfigCommand).addCommand(removeConfigCommand).addCommand(validateTokenCommand);
6990
7660
  }
6991
7661
  });
6992
7662
 
6993
7663
  // src/commands/diff.ts
6994
- import { Command as Command16 } from "commander";
7664
+ import { Command as Command17 } from "commander";
6995
7665
  import chalk3 from "chalk";
6996
7666
  async function compareTemplate(projectPath, type, localConfig, remoteTag, remoteBranch, gitlabConfig) {
6997
7667
  try {
@@ -7170,7 +7840,7 @@ var init_diff = __esm({
7170
7840
  init_utils();
7171
7841
  init_user_config();
7172
7842
  init_gitlab_api();
7173
- diffCommand = new Command16("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) => {
7843
+ diffCommand = new Command17("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) => {
7174
7844
  try {
7175
7845
  logger.header("\u6A21\u677F\u7248\u672C\u5BF9\u6BD4");
7176
7846
  logger.newLine();
@@ -7292,10 +7962,10 @@ var init_diff = __esm({
7292
7962
 
7293
7963
  // src/index.ts
7294
7964
  var index_exports = {};
7295
- import { Command as Command17 } from "commander";
7965
+ import { Command as Command18 } from "commander";
7296
7966
  import chalk4 from "chalk";
7297
- import fs5 from "fs-extra";
7298
- import path20 from "path";
7967
+ import fs6 from "fs-extra";
7968
+ import path21 from "path";
7299
7969
  import { fileURLToPath as fileURLToPath2 } from "url";
7300
7970
  function showHelp() {
7301
7971
  console.log("");
@@ -7307,6 +7977,7 @@ function showHelp() {
7307
7977
  console.log(" team-cli breakdown [spec-file] \u5C06 spec \u62C6\u5206\u4E3A milestones \u548C todos");
7308
7978
  console.log(" team-cli dev \u5F00\u53D1\u6A21\u5F0F\uFF0C\u6267\u884C\u5177\u4F53\u4EFB\u52A1");
7309
7979
  console.log(" team-cli accept [spec-file] \u9A8C\u6536\u529F\u80FD\uFF0C\u8D70\u67E5\u6240\u6709\u9700\u6C42");
7980
+ console.log(" team-cli add-module \u4E3A\u5DF2\u6709\u9879\u76EE\u65B0\u589E\u6A21\u5757");
7310
7981
  console.log(" team-cli add-feature <name> \u6DFB\u52A0\u65B0\u529F\u80FD");
7311
7982
  console.log(" team-cli bugfix \u521B\u5EFA Bugfix \u8BB0\u5F55");
7312
7983
  console.log(" team-cli hotfix \u521B\u5EFA Hotfix");
@@ -7367,6 +8038,7 @@ var init_index = __esm({
7367
8038
  init_bugfix();
7368
8039
  init_bugfix();
7369
8040
  init_accept();
8041
+ init_add_module();
7370
8042
  init_lint();
7371
8043
  init_status();
7372
8044
  init_detect_deps();
@@ -7376,9 +8048,9 @@ var init_index = __esm({
7376
8048
  init_update();
7377
8049
  init_config();
7378
8050
  init_diff();
7379
- __dirname2 = path20.dirname(fileURLToPath2(import.meta.url));
7380
- pkg = fs5.readJsonSync(path20.join(__dirname2, "../package.json"));
7381
- program = new Command17();
8051
+ __dirname2 = path21.dirname(fileURLToPath2(import.meta.url));
8052
+ pkg = fs6.readJsonSync(path21.join(__dirname2, "../package.json"));
8053
+ program = new Command18();
7382
8054
  program.name("team-cli").description("AI-Native \u56E2\u961F\u7814\u53D1\u811A\u624B\u67B6").version(pkg.version);
7383
8055
  program.option("-v, --verbose", "\u8BE6\u7EC6\u8F93\u51FA\u6A21\u5F0F").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F");
7384
8056
  program.addCommand(initCommand);
@@ -7389,6 +8061,7 @@ var init_index = __esm({
7389
8061
  program.addCommand(bugfixCommand);
7390
8062
  program.addCommand(hotfixCommand);
7391
8063
  program.addCommand(acceptCommand);
8064
+ program.addCommand(addModuleCommand);
7392
8065
  program.addCommand(lintCommand);
7393
8066
  program.addCommand(statusCommand);
7394
8067
  program.addCommand(detectDepsCommand);