wukong-gitlog-cli 1.0.13 → 1.0.15
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 +21 -0
- package/README.md +4 -0
- package/README.zh-CN.md +4 -0
- package/package.json +1 -1
- package/src/cli.mjs +107 -19
- package/web/app.js +1000 -54
- package/web/index.html +25 -1
- package/web/static/style.css +105 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
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
|
+
### [1.0.15](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.14...v1.0.15) (2025-12-03)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* 🎸 dayDetailSidebar ([21b99fd](https://github.com/tomatobybike/wukong-gitlog-cli/commit/21b99fd859af45641987dfd5f50a8c5604a380c5))
|
|
11
|
+
* 🎸 each day ([ffa3d5e](https://github.com/tomatobybike/wukong-gitlog-cli/commit/ffa3d5e6192a69a727c747ffa325b56ae5bf78d4))
|
|
12
|
+
* 🎸 hourlyOvertimeChart ([93f62b7](https://github.com/tomatobybike/wukong-gitlog-cli/commit/93f62b7b9906154a16bdcf38bd7b0979d44bebfe))
|
|
13
|
+
* 🎸 latestOvertimeDay ([40cb157](https://github.com/tomatobybike/wukong-gitlog-cli/commit/40cb15788411441306e0c838108817fa36268624))
|
|
14
|
+
* 🎸 next day cutof ([efe6a5f](https://github.com/tomatobybike/wukong-gitlog-cli/commit/efe6a5f41e54ca0fdadb3c0071f1fcf7a286e6c2))
|
|
15
|
+
* 🎸 onDayClick ([9132b54](https://github.com/tomatobybike/wukong-gitlog-cli/commit/9132b5423449ceb8a964d813dfd4931834993913))
|
|
16
|
+
* 🎸 renderKpi ([e26cc0d](https://github.com/tomatobybike/wukong-gitlog-cli/commit/e26cc0deac83a7e9bccaae81ba572da0e39e82b3))
|
|
17
|
+
* 🎸 week range ([cd565da](https://github.com/tomatobybike/wukong-gitlog-cli/commit/cd565dac98b6c38442eeacdd6dd423b93d657ab0))
|
|
18
|
+
|
|
19
|
+
### [1.0.14](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.13...v1.0.14) (2025-12-02)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* 🎸 charts show better ([bfceb54](https://github.com/tomatobybike/wukong-gitlog-cli/commit/bfceb54b2bd67cc6bd67d93550799592119daff2))
|
|
25
|
+
|
|
5
26
|
### [1.0.13](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.12...v1.0.13) (2025-12-01)
|
|
6
27
|
|
|
7
28
|
|
package/README.md
CHANGED
package/README.zh-CN.md
CHANGED
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { Command } from 'commander'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
|
+
import isoWeek from 'dayjs/plugin/isoWeek.js'
|
|
4
5
|
import fs from 'fs'
|
|
5
6
|
import path from 'path'
|
|
6
7
|
import { fileURLToPath } from 'url'
|
|
@@ -31,6 +32,8 @@ const pkg = JSON.parse(
|
|
|
31
32
|
fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8')
|
|
32
33
|
)
|
|
33
34
|
|
|
35
|
+
dayjs.extend(isoWeek)
|
|
36
|
+
|
|
34
37
|
const PKG_NAME = pkg.name
|
|
35
38
|
const VERSION = pkg.version
|
|
36
39
|
|
|
@@ -51,6 +54,21 @@ const version = async () => {
|
|
|
51
54
|
process.exit(0)
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
/** 将 "2025-W48" → { start: '2025-11-24', end: '2025-11-30' } */
|
|
58
|
+
export function getWeekRange(periodStr) {
|
|
59
|
+
// periodStr = "2025-W48"
|
|
60
|
+
const [year, w] = periodStr.split('-W')
|
|
61
|
+
const week = parseInt(w, 10)
|
|
62
|
+
|
|
63
|
+
const start = dayjs().year(year).isoWeek(week).startOf('week') // Monday
|
|
64
|
+
const end = dayjs().year(year).isoWeek(week).endOf('week') // Sunday
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
start: start.format('YYYY-MM-DD'),
|
|
68
|
+
end: end.format('YYYY-MM-DD')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
const main = async () => {
|
|
55
73
|
const program = new Command()
|
|
56
74
|
|
|
@@ -343,6 +361,7 @@ const main = async () => {
|
|
|
343
361
|
})
|
|
344
362
|
return {
|
|
345
363
|
period: k,
|
|
364
|
+
range: getWeekRange(k),
|
|
346
365
|
total: s.total,
|
|
347
366
|
outsideWorkCount: s.outsideWorkCount,
|
|
348
367
|
outsideWorkRate: s.outsideWorkRate,
|
|
@@ -380,7 +399,10 @@ const main = async () => {
|
|
|
380
399
|
nonWorkdayRate: s.nonWorkdayRate
|
|
381
400
|
}
|
|
382
401
|
})
|
|
383
|
-
const dataMonthlyFile = outputFilePath(
|
|
402
|
+
const dataMonthlyFile = outputFilePath(
|
|
403
|
+
'data/overtime-monthly.mjs',
|
|
404
|
+
outDir
|
|
405
|
+
)
|
|
384
406
|
const monthlyModule = `export default ${JSON.stringify(monthlySeries, null, 2)};\n`
|
|
385
407
|
writeTextFile(dataMonthlyFile, monthlyModule)
|
|
386
408
|
console.log(chalk.green(`Monthly series 已导出: ${dataMonthlyFile}`))
|
|
@@ -388,29 +410,92 @@ const main = async () => {
|
|
|
388
410
|
// 新增:每日最晚提交小时(用于显著展示加班严重程度)
|
|
389
411
|
const dayGroups2 = groupRecords(records, 'day')
|
|
390
412
|
const dayKeys2 = Object.keys(dayGroups2).sort()
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
413
|
+
|
|
414
|
+
// 次日凌晨归并窗口(默认 6 点前仍算前一天的加班)
|
|
415
|
+
const overnightCutoff = Number.isFinite(opts.overnightCutoff)
|
|
416
|
+
? opts.overnightCutoff
|
|
417
|
+
: 6
|
|
418
|
+
// 次日上班时间(默认按 workStart,若未指定则 9 点)
|
|
419
|
+
const workStartHour =
|
|
420
|
+
opts.workStart || opts.workStart === 0 ? opts.workStart : 9
|
|
421
|
+
const workEndHour =
|
|
422
|
+
opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18
|
|
423
|
+
|
|
424
|
+
// 有些日期「本身没有 commit」,但第二天凌晨有提交要归并到这一天,
|
|
425
|
+
// 需要补出这些“虚拟日期”,否则 latestByDay 会漏掉这天。
|
|
426
|
+
const virtualPrevDays = new Set()
|
|
427
|
+
records.forEach((r) => {
|
|
428
|
+
const d = new Date(r.date)
|
|
429
|
+
if (Number.isNaN(d.valueOf())) return
|
|
430
|
+
const h = d.getHours()
|
|
431
|
+
if (h < 0 || h >= overnightCutoff || h >= workStartHour) return
|
|
432
|
+
const curDay = dayjs(d).format('YYYY-MM-DD')
|
|
433
|
+
const prevDay = dayjs(curDay).subtract(1, 'day').format('YYYY-MM-DD')
|
|
434
|
+
if (!dayGroups2[prevDay]) {
|
|
435
|
+
virtualPrevDays.add(prevDay)
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const allDayKeys = Array.from(
|
|
440
|
+
new Set([...dayKeys2, ...virtualPrevDays])
|
|
441
|
+
).sort()
|
|
442
|
+
|
|
443
|
+
const latestByDay = allDayKeys.map((k) => {
|
|
444
|
+
const list = dayGroups2[k] || []
|
|
445
|
+
|
|
446
|
+
// 1) 当天「下班后」的提交:只统计 >= workEndHour 的小时
|
|
447
|
+
const sameDayHours = list
|
|
448
|
+
.map((r) => new Date(r.date))
|
|
449
|
+
.filter((d) => !Number.isNaN(d.valueOf()))
|
|
450
|
+
.map((d) => d.getHours())
|
|
451
|
+
.filter((h) => h >= workEndHour && h < 24)
|
|
452
|
+
|
|
453
|
+
// 2) 次日凌晨、但仍算前一日加班的提交
|
|
400
454
|
const nextKey = dayjs(k).add(1, 'day').format('YYYY-MM-DD')
|
|
401
455
|
const early = dayGroups2[nextKey] || []
|
|
402
456
|
const earlyHours = early
|
|
403
457
|
.map((r) => new Date(r.date))
|
|
404
458
|
.filter((d) => !Number.isNaN(d.valueOf()))
|
|
405
459
|
.map((d) => d.getHours())
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
460
|
+
// 只看 [0, overnightCutoff) 之间的小时,
|
|
461
|
+
// 并且默认认为 < workStartHour 属于「次日上班前」
|
|
462
|
+
.filter(
|
|
463
|
+
(h) =>
|
|
464
|
+
h >= 0 &&
|
|
465
|
+
h < overnightCutoff &&
|
|
466
|
+
// 保护性判断:若有人把 overnightCutoff 设得大于上班时间,
|
|
467
|
+
// 我们仍然只统计到上班时间为止
|
|
468
|
+
h < workStartHour
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
// 3) 计算「逻辑上的最晚加班时间」
|
|
472
|
+
// - 当天晚上的用原始小时(如 22 点)
|
|
473
|
+
// - 次日凌晨的用 24 + 小时(如 1 点 → 25)
|
|
474
|
+
const overtimeValues = [
|
|
475
|
+
...sameDayHours.map((h) => h),
|
|
476
|
+
...earlyHours.map((h) => 24 + h)
|
|
477
|
+
]
|
|
478
|
+
|
|
479
|
+
if (overtimeValues.length === 0) {
|
|
480
|
+
// 这一天没有任何「下班后到次日上班前」的提交
|
|
481
|
+
return {
|
|
482
|
+
date: k,
|
|
483
|
+
latestHour: null,
|
|
484
|
+
latestHourNormalized: null
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const latestHourNormalized = Math.max(...overtimeValues)
|
|
489
|
+
|
|
490
|
+
// latestHour 保留「当天自然日内」的最晚提交通常小时数,供前端需要时参考
|
|
491
|
+
const sameDayMax =
|
|
492
|
+
sameDayHours.length > 0 ? Math.max(...sameDayHours) : null
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
date: k,
|
|
496
|
+
latestHour: sameDayMax,
|
|
497
|
+
latestHourNormalized
|
|
498
|
+
}
|
|
414
499
|
})
|
|
415
500
|
const dataLatestByDayFile = outputFilePath(
|
|
416
501
|
'data/overtime-latest-by-day.mjs',
|
|
@@ -432,7 +517,10 @@ const main = async () => {
|
|
|
432
517
|
lunchEnd: opts.lunchEnd || 14,
|
|
433
518
|
overnightCutoff
|
|
434
519
|
}
|
|
435
|
-
writeTextFile(
|
|
520
|
+
writeTextFile(
|
|
521
|
+
configFile,
|
|
522
|
+
`export default ${JSON.stringify(cfg, null, 2)};\n`
|
|
523
|
+
)
|
|
436
524
|
console.log(chalk.green(`Config 已导出: ${configFile}`))
|
|
437
525
|
} catch (e) {
|
|
438
526
|
console.warn('Export config failed:', e && e.message ? e.message : e)
|