@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 +12 -0
- package/README.md +1 -1
- package/dist/index.js +155 -71
- package/docs/commands/config.md +4 -1
- package/docs/commands/review.md +3 -0
- package/docs/config/config-file.md +19 -1
- package/docs/config/index.md +3 -1
- package/docs/guide/ai-review.md +6 -0
- package/docs/guide/tag-management.md +14 -1
- package/package.json +2 -2
- package/src/commands/init.ts +18 -0
- package/src/commands/review.ts +59 -16
- package/src/commands/tag.ts +39 -15
- package/src/config.ts +3 -0
- package/src/tag-utils.ts +37 -0
- package/src/update-notifier.ts +46 -42
- package/tests/config.test.ts +18 -0
- package/tests/init.test.ts +111 -275
- package/tests/tag.test.ts +67 -24
- package/tests/update-notifier.test.ts +29 -76
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-
|
|
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
|
|
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
|
-
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
833
|
-
|
|
834
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2885
|
+
async function getLatestVersion(packageName) {
|
|
2829
2886
|
return new Promise((resolve) => {
|
|
2830
|
-
const npmView =
|
|
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
|
|
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 (
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
4074
|
-
const
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
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
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
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(
|
|
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.
|
|
4288
|
+
var version = true ? "0.6.0" : "0.0.0-dev";
|
|
4205
4289
|
async function mainMenu() {
|
|
4206
4290
|
console.log(
|
|
4207
4291
|
colors.green(`
|
package/docs/commands/config.md
CHANGED
|
@@ -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. **版本控制** - 跟踪配置文件的变更历史
|
package/docs/commands/review.md
CHANGED
|
@@ -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 的关键。
|
package/docs/config/index.md
CHANGED
|
@@ -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 可以完美适应你的工作流程。从简单的个人项目到复杂的企业级应用,都能找到合适的配置方案。
|
package/docs/guide/ai-review.md
CHANGED
|
@@ -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.
|
|
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": ">=
|
|
43
|
+
"node": ">=21.3.0",
|
|
44
44
|
"npm": ">=9.0.0"
|
|
45
45
|
},
|
|
46
46
|
"repository": {
|
package/src/commands/init.ts
CHANGED
|
@@ -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: "创建分支后是否自动推送?",
|