@zjex/git-workflow 0.3.6 → 0.3.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 +61 -63
- package/package.json +1 -1
- package/src/commands/log.ts +209 -182
- package/src/index.ts +2 -2
- package/src/utils.ts +2 -0
- package/tsup.config.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -67,6 +67,8 @@ var init_utils = __esm({
|
|
|
67
67
|
orange: (s) => `\x1B[38;5;208m${s}\x1B[0m`,
|
|
68
68
|
lightPurple: (s) => `\x1B[38;5;141m${s}\x1B[0m`,
|
|
69
69
|
white: (s) => `\x1B[37m${s}\x1B[0m`,
|
|
70
|
+
brightYellow: (s) => `\x1B[93m${s}\x1B[0m`,
|
|
71
|
+
// 亮黄色
|
|
70
72
|
reset: "\x1B[0m"
|
|
71
73
|
};
|
|
72
74
|
TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
@@ -2602,57 +2604,49 @@ function parseGitLog(output) {
|
|
|
2602
2604
|
}
|
|
2603
2605
|
return commits;
|
|
2604
2606
|
}
|
|
2605
|
-
function getCommitTypeIcon(subject) {
|
|
2606
|
-
const lowerSubject = subject.toLowerCase();
|
|
2607
|
-
if (lowerSubject.includes("feat") || lowerSubject.includes("feature")) return "\u2728";
|
|
2608
|
-
if (lowerSubject.includes("fix") || lowerSubject.includes("bug")) return "\u{1F41B}";
|
|
2609
|
-
if (lowerSubject.includes("docs") || lowerSubject.includes("doc")) return "\u{1F4DA}";
|
|
2610
|
-
if (lowerSubject.includes("style")) return "\u{1F484}";
|
|
2611
|
-
if (lowerSubject.includes("refactor")) return "\u267B\uFE0F";
|
|
2612
|
-
if (lowerSubject.includes("test")) return "\u{1F9EA}";
|
|
2613
|
-
if (lowerSubject.includes("chore")) return "\u{1F527}";
|
|
2614
|
-
if (lowerSubject.includes("perf")) return "\u26A1";
|
|
2615
|
-
if (lowerSubject.includes("ci")) return "\u{1F477}";
|
|
2616
|
-
if (lowerSubject.includes("build")) return "\u{1F4E6}";
|
|
2617
|
-
if (lowerSubject.includes("revert")) return "\u23EA";
|
|
2618
|
-
if (lowerSubject.includes("merge")) return "\u{1F500}";
|
|
2619
|
-
if (lowerSubject.includes("release") || lowerSubject.includes("version")) return "\u{1F516}";
|
|
2620
|
-
return "\u{1F4DD}";
|
|
2621
|
-
}
|
|
2622
2607
|
function groupCommitsByDate(commits) {
|
|
2623
2608
|
const groups = /* @__PURE__ */ new Map();
|
|
2609
|
+
const dateOrder = [];
|
|
2624
2610
|
for (const commit2 of commits) {
|
|
2625
2611
|
const date = commit2.date;
|
|
2626
2612
|
if (!groups.has(date)) {
|
|
2627
2613
|
groups.set(date, []);
|
|
2614
|
+
dateOrder.push(date);
|
|
2628
2615
|
}
|
|
2629
2616
|
groups.get(date).push(commit2);
|
|
2630
2617
|
}
|
|
2631
|
-
|
|
2618
|
+
const orderedGroups = /* @__PURE__ */ new Map();
|
|
2619
|
+
for (const date of dateOrder) {
|
|
2620
|
+
orderedGroups.set(date, groups.get(date));
|
|
2621
|
+
}
|
|
2622
|
+
return orderedGroups;
|
|
2632
2623
|
}
|
|
2633
2624
|
function formatRelativeTime(relativeDate) {
|
|
2634
2625
|
let result = relativeDate;
|
|
2635
2626
|
const timeMap = {
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2627
|
+
second: "\u79D2",
|
|
2628
|
+
seconds: "\u79D2",
|
|
2629
|
+
minute: "\u5206\u949F",
|
|
2630
|
+
minutes: "\u5206\u949F",
|
|
2631
|
+
hour: "\u5C0F\u65F6",
|
|
2632
|
+
hours: "\u5C0F\u65F6",
|
|
2633
|
+
day: "\u5929",
|
|
2634
|
+
days: "\u5929",
|
|
2635
|
+
week: "\u5468",
|
|
2636
|
+
weeks: "\u5468",
|
|
2637
|
+
month: "\u4E2A\u6708",
|
|
2638
|
+
months: "\u4E2A\u6708",
|
|
2639
|
+
year: "\u5E74",
|
|
2640
|
+
years: "\u5E74",
|
|
2641
|
+
ago: "\u524D"
|
|
2651
2642
|
};
|
|
2652
2643
|
for (const [en, zh] of Object.entries(timeMap)) {
|
|
2653
2644
|
result = result.replace(new RegExp(`\\b${en}\\b`, "g"), zh);
|
|
2654
2645
|
}
|
|
2655
|
-
result = result.replace(
|
|
2646
|
+
result = result.replace(
|
|
2647
|
+
/(\d+)\s+(秒|分钟|小时|天|周|个月|年)\s+前/g,
|
|
2648
|
+
"$1$2\u524D"
|
|
2649
|
+
);
|
|
2656
2650
|
const match = result.match(/(\d+)(分钟|小时|天|周|个月|年)前/);
|
|
2657
2651
|
if (match) {
|
|
2658
2652
|
const num = parseInt(match[1]);
|
|
@@ -2699,28 +2693,25 @@ function supportsColor() {
|
|
|
2699
2693
|
function formatTimelineStyle(commits) {
|
|
2700
2694
|
const groupedCommits = groupCommitsByDate(commits);
|
|
2701
2695
|
let output = "";
|
|
2702
|
-
const sortedDates = Array.from(groupedCommits.keys())
|
|
2703
|
-
(a, b) => new Date(b).getTime() - new Date(a).getTime()
|
|
2704
|
-
);
|
|
2696
|
+
const sortedDates = Array.from(groupedCommits.keys());
|
|
2705
2697
|
const useColors = supportsColor() || process.env.FORCE_COLOR;
|
|
2706
2698
|
for (let dateIndex = 0; dateIndex < sortedDates.length; dateIndex++) {
|
|
2707
2699
|
const date = sortedDates[dateIndex];
|
|
2708
2700
|
const dateCommits = groupedCommits.get(date);
|
|
2709
2701
|
const dateTitle = `\u{1F4C5} Commits on ${date}`;
|
|
2710
2702
|
if (useColors) {
|
|
2711
|
-
output +=
|
|
2703
|
+
output += colors.bold(colors.yellow(dateTitle)) + "\n";
|
|
2712
2704
|
} else {
|
|
2713
|
-
output +=
|
|
2705
|
+
output += dateTitle + "\n";
|
|
2714
2706
|
}
|
|
2715
2707
|
for (let commitIndex = 0; commitIndex < dateCommits.length; commitIndex++) {
|
|
2716
2708
|
const commit2 = dateCommits[commitIndex];
|
|
2717
|
-
const icon = getCommitTypeIcon(commit2.subject);
|
|
2718
2709
|
const { title, tasks } = parseCommitSubject(commit2.subject);
|
|
2719
2710
|
const commitContent = [];
|
|
2720
2711
|
if (useColors) {
|
|
2721
|
-
commitContent.push(
|
|
2712
|
+
commitContent.push(colors.bold(colors.white(title)));
|
|
2722
2713
|
} else {
|
|
2723
|
-
commitContent.push(
|
|
2714
|
+
commitContent.push(title);
|
|
2724
2715
|
}
|
|
2725
2716
|
if (tasks.length > 0) {
|
|
2726
2717
|
commitContent.push("");
|
|
@@ -2734,8 +2725,12 @@ function formatTimelineStyle(commits) {
|
|
|
2734
2725
|
}
|
|
2735
2726
|
commitContent.push("");
|
|
2736
2727
|
if (useColors) {
|
|
2737
|
-
commitContent.push(
|
|
2738
|
-
|
|
2728
|
+
commitContent.push(
|
|
2729
|
+
`${colors.dim("\u{1F464}")} ${colors.blue(commit2.author)} ${colors.dim(
|
|
2730
|
+
"committed"
|
|
2731
|
+
)} ${colors.green(formatRelativeTime(commit2.relativeDate))}`
|
|
2732
|
+
);
|
|
2733
|
+
commitContent.push(`${colors.dim("\u{1F517}")} ${colors.orange(commit2.hash)}`);
|
|
2739
2734
|
if (commit2.refs && commit2.refs.trim()) {
|
|
2740
2735
|
const refs = commit2.refs.trim();
|
|
2741
2736
|
const refParts = refs.split(", ");
|
|
@@ -2751,16 +2746,24 @@ function formatTimelineStyle(commits) {
|
|
|
2751
2746
|
}
|
|
2752
2747
|
});
|
|
2753
2748
|
if (branches.length > 0) {
|
|
2754
|
-
commitContent.push(
|
|
2749
|
+
commitContent.push(
|
|
2750
|
+
`${colors.dim("\u{1F33F}")} ${colors.lightPurple(branches.join(", "))}`
|
|
2751
|
+
);
|
|
2755
2752
|
}
|
|
2756
2753
|
if (tags.length > 0) {
|
|
2757
2754
|
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
2758
|
-
commitContent.push(
|
|
2755
|
+
commitContent.push(
|
|
2756
|
+
`${colors.dim("\u{1F516}")} ${colors.brightYellow(tagText)}`
|
|
2757
|
+
);
|
|
2759
2758
|
}
|
|
2760
2759
|
}
|
|
2761
2760
|
} else {
|
|
2762
|
-
commitContent.push(
|
|
2763
|
-
|
|
2761
|
+
commitContent.push(
|
|
2762
|
+
`\u{1F464} ${commit2.author} committed ${formatRelativeTime(
|
|
2763
|
+
commit2.relativeDate
|
|
2764
|
+
)}`
|
|
2765
|
+
);
|
|
2766
|
+
commitContent.push(`\u{1F517} ${commit2.hash}`);
|
|
2764
2767
|
if (commit2.refs && commit2.refs.trim()) {
|
|
2765
2768
|
const refs = commit2.refs.trim();
|
|
2766
2769
|
const refParts = refs.split(", ");
|
|
@@ -2786,7 +2789,7 @@ function formatTimelineStyle(commits) {
|
|
|
2786
2789
|
}
|
|
2787
2790
|
const commitBox = boxen3(commitContent.join("\n"), {
|
|
2788
2791
|
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
2789
|
-
margin: { top: 0, bottom:
|
|
2792
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
2790
2793
|
borderStyle: "round",
|
|
2791
2794
|
borderColor: "gray"
|
|
2792
2795
|
});
|
|
@@ -2802,11 +2805,17 @@ function startInteractivePager(content) {
|
|
|
2802
2805
|
stdio: ["pipe", "inherit", "inherit"],
|
|
2803
2806
|
env: { ...process.env, LESS: "-R -S -F -X -i" }
|
|
2804
2807
|
});
|
|
2808
|
+
pagerProcess.stdin.on("error", (err) => {
|
|
2809
|
+
if (err.code !== "EPIPE") {
|
|
2810
|
+
console.error(err);
|
|
2811
|
+
}
|
|
2812
|
+
});
|
|
2805
2813
|
pagerProcess.stdin.write(content);
|
|
2806
2814
|
pagerProcess.stdin.end();
|
|
2807
2815
|
pagerProcess.on("exit", () => {
|
|
2816
|
+
process.exit(0);
|
|
2808
2817
|
});
|
|
2809
|
-
pagerProcess.on("error", (
|
|
2818
|
+
pagerProcess.on("error", () => {
|
|
2810
2819
|
console.log(content);
|
|
2811
2820
|
});
|
|
2812
2821
|
} catch (error) {
|
|
@@ -2822,9 +2831,6 @@ function executeTimelineLog(options) {
|
|
|
2822
2831
|
if (options.until) cmd += ` --until="${options.until}"`;
|
|
2823
2832
|
if (options.grep) cmd += ` --grep="${options.grep}"`;
|
|
2824
2833
|
if (options.all) cmd += ` --all`;
|
|
2825
|
-
if (options.interactive && !options.limit) {
|
|
2826
|
-
cmd += ` -50`;
|
|
2827
|
-
}
|
|
2828
2834
|
const output = execSync8(cmd, {
|
|
2829
2835
|
encoding: "utf8",
|
|
2830
2836
|
stdio: "pipe",
|
|
@@ -2833,14 +2839,6 @@ function executeTimelineLog(options) {
|
|
|
2833
2839
|
if (output.trim()) {
|
|
2834
2840
|
const commits = parseGitLog(output);
|
|
2835
2841
|
let fullOutput = "";
|
|
2836
|
-
const title = `\u{1F4CA} \u5171\u663E\u793A ${commits.length} \u4E2A\u63D0\u4EA4`;
|
|
2837
|
-
fullOutput += "\n" + boxen3(title, {
|
|
2838
|
-
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
2839
|
-
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
2840
|
-
borderStyle: "double",
|
|
2841
|
-
borderColor: "green",
|
|
2842
|
-
textAlignment: "center"
|
|
2843
|
-
}) + "\n";
|
|
2844
2842
|
const timelineOutput = formatTimelineStyle(commits);
|
|
2845
2843
|
fullOutput += timelineOutput;
|
|
2846
2844
|
if (options.interactive) {
|
|
@@ -2916,7 +2914,7 @@ process.on("SIGTERM", () => {
|
|
|
2916
2914
|
console.log("");
|
|
2917
2915
|
process.exit(0);
|
|
2918
2916
|
});
|
|
2919
|
-
var version = true ? "0.3.
|
|
2917
|
+
var version = true ? "0.3.7" : "0.0.0-dev";
|
|
2920
2918
|
async function mainMenu() {
|
|
2921
2919
|
console.log(
|
|
2922
2920
|
colors.green(`
|
package/package.json
CHANGED
package/src/commands/log.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zjex/git-workflow - Log 命令
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* 提供GitHub风格的时间线日志查看功能
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -43,13 +43,13 @@ interface CommitInfo {
|
|
|
43
43
|
*/
|
|
44
44
|
function parseGitLog(output: string): CommitInfo[] {
|
|
45
45
|
const commits: CommitInfo[] = [];
|
|
46
|
-
const lines = output.trim().split(
|
|
47
|
-
|
|
46
|
+
const lines = output.trim().split("\n");
|
|
47
|
+
|
|
48
48
|
for (const line of lines) {
|
|
49
49
|
if (!line.trim()) continue;
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// 使用分隔符解析
|
|
52
|
-
const parts = line.split(
|
|
52
|
+
const parts = line.split("|");
|
|
53
53
|
if (parts.length >= 6) {
|
|
54
54
|
commits.push({
|
|
55
55
|
hash: parts[0],
|
|
@@ -58,11 +58,11 @@ function parseGitLog(output: string): CommitInfo[] {
|
|
|
58
58
|
author: parts[3],
|
|
59
59
|
date: parts[4],
|
|
60
60
|
relativeDate: parts[5],
|
|
61
|
-
refs: parts[6] ||
|
|
61
|
+
refs: parts[6] || "",
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
return commits;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -71,39 +71,50 @@ function parseGitLog(output: string): CommitInfo[] {
|
|
|
71
71
|
*/
|
|
72
72
|
function getCommitTypeIcon(subject: string): string {
|
|
73
73
|
const lowerSubject = subject.toLowerCase();
|
|
74
|
-
|
|
75
|
-
if (lowerSubject.includes(
|
|
76
|
-
|
|
77
|
-
if (lowerSubject.includes(
|
|
78
|
-
if (lowerSubject.includes(
|
|
79
|
-
|
|
80
|
-
if (lowerSubject.includes(
|
|
81
|
-
if (lowerSubject.includes(
|
|
82
|
-
if (lowerSubject.includes(
|
|
83
|
-
if (lowerSubject.includes(
|
|
84
|
-
if (lowerSubject.includes(
|
|
85
|
-
if (lowerSubject.includes(
|
|
86
|
-
if (lowerSubject.includes(
|
|
87
|
-
if (lowerSubject.includes(
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
|
|
75
|
+
if (lowerSubject.includes("feat") || lowerSubject.includes("feature"))
|
|
76
|
+
return "✨";
|
|
77
|
+
if (lowerSubject.includes("fix") || lowerSubject.includes("bug")) return "🐛";
|
|
78
|
+
if (lowerSubject.includes("docs") || lowerSubject.includes("doc"))
|
|
79
|
+
return "📚";
|
|
80
|
+
if (lowerSubject.includes("style")) return "💄";
|
|
81
|
+
if (lowerSubject.includes("refactor")) return "♻️";
|
|
82
|
+
if (lowerSubject.includes("test")) return "🧪";
|
|
83
|
+
if (lowerSubject.includes("chore")) return "🔧";
|
|
84
|
+
if (lowerSubject.includes("perf")) return "⚡";
|
|
85
|
+
if (lowerSubject.includes("ci")) return "👷";
|
|
86
|
+
if (lowerSubject.includes("build")) return "📦";
|
|
87
|
+
if (lowerSubject.includes("revert")) return "⏪";
|
|
88
|
+
if (lowerSubject.includes("merge")) return "🔀";
|
|
89
|
+
if (lowerSubject.includes("release") || lowerSubject.includes("version"))
|
|
90
|
+
return "🔖";
|
|
91
|
+
|
|
92
|
+
return "📝";
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
93
|
-
*
|
|
96
|
+
* 按日期分组提交(保持原始提交顺序)
|
|
94
97
|
*/
|
|
95
98
|
function groupCommitsByDate(commits: CommitInfo[]): Map<string, CommitInfo[]> {
|
|
96
99
|
const groups = new Map<string, CommitInfo[]>();
|
|
97
|
-
|
|
100
|
+
const dateOrder: string[] = []; // 记录日期出现的顺序
|
|
101
|
+
|
|
98
102
|
for (const commit of commits) {
|
|
99
103
|
const date = commit.date;
|
|
100
104
|
if (!groups.has(date)) {
|
|
101
105
|
groups.set(date, []);
|
|
106
|
+
dateOrder.push(date); // 按出现顺序记录日期
|
|
102
107
|
}
|
|
103
108
|
groups.get(date)!.push(commit);
|
|
104
109
|
}
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
|
|
111
|
+
// 返回按出现顺序排列的 Map
|
|
112
|
+
const orderedGroups = new Map<string, CommitInfo[]>();
|
|
113
|
+
for (const date of dateOrder) {
|
|
114
|
+
orderedGroups.set(date, groups.get(date)!);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return orderedGroups;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
/**
|
|
@@ -111,92 +122,101 @@ function groupCommitsByDate(commits: CommitInfo[]): Map<string, CommitInfo[]> {
|
|
|
111
122
|
*/
|
|
112
123
|
function formatRelativeTime(relativeDate: string): string {
|
|
113
124
|
let result = relativeDate;
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
// 先替换英文单词为中文
|
|
116
127
|
const timeMap: { [key: string]: string } = {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
second: "秒",
|
|
129
|
+
seconds: "秒",
|
|
130
|
+
minute: "分钟",
|
|
131
|
+
minutes: "分钟",
|
|
132
|
+
hour: "小时",
|
|
133
|
+
hours: "小时",
|
|
134
|
+
day: "天",
|
|
135
|
+
days: "天",
|
|
136
|
+
week: "周",
|
|
137
|
+
weeks: "周",
|
|
138
|
+
month: "个月",
|
|
139
|
+
months: "个月",
|
|
140
|
+
year: "年",
|
|
141
|
+
years: "年",
|
|
142
|
+
ago: "前",
|
|
132
143
|
};
|
|
133
|
-
|
|
144
|
+
|
|
134
145
|
for (const [en, zh] of Object.entries(timeMap)) {
|
|
135
|
-
result = result.replace(new RegExp(`\\b${en}\\b`,
|
|
146
|
+
result = result.replace(new RegExp(`\\b${en}\\b`, "g"), zh);
|
|
136
147
|
}
|
|
137
|
-
|
|
148
|
+
|
|
138
149
|
// 去掉数字和单位之间的空格,以及单位和"前"之间的空格
|
|
139
150
|
// 例如:"22 分钟 前" -> "22分钟前"
|
|
140
|
-
result = result.replace(
|
|
141
|
-
|
|
151
|
+
result = result.replace(
|
|
152
|
+
/(\d+)\s+(秒|分钟|小时|天|周|个月|年)\s+前/g,
|
|
153
|
+
"$1$2前"
|
|
154
|
+
);
|
|
155
|
+
|
|
142
156
|
// 简化显示格式
|
|
143
157
|
const match = result.match(/(\d+)(分钟|小时|天|周|个月|年)前/);
|
|
144
158
|
if (match) {
|
|
145
159
|
const num = parseInt(match[1]);
|
|
146
160
|
const unit = match[2];
|
|
147
|
-
|
|
161
|
+
|
|
148
162
|
// 超过60分钟显示小时
|
|
149
|
-
if (unit ===
|
|
163
|
+
if (unit === "分钟" && num >= 60) {
|
|
150
164
|
const hours = Math.floor(num / 60);
|
|
151
165
|
return `${hours}小时前`;
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
|
|
154
168
|
// 超过24小时显示天数
|
|
155
|
-
if (unit ===
|
|
169
|
+
if (unit === "小时" && num >= 24) {
|
|
156
170
|
const days = Math.floor(num / 24);
|
|
157
171
|
return `${days}天前`;
|
|
158
172
|
}
|
|
159
|
-
|
|
173
|
+
|
|
160
174
|
// 超过7天显示周数
|
|
161
|
-
if (unit ===
|
|
175
|
+
if (unit === "天" && num >= 7 && num < 30) {
|
|
162
176
|
const weeks = Math.floor(num / 7);
|
|
163
177
|
return `${weeks}周前`;
|
|
164
178
|
}
|
|
165
|
-
|
|
179
|
+
|
|
166
180
|
// 超过30天显示月数
|
|
167
|
-
if (unit ===
|
|
181
|
+
if (unit === "天" && num >= 30) {
|
|
168
182
|
const months = Math.floor(num / 30);
|
|
169
183
|
return `${months}个月前`;
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
|
|
172
186
|
// 超过4周显示月数
|
|
173
|
-
if (unit ===
|
|
187
|
+
if (unit === "周" && num >= 4) {
|
|
174
188
|
const months = Math.floor(num / 4);
|
|
175
189
|
return `${months}个月前`;
|
|
176
190
|
}
|
|
177
|
-
|
|
191
|
+
|
|
178
192
|
// 超过12个月显示年数
|
|
179
|
-
if (unit ===
|
|
193
|
+
if (unit === "个月" && num >= 12) {
|
|
180
194
|
const years = Math.floor(num / 12);
|
|
181
195
|
return `${years}年前`;
|
|
182
196
|
}
|
|
183
197
|
}
|
|
184
|
-
|
|
198
|
+
|
|
185
199
|
return result;
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
/**
|
|
189
203
|
* 解析提交主题,分离标题和子任务
|
|
190
204
|
*/
|
|
191
|
-
function parseCommitSubject(subject: string): {
|
|
205
|
+
function parseCommitSubject(subject: string): {
|
|
206
|
+
title: string;
|
|
207
|
+
tasks: string[];
|
|
208
|
+
} {
|
|
192
209
|
// 检查是否包含 " - " 分隔的子任务
|
|
193
|
-
if (subject.includes(
|
|
194
|
-
const parts = subject.split(
|
|
210
|
+
if (subject.includes(" - ")) {
|
|
211
|
+
const parts = subject.split(" - ");
|
|
195
212
|
const title = parts[0].trim();
|
|
196
|
-
const tasks = parts
|
|
213
|
+
const tasks = parts
|
|
214
|
+
.slice(1)
|
|
215
|
+
.map((task) => task.trim())
|
|
216
|
+
.filter((task) => task.length > 0);
|
|
197
217
|
return { title, tasks };
|
|
198
218
|
}
|
|
199
|
-
|
|
219
|
+
|
|
200
220
|
return { title: subject, tasks: [] };
|
|
201
221
|
}
|
|
202
222
|
|
|
@@ -213,137 +233,146 @@ function supportsColor(): boolean {
|
|
|
213
233
|
*/
|
|
214
234
|
function formatTimelineStyle(commits: CommitInfo[]): string {
|
|
215
235
|
const groupedCommits = groupCommitsByDate(commits);
|
|
216
|
-
let output =
|
|
217
|
-
|
|
218
|
-
//
|
|
219
|
-
const sortedDates = Array.from(groupedCommits.keys())
|
|
220
|
-
|
|
221
|
-
);
|
|
222
|
-
|
|
236
|
+
let output = "";
|
|
237
|
+
|
|
238
|
+
// 保持原始提交顺序(已在 groupCommitsByDate 中按出现顺序排列)
|
|
239
|
+
const sortedDates = Array.from(groupedCommits.keys());
|
|
240
|
+
|
|
223
241
|
const useColors = supportsColor() || process.env.FORCE_COLOR;
|
|
224
|
-
|
|
242
|
+
|
|
225
243
|
for (let dateIndex = 0; dateIndex < sortedDates.length; dateIndex++) {
|
|
226
244
|
const date = sortedDates[dateIndex];
|
|
227
245
|
const dateCommits = groupedCommits.get(date)!;
|
|
228
|
-
|
|
246
|
+
|
|
229
247
|
// 日期标题 - 使用黄色突出显示
|
|
230
248
|
const dateTitle = `📅 Commits on ${date}`;
|
|
231
249
|
if (useColors) {
|
|
232
|
-
output +=
|
|
250
|
+
output += colors.bold(colors.yellow(dateTitle)) + "\n";
|
|
233
251
|
} else {
|
|
234
|
-
output +=
|
|
252
|
+
output += dateTitle + "\n";
|
|
235
253
|
}
|
|
236
|
-
|
|
254
|
+
|
|
237
255
|
// 该日期下的提交
|
|
238
256
|
for (let commitIndex = 0; commitIndex < dateCommits.length; commitIndex++) {
|
|
239
257
|
const commit = dateCommits[commitIndex];
|
|
240
|
-
const icon = getCommitTypeIcon(commit.subject);
|
|
241
258
|
const { title, tasks } = parseCommitSubject(commit.subject);
|
|
242
|
-
|
|
259
|
+
|
|
243
260
|
// 构建提交内容
|
|
244
261
|
const commitContent = [];
|
|
245
|
-
|
|
246
|
-
// 主标题 -
|
|
262
|
+
|
|
263
|
+
// 主标题 - 使用白色加粗,保持原始提交信息
|
|
247
264
|
if (useColors) {
|
|
248
|
-
commitContent.push(
|
|
265
|
+
commitContent.push(colors.bold(colors.white(title)));
|
|
249
266
|
} else {
|
|
250
|
-
commitContent.push(
|
|
267
|
+
commitContent.push(title);
|
|
251
268
|
}
|
|
252
|
-
|
|
269
|
+
|
|
253
270
|
// 如果有子任务,添加子任务列表
|
|
254
271
|
if (tasks.length > 0) {
|
|
255
|
-
commitContent.push(
|
|
256
|
-
tasks.forEach(task => {
|
|
272
|
+
commitContent.push(""); // 空行分隔
|
|
273
|
+
tasks.forEach((task) => {
|
|
257
274
|
if (useColors) {
|
|
258
|
-
commitContent.push(` ${colors.dim(
|
|
275
|
+
commitContent.push(` ${colors.dim("–")} ${colors.dim(task)}`);
|
|
259
276
|
} else {
|
|
260
277
|
commitContent.push(` – ${task}`);
|
|
261
278
|
}
|
|
262
279
|
});
|
|
263
280
|
}
|
|
264
|
-
|
|
281
|
+
|
|
265
282
|
// 空行分隔
|
|
266
|
-
commitContent.push(
|
|
267
|
-
|
|
283
|
+
commitContent.push("");
|
|
284
|
+
|
|
268
285
|
// 作者和时间信息
|
|
269
286
|
if (useColors) {
|
|
270
|
-
commitContent.push(
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
commitContent.push(
|
|
288
|
+
`${colors.dim("👤")} ${colors.blue(commit.author)} ${colors.dim(
|
|
289
|
+
"committed"
|
|
290
|
+
)} ${colors.green(formatRelativeTime(commit.relativeDate))}`
|
|
291
|
+
);
|
|
292
|
+
// Hash信息 - 使用橙色,显示完整 hash
|
|
293
|
+
commitContent.push(`${colors.dim("🔗")} ${colors.orange(commit.hash)}`);
|
|
273
294
|
// 如果有分支/标签信息 - 区分显示
|
|
274
295
|
if (commit.refs && commit.refs.trim()) {
|
|
275
296
|
const refs = commit.refs.trim();
|
|
276
297
|
// 解析并分别显示分支和标签
|
|
277
|
-
const refParts = refs.split(
|
|
298
|
+
const refParts = refs.split(", ");
|
|
278
299
|
const branches: string[] = [];
|
|
279
300
|
const tags: string[] = [];
|
|
280
|
-
|
|
281
|
-
refParts.forEach(ref => {
|
|
282
|
-
if (ref.startsWith(
|
|
283
|
-
tags.push(ref.replace(
|
|
284
|
-
} else if (ref.includes(
|
|
301
|
+
|
|
302
|
+
refParts.forEach((ref) => {
|
|
303
|
+
if (ref.startsWith("tag: ")) {
|
|
304
|
+
tags.push(ref.replace("tag: ", ""));
|
|
305
|
+
} else if (ref.includes("/") || ref === "HEAD") {
|
|
285
306
|
branches.push(ref);
|
|
286
307
|
} else {
|
|
287
308
|
branches.push(ref);
|
|
288
309
|
}
|
|
289
310
|
});
|
|
290
|
-
|
|
311
|
+
|
|
291
312
|
// 显示分支信息
|
|
292
313
|
if (branches.length > 0) {
|
|
293
|
-
commitContent.push(
|
|
314
|
+
commitContent.push(
|
|
315
|
+
`${colors.dim("🌿")} ${colors.lightPurple(branches.join(", "))}`
|
|
316
|
+
);
|
|
294
317
|
}
|
|
295
|
-
|
|
318
|
+
|
|
296
319
|
// 显示标签信息
|
|
297
320
|
if (tags.length > 0) {
|
|
298
|
-
const tagText = tags.map(tag => `tag ${tag}`).join(
|
|
299
|
-
commitContent.push(
|
|
321
|
+
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
322
|
+
commitContent.push(
|
|
323
|
+
`${colors.dim("🔖")} ${colors.brightYellow(tagText)}`
|
|
324
|
+
);
|
|
300
325
|
}
|
|
301
326
|
}
|
|
302
327
|
} else {
|
|
303
|
-
commitContent.push(
|
|
304
|
-
|
|
328
|
+
commitContent.push(
|
|
329
|
+
`👤 ${commit.author} committed ${formatRelativeTime(
|
|
330
|
+
commit.relativeDate
|
|
331
|
+
)}`
|
|
332
|
+
);
|
|
333
|
+
commitContent.push(`🔗 ${commit.hash}`);
|
|
305
334
|
if (commit.refs && commit.refs.trim()) {
|
|
306
335
|
const refs = commit.refs.trim();
|
|
307
336
|
// 解析并分别显示分支和标签
|
|
308
|
-
const refParts = refs.split(
|
|
337
|
+
const refParts = refs.split(", ");
|
|
309
338
|
const branches: string[] = [];
|
|
310
339
|
const tags: string[] = [];
|
|
311
|
-
|
|
312
|
-
refParts.forEach(ref => {
|
|
313
|
-
if (ref.startsWith(
|
|
314
|
-
tags.push(ref.replace(
|
|
315
|
-
} else if (ref.includes(
|
|
340
|
+
|
|
341
|
+
refParts.forEach((ref) => {
|
|
342
|
+
if (ref.startsWith("tag: ")) {
|
|
343
|
+
tags.push(ref.replace("tag: ", ""));
|
|
344
|
+
} else if (ref.includes("/") || ref === "HEAD") {
|
|
316
345
|
branches.push(ref);
|
|
317
346
|
} else {
|
|
318
347
|
branches.push(ref);
|
|
319
348
|
}
|
|
320
349
|
});
|
|
321
|
-
|
|
350
|
+
|
|
322
351
|
// 显示分支信息
|
|
323
352
|
if (branches.length > 0) {
|
|
324
|
-
commitContent.push(`🌿 ${branches.join(
|
|
353
|
+
commitContent.push(`🌿 ${branches.join(", ")}`);
|
|
325
354
|
}
|
|
326
|
-
|
|
355
|
+
|
|
327
356
|
// 显示标签信息
|
|
328
357
|
if (tags.length > 0) {
|
|
329
|
-
const tagText = tags.map(tag => `tag ${tag}`).join(
|
|
358
|
+
const tagText = tags.map((tag) => `tag ${tag}`).join(", ");
|
|
330
359
|
commitContent.push(`🔖 ${tagText}`);
|
|
331
360
|
}
|
|
332
361
|
}
|
|
333
362
|
}
|
|
334
|
-
|
|
363
|
+
|
|
335
364
|
// 使用boxen
|
|
336
|
-
const commitBox = boxen(commitContent.join(
|
|
365
|
+
const commitBox = boxen(commitContent.join("\n"), {
|
|
337
366
|
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
338
|
-
margin: { top: 0, bottom:
|
|
339
|
-
borderStyle:
|
|
340
|
-
borderColor:
|
|
367
|
+
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
368
|
+
borderStyle: "round",
|
|
369
|
+
borderColor: "gray",
|
|
341
370
|
});
|
|
342
|
-
|
|
343
|
-
output += commitBox +
|
|
371
|
+
|
|
372
|
+
output += commitBox + "\n";
|
|
344
373
|
}
|
|
345
374
|
}
|
|
346
|
-
|
|
375
|
+
|
|
347
376
|
return output;
|
|
348
377
|
}
|
|
349
378
|
|
|
@@ -352,34 +381,42 @@ function formatTimelineStyle(commits: CommitInfo[]): string {
|
|
|
352
381
|
*/
|
|
353
382
|
function startInteractivePager(content: string): void {
|
|
354
383
|
// 使用系统的 less 命令作为分页器,启用颜色支持
|
|
355
|
-
const pager = process.env.PAGER ||
|
|
356
|
-
|
|
384
|
+
const pager = process.env.PAGER || "less";
|
|
385
|
+
|
|
357
386
|
try {
|
|
358
387
|
// -R: 支持ANSI颜色代码
|
|
359
388
|
// -S: 不换行长行
|
|
360
389
|
// -F: 如果内容少于一屏则直接退出
|
|
361
390
|
// -X: 不清屏
|
|
362
391
|
// -i: 忽略大小写搜索
|
|
363
|
-
const pagerProcess = spawn(pager, [
|
|
364
|
-
stdio: [
|
|
365
|
-
env: { ...process.env, LESS:
|
|
392
|
+
const pagerProcess = spawn(pager, ["-R", "-S", "-F", "-X", "-i"], {
|
|
393
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
394
|
+
env: { ...process.env, LESS: "-R -S -F -X -i" },
|
|
366
395
|
});
|
|
367
|
-
|
|
396
|
+
|
|
397
|
+
// 处理 stdin 的 EPIPE 错误(当 less 提前退出时)
|
|
398
|
+
pagerProcess.stdin.on("error", (err: NodeJS.ErrnoException) => {
|
|
399
|
+
if (err.code !== "EPIPE") {
|
|
400
|
+
// 只忽略 EPIPE 错误,其他错误输出
|
|
401
|
+
console.error(err);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
368
405
|
// 将内容写入分页器
|
|
369
406
|
pagerProcess.stdin.write(content);
|
|
370
407
|
pagerProcess.stdin.end();
|
|
371
|
-
|
|
408
|
+
|
|
372
409
|
// 处理分页器退出
|
|
373
|
-
pagerProcess.on(
|
|
374
|
-
//
|
|
410
|
+
pagerProcess.on("exit", () => {
|
|
411
|
+
// 分页器退出后正常退出程序
|
|
412
|
+
process.exit(0);
|
|
375
413
|
});
|
|
376
|
-
|
|
414
|
+
|
|
377
415
|
// 处理错误
|
|
378
|
-
pagerProcess.on(
|
|
416
|
+
pagerProcess.on("error", () => {
|
|
379
417
|
// 如果分页器启动失败,直接输出内容
|
|
380
418
|
console.log(content);
|
|
381
419
|
});
|
|
382
|
-
|
|
383
420
|
} catch (error) {
|
|
384
421
|
// 如果出错,直接输出内容
|
|
385
422
|
console.log(content);
|
|
@@ -393,7 +430,7 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
393
430
|
try {
|
|
394
431
|
// 构建Git命令
|
|
395
432
|
let cmd = 'git log --pretty=format:"%H|%h|%s|%an|%ad|%ar|%D" --date=short';
|
|
396
|
-
|
|
433
|
+
|
|
397
434
|
// 添加选项
|
|
398
435
|
if (options.limit && !options.interactive) cmd += ` -${options.limit}`;
|
|
399
436
|
if (options.author) cmd += ` --author="${options.author}"`;
|
|
@@ -401,53 +438,41 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
401
438
|
if (options.until) cmd += ` --until="${options.until}"`;
|
|
402
439
|
if (options.grep) cmd += ` --grep="${options.grep}"`;
|
|
403
440
|
if (options.all) cmd += ` --all`;
|
|
404
|
-
|
|
405
|
-
// 交互式模式默认显示更多提交
|
|
406
|
-
if (options.interactive && !options.limit) {
|
|
407
|
-
cmd += ` -50`; // 默认显示50个提交
|
|
408
|
-
}
|
|
409
441
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
442
|
+
// 交互式模式默认显示全部提交(不限制数量)
|
|
443
|
+
|
|
444
|
+
const output = execSync(cmd, {
|
|
445
|
+
encoding: "utf8",
|
|
446
|
+
stdio: "pipe",
|
|
447
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
414
448
|
});
|
|
415
449
|
|
|
416
450
|
if (output.trim()) {
|
|
417
451
|
const commits = parseGitLog(output);
|
|
418
|
-
|
|
452
|
+
|
|
419
453
|
// 构建完整输出
|
|
420
|
-
let fullOutput =
|
|
421
|
-
|
|
422
|
-
// 显示标题
|
|
423
|
-
const title = `📊 共显示 ${commits.length} 个提交`;
|
|
424
|
-
fullOutput += '\n' + boxen(title, {
|
|
425
|
-
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
426
|
-
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
427
|
-
borderStyle: 'double',
|
|
428
|
-
borderColor: 'green',
|
|
429
|
-
textAlignment: 'center'
|
|
430
|
-
}) + '\n';
|
|
431
|
-
|
|
454
|
+
let fullOutput = "";
|
|
455
|
+
|
|
432
456
|
// 显示时间线
|
|
433
457
|
const timelineOutput = formatTimelineStyle(commits);
|
|
434
458
|
fullOutput += timelineOutput;
|
|
435
|
-
|
|
459
|
+
|
|
436
460
|
// 根据是否交互式模式选择输出方式
|
|
437
461
|
if (options.interactive) {
|
|
438
462
|
startInteractivePager(fullOutput);
|
|
439
463
|
} else {
|
|
440
464
|
console.log(fullOutput);
|
|
441
465
|
}
|
|
442
|
-
|
|
443
466
|
} else {
|
|
444
|
-
const noCommitsMsg =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
467
|
+
const noCommitsMsg =
|
|
468
|
+
"\n" +
|
|
469
|
+
boxen("📭 没有找到匹配的提交记录", {
|
|
470
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
471
|
+
borderStyle: "round",
|
|
472
|
+
borderColor: "yellow",
|
|
473
|
+
textAlignment: "center",
|
|
474
|
+
});
|
|
475
|
+
|
|
451
476
|
if (options.interactive) {
|
|
452
477
|
startInteractivePager(noCommitsMsg);
|
|
453
478
|
} else {
|
|
@@ -455,20 +480,22 @@ function executeTimelineLog(options: LogOptions): void {
|
|
|
455
480
|
}
|
|
456
481
|
}
|
|
457
482
|
} catch (error: any) {
|
|
458
|
-
let errorMessage =
|
|
483
|
+
let errorMessage = "❌ 执行失败";
|
|
459
484
|
if (error.status === 128) {
|
|
460
|
-
errorMessage =
|
|
485
|
+
errorMessage = "❌ Git仓库错误或没有提交记录";
|
|
461
486
|
} else {
|
|
462
487
|
errorMessage = `❌ 执行失败: ${error.message}`;
|
|
463
488
|
}
|
|
464
|
-
|
|
465
|
-
const errorBox =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
489
|
+
|
|
490
|
+
const errorBox =
|
|
491
|
+
"\n" +
|
|
492
|
+
boxen(errorMessage, {
|
|
493
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
494
|
+
borderStyle: "round",
|
|
495
|
+
borderColor: "red",
|
|
496
|
+
textAlignment: "center",
|
|
497
|
+
});
|
|
498
|
+
|
|
472
499
|
if (options.interactive) {
|
|
473
500
|
startInteractivePager(errorBox);
|
|
474
501
|
} else {
|
|
@@ -485,12 +512,12 @@ export async function log(options: LogOptions = {}): Promise<void> {
|
|
|
485
512
|
if (options.interactive === undefined) {
|
|
486
513
|
options.interactive = true;
|
|
487
514
|
}
|
|
488
|
-
|
|
515
|
+
|
|
489
516
|
// 交互式模式下不设置默认limit
|
|
490
517
|
if (!options.interactive && !options.limit) {
|
|
491
518
|
options.limit = 10;
|
|
492
519
|
}
|
|
493
|
-
|
|
520
|
+
|
|
494
521
|
executeTimelineLog(options);
|
|
495
522
|
}
|
|
496
523
|
|
|
@@ -500,4 +527,4 @@ export async function log(options: LogOptions = {}): Promise<void> {
|
|
|
500
527
|
export async function quickLog(limit: number = 10): Promise<void> {
|
|
501
528
|
const options: LogOptions = { limit };
|
|
502
529
|
executeTimelineLog(options);
|
|
503
|
-
}
|
|
530
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -356,11 +356,11 @@ cli
|
|
|
356
356
|
.action(async (options: any) => {
|
|
357
357
|
await checkForUpdates(version, "@zjex/git-workflow");
|
|
358
358
|
checkGitRepo();
|
|
359
|
-
|
|
359
|
+
|
|
360
360
|
// 构建选项对象 - 默认交互式模式
|
|
361
361
|
const logOptions: any = { interactive: true };
|
|
362
362
|
if (options.limit) logOptions.limit = parseInt(options.limit);
|
|
363
|
-
|
|
363
|
+
|
|
364
364
|
return log(logOptions);
|
|
365
365
|
});
|
|
366
366
|
|
package/src/utils.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface Colors {
|
|
|
12
12
|
orange: (s: string) => string;
|
|
13
13
|
lightPurple: (s: string) => string;
|
|
14
14
|
white: (s: string) => string;
|
|
15
|
+
brightYellow: (s: string) => string;
|
|
15
16
|
reset: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -27,6 +28,7 @@ export const colors: Colors = {
|
|
|
27
28
|
orange: (s) => `\x1b[38;5;208m${s}\x1b[0m`,
|
|
28
29
|
lightPurple: (s) => `\x1b[38;5;141m${s}\x1b[0m`,
|
|
29
30
|
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
|
31
|
+
brightYellow: (s) => `\x1b[93m${s}\x1b[0m`, // 亮黄色
|
|
30
32
|
reset: "\x1b[0m",
|
|
31
33
|
};
|
|
32
34
|
|
package/tsup.config.ts
CHANGED
|
@@ -20,7 +20,7 @@ export default defineConfig({
|
|
|
20
20
|
"cac",
|
|
21
21
|
],
|
|
22
22
|
banner: {
|
|
23
|
-
js: "#!/usr/bin/env -S node --
|
|
23
|
+
js: "#!/usr/bin/env -S node --disable-warning=ExperimentalWarning",
|
|
24
24
|
},
|
|
25
25
|
define: {
|
|
26
26
|
__VERSION__: JSON.stringify(pkg.version),
|