@zjex/git-workflow 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.5.3](https://github.com/iamzjt-front-end/git-workflow/compare/v0.5.2...v0.5.3) (2026-02-06)
4
+
5
+ - 🔖 chore(release): 发布 v0.5.3 ([a6b327d](https://github.com/iamzjt-front-end/git-workflow/commit/a6b327d))
6
+ - docs(review): Clarify commit range syntax and refactor parsing logic ([63a77d2](https://github.com/iamzjt-front-end/git-workflow/commit/63a77d2))
7
+ - 📝 docs: 自动更新测试数量徽章 [skip ci] ([21e365c](https://github.com/iamzjt-front-end/git-workflow/commit/21e365c))
8
+ - feat(review): Add commit range syntax support and improve update notifier ([93184b0](https://github.com/iamzjt-front-end/git-workflow/commit/93184b0))
9
+
10
+ ## [v0.5.2](https://github.com/iamzjt-front-end/git-workflow/compare/v0.5.1...v0.5.2) (2026-02-06)
11
+
12
+ - 🔖 chore(release): 发布 v0.5.2 ([d2b659c](https://github.com/iamzjt-front-end/git-workflow/commit/d2b659c))
13
+ - refactor(update-notifier): Simplify version checking logic ([165e21f](https://github.com/iamzjt-front-end/git-workflow/commit/165e21f))
14
+
3
15
  ## [v0.5.1](https://github.com/iamzjt-front-end/git-workflow/compare/v0.5.0...v0.5.1) (2026-02-06)
4
16
 
5
17
  - 🔖 chore(release): 发布 v0.5.1 ([88c2089](https://github.com/iamzjt-front-end/git-workflow/commit/88c2089))
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://github.com/iamzjt-front-end/git-workflow"><img src="https://img.shields.io/github/stars/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=F59E0B" alt="github stars"></a>
13
13
  <a href="https://github.com/iamzjt-front-end/git-workflow/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@zjex/git-workflow?style=flat&colorA=18181B&colorB=10B981" alt="license"></a>
14
14
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-339933?style=flat&logo=node.js&logoColor=white&colorA=18181B" alt="node version"></a>
15
- <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-573%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
15
+ <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-579%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
16
16
  <a href="https://github.com/iamzjt-front-end/git-workflow/issues"><img src="https://img.shields.io/github/issues/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=EC4899" alt="issues"></a>
17
17
  </p>
18
18
 
package/dist/index.js CHANGED
@@ -175,18 +175,19 @@ __export(update_notifier_exports, {
175
175
  checkForUpdates: () => checkForUpdates,
176
176
  clearUpdateCache: () => clearUpdateCache
177
177
  });
178
- import { execSync as execSync3 } from "child_process";
178
+ import { execSync as execSync3, spawn as spawn3 } from "child_process";
179
179
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync3, unlinkSync as unlinkSync2 } from "fs";
180
180
  import { homedir as homedir3 } from "os";
181
181
  import { join as join4 } from "path";
182
182
  import boxen2 from "boxen";
183
183
  import { select as select7 } from "@inquirer/prompts";
184
184
  import ora5 from "ora";
185
+ import semver from "semver";
185
186
  async function checkForUpdates(currentVersion, packageName = "@zjex/git-workflow", interactive = false) {
186
187
  try {
187
188
  const cache = readCache();
188
189
  const now = Date.now();
189
- if (cache?.latestVersion && cache.latestVersion !== currentVersion) {
190
+ if (cache?.latestVersion && semver.gt(cache.latestVersion, currentVersion)) {
190
191
  const isDismissed = cache.lastDismiss && now - cache.lastDismiss < DISMISS_INTERVAL;
191
192
  if (!isDismissed) {
192
193
  if (interactive) {
@@ -205,29 +206,45 @@ async function checkForUpdates(currentVersion, packageName = "@zjex/git-workflow
205
206
  }
206
207
  }
207
208
  }
208
- backgroundCheck(currentVersion, packageName);
209
+ spawnBackgroundCheck(packageName);
209
210
  } catch (error) {
210
211
  if (error?.constructor?.name === "ExitPromptError") {
211
212
  throw error;
212
213
  }
213
214
  }
214
215
  }
215
- function backgroundCheck(currentVersion, packageName) {
216
- setImmediate(async () => {
217
- try {
218
- const latestVersion = await getLatestVersion(packageName);
219
- if (latestVersion) {
220
- const cache = readCache() || {};
221
- writeCache({
222
- ...cache,
223
- lastCheck: Date.now(),
224
- latestVersion,
225
- checkedVersion: currentVersion
226
- });
227
- }
228
- } catch {
229
- }
230
- });
216
+ function spawnBackgroundCheck(packageName) {
217
+ try {
218
+ const cacheFile = join4(homedir3(), CACHE_FILE);
219
+ const script = `
220
+ const { execSync } = require('child_process');
221
+ const { writeFileSync, readFileSync, existsSync } = require('fs');
222
+ try {
223
+ const version = execSync('npm view ${packageName} version', {
224
+ encoding: 'utf-8',
225
+ timeout: 10000,
226
+ stdio: ['pipe', 'pipe', 'ignore']
227
+ }).trim();
228
+ if (version) {
229
+ let cache = {};
230
+ try {
231
+ if (existsSync('${cacheFile}')) {
232
+ cache = JSON.parse(readFileSync('${cacheFile}', 'utf-8'));
233
+ }
234
+ } catch {}
235
+ cache.lastCheck = Date.now();
236
+ cache.latestVersion = version;
237
+ writeFileSync('${cacheFile}', JSON.stringify(cache), 'utf-8');
238
+ }
239
+ } catch {}
240
+ `;
241
+ const child = spawn3("node", ["-e", script], {
242
+ detached: true,
243
+ stdio: "ignore"
244
+ });
245
+ child.unref();
246
+ } catch {
247
+ }
231
248
  }
232
249
  function isUsingVolta() {
233
250
  try {
@@ -237,19 +254,6 @@ function isUsingVolta() {
237
254
  return false;
238
255
  }
239
256
  }
240
- async function getLatestVersion(packageName) {
241
- try {
242
- const result = execSync3(`npm view ${packageName} version`, {
243
- encoding: "utf-8",
244
- timeout: 3e3,
245
- stdio: ["pipe", "pipe", "ignore"]
246
- // 忽略 stderr
247
- });
248
- return result.trim();
249
- } catch {
250
- return null;
251
- }
252
- }
253
257
  function showSimpleNotification(current, latest) {
254
258
  const message = `${colors.yellow("\u{1F389} \u53D1\u73B0\u65B0\u7248\u672C")} ${colors.dim(
255
259
  current
@@ -417,6 +421,7 @@ var defaultConfig = {
417
421
  requireId: false,
418
422
  featureIdLabel: "Story ID",
419
423
  hotfixIdLabel: "Issue ID",
424
+ tagLookupStrategy: "latest",
420
425
  autoStage: true,
421
426
  useEmoji: true
422
427
  };
@@ -743,13 +748,38 @@ async function deleteBranch(branchArg) {
743
748
  init_utils();
744
749
  import { select as select2, input as input2 } from "@inquirer/prompts";
745
750
  import ora2 from "ora";
751
+
752
+ // src/tag-utils.ts
753
+ function isValidVersionTag(tag) {
754
+ return /\d/.test(tag);
755
+ }
756
+ function extractTagPrefix(tag) {
757
+ return tag.replace(/\d.*/, "");
758
+ }
759
+ function normalizeTagLookupStrategy(value) {
760
+ return value === "all" ? "all" : "latest";
761
+ }
762
+ function getLatestTagCommand(prefix, strategy) {
763
+ if (strategy === "latest") {
764
+ return `git for-each-ref --sort=-creatordate --format="%(refname:short)" "refs/tags/${prefix}*"`;
765
+ }
766
+ return `git tag -l "${prefix}*" --sort=-v:refname`;
767
+ }
768
+ function shouldFetchAllTagsForCreateTag(strategy, prefix) {
769
+ if (strategy === "all") {
770
+ return true;
771
+ }
772
+ return !prefix;
773
+ }
774
+
775
+ // src/commands/tag.ts
746
776
  async function listTags(prefix) {
747
777
  const spinner = ora2("\u6B63\u5728\u83B7\u53D6 tags...").start();
748
778
  exec("git fetch --tags", true);
749
779
  spinner.stop();
750
780
  const pattern = prefix ? `${prefix}*` : "";
751
781
  const allTags = execOutput(`git tag -l ${pattern} --sort=v:refname`).split("\n").filter(Boolean);
752
- const tags = allTags.filter((tag) => /\d/.test(tag));
782
+ const tags = allTags.filter(isValidVersionTag);
753
783
  if (tags.length === 0) {
754
784
  console.log(
755
785
  colors.yellow(prefix ? `\u6CA1\u6709 '${prefix}' \u5F00\u5934\u7684 tag` : "\u6CA1\u6709 tag")
@@ -769,7 +799,7 @@ async function listTags(prefix) {
769
799
  }
770
800
  const grouped = /* @__PURE__ */ new Map();
771
801
  tags.forEach((tag) => {
772
- const prefix2 = tag.replace(/\d.*/, "") || "(\u65E0\u524D\u7F00)";
802
+ const prefix2 = extractTagPrefix(tag) || "(\u65E0\u524D\u7F00)";
773
803
  if (!grouped.has(prefix2)) {
774
804
  grouped.set(prefix2, []);
775
805
  }
@@ -823,23 +853,28 @@ async function listTags(prefix) {
823
853
  console.log(" " + row);
824
854
  }
825
855
  }
826
- function getLatestTag(prefix) {
827
- const tags = execOutput(`git tag -l "${prefix}*" --sort=-v:refname`).split("\n").filter((tag) => tag && /\d/.test(tag));
856
+ function getLatestTag(prefix, strategy = "latest") {
857
+ const tags = execOutput(getLatestTagCommand(prefix, strategy)).split("\n").filter((tag) => tag && isValidVersionTag(tag));
828
858
  return tags[0] || "";
829
859
  }
830
860
  async function createTag(inputPrefix) {
831
861
  const config2 = getConfig();
832
- const fetchSpinner = ora2("\u6B63\u5728\u83B7\u53D6 tags...").start();
833
- exec("git fetch --tags", true);
834
- fetchSpinner.stop();
862
+ const tagLookupStrategy = normalizeTagLookupStrategy(
863
+ config2.tagLookupStrategy
864
+ );
835
865
  divider();
836
866
  let prefix = inputPrefix;
837
867
  if (!prefix && config2.defaultTagPrefix) {
838
868
  prefix = config2.defaultTagPrefix;
839
869
  console.log(colors.dim(`(\u4F7F\u7528\u914D\u7F6E\u7684\u9ED8\u8BA4\u524D\u7F00: ${prefix})`));
840
870
  }
871
+ if (shouldFetchAllTagsForCreateTag(tagLookupStrategy, prefix)) {
872
+ const fetchSpinner = ora2("\u6B63\u5728\u83B7\u53D6 tags...").start();
873
+ exec("git fetch --tags", true);
874
+ fetchSpinner.stop();
875
+ }
841
876
  if (!prefix) {
842
- const allTags = execOutput("git tag -l").split("\n").filter((tag) => tag && /\d/.test(tag));
877
+ const allTags = execOutput("git tag -l").split("\n").filter((tag) => tag && isValidVersionTag(tag));
843
878
  if (allTags.length === 0) {
844
879
  prefix = await input2({
845
880
  message: "\u5F53\u524D\u4ED3\u5E93\u6CA1\u6709 tag\uFF0C\u8BF7\u8F93\u5165\u524D\u7F00 (\u5982 v):",
@@ -885,9 +920,7 @@ async function createTag(inputPrefix) {
885
920
  }
886
921
  return;
887
922
  }
888
- const prefixes = [
889
- ...new Set(allTags.map((t) => t.replace(/\d.*/, "")).filter(Boolean))
890
- ];
923
+ const prefixes = [...new Set(allTags.map(extractTagPrefix).filter(Boolean))];
891
924
  if (prefixes.length === 0) {
892
925
  prefix = await input2({
893
926
  message: "\u8BF7\u8F93\u5165 tag \u524D\u7F00 (\u5982 v):",
@@ -900,7 +933,7 @@ async function createTag(inputPrefix) {
900
933
  }
901
934
  } else {
902
935
  const prefixWithDate = prefixes.map((p) => {
903
- const latest = getLatestTag(p);
936
+ const latest = getLatestTag(p, tagLookupStrategy);
904
937
  const date = latest ? execOutput(`git log -1 --format=%ct "${latest}" 2>/dev/null`) : "0";
905
938
  return { prefix: p, latest, date: parseInt(date) || 0 };
906
939
  });
@@ -925,7 +958,13 @@ async function createTag(inputPrefix) {
925
958
  }
926
959
  }
927
960
  }
928
- const latestTag = getLatestTag(prefix);
961
+ let latestTag = getLatestTag(prefix, tagLookupStrategy);
962
+ if (!latestTag && tagLookupStrategy === "latest") {
963
+ const fetchSpinner = ora2("\u672C\u5730\u672A\u627E\u5230\u5BF9\u5E94 tag\uFF0C\u6B63\u5728\u5168\u91CF\u540C\u6B65\u4E00\u6B21...").start();
964
+ exec("git fetch --tags", true);
965
+ fetchSpinner.stop();
966
+ latestTag = getLatestTag(prefix, tagLookupStrategy);
967
+ }
929
968
  if (!latestTag) {
930
969
  const newTag = `${prefix}1.0.0`;
931
970
  console.log(
@@ -944,7 +983,8 @@ async function createTag(inputPrefix) {
944
983
  }
945
984
  return;
946
985
  }
947
- console.log(colors.yellow(`\u5F53\u524D\u6700\u65B0 tag: ${latestTag}`));
986
+ const strategyText = tagLookupStrategy === "latest" ? "\u6700\u65B0\u521B\u5EFA" : "\u7248\u672C\u6392\u5E8F";
987
+ console.log(colors.yellow(`\u5F53\u524D\u57FA\u51C6 tag (${strategyText}): ${latestTag}`));
948
988
  divider();
949
989
  const version2 = latestTag.slice(prefix.length);
950
990
  const preReleaseMatch = version2.match(
@@ -1584,6 +1624,23 @@ async function init() {
1584
1624
  theme
1585
1625
  });
1586
1626
  if (defaultTagPrefix) config2.defaultTagPrefix = defaultTagPrefix;
1627
+ const tagLookupStrategy = await select4({
1628
+ message: "Tag \u9012\u589E\u57FA\u51C6\u7B56\u7565:",
1629
+ choices: [
1630
+ {
1631
+ name: "\u4EC5\u57FA\u4E8E\u6700\u65B0\u521B\u5EFA\u7684 Tag\uFF08\u9ED8\u8BA4\uFF09",
1632
+ value: "latest",
1633
+ description: "\u907F\u514D\u5386\u53F2\u8BEF\u6253\u7684\u9AD8\u7248\u672C tag \u5E72\u6270\u540E\u7EED\u9012\u589E"
1634
+ },
1635
+ {
1636
+ name: "\u5168\u91CF\u6392\u5E8F",
1637
+ value: "all",
1638
+ description: "\u5168\u91CF\u62C9\u53D6 tags\uFF0C\u5E76\u6309\u7248\u672C\u53F7\u6392\u5E8F\u540E\u53D6\u6700\u65B0\u503C"
1639
+ }
1640
+ ],
1641
+ theme
1642
+ });
1643
+ config2.tagLookupStrategy = tagLookupStrategy;
1587
1644
  const autoPushChoice = await select4({
1588
1645
  message: "\u521B\u5EFA\u5206\u652F\u540E\u662F\u5426\u81EA\u52A8\u63A8\u9001?",
1589
1646
  choices: [
@@ -2808,10 +2865,10 @@ init_update_notifier();
2808
2865
 
2809
2866
  // src/commands/update.ts
2810
2867
  init_utils();
2811
- import { execSync as execSync4, spawn as spawn3 } from "child_process";
2868
+ import { execSync as execSync4, spawn as spawn4 } from "child_process";
2812
2869
  import ora6 from "ora";
2813
2870
  import boxen3 from "boxen";
2814
- import semver from "semver";
2871
+ import semver2 from "semver";
2815
2872
  import { existsSync as existsSync4, unlinkSync as unlinkSync3 } from "fs";
2816
2873
  import { homedir as homedir4 } from "os";
2817
2874
  import { join as join5 } from "path";
@@ -2825,9 +2882,9 @@ function clearUpdateCache2() {
2825
2882
  } catch {
2826
2883
  }
2827
2884
  }
2828
- async function getLatestVersion2(packageName) {
2885
+ async function getLatestVersion(packageName) {
2829
2886
  return new Promise((resolve) => {
2830
- const npmView = spawn3("npm", ["view", packageName, "version"], {
2887
+ const npmView = spawn4("npm", ["view", packageName, "version"], {
2831
2888
  stdio: ["ignore", "pipe", "ignore"],
2832
2889
  timeout: 5e3
2833
2890
  });
@@ -2865,14 +2922,14 @@ async function update(currentVersion) {
2865
2922
  console.log("");
2866
2923
  const spinner = ora6("\u6B63\u5728\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F...").start();
2867
2924
  try {
2868
- const latestVersion = await getLatestVersion2(packageName);
2925
+ const latestVersion = await getLatestVersion(packageName);
2869
2926
  if (!latestVersion) {
2870
2927
  spinner.fail("\u65E0\u6CD5\u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F");
2871
2928
  console.log(colors.dim(" \u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u540E\u91CD\u8BD5"));
2872
2929
  return;
2873
2930
  }
2874
2931
  spinner.stop();
2875
- if (semver.gte(currentVersion, latestVersion)) {
2932
+ if (semver2.gte(currentVersion, latestVersion)) {
2876
2933
  console.log(
2877
2934
  boxen3(
2878
2935
  [
@@ -2916,7 +2973,7 @@ async function update(currentVersion) {
2916
2973
  console.log("");
2917
2974
  const updateCommand = usingVolta ? `volta install ${packageName}@latest` : `npm install -g ${packageName}@latest`;
2918
2975
  const [command, ...args] = updateCommand.split(" ");
2919
- const updateProcess = spawn3(command, args, {
2976
+ const updateProcess = spawn4(command, args, {
2920
2977
  stdio: "inherit"
2921
2978
  // 继承父进程的 stdio,显示实时输出
2922
2979
  });
@@ -2987,7 +3044,7 @@ async function update(currentVersion) {
2987
3044
  init_utils();
2988
3045
  import { execSync as execSync5 } from "child_process";
2989
3046
  import boxen4 from "boxen";
2990
- import { spawn as spawn4 } from "child_process";
3047
+ import { spawn as spawn5 } from "child_process";
2991
3048
  function parseGitLog(output) {
2992
3049
  const commits = [];
2993
3050
  const lines = output.trim().split("\n");
@@ -3205,7 +3262,7 @@ function formatTimelineStyle(commits) {
3205
3262
  function startInteractivePager(content) {
3206
3263
  const pager = process.env.PAGER || "less";
3207
3264
  try {
3208
- const pagerProcess = spawn4(pager, ["-R", "-S", "-F", "-X", "-i"], {
3265
+ const pagerProcess = spawn5(pager, ["-R", "-S", "-F", "-X", "-i"], {
3209
3266
  stdio: ["pipe", "inherit", "inherit"],
3210
3267
  env: { ...process.env, LESS: "-R -S -F -X -i" }
3211
3268
  });
@@ -3621,16 +3678,19 @@ var AI_PROVIDERS2 = {
3621
3678
  defaultModel: "qwen2.5-coder:14b"
3622
3679
  }
3623
3680
  };
3681
+ function parseCommitLine(line) {
3682
+ const parts = line.split("|");
3683
+ if (parts.length < 5) return null;
3684
+ const [hash, shortHash, subject, author, date] = parts;
3685
+ return { hash, shortHash, subject, author, date };
3686
+ }
3624
3687
  function getRecentCommits3(limit = 20) {
3625
3688
  try {
3626
3689
  const output = execOutput(
3627
3690
  `git log -${limit} --pretty=format:"%H|%h|%s|%an|%ad" --date=short`
3628
3691
  );
3629
3692
  if (!output) return [];
3630
- return output.split("\n").filter(Boolean).map((line) => {
3631
- const [hash, shortHash, subject, author, date] = line.split("|");
3632
- return { hash, shortHash, subject, author, date };
3633
- });
3693
+ return output.split("\n").filter(Boolean).map((line) => parseCommitLine(line)).filter((c) => c !== null);
3634
3694
  } catch {
3635
3695
  return [];
3636
3696
  }
@@ -4070,25 +4130,49 @@ async function review(hashes, options = {}) {
4070
4130
  let diff = "";
4071
4131
  let commits = [];
4072
4132
  if (hashes && hashes.length > 0) {
4073
- commits = hashes.map((hash) => {
4074
- const info = execOutput(
4075
- `git log -1 --pretty=format:"%H|%h|%s|%an|%ad" --date=short ${hash}`
4076
- );
4077
- if (!info) {
4078
- console.log(colors.red(`\u274C \u627E\u4E0D\u5230 commit: ${hash}`));
4133
+ if (hashes.length === 1 && hashes[0].includes("..") && !hashes[0].includes("...")) {
4134
+ const range = hashes[0];
4135
+ const [startHash, endHash] = range.split("..");
4136
+ const inclusiveRange = `${startHash}^..${endHash}`;
4137
+ try {
4138
+ const output = execOutput(
4139
+ `git log ${inclusiveRange} --pretty=format:"%H|%h|%s|%an|%ad" --date=short --reverse`
4140
+ );
4141
+ if (!output) {
4142
+ console.log(colors.red(`\u274C \u65E0\u6548\u7684 commit \u8303\u56F4: ${range}`));
4143
+ process.exit(1);
4144
+ }
4145
+ commits = output.split("\n").filter(Boolean).map((line) => parseCommitLine(line)).filter((c) => c !== null);
4146
+ diff = execOutput(`git diff ${inclusiveRange}`) || "";
4147
+ } catch {
4148
+ console.log(colors.red(`\u274C \u65E0\u6548\u7684 commit \u8303\u56F4: ${range}`));
4079
4149
  process.exit(1);
4080
4150
  }
4081
- const [fullHash, shortHash, subject, author, date] = info.split("|");
4082
- return { hash: fullHash, shortHash, subject, author, date };
4083
- });
4084
- diff = getMultipleCommitsDiff(hashes);
4151
+ } else {
4152
+ commits = hashes.map((hash) => {
4153
+ const info = execOutput(
4154
+ `git log -1 --pretty=format:"%H|%h|%s|%an|%ad" --date=short ${hash}`
4155
+ );
4156
+ if (!info) {
4157
+ console.log(colors.red(`\u274C \u627E\u4E0D\u5230 commit: ${hash}`));
4158
+ process.exit(1);
4159
+ }
4160
+ const commit2 = parseCommitLine(info);
4161
+ if (!commit2) {
4162
+ console.log(colors.red(`\u274C \u65E0\u6CD5\u89E3\u6790 commit \u4FE1\u606F: ${hash}`));
4163
+ process.exit(1);
4164
+ }
4165
+ return commit2;
4166
+ });
4167
+ diff = getMultipleCommitsDiff(hashes);
4168
+ }
4085
4169
  } else if (options.last) {
4086
4170
  commits = getRecentCommits3(options.last);
4087
4171
  diff = getMultipleCommitsDiff(commits.map((c) => c.hash));
4088
4172
  } else if (options.staged) {
4089
4173
  diff = getStagedDiff();
4090
4174
  } else {
4091
- const recentCommits = getRecentCommits3(20);
4175
+ const recentCommits = getRecentCommits3(10);
4092
4176
  const stagedDiff = getStagedDiff();
4093
4177
  const choices = [];
4094
4178
  if (stagedDiff) {
@@ -4201,7 +4285,7 @@ process.on("SIGTERM", () => {
4201
4285
  console.log("");
4202
4286
  process.exit(0);
4203
4287
  });
4204
- var version = true ? "0.5.2" : "0.0.0-dev";
4288
+ var version = true ? "0.6.0" : "0.0.0-dev";
4205
4289
  async function mainMenu() {
4206
4290
  console.log(
4207
4291
  colors.green(`
@@ -89,6 +89,9 @@ $ gw init
89
89
 
90
90
  ```bash
91
91
  ? 默认 Tag 前缀 (留空则每次选择): v
92
+ ? Tag 递增基准策略:
93
+ ❯ 仅基于最新创建的 Tag(默认)
94
+ 全量排序
92
95
  ```
93
96
 
94
97
  ### 推送配置
@@ -343,4 +346,4 @@ vim .gwrc.json
343
346
 
344
347
  1. **定期更新** - 根据需求更新配置
345
348
  2. **备份配置** - 备份重要的配置文件
346
- 3. **版本控制** - 跟踪配置文件的变更历史
349
+ 3. **版本控制** - 跟踪配置文件的变更历史
@@ -14,6 +14,9 @@ gw review abc1234
14
14
  # 审查多个 commits
15
15
  gw review abc1234 def5678
16
16
 
17
+ # 审查 commit 范围(包含 abc1234 和 def5678 的所有 commits)
18
+ gw review abc1234..def5678
19
+
17
20
  # 审查最近 N 个 commits
18
21
  gw review -n 3
19
22
  gw review --last 3
@@ -42,6 +42,7 @@ gw.config.json # 明确的配置文件名
42
42
  "featureIdLabel": "Story ID",
43
43
  "hotfixIdLabel": "Issue ID",
44
44
  "defaultTagPrefix": "v",
45
+ "tagLookupStrategy": "latest",
45
46
  "autoPush": true,
46
47
  "autoStage": true,
47
48
  "useEmoji": true
@@ -59,6 +60,7 @@ gw.config.json # 明确的配置文件名
59
60
  "featureIdLabel": "Story ID",
60
61
  "hotfixIdLabel": "Issue ID",
61
62
  "defaultTagPrefix": "v",
63
+ "tagLookupStrategy": "latest",
62
64
  "autoPush": true,
63
65
  "autoStage": true,
64
66
  "useEmoji": true,
@@ -232,6 +234,22 @@ gw.config.json # 明确的配置文件名
232
234
  - 创建 tag 时直接使用 `v` 前缀
233
235
  - 跳过前缀选择界面
234
236
 
237
+ #### tagLookupStrategy
238
+
239
+ **类型:** `"all" | "latest"`
240
+ **默认值:** `"latest"`
241
+ **说明:** 创建 tag 时如何确定递增基准
242
+
243
+ ```json
244
+ {
245
+ "tagLookupStrategy": "latest"
246
+ }
247
+ ```
248
+
249
+ **选项说明:**
250
+ - `"latest"` - 默认行为。优先基于本地最新创建的 tag 递增,避免历史误打的高版本 tag 干扰排序;如果当前前缀在本地不存在,会自动回退到一次全量同步
251
+ - `"all"` - 全量拉取 tags,并按版本号排序后取最新值
252
+
235
253
  ### 提交配置
236
254
 
237
255
  #### autoStage
@@ -773,4 +791,4 @@ cp ../other-project/.gwrc.json .
773
791
 
774
792
  ---
775
793
 
776
- 通过合理配置,Git Workflow 可以完美适应你的工作流程。配置文件是工具的核心,掌握配置文件的使用是高效使用 Git Workflow 的关键。
794
+ 通过合理配置,Git Workflow 可以完美适应你的工作流程。配置文件是工具的核心,掌握配置文件的使用是高效使用 Git Workflow 的关键。
@@ -144,6 +144,7 @@ gw init
144
144
  "featureIdLabel": "Jira ID",
145
145
  "hotfixIdLabel": "Bug ID",
146
146
  "defaultTagPrefix": "v",
147
+ "tagLookupStrategy": "latest",
147
148
  "autoPush": true,
148
149
  "autoStage": true,
149
150
  "useEmoji": true,
@@ -186,6 +187,7 @@ gw init
186
187
  | 配置项 | 类型 | 默认值 | 说明 |
187
188
  | ------------------ | -------- | ------ | --------------------------------- |
188
189
  | `defaultTagPrefix` | `string` | - | 默认 tag 前缀,设置后跳过选择步骤 |
190
+ | `tagLookupStrategy` | `"all" \| "latest"` | `"latest"` | tag 递增基准策略:`latest` 优先基于最新创建的 tag,`all` 按版本全量排序 |
189
191
 
190
192
  ### 提交配置
191
193
 
@@ -475,4 +477,4 @@ cp project-a/.gwrc.json project-b/.gwrc.json
475
477
 
476
478
  ---
477
479
 
478
- 通过合理的配置,Git Workflow 可以完美适应你的工作流程。从简单的个人项目到复杂的企业级应用,都能找到合适的配置方案。
480
+ 通过合理的配置,Git Workflow 可以完美适应你的工作流程。从简单的个人项目到复杂的企业级应用,都能找到合适的配置方案。
@@ -21,6 +21,9 @@ gw review abc1234
21
21
  # 审查多个 commits
22
22
  gw review abc1234 def5678
23
23
 
24
+ # 审查 commit 范围(包含 abc1234 和 def5678 的所有 commits)
25
+ gw review abc1234..def5678
26
+
24
27
  # 审查最近 N 个 commits
25
28
  gw review -n 3
26
29
 
@@ -137,6 +140,9 @@ gw c
137
140
  ```bash
138
141
  # 审查某个 PR 的所有 commits
139
142
  gw review abc1234 def5678 ghi9012
143
+
144
+ # 或使用范围语法审查从 abc1234 到 def5678 的所有 commits(包含两端)
145
+ gw review abc1234..def5678
140
146
  ```
141
147
 
142
148
  ### 3. 定期代码审计
@@ -436,6 +436,19 @@ jobs:
436
436
 
437
437
  设置后,创建标签时会跳过前缀选择步骤。
438
438
 
439
+ ### Tag 基准策略
440
+
441
+ ```json
442
+ {
443
+ "defaultTagPrefix": "v",
444
+ "tagLookupStrategy": "latest"
445
+ }
446
+ ```
447
+
448
+ `tagLookupStrategy` 支持两种模式:
449
+ - `all`:全量拉取并按版本号排序,兼容当前默认行为
450
+ - `latest`:优先基于最新创建的 tag 递增,适合历史上出现过误打高版本 tag 的仓库;如果本地没有该前缀的 tag,会自动回退一次全量同步
451
+
439
452
  ### 版本格式配置
440
453
 
441
454
  ```json
@@ -611,4 +624,4 @@ jobs:
611
624
 
612
625
  ---
613
626
 
614
- 通过系统化的 Tag 管理,你可以建立清晰的版本发布流程。Git Workflow 的智能版本递增和前缀检测功能,让版本管理变得简单而规范。
627
+ 通过系统化的 Tag 管理,你可以建立清晰的版本发布流程。Git Workflow 的智能版本递增和前缀检测功能,让版本管理变得简单而规范。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zjex/git-workflow",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "🚀 极简的 Git 工作流 CLI 工具,让分支管理和版本发布变得轻松愉快",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,7 +40,7 @@
40
40
  "author": "zjex",
41
41
  "license": "MIT",
42
42
  "engines": {
43
- "node": ">=18.0.0",
43
+ "node": ">=21.3.0",
44
44
  "npm": ">=9.0.0"
45
45
  },
46
46
  "repository": {
@@ -148,6 +148,24 @@ export async function init(): Promise<void> {
148
148
  });
149
149
  if (defaultTagPrefix) config.defaultTagPrefix = defaultTagPrefix;
150
150
 
151
+ const tagLookupStrategy = await select({
152
+ message: "Tag 递增基准策略:",
153
+ choices: [
154
+ {
155
+ name: "仅基于最新创建的 Tag(默认)",
156
+ value: "latest",
157
+ description: "避免历史误打的高版本 tag 干扰后续递增",
158
+ },
159
+ {
160
+ name: "全量排序",
161
+ value: "all",
162
+ description: "全量拉取 tags,并按版本号排序后取最新值",
163
+ },
164
+ ],
165
+ theme,
166
+ });
167
+ config.tagLookupStrategy = tagLookupStrategy as "all" | "latest";
168
+
151
169
  // 自动推送
152
170
  const autoPushChoice = await select({
153
171
  message: "创建分支后是否自动推送?",