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 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
@@ -53,6 +53,10 @@ wukong-gitlog-cli --help
53
53
 
54
54
  ## Usage
55
55
 
56
+ ```bash
57
+ wukong-gitlog-cli --overtime --serve --port 5555
58
+ ```
59
+
56
60
  ```bash
57
61
  wukong-gitlog-cli [options]
58
62
  ```
package/README.zh-CN.md CHANGED
@@ -55,6 +55,10 @@ wukong-gitlog-cli --help
55
55
 
56
56
  ## 🚀 使用方法
57
57
 
58
+ ```bash
59
+ wukong-gitlog-cli --overtime --serve --port 5555
60
+ ```
61
+
58
62
  ```bash
59
63
  wukong-gitlog-cli [options]
60
64
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
5
  "keywords": [
6
6
  "git",
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('data/overtime-monthly.mjs', outDir)
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
- const overnightCutoff = Number.isFinite(opts.overnightCutoff) ? opts.overnightCutoff : 6
392
- const latestByDay = dayKeys2.map((k) => {
393
- const list = dayGroups2[k]
394
- const vals = list
395
- .map((r) => ({ r, _dt: new Date(r.date) }))
396
- .filter((x) => x._dt && !Number.isNaN(x._dt.valueOf()))
397
- .sort((a, b) => a._dt.valueOf() - b._dt.valueOf())
398
- const last = vals.length > 0 ? vals[vals.length - 1] : null
399
- const hour = last ? new Date(last.r.date).getHours() : null
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
- .filter((h) => h >= 0 && h < overnightCutoff)
407
- const earlyMax = earlyHours.length > 0 ? Math.max(...earlyHours) : null
408
- const normalized = typeof earlyMax === 'number' ? 24 + earlyMax : null
409
- const latestHourNormalized = Math.max(
410
- typeof hour === 'number' ? hour : -1,
411
- typeof normalized === 'number' ? normalized : -1
412
- )
413
- return { date: k, latestHour: hour, latestHourNormalized: latestHourNormalized >= 0 ? latestHourNormalized : null }
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(configFile, `export default ${JSON.stringify(cfg, null, 2)};\n`)
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)