wukong-gitlog-cli 1.0.20 → 1.0.22

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,15 @@
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.22](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.21...v1.0.22) (2025-12-08)
6
+
7
+ ### [1.0.21](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.20...v1.0.21) (2025-12-08)
8
+
9
+
10
+ ### Features
11
+
12
+ * 🎸 weekly rangeMap ([8389e5e](https://github.com/tomatobybike/wukong-gitlog-cli/commit/8389e5e700640273906a4e616333e318a5961d12))
13
+
5
14
  ### [1.0.20](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.19...v1.0.20) (2025-12-05)
6
15
 
7
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
5
  "keywords": [
6
6
  "git",
@@ -116,5 +116,6 @@
116
116
  "prettier-plugin-packagejson": "2.5.19",
117
117
  "sort-package-json": "3.4.0",
118
118
  "standard-version": "9.5.0"
119
- }
119
+ },
120
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
120
121
  }
package/web/app.js CHANGED
@@ -16,6 +16,34 @@ function getIsoWeekKey(dStr) {
16
16
  return `${year}-W${String(weekNo).padStart(2, '0')}`
17
17
  }
18
18
 
19
+ function formatDateYMD(d) {
20
+ const yyyy = d.getFullYear()
21
+ const mm = String(d.getMonth() + 1).padStart(2, '0')
22
+ const dd = String(d.getDate()).padStart(2, '0')
23
+ return `${yyyy}-${mm}-${dd}`
24
+ }
25
+
26
+ function getISOWeekRange(isoYear, isoWeek) {
27
+ // 找到 ISO 年的第一个周一
28
+ // ISO 年的第 1 周包含 1 月 4 日
29
+ const simple = new Date(isoYear, 0, 4)
30
+ const dayOfWeek = simple.getDay() || 7 // Sunday=7
31
+ const firstMonday = new Date(simple)
32
+ firstMonday.setDate(simple.getDate() - dayOfWeek + 1)
33
+
34
+ // 计算目标周的周一
35
+ const monday = new Date(firstMonday)
36
+ monday.setDate(firstMonday.getDate() + (isoWeek - 1) * 7)
37
+
38
+ const sunday = new Date(monday)
39
+ sunday.setDate(monday.getDate() + 6)
40
+
41
+ return {
42
+ start: formatDateYMD(monday),
43
+ end: formatDateYMD(sunday)
44
+ }
45
+ }
46
+
19
47
  async function loadData() {
20
48
  try {
21
49
  const [
@@ -42,7 +70,15 @@ async function loadData() {
42
70
  const latestByDay = latestByDayModule.default || []
43
71
  const config = configModule.default || {}
44
72
  const authorChanges = authorChangesModule.default || {}
45
- return { commits, stats, weekly, monthly, latestByDay, config, authorChanges }
73
+ return {
74
+ commits,
75
+ stats,
76
+ weekly,
77
+ monthly,
78
+ latestByDay,
79
+ config,
80
+ authorChanges
81
+ }
46
82
  } catch (err) {
47
83
  console.error('Load data failed', err)
48
84
  return { commits: [], stats: {}, weekly: [], monthly: [], latestByDay: [] }
@@ -60,8 +96,6 @@ function renderCommitsTablePage() {
60
96
  const start = (page - 1) * pageSize
61
97
  const end = start + pageSize
62
98
  filtered.slice(start, end).forEach((c) => {
63
- // FIXME: remove debug log before production
64
- console.log('❌', 'c', c);
65
99
  const tr = document.createElement('tr')
66
100
  tr.innerHTML = `<td>${c.hash.slice(0, 8)}</td><td>${c.author}</td><td>${formatDate(c.date)}</td><td>${c.message}</td><td>${c.changed}</td>`
67
101
  tbody.appendChild(tr)
@@ -1244,7 +1278,6 @@ function groupCommitsByHour(commits) {
1244
1278
  return byHour
1245
1279
  }
1246
1280
 
1247
-
1248
1281
  // 基于 latestByDay + cutoff/endHour 统计「最晚加班的一天 / 一周 / 一月」
1249
1282
  function computeAndRenderLatestOvertime(latestByDay) {
1250
1283
  if (!Array.isArray(latestByDay) || latestByDay.length === 0) return
@@ -1337,21 +1370,21 @@ function computeAndRenderLatestOvertime(latestByDay) {
1337
1370
  }
1338
1371
 
1339
1372
  function buildDataset(stats, type) {
1340
- const dataMap = stats[type]; // { author: { period: changed } }
1373
+ const dataMap = stats[type] // { author: { period: changed } }
1341
1374
 
1342
- const authors = Object.keys(dataMap);
1343
- const allPeriods = Array.from(new Set(
1344
- authors.flatMap(a => Object.keys(dataMap[a]))
1345
- )).sort();
1375
+ const authors = Object.keys(dataMap)
1376
+ const allPeriods = Array.from(
1377
+ new Set(authors.flatMap((a) => Object.keys(dataMap[a])))
1378
+ ).sort()
1346
1379
 
1347
- const series = authors.map(a => ({
1380
+ const series = authors.map((a) => ({
1348
1381
  name: a,
1349
1382
  type: 'line',
1350
1383
  smooth: true,
1351
- data: allPeriods.map(p => dataMap[a][p] || 0)
1352
- }));
1384
+ data: allPeriods.map((p) => dataMap[a][p] || 0)
1385
+ }))
1353
1386
 
1354
- return { authors, allPeriods, series };
1387
+ return { authors, allPeriods, series }
1355
1388
  }
1356
1389
 
1357
1390
  const drawChangeTrends = (stats) => {
@@ -1361,6 +1394,7 @@ const drawChangeTrends = (stats) => {
1361
1394
 
1362
1395
  function render(t) {
1363
1396
  const { authors, allPeriods, series } = buildDataset(stats, t)
1397
+
1364
1398
  chart.setOption({
1365
1399
  tooltip: { trigger: 'axis' },
1366
1400
  legend: { data: authors },
@@ -1387,13 +1421,7 @@ const drawChangeTrends = (stats) => {
1387
1421
  }
1388
1422
 
1389
1423
  // ========= 开发者加班趋势(基于 commits 现场计算) =========
1390
- function buildAuthorOvertimeDataset(
1391
- commits,
1392
- type,
1393
- startHour,
1394
- endHour,
1395
- cutoff
1396
- ) {
1424
+ function buildAuthorOvertimeDataset(commits, type, startHour, endHour, cutoff) {
1397
1425
  const byAuthor = new Map()
1398
1426
  const periods = new Set()
1399
1427
 
@@ -1428,7 +1456,7 @@ function buildAuthorOvertimeDataset(
1428
1456
  name: a,
1429
1457
  type: 'line',
1430
1458
  smooth: true,
1431
- data: allPeriods.map((p) => (byAuthor.get(a)[p] || 0))
1459
+ data: allPeriods.map((p) => byAuthor.get(a)[p] || 0)
1432
1460
  }))
1433
1461
  return { authors, allPeriods, series }
1434
1462
  }
@@ -1456,8 +1484,45 @@ function drawAuthorOvertimeTrends(commits, stats) {
1456
1484
  endHour,
1457
1485
  cutoff
1458
1486
  )
1487
+ ds.rangeMap = {}
1488
+
1489
+ for (const period of ds.allPeriods) {
1490
+ if (period.includes('-W')) {
1491
+ const [yy, ww] = period.split('-W')
1492
+ ds.rangeMap[period] = getISOWeekRange(Number(yy), Number(ww))
1493
+ }
1494
+ }
1459
1495
  chart.setOption({
1460
- tooltip: { trigger: 'axis' },
1496
+ tooltip: {
1497
+ trigger: 'axis',
1498
+ formatter(params) {
1499
+ if (!params || !params.length) return ''
1500
+
1501
+ const p = params[0]
1502
+ const label = p.axisValue
1503
+ const isWeekly = type === 'weekly'
1504
+
1505
+ let extra = ''
1506
+ if (isWeekly && ds.rangeMap && ds.rangeMap[label]) {
1507
+ const { start, end } = ds.rangeMap[label]
1508
+ extra = `<div style="margin-top:4px;color:#999;font-size:12px">
1509
+ 周区间:${start} ~ ${end}
1510
+ </div>`
1511
+ }
1512
+
1513
+ const lines = params
1514
+ .map(
1515
+ (item) => `${item.marker}${item.seriesName}: ${item.data} 小时`
1516
+ )
1517
+ .join('<br/>')
1518
+
1519
+ return `
1520
+ <div>${label}</div>
1521
+ ${extra}
1522
+ ${lines}
1523
+ `
1524
+ }
1525
+ },
1461
1526
  legend: { data: ds.authors },
1462
1527
  xAxis: { type: 'category', data: ds.allPeriods },
1463
1528
  yAxis: { type: 'value' },
@@ -1533,7 +1598,9 @@ function renderWeeklyRiskSummary(
1533
1598
  const curTotal = Array.from(curMap.values()).reduce((a, b) => a + b, 0)
1534
1599
  const prevTotal = Array.from(prevMap.values()).reduce((a, b) => a + b, 0)
1535
1600
  const delta =
1536
- prevTotal > 0 ? Math.round(((curTotal - prevTotal) / prevTotal) * 100) : null
1601
+ prevTotal > 0
1602
+ ? Math.round(((curTotal - prevTotal) / prevTotal) * 100)
1603
+ : null
1537
1604
 
1538
1605
  // 找当前周最“活跃”的人(加班提交最多),并统计他加班的自然日数
1539
1606
  let topAuthor = null
@@ -1596,7 +1663,9 @@ function computeAuthorDailyMaxOvertime(commits, startHour, endHour, cutoff) {
1596
1663
  dayKey = d.toISOString().slice(0, 10)
1597
1664
  } else if (h >= 0 && h < cutoff && h < startHour) {
1598
1665
  overtime = 24 - endHour + h
1599
- const cur = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))
1666
+ const cur = new Date(
1667
+ Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())
1668
+ )
1600
1669
  cur.setUTCDate(cur.getUTCDate() - 1)
1601
1670
  dayKey = cur.toISOString().slice(0, 10)
1602
1671
  }
@@ -1618,7 +1687,12 @@ function renderWeeklyDurationRiskSummary(
1618
1687
  if (!box) return
1619
1688
  const now = new Date()
1620
1689
  const curWeek = getIsoWeekKey(now.toISOString().slice(0, 10))
1621
- const byAuthorDay = computeAuthorDailyMaxOvertime(commits, startHour, endHour, cutoff)
1690
+ const byAuthorDay = computeAuthorDailyMaxOvertime(
1691
+ commits,
1692
+ startHour,
1693
+ endHour,
1694
+ cutoff
1695
+ )
1622
1696
  const sums = []
1623
1697
  byAuthorDay.forEach((dayMap, author) => {
1624
1698
  let total = 0
@@ -1639,7 +1713,9 @@ function renderWeeklyDurationRiskSummary(
1639
1713
  let level = '轻度'
1640
1714
  if (total >= 12) level = '严重'
1641
1715
  else if (total >= 6) level = '中度'
1642
- lines.push(`${author} 本周累计加班 ${total.toFixed(2)} 小时(${level})。`)
1716
+ lines.push(
1717
+ `${author} 本周累计加班 ${total.toFixed(2)} 小时(${level})。`
1718
+ )
1643
1719
  })
1644
1720
  }
1645
1721
  box.innerHTML = `
@@ -1663,7 +1739,12 @@ function renderMonthlyDurationRiskSummary(
1663
1739
  if (!box) return
1664
1740
  const now = new Date()
1665
1741
  const curMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
1666
- const byAuthorDay = computeAuthorDailyMaxOvertime(commits, startHour, endHour, cutoff)
1742
+ const byAuthorDay = computeAuthorDailyMaxOvertime(
1743
+ commits,
1744
+ startHour,
1745
+ endHour,
1746
+ cutoff
1747
+ )
1667
1748
  const sums = []
1668
1749
  byAuthorDay.forEach((dayMap, author) => {
1669
1750
  let total = 0
@@ -1684,7 +1765,9 @@ function renderMonthlyDurationRiskSummary(
1684
1765
  let level = '轻度'
1685
1766
  if (total >= 20) level = '严重'
1686
1767
  else if (total >= 10) level = '中度'
1687
- lines.push(`${author} 本月累计加班 ${total.toFixed(2)} 小时(${level})。`)
1768
+ lines.push(
1769
+ `${author} 本月累计加班 ${total.toFixed(2)} 小时(${level})。`
1770
+ )
1688
1771
  })
1689
1772
  }
1690
1773
  box.innerHTML = `
@@ -1707,11 +1790,18 @@ function renderRolling30DurationRiskSummary(
1707
1790
  const box = document.getElementById('rolling30DurationRiskSummary')
1708
1791
  if (!box) return
1709
1792
  const now = new Date()
1710
- const utcToday = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
1793
+ const utcToday = new Date(
1794
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
1795
+ )
1711
1796
  utcToday.setUTCDate(utcToday.getUTCDate() - 29)
1712
1797
  const startKey = utcToday.toISOString().slice(0, 10)
1713
1798
 
1714
- const byAuthorDay = computeAuthorDailyMaxOvertime(commits, startHour, endHour, cutoff)
1799
+ const byAuthorDay = computeAuthorDailyMaxOvertime(
1800
+ commits,
1801
+ startHour,
1802
+ endHour,
1803
+ cutoff
1804
+ )
1715
1805
  const sums = []
1716
1806
  byAuthorDay.forEach((dayMap, author) => {
1717
1807
  let total = 0
@@ -1731,7 +1821,9 @@ function renderRolling30DurationRiskSummary(
1731
1821
  let level = '轻度'
1732
1822
  if (total >= 20) level = '严重'
1733
1823
  else if (total >= 10) level = '中度'
1734
- lines.push(`${author} 最近30天累计加班 ${total.toFixed(2)} 小时(${level})。`)
1824
+ lines.push(
1825
+ `${author} 最近30天累计加班 ${total.toFixed(2)} 小时(${level})。`
1826
+ )
1735
1827
  })
1736
1828
  }
1737
1829
  box.innerHTML = `
@@ -1766,7 +1858,8 @@ function renderMonthlyRiskSummary(
1766
1858
  const d = new Date(c.date)
1767
1859
  if (Number.isNaN(d.valueOf())) return
1768
1860
  const h = d.getHours()
1769
- const isOT = (h >= endHour && h < 24) || (h >= 0 && h < cutoff && h < startHour)
1861
+ const isOT =
1862
+ (h >= endHour && h < 24) || (h >= 0 && h < cutoff && h < startHour)
1770
1863
  if (!isOT) return
1771
1864
 
1772
1865
  const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`
@@ -1785,14 +1878,18 @@ function renderMonthlyRiskSummary(
1785
1878
  const mm = monthMax.get(key)
1786
1879
  const cur = mm.get(author)
1787
1880
  const dateStr = d.toISOString().slice(0, 10)
1788
- if (!cur || overtime > cur.max) mm.set(author, { max: overtime, date: dateStr })
1881
+ if (!cur || overtime > cur.max)
1882
+ mm.set(author, { max: overtime, date: dateStr })
1789
1883
  })
1790
1884
 
1791
1885
  const curMap = monthAuthor.get(curKey) || new Map()
1792
1886
  const prevMap = monthAuthor.get(prevKey) || new Map()
1793
1887
  const curTotal = Array.from(curMap.values()).reduce((a, b) => a + b, 0)
1794
1888
  const prevTotal = Array.from(prevMap.values()).reduce((a, b) => a + b, 0)
1795
- const delta = prevTotal > 0 ? Math.round(((curTotal - prevTotal) / prevTotal) * 100) : null
1889
+ const delta =
1890
+ prevTotal > 0
1891
+ ? Math.round(((curTotal - prevTotal) / prevTotal) * 100)
1892
+ : null
1796
1893
 
1797
1894
  let topAuthor = null
1798
1895
  let top = { max: -1, date: null }
@@ -1830,7 +1927,9 @@ function renderMonthlyRiskSummary(
1830
1927
  else if (top.max < prevMax) trend2 = '较上月提前'
1831
1928
  else trend2 = '与上月持平'
1832
1929
  }
1833
- lines.push(`${topAuthor} 本月最晚超出下班 ${top.max.toFixed(2)} 小时(${top.date}),${trend2}。`)
1930
+ lines.push(
1931
+ `${topAuthor} 本月最晚超出下班 ${top.max.toFixed(2)} 小时(${top.date}),${trend2}。`
1932
+ )
1834
1933
  if (top.max >= 2) lines.push('已超过 2 小时,存在严重加班风险。')
1835
1934
  else if (top.max >= 1) lines.push('已超过 1 小时,存在中度加班风险。')
1836
1935
  }
@@ -1850,13 +1949,7 @@ function renderMonthlyRiskSummary(
1850
1949
  }
1851
1950
 
1852
1951
  // ========= 开发者加班“最晚”趋势(每期取最大超时) =========
1853
- function buildAuthorLatestDataset(
1854
- commits,
1855
- type,
1856
- startHour,
1857
- endHour,
1858
- cutoff
1859
- ) {
1952
+ function buildAuthorLatestDataset(commits, type, startHour, endHour, cutoff) {
1860
1953
  const byAuthor = new Map()
1861
1954
  const periods = new Set()
1862
1955
 
@@ -1867,8 +1960,7 @@ function buildAuthorLatestDataset(
1867
1960
 
1868
1961
  let overtime = null
1869
1962
  if (h >= endHour && h < 24) overtime = h - endHour
1870
- else if (h >= 0 && h < cutoff && h < startHour)
1871
- overtime = 24 - endHour + h
1963
+ else if (h >= 0 && h < cutoff && h < startHour) overtime = 24 - endHour + h
1872
1964
  if (overtime == null) return
1873
1965
 
1874
1966
  let key
@@ -1889,6 +1981,7 @@ function buildAuthorLatestDataset(
1889
1981
  })
1890
1982
 
1891
1983
  const allPeriods = Array.from(periods).sort()
1984
+
1892
1985
  const authors = Array.from(byAuthor.keys()).sort()
1893
1986
  const series = authors.map((a) => ({
1894
1987
  name: a,
@@ -1922,8 +2015,45 @@ function drawAuthorLatestOvertimeTrends(commits, stats) {
1922
2015
  endHour,
1923
2016
  cutoff
1924
2017
  )
2018
+ ds.rangeMap = {}
2019
+
2020
+ for (const period of ds.allPeriods) {
2021
+ if (period.includes('-W')) {
2022
+ const [yy, ww] = period.split('-W')
2023
+ ds.rangeMap[period] = getISOWeekRange(Number(yy), Number(ww))
2024
+ }
2025
+ }
1925
2026
  chart.setOption({
1926
- tooltip: { trigger: 'axis' },
2027
+ tooltip: {
2028
+ trigger: 'axis',
2029
+ formatter(params) {
2030
+ if (!params || !params.length) return ''
2031
+
2032
+ const p = params[0]
2033
+ const label = p.axisValue
2034
+ const isWeekly = type === 'weekly'
2035
+
2036
+ let extra = ''
2037
+ if (isWeekly && ds.rangeMap && ds.rangeMap[label]) {
2038
+ const { start, end } = ds.rangeMap[label]
2039
+ extra = `<div style="margin-top:4px;color:#999;font-size:12px">
2040
+ 周区间:${start} ~ ${end}
2041
+ </div>`
2042
+ }
2043
+
2044
+ const lines = params
2045
+ .map(
2046
+ (item) => `${item.marker}${item.seriesName}: ${item.data} 小时`
2047
+ )
2048
+ .join('<br/>')
2049
+
2050
+ return `
2051
+ <div>${label}</div>
2052
+ ${extra}
2053
+ ${lines}
2054
+ `
2055
+ }
2056
+ },
1927
2057
  legend: { data: ds.authors },
1928
2058
  xAxis: { type: 'category', data: ds.allPeriods },
1929
2059
  yAxis: {
@@ -1974,8 +2104,7 @@ function renderLatestRiskSummary(
1974
2104
  const h = d.getHours()
1975
2105
  let overtime = null
1976
2106
  if (h >= endHour && h < 24) overtime = h - endHour
1977
- else if (h >= 0 && h < cutoff && h < startHour)
1978
- overtime = 24 - endHour + h
2107
+ else if (h >= 0 && h < cutoff && h < startHour) overtime = 24 - endHour + h
1979
2108
  if (overtime == null) return
1980
2109
 
1981
2110
  const wKey = getIsoWeekKey(d.toISOString().slice(0, 10))
@@ -2107,7 +2236,9 @@ function renderLatestMonthlyRiskSummary(
2107
2236
  else if (top.max < prevMax) trend = '较上月提前'
2108
2237
  else trend = '与上月持平'
2109
2238
  }
2110
- lines.push(`${topAuthor} 本月最晚超出下班 ${top.max.toFixed(2)} 小时(${top.date}),${trend}。`)
2239
+ lines.push(
2240
+ `${topAuthor} 本月最晚超出下班 ${top.max.toFixed(2)} 小时(${top.date}),${trend}。`
2241
+ )
2111
2242
  if (top.max >= 2) {
2112
2243
  lines.push('已超过 2 小时,存在严重加班风险,请关注工作节奏。')
2113
2244
  } else if (top.max >= 1) {
@@ -2137,8 +2268,7 @@ async function main() {
2137
2268
  latestByDay,
2138
2269
  config,
2139
2270
  authorChanges
2140
- } =
2141
- await loadData()
2271
+ } = await loadData()
2142
2272
  commitsAll = commits
2143
2273
  filtered = commitsAll.slice()
2144
2274
  window.__overtimeEndHour =
@@ -2185,7 +2315,6 @@ async function main() {
2185
2315
  renderKpi(stats)
2186
2316
  }
2187
2317
 
2188
-
2189
2318
  // 抽屉关闭交互(按钮 + 点击遮罩)
2190
2319
  document.getElementById('sidebarClose').onclick = () => {
2191
2320
  document.getElementById('dayDetailSidebar').classList.remove('show')
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import('../src/cli.mjs')