wukong-gitlog-cli 0.0.9 → 0.0.10
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 +8 -0
- package/package.json +1 -1
- package/src/overtime.mjs +52 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.10](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v0.0.9...v0.0.10) (2025-11-28)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* 🎸 each hour percent ([4f1a7e7](https://github.com/tomatobybike/wukong-gitlog-cli/commit/4f1a7e75a1c0d83c92aabf42e97932421c349b8d))
|
|
11
|
+
* 🎸 hourline ([9035ecb](https://github.com/tomatobybike/wukong-gitlog-cli/commit/9035ecb33295fa5f4ff64bbd9b9281c543247683))
|
|
12
|
+
|
|
5
13
|
### [0.0.9](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v0.0.8...v0.0.9) (2025-11-28)
|
|
6
14
|
|
|
7
15
|
### [0.0.8](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v0.0.7...v0.0.8) (2025-11-28)
|
package/package.json
CHANGED
package/src/overtime.mjs
CHANGED
|
@@ -64,6 +64,8 @@ export function analyzeOvertime(records, opts = {}) {
|
|
|
64
64
|
hd.init('CN');
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// 新增:每小时分布统计
|
|
68
|
+
const hourlyCommits = Array(24).fill(0);
|
|
67
69
|
records.forEach((r) => {
|
|
68
70
|
const dt = parseCommitDate(r.date);
|
|
69
71
|
if (!dt || !dt.isValid()) return; // skip
|
|
@@ -72,6 +74,12 @@ export function analyzeOvertime(records, opts = {}) {
|
|
|
72
74
|
const isHoliday = !!hd.isHoliday(dt.toDate());
|
|
73
75
|
const isNonWork = isWeekend(dt) || isHoliday;
|
|
74
76
|
|
|
77
|
+
// 每小时分布
|
|
78
|
+
const hour = dt.hour();
|
|
79
|
+
if (hour >= 0 && hour < 24) {
|
|
80
|
+
hourlyCommits[hour]++;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
if (outside) {
|
|
76
84
|
outsideWorkCount++;
|
|
77
85
|
// 记录最新的加班提交
|
|
@@ -134,6 +142,8 @@ export function analyzeOvertime(records, opts = {}) {
|
|
|
134
142
|
// sort perAuthor by outsideWorkRate desc
|
|
135
143
|
perAuthor.sort((a, b) => b.outsideWorkRate - a.outsideWorkRate || b.total - a.total);
|
|
136
144
|
|
|
145
|
+
// 新增:每小时分布百分比
|
|
146
|
+
const hourlyPercent = hourlyCommits.map(v => total ? +(v / total).toFixed(3) : 0);
|
|
137
147
|
return {
|
|
138
148
|
total,
|
|
139
149
|
outsideWorkCount,
|
|
@@ -148,16 +158,18 @@ export function analyzeOvertime(records, opts = {}) {
|
|
|
148
158
|
latestOutsideCommit: latestOutsideCommit || null,
|
|
149
159
|
startHour,
|
|
150
160
|
endHour,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
lunchStart,
|
|
162
|
+
lunchEnd,
|
|
163
|
+
country,
|
|
164
|
+
holidayCount,
|
|
165
|
+
holidayRate: total ? +(holidayCount / total).toFixed(3) : 0,
|
|
166
|
+
hourlyCommits,
|
|
167
|
+
hourlyPercent,
|
|
156
168
|
};
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
export function renderOvertimeText(stats) {
|
|
160
|
-
const { total, outsideWorkCount, nonWorkdayCount, holidayCount, outsideWorkRate, nonWorkdayRate, holidayRate, perAuthor, startHour, endHour, lunchStart, lunchEnd, country } = stats;
|
|
172
|
+
const { total, outsideWorkCount, nonWorkdayCount, holidayCount, outsideWorkRate, nonWorkdayRate, holidayRate, perAuthor, startHour, endHour, lunchStart, lunchEnd, country, hourlyCommits = [], hourlyPercent = [] } = stats;
|
|
161
173
|
const { startCommit, endCommit, latestCommit, latestOutsideCommit } = stats;
|
|
162
174
|
const lines = [];
|
|
163
175
|
|
|
@@ -206,6 +218,18 @@ export function renderOvertimeText(stats) {
|
|
|
206
218
|
lines.push(`下班时间(工作时间外)提交数:${outsideWorkCount},占比:${(outsideWorkRate * 100).toFixed(1)}%`);
|
|
207
219
|
lines.push(`非工作日(周末)提交数:${nonWorkdayCount},占比:${(nonWorkdayRate * 100).toFixed(1)}%`);
|
|
208
220
|
lines.push('');
|
|
221
|
+
lines.push('每小时分布(提交数/占比):');
|
|
222
|
+
const hourLine = ' Hour | Count | Percent';
|
|
223
|
+
lines.push(hourLine);
|
|
224
|
+
lines.push(' -----|-------|--------');
|
|
225
|
+
for (let h = 0; h < 24; h++) {
|
|
226
|
+
const cnt = hourlyCommits[h] || 0;
|
|
227
|
+
if (cnt > 0) {
|
|
228
|
+
const pct = hourlyPercent[h] ? (hourlyPercent[h] * 100).toFixed(1) : '0.0';
|
|
229
|
+
lines.push(` ${String(h).padStart(2, '0')} | ${String(cnt).padStart(5, ' ')} | ${pct.padStart(6, ' ')}%`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
lines.push('');
|
|
209
233
|
lines.push('按人员统计:');
|
|
210
234
|
// header
|
|
211
235
|
const header = ` ${padDisplayEnd('Name', cols.name)} | ${padDisplayStart('总数', cols.total)} | ${padDisplayStart('下班外数', cols.outside)} | ${padDisplayStart('下班外占比', cols.outsideRate)} | ${padDisplayStart('非工作日数', cols.nonWork)} | ${padDisplayStart('非工作日占比', cols.nonWorkRate)} | ${padDisplayStart('假日数', cols.holiday)} | ${padDisplayStart('假日占比', cols.holidayRate)}`;
|
|
@@ -224,7 +248,7 @@ export function renderOvertimeText(stats) {
|
|
|
224
248
|
}
|
|
225
249
|
|
|
226
250
|
export function renderOvertimeTab(stats) {
|
|
227
|
-
const { total, outsideWorkCount, nonWorkdayCount, holidayCount, outsideWorkRate, nonWorkdayRate, holidayRate, perAuthor, startHour, endHour, lunchStart, lunchEnd, country } = stats;
|
|
251
|
+
const { total, outsideWorkCount, nonWorkdayCount, holidayCount, outsideWorkRate, nonWorkdayRate, holidayRate, perAuthor, startHour, endHour, lunchStart, lunchEnd, country, hourlyCommits = [], hourlyPercent = [] } = stats;
|
|
228
252
|
const { startCommit, endCommit, latestCommit, latestOutsideCommit } = stats;
|
|
229
253
|
const rows = [];
|
|
230
254
|
rows.push(`总提交数:\t${total}`);
|
|
@@ -236,6 +260,16 @@ export function renderOvertimeTab(stats) {
|
|
|
236
260
|
rows.push(`下班时间(工作时间外)提交数:\t${outsideWorkCount}\t占比:\t${(outsideWorkRate * 100).toFixed(1)}%`);
|
|
237
261
|
rows.push(`非工作日(周末)提交数:\t${nonWorkdayCount}\t占比:\t${(nonWorkdayRate * 100).toFixed(1)}%`);
|
|
238
262
|
rows.push('');
|
|
263
|
+
rows.push('每小时分布(提交数/占比):');
|
|
264
|
+
rows.push(['Hour', 'Count', 'Percent'].join('\t'));
|
|
265
|
+
for (let h = 0; h < 24; h++) {
|
|
266
|
+
const cnt = hourlyCommits[h] || 0;
|
|
267
|
+
if (cnt > 0) {
|
|
268
|
+
const pct = hourlyPercent[h] ? (hourlyPercent[h] * 100).toFixed(1) : '0.0';
|
|
269
|
+
rows.push([String(h).padStart(2, '0'), cnt, `${pct}%`].join('\t'));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
rows.push('');
|
|
239
273
|
rows.push(['Name', '总数', '下班外数', '下班外占比', '非工作日数', '非工作日占比', '假日数', '假日占比'].join('\t'));
|
|
240
274
|
perAuthor.forEach((p) => {
|
|
241
275
|
rows.push([p.name || '-', p.total, p.outsideWorkCount, `${(p.outsideWorkRate * 100).toFixed(1)}%`, p.nonWorkdayCount, `${(p.nonWorkdayRate * 100).toFixed(1)}%`, p.holidayCount || 0, `${((p.holidayCount || 0) / p.total * 100).toFixed(1)}%`].join('\t'));
|
|
@@ -252,12 +286,22 @@ function escapeCsv(v) {
|
|
|
252
286
|
}
|
|
253
287
|
|
|
254
288
|
export function renderOvertimeCsv(stats) {
|
|
255
|
-
const { perAuthor, latestOutsideCommit, country } = stats;
|
|
289
|
+
const { perAuthor, latestOutsideCommit, country, hourlyCommits = [], hourlyPercent = [], total = 0 } = stats;
|
|
256
290
|
const rows = [];
|
|
257
291
|
if (latestOutsideCommit) {
|
|
258
292
|
rows.push(`# 加班最晚的一次提交,Hash,Author,Date,Message`);
|
|
259
293
|
rows.push(`# ,${escapeCsv(latestOutsideCommit.hash)},${escapeCsv(latestOutsideCommit.author)},${escapeCsv(formatDateForCountry(latestOutsideCommit.date, country))},${escapeCsv(latestOutsideCommit.message)}`);
|
|
260
294
|
}
|
|
295
|
+
rows.push('');
|
|
296
|
+
rows.push('Hour,Count,Percent');
|
|
297
|
+
for (let h = 0; h < 24; h++) {
|
|
298
|
+
const cnt = hourlyCommits[h] || 0;
|
|
299
|
+
if (cnt > 0) {
|
|
300
|
+
const pct = hourlyPercent[h] ? (hourlyPercent[h] * 100).toFixed(1) : '0.0';
|
|
301
|
+
rows.push(`${h.toString().padStart(2, '0')},${cnt},${pct}%`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
rows.push('');
|
|
261
305
|
rows.push('Name,Total,OutsideCount,OutsideRate,NonWorkdayCount,NonWorkdayRate,HolidayCount,HolidayRate');
|
|
262
306
|
perAuthor.forEach((p) => {
|
|
263
307
|
rows.push(`${escapeCsv(p.name)},${p.total},${p.outsideWorkCount},${(p.outsideWorkRate * 100).toFixed(1)}%,${p.nonWorkdayCount},${(p.nonWorkdayRate * 100).toFixed(1)}%,${p.holidayCount || 0},${((p.holidayCount || 0) / p.total * 100).toFixed(1)}%`);
|