@zjex/git-workflow 0.4.6 → 0.4.7

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/index.js CHANGED
@@ -84,7 +84,8 @@ function execAsync(command, spinner) {
84
84
  ${stdoutOutput}`));
85
85
  }
86
86
  if (errorOutput) {
87
- console.log(colors.dim(`[DEBUG] \u9519\u8BEF\u8F93\u51FA:
87
+ const label = code === 0 ? "\u8F93\u51FA\u4FE1\u606F" : "\u9519\u8BEF\u8F93\u51FA";
88
+ console.log(colors.dim(`[DEBUG] ${label}:
88
89
  ${errorOutput}`));
89
90
  }
90
91
  }
@@ -1059,6 +1060,25 @@ async function createTag(inputPrefix) {
1059
1060
  }
1060
1061
  async function doCreateTag(tagName) {
1061
1062
  divider();
1063
+ const hasCommits = execOutput("git rev-parse HEAD 2>/dev/null");
1064
+ if (!hasCommits) {
1065
+ console.log(colors.red("\u5F53\u524D\u4ED3\u5E93\u6CA1\u6709\u4EFB\u4F55\u63D0\u4EA4"));
1066
+ console.log("");
1067
+ console.log(colors.dim(" \u63D0\u793A: \u9700\u8981\u5148\u521B\u5EFA\u81F3\u5C11\u4E00\u4E2A\u63D0\u4EA4\u624D\u80FD\u6253 tag:"));
1068
+ console.log(colors.cyan(" git add ."));
1069
+ console.log(colors.cyan(' git commit -m "Initial commit"'));
1070
+ console.log(colors.cyan(" gw tag"));
1071
+ return;
1072
+ }
1073
+ const existingTags = execOutput("git tag -l").split("\n").filter(Boolean);
1074
+ if (existingTags.includes(tagName)) {
1075
+ console.log(colors.red(`Tag ${tagName} \u5DF2\u5B58\u5728`));
1076
+ console.log("");
1077
+ console.log(colors.dim(" \u63D0\u793A: \u5982\u9700\u91CD\u65B0\u521B\u5EFA\uFF0C\u8BF7\u5148\u5220\u9664\u65E7 tag:"));
1078
+ console.log(colors.cyan(` git tag -d ${tagName}`));
1079
+ console.log(colors.cyan(` git push origin --delete ${tagName}`));
1080
+ return;
1081
+ }
1062
1082
  const spinner = ora2(`\u6B63\u5728\u521B\u5EFA tag: ${tagName}`).start();
1063
1083
  const success = await execWithSpinner(
1064
1084
  `git tag -a "${tagName}" -m "Release ${tagName}"`,
@@ -1206,9 +1226,12 @@ async function updateTag() {
1206
1226
  spinner.fail("tag \u91CD\u547D\u540D\u5931\u8D25");
1207
1227
  return;
1208
1228
  }
1209
- const deleteSuccess = await execAsync(`git tag -d "${oldTag}"`, spinner);
1210
- if (!deleteSuccess) {
1229
+ const deleteResult = await execAsync(`git tag -d "${oldTag}"`, spinner);
1230
+ if (!deleteResult.success) {
1211
1231
  spinner.fail("\u5220\u9664\u65E7 tag \u5931\u8D25");
1232
+ if (deleteResult.error) {
1233
+ console.log(colors.dim(` ${deleteResult.error}`));
1234
+ }
1212
1235
  return;
1213
1236
  }
1214
1237
  spinner.succeed(`Tag \u5DF2\u91CD\u547D\u540D: ${oldTag} \u2192 ${newTag}`);
@@ -1222,26 +1245,32 @@ async function updateTag() {
1222
1245
  });
1223
1246
  if (pushRemote) {
1224
1247
  const pushSpinner = ora2("\u6B63\u5728\u540C\u6B65\u5230\u8FDC\u7A0B...").start();
1225
- const pushNewSuccess = await execAsync(
1248
+ const pushNewResult = await execAsync(
1226
1249
  `git push origin "${newTag}"`,
1227
1250
  pushSpinner
1228
1251
  );
1229
- if (!pushNewSuccess) {
1252
+ if (!pushNewResult.success) {
1230
1253
  pushSpinner.warn(
1231
1254
  `\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C:
1232
1255
  git push origin ${newTag}
1233
1256
  git push origin --delete ${oldTag}`
1234
1257
  );
1258
+ if (pushNewResult.error) {
1259
+ console.log(colors.dim(` ${pushNewResult.error}`));
1260
+ }
1235
1261
  return;
1236
1262
  }
1237
- const deleteOldSuccess = await execAsync(
1263
+ const deleteOldResult = await execAsync(
1238
1264
  `git push origin --delete "${oldTag}"`,
1239
1265
  pushSpinner
1240
1266
  );
1241
- if (!deleteOldSuccess) {
1267
+ if (!deleteOldResult.success) {
1242
1268
  pushSpinner.warn(
1243
1269
  `\u8FDC\u7A0B\u65E7 tag \u5220\u9664\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C: git push origin --delete ${oldTag}`
1244
1270
  );
1271
+ if (deleteOldResult.error) {
1272
+ console.log(colors.dim(` ${deleteOldResult.error}`));
1273
+ }
1245
1274
  return;
1246
1275
  }
1247
1276
  pushSpinner.succeed(`\u8FDC\u7A0B tag \u5DF2\u540C\u6B65: ${oldTag} \u2192 ${newTag}`);
@@ -1294,8 +1323,8 @@ async function cleanInvalidTags() {
1294
1323
  let localSuccess = 0;
1295
1324
  let localFailed = 0;
1296
1325
  for (const tag of invalidTags) {
1297
- const success = await execAsync(`git tag -d "${tag}"`, localSpinner);
1298
- if (success) {
1326
+ const result = await execAsync(`git tag -d "${tag}"`, localSpinner);
1327
+ if (result.success) {
1299
1328
  localSuccess++;
1300
1329
  } else {
1301
1330
  localFailed++;
@@ -1321,11 +1350,11 @@ async function cleanInvalidTags() {
1321
1350
  let remoteSuccess = 0;
1322
1351
  let remoteFailed = 0;
1323
1352
  for (const tag of invalidTags) {
1324
- const success = await execAsync(
1353
+ const result = await execAsync(
1325
1354
  `git push origin --delete "${tag}"`,
1326
1355
  remoteSpinner
1327
1356
  );
1328
- if (success) {
1357
+ if (result.success) {
1329
1358
  remoteSuccess++;
1330
1359
  } else {
1331
1360
  remoteFailed++;
@@ -2806,16 +2835,28 @@ function clearUpdateCache2() {
2806
2835
  }
2807
2836
  }
2808
2837
  async function getLatestVersion2(packageName) {
2809
- try {
2810
- const result = execSync4(`npm view ${packageName} version`, {
2811
- encoding: "utf-8",
2812
- timeout: 3e3,
2813
- stdio: ["pipe", "pipe", "ignore"]
2838
+ return new Promise((resolve) => {
2839
+ const npmView = spawn3("npm", ["view", packageName, "version"], {
2840
+ stdio: ["ignore", "pipe", "ignore"],
2841
+ timeout: 5e3
2814
2842
  });
2815
- return result.trim();
2816
- } catch {
2817
- return null;
2818
- }
2843
+ let output = "";
2844
+ if (npmView.stdout) {
2845
+ npmView.stdout.on("data", (data) => {
2846
+ output += data.toString();
2847
+ });
2848
+ }
2849
+ npmView.on("close", (code) => {
2850
+ if (code === 0 && output.trim()) {
2851
+ resolve(output.trim());
2852
+ } else {
2853
+ resolve(null);
2854
+ }
2855
+ });
2856
+ npmView.on("error", () => {
2857
+ resolve(null);
2858
+ });
2859
+ });
2819
2860
  }
2820
2861
  function isUsingVolta2() {
2821
2862
  try {
@@ -3586,7 +3627,7 @@ process.on("SIGTERM", () => {
3586
3627
  console.log("");
3587
3628
  process.exit(0);
3588
3629
  });
3589
- var version = true ? "0.4.6" : "0.0.0-dev";
3630
+ var version = true ? "0.4.7" : "0.0.0-dev";
3590
3631
  async function mainMenu() {
3591
3632
  console.log(
3592
3633
  colors.green(`
@@ -54,6 +54,8 @@ export default defineConfig({
54
54
  items: [
55
55
  { text: "开发指南", link: "/guide/development" },
56
56
  { text: "测试指南", link: "/guide/testing" },
57
+ { text: "Debug 模式", link: "/guide/debug-mode" },
58
+ { text: "命令引号处理", link: "/guide/command-quotes-handling" },
57
59
  { text: "API 文档", link: "/guide/api" },
58
60
  { text: "贡献指南", link: "/guide/contributing" },
59
61
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zjex/git-workflow",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "🚀 极简的 Git 工作流 CLI 工具,让分支管理和版本发布变得轻松愉快",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@ for (let i = 0; i < tags.length; i++) {
30
30
  if (!previousTag) break;
31
31
 
32
32
  changelog += `## [${currentTag}](https://github.com/iamzjt-front-end/git-workflow/compare/${previousTag}...${currentTag}) (${getTagDate(
33
- currentTag
33
+ currentTag,
34
34
  )})\n\n`;
35
35
 
36
36
  // 获取该版本的提交
@@ -39,79 +39,30 @@ for (let i = 0; i < tags.length; i++) {
39
39
  {
40
40
  encoding: "utf8",
41
41
  env: { ...process.env, LANG: "zh_CN.UTF-8", LC_ALL: "zh_CN.UTF-8" },
42
- }
42
+ },
43
43
  )
44
44
  .trim()
45
45
  .split("\n")
46
46
  .filter(Boolean);
47
47
 
48
- // 按类型分组
49
- const groups = {
50
- " Features": [],
51
- "🐛 Bug Fixes": [],
52
- "📖 Documentation": [],
53
- "🎨 Styles": [],
54
- "♻️ Refactors": [],
55
- "⚡ Performance": [],
56
- "✅ Tests": [],
57
- "🔧 Chore": [],
58
- "🤖 CI": [],
48
+ // 格式化消息:处理多行内容(用 - 分隔的子任务)
49
+ const formatMessage = (msg, commitLink) => {
50
+ const parts = msg.split(" - ");
51
+ if (parts.length === 1) {
52
+ // 没有子任务,直接返回
53
+ return `${msg} ${commitLink}`;
54
+ }
55
+ // 有子任务,主标题后面加 commit hash,子任务换行缩进
56
+ const mainTitle = parts[0];
57
+ const subTasks = parts.slice(1);
58
+ return `${mainTitle} ${commitLink}\n - ${subTasks.join("\n - ")}`;
59
59
  };
60
60
 
61
+ // 直接输出提交信息,不分组
61
62
  commits.forEach((commit) => {
62
63
  const [message, hash] = commit.split("|");
63
64
  const link = `([${hash}](https://github.com/iamzjt-front-end/git-workflow/commit/${hash}))`;
64
-
65
- if (message.match(/^(feat|✨)/i)) {
66
- groups["✨ Features"].push(
67
- `- ${message.replace(/^(feat|✨)[:(]\w*\)?:?\s*/i, "")} ${link}`
68
- );
69
- } else if (message.match(/^(fix|🐛)/i)) {
70
- groups["🐛 Bug Fixes"].push(
71
- `- ${message.replace(/^(fix|🐛)[:(]\w*\)?:?\s*/i, "")} ${link}`
72
- );
73
- } else if (message.match(/^(docs|📖|📝)/i)) {
74
- groups["📖 Documentation"].push(
75
- `- ${message.replace(/^(docs|📖|📝)[:(]\w*\)?:?\s*/i, "")} ${link}`
76
- );
77
- } else if (message.match(/^(style|🎨)/i)) {
78
- groups["🎨 Styles"].push(
79
- `- ${message.replace(/^(style|🎨)[:(]\w*\)?:?\s*/i, "")} ${link}`
80
- );
81
- } else if (message.match(/^(refactor|♻️)/i)) {
82
- groups["♻️ Refactors"].push(
83
- `- ${message.replace(/^(refactor|♻️)[:(]\w*\)?:?\s*/i, "")} ${link}`
84
- );
85
- } else if (message.match(/^(perf|⚡)/i)) {
86
- groups["⚡ Performance"].push(
87
- `- ${message.replace(/^(perf|⚡)[:(]\w*\)?:?\s*/i, "")} ${link}`
88
- );
89
- } else if (message.match(/^(test|✅)/i)) {
90
- groups["✅ Tests"].push(
91
- `- ${message.replace(/^(test|✅)[:(]\w*\)?:?\s*/i, "")} ${link}`
92
- );
93
- } else if (message.match(/^(chore|🔧|🏡)/i)) {
94
- groups["🔧 Chore"].push(
95
- `- ${message.replace(/^(chore|🔧|🏡)[:(]\w*\)?:?\s*/i, "")} ${link}`
96
- );
97
- } else if (message.match(/^(ci|🤖)/i)) {
98
- groups["🤖 CI"].push(
99
- `- ${message.replace(/^(ci|🤖)[:(]\w*\)?:?\s*/i, "")} ${link}`
100
- );
101
- } else {
102
- groups["🔧 Chore"].push(`- ${message} ${link}`);
103
- }
104
- });
105
-
106
- // 输出各分组
107
- Object.entries(groups).forEach(([title, items]) => {
108
- if (items.length > 0) {
109
- changelog += `### ${title}\n\n`;
110
- items.forEach((item) => {
111
- changelog += `${item}\n`;
112
- });
113
- changelog += "\n";
114
- }
65
+ changelog += `- ${formatMessage(message, link)}\n`;
115
66
  });
116
67
 
117
68
  changelog += "\n";
@@ -418,6 +418,29 @@ export async function createTag(inputPrefix?: string): Promise<void> {
418
418
  async function doCreateTag(tagName: string): Promise<void> {
419
419
  divider();
420
420
 
421
+ // 检查是否有提交
422
+ const hasCommits = execOutput("git rev-parse HEAD 2>/dev/null");
423
+ if (!hasCommits) {
424
+ console.log(colors.red("当前仓库没有任何提交"));
425
+ console.log("");
426
+ console.log(colors.dim(" 提示: 需要先创建至少一个提交才能打 tag:"));
427
+ console.log(colors.cyan(" git add ."));
428
+ console.log(colors.cyan(' git commit -m "Initial commit"'));
429
+ console.log(colors.cyan(" gw tag"));
430
+ return;
431
+ }
432
+
433
+ // 检查 tag 是否已存在
434
+ const existingTags = execOutput("git tag -l").split("\n").filter(Boolean);
435
+ if (existingTags.includes(tagName)) {
436
+ console.log(colors.red(`Tag ${tagName} 已存在`));
437
+ console.log("");
438
+ console.log(colors.dim(" 提示: 如需重新创建,请先删除旧 tag:"));
439
+ console.log(colors.cyan(` git tag -d ${tagName}`));
440
+ console.log(colors.cyan(` git push origin --delete ${tagName}`));
441
+ return;
442
+ }
443
+
421
444
  const spinner = ora(`正在创建 tag: ${tagName}`).start();
422
445
  const success = await execWithSpinner(
423
446
  `git tag -a "${tagName}" -m "Release ${tagName}"`,
@@ -614,9 +637,12 @@ export async function updateTag(): Promise<void> {
614
637
  }
615
638
 
616
639
  // 删除旧 tag
617
- const deleteSuccess = await execAsync(`git tag -d "${oldTag}"`, spinner);
618
- if (!deleteSuccess) {
640
+ const deleteResult = await execAsync(`git tag -d "${oldTag}"`, spinner);
641
+ if (!deleteResult.success) {
619
642
  spinner.fail("删除旧 tag 失败");
643
+ if (deleteResult.error) {
644
+ console.log(colors.dim(` ${deleteResult.error}`));
645
+ }
620
646
  return;
621
647
  }
622
648
 
@@ -635,26 +661,32 @@ export async function updateTag(): Promise<void> {
635
661
  const pushSpinner = ora("正在同步到远程...").start();
636
662
 
637
663
  // 推送新 tag
638
- const pushNewSuccess = await execAsync(
664
+ const pushNewResult = await execAsync(
639
665
  `git push origin "${newTag}"`,
640
666
  pushSpinner,
641
667
  );
642
- if (!pushNewSuccess) {
668
+ if (!pushNewResult.success) {
643
669
  pushSpinner.warn(
644
670
  `远程同步失败,可稍后手动执行:\n git push origin ${newTag}\n git push origin --delete ${oldTag}`,
645
671
  );
672
+ if (pushNewResult.error) {
673
+ console.log(colors.dim(` ${pushNewResult.error}`));
674
+ }
646
675
  return;
647
676
  }
648
677
 
649
678
  // 删除远程旧 tag
650
- const deleteOldSuccess = await execAsync(
679
+ const deleteOldResult = await execAsync(
651
680
  `git push origin --delete "${oldTag}"`,
652
681
  pushSpinner,
653
682
  );
654
- if (!deleteOldSuccess) {
683
+ if (!deleteOldResult.success) {
655
684
  pushSpinner.warn(
656
685
  `远程旧 tag 删除失败,可稍后手动执行: git push origin --delete ${oldTag}`,
657
686
  );
687
+ if (deleteOldResult.error) {
688
+ console.log(colors.dim(` ${deleteOldResult.error}`));
689
+ }
658
690
  return;
659
691
  }
660
692
 
@@ -729,8 +761,8 @@ export async function cleanInvalidTags(): Promise<void> {
729
761
  let localFailed = 0;
730
762
 
731
763
  for (const tag of invalidTags) {
732
- const success = await execAsync(`git tag -d "${tag}"`, localSpinner);
733
- if (success) {
764
+ const result = await execAsync(`git tag -d "${tag}"`, localSpinner);
765
+ if (result.success) {
734
766
  localSuccess++;
735
767
  } else {
736
768
  localFailed++;
@@ -761,11 +793,11 @@ export async function cleanInvalidTags(): Promise<void> {
761
793
  let remoteFailed = 0;
762
794
 
763
795
  for (const tag of invalidTags) {
764
- const success = await execAsync(
796
+ const result = await execAsync(
765
797
  `git push origin --delete "${tag}"`,
766
798
  remoteSpinner,
767
799
  );
768
- if (success) {
800
+ if (result.success) {
769
801
  remoteSuccess++;
770
802
  } else {
771
803
  remoteFailed++;
@@ -27,16 +27,32 @@ function clearUpdateCache(): void {
27
27
  * 获取 npm 上的最新版本
28
28
  */
29
29
  async function getLatestVersion(packageName: string): Promise<string | null> {
30
- try {
31
- const result = execSync(`npm view ${packageName} version`, {
32
- encoding: "utf-8",
33
- timeout: 3000,
34
- stdio: ["pipe", "pipe", "ignore"],
30
+ return new Promise((resolve) => {
31
+ const npmView = spawn("npm", ["view", packageName, "version"], {
32
+ stdio: ["ignore", "pipe", "ignore"],
33
+ timeout: 5000,
35
34
  });
36
- return result.trim();
37
- } catch {
38
- return null;
39
- }
35
+
36
+ let output = "";
37
+
38
+ if (npmView.stdout) {
39
+ npmView.stdout.on("data", (data) => {
40
+ output += data.toString();
41
+ });
42
+ }
43
+
44
+ npmView.on("close", (code) => {
45
+ if (code === 0 && output.trim()) {
46
+ resolve(output.trim());
47
+ } else {
48
+ resolve(null);
49
+ }
50
+ });
51
+
52
+ npmView.on("error", () => {
53
+ resolve(null);
54
+ });
55
+ });
40
56
  }
41
57
 
42
58
  /**
package/src/utils.ts CHANGED
@@ -166,7 +166,9 @@ export function execAsync(
166
166
  console.log(colors.dim(`[DEBUG] 标准输出:\n${stdoutOutput}`));
167
167
  }
168
168
  if (errorOutput) {
169
- console.log(colors.dim(`[DEBUG] 错误输出:\n${errorOutput}`));
169
+ // 根据退出码决定标签:成功时显示"输出信息",失败时显示"错误输出"
170
+ const label = code === 0 ? "输出信息" : "错误输出";
171
+ console.log(colors.dim(`[DEBUG] ${label}:\n${errorOutput}`));
170
172
  }
171
173
  }
172
174