wukong-gitlog-cli 1.0.39 → 1.0.41

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.
Files changed (125) hide show
  1. package/.eslintrc +1 -0
  2. package/.prettierrc +2 -1
  3. package/CHANGELOG.md +97 -0
  4. package/README.md +97 -172
  5. package/README.zh-CN.md +88 -137
  6. package/bin/wukong-gitlog-cli +0 -0
  7. package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +2871 -0
  8. package/package.json +32 -29
  9. package/rc/.wukonggitlogrc +53 -0
  10. package/scripts/compareHourlyCounts.mjs +42 -0
  11. package/scripts/compareLatest.mjs +106 -0
  12. package/src/app/analyzeAction.mjs +120 -0
  13. package/src/app/exportAction.mjs +215 -0
  14. package/src/app/exportActionProgress.mjs +37 -0
  15. package/src/app/helpers.mjs +292 -0
  16. package/src/app/initAction.mjs +110 -0
  17. package/src/app/initActionWithTemp.mjs +192 -0
  18. package/src/app/journalAction.mjs +117 -0
  19. package/src/app/overtimeAction.mjs +100 -0
  20. package/src/app/runProfileEnd.mjs +0 -0
  21. package/src/app/serveAction.mjs +73 -0
  22. package/src/app/versionAction.mjs +7 -0
  23. package/src/cli/defineOptions.mjs +209 -0
  24. package/src/cli/index.mjs +0 -0
  25. package/src/cli/parseOptions.mjs +126 -8
  26. package/src/constants/index.mjs +16 -2
  27. package/src/domain/author/analyze.mjs +6 -0
  28. package/src/domain/author/map.mjs +0 -0
  29. package/src/domain/export/exportAuthor.mjs +28 -0
  30. package/src/domain/export/exportAuthorChanges.mjs +27 -0
  31. package/src/domain/export/exportAuthorChangesJson.mjs +31 -0
  32. package/src/domain/export/exportByMonth.mjs +157 -0
  33. package/src/domain/export/exportByWeek.mjs +121 -0
  34. package/src/domain/export/exportCommits.mjs +26 -0
  35. package/src/domain/export/exportCommitsExcel.mjs +45 -0
  36. package/src/domain/export/exportCommitsJson.mjs +31 -0
  37. package/src/domain/export/index.mjs +91 -0
  38. package/src/domain/git/ensureGitAvailable.mjs +66 -0
  39. package/src/domain/git/ensureGitRepo.mjs +41 -0
  40. package/src/domain/git/getGitFeatures.mjs +59 -0
  41. package/src/domain/git/getGitLogs.mjs +326 -0
  42. package/src/domain/git/getGitUser.mjs +44 -0
  43. package/src/domain/git/getRepoRoot.mjs +32 -0
  44. package/src/domain/git/gitCapability.mjs +119 -0
  45. package/src/domain/git/index.mjs +96 -0
  46. package/src/domain/git/resolveGerrit.mjs +102 -0
  47. package/src/domain/overtime/analyze.mjs +48 -0
  48. package/src/domain/overtime/index.mjs +3 -0
  49. package/src/domain/overtime/perPeriod.mjs +15 -0
  50. package/src/domain/overtime/render.mjs +15 -0
  51. package/src/i18n/index.mjs +38 -0
  52. package/src/i18n/resources.mjs +252 -0
  53. package/src/index.mjs +132 -649
  54. package/src/infra/cache.mjs +0 -0
  55. package/src/infra/configStore.mjs +128 -0
  56. package/src/infra/fs.mjs +0 -0
  57. package/src/infra/path.mjs +0 -0
  58. package/src/output/csv/overtime.mjs +12 -0
  59. package/src/output/csv.mjs +0 -0
  60. package/src/output/data/readData.mjs +54 -0
  61. package/src/output/data/writeData.mjs +145 -0
  62. package/src/output/excel/commits.mjs +9 -0
  63. package/src/output/excel/outputExcelDayReport.mjs +92 -0
  64. package/src/output/excel/perPeriod.mjs +24 -0
  65. package/src/{excel.mjs → output/excel.mjs} +3 -2
  66. package/src/output/index.mjs +79 -0
  67. package/src/output/json/overtime.mjs +9 -0
  68. package/src/output/tab/overtime.mjs +12 -0
  69. package/src/output/tab.mjs +0 -0
  70. package/src/output/text/commits.mjs +9 -0
  71. package/src/output/text/index.mjs +3 -0
  72. package/src/output/text/outputTxtDayReport.mjs +74 -0
  73. package/src/output/text/overtime.mjs +18 -0
  74. package/src/output/utils/getEsmJs.mjs +10 -0
  75. package/src/output/utils/index.mjs +14 -0
  76. package/src/output/utils/outputPath.mjs +19 -0
  77. package/src/output/utils/writeFile.mjs +10 -0
  78. package/src/serve/index.mjs +0 -0
  79. package/src/{server.mjs → serve/startServer.mjs} +21 -3
  80. package/src/serve/writeData.mjs +0 -0
  81. package/src/utils/authorNormalizer.mjs +28 -2
  82. package/src/utils/buildAuthorChangeStats.mjs +44 -0
  83. package/src/utils/deepMerge.mjs +13 -0
  84. package/src/utils/getPackage.mjs +11 -0
  85. package/src/utils/getProfileDirFile.mjs +12 -0
  86. package/src/utils/{file.mjs → groupRecords.mjs} +8 -9
  87. package/src/utils/index.mjs +5 -2
  88. package/src/utils/logger.mjs +28 -17
  89. package/src/utils/profiler.mjs +0 -101
  90. package/src/utils/resolve.mjs +11 -0
  91. package/src/utils/showVersionInfo.mjs +6 -2
  92. package/src/utils/time.mjs +0 -0
  93. package/src/utils/wait.mjs +2 -0
  94. package/web/app.js +3197 -257
  95. package/web/index.html +171 -22
  96. package/web/revoke/alpha1/app.js +4324 -0
  97. package/web/revoke/alpha1/index.html +266 -0
  98. package/web/revoke/app.before.js +3139 -0
  99. package/web/revoke/index-before.html +181 -0
  100. package/web/static/style.css +116 -9
  101. package/src/git.mjs +0 -256
  102. package/src/handlers/handleServe.mjs +0 -203
  103. package/src/lib/configStore.mjs +0 -11
  104. package/src/lib/memoize.mjs +0 -14
  105. package/src/utils/analyzeOvertimeCached.mjs +0 -7
  106. package/src/utils/checkUpdate.mjs +0 -130
  107. package/src/utils/exitWithTime.mjs +0 -17
  108. package/src/utils/handleSuccess.mjs +0 -9
  109. package/src/utils/logDev.mjs +0 -19
  110. package/src/utils/output.mjs +0 -26
  111. package/src/utils/profiler/diff.mjs +0 -26
  112. package/src/utils/profiler/format.mjs +0 -11
  113. package/src/utils/profiler/index.mjs +0 -144
  114. package/src/utils/profiler/trace.mjs +0 -26
  115. package/src/utils/time/scopeTimer.mjs +0 -37
  116. package/src/utils/time/timer.mjs +0 -33
  117. package/src/utils/time/withTimer.mjs +0 -11
  118. package/src/utils/timer.mjs +0 -35
  119. /package/src/{overtime → domain/overtime}/createOvertimeStats.mjs +0 -0
  120. /package/src/{overtime → domain/overtime}/overtime.mjs +0 -0
  121. /package/src/{json.mjs → output/json.mjs} +0 -0
  122. /package/src/{renderAuthorMapText.mjs → output/renderAuthorMapText.mjs} +0 -0
  123. /package/src/{stats-text.mjs → output/stats-text.mjs} +0 -0
  124. /package/src/{stats.mjs → output/stats.mjs} +0 -0
  125. /package/src/{text.mjs → output/text.mjs} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
5
  "keywords": [
6
6
  "git",
@@ -40,29 +40,28 @@
40
40
  "scripts": {
41
41
  "build": "node scripts/esbuild.config.mjs",
42
42
  "check:npm:login": "node scripts/check-npm-login.mjs",
43
- "cli:excel-demo": "node ./src/cli.mjs --overtime --format excel --stats --limit 5 --out commits.xlsx",
44
- "cli:excel-demo-parent": "node ./src/cli.mjs --out-parent --format excel --stats --limit 5 --out commits-parent.xlsx",
45
- "cli:gerrit-changeid-demo": "node ./src/cli.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{changeId}}\" --limit 5 --out commits-gerrit-changeid.txt",
46
- "cli:gerrit-demo": "node ./src/cli.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{hash}}\" --limit 5 --out commits-gerrit.txt",
47
- "cli:help": "node ./src/cli.mjs --help",
48
- "cli:json-demo": "node ./src/cli.mjs --json --limit 5 --out commits.json",
49
- "cli:overtime-demo": "node ./src/cli.mjs --overtime --format text --limit 200",
50
- "cli:overtime-excel": "node ./src/cli.mjs --overtime --limit 50 --format excel --out commits.xlsx",
51
- "cli:overtime-excel-cn": "node ./src/cli.mjs --overtime --limit 50 --format excel --out commits.xlsx --country CN",
52
- "cli:overtime-excel-cn-outdir": "node ./src/cli.mjs --out-dir ../output --overtime --limit 50 --format excel --out commits.xlsx --country CN",
53
- "cli:overtime-excel-cn-parent": "node ./src/cli.mjs --out-parent --overtime --limit 50 --format excel --out commits.xlsx --country CN",
54
- "cli:overtime-per-period-csv-tab": "node ./src/cli.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab",
55
- "cli:overtime-per-period-only": "node ./src/cli.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab,xlsx --per-period-only",
56
- "cli:overtime-per-period-xlsx-files": "node ./src/cli.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats xlsx --per-period-excel-mode files",
57
- "cli:overtime-per-period-xlsx-sheets": "node ./src/cli.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab,xlsx --per-period-excel-mode sheets",
58
- "cli:overtime-text": "node ./src/cli.mjs --overtime --limit 100 --format text --out commits.txt",
59
- "cli:overtime-text-us": "node ./src/cli.mjs --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
60
- "cli:overtime-text-us-outdir": "node ./src/cli.mjs --out-dir ../output --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
61
- "cli:overtime-text-us-parent": "node ./src/cli.mjs --out-parent --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
62
- "cli:serve": "node ./src/cli.mjs --serve --overtime --format text --out commits.txt",
63
- "cli:serve-only": "node ./src/cli.mjs --serve-only",
64
- "cli:text-demo": "node ./src/cli.mjs --format text --limit 5 --out commits.txt",
65
- "cli:text-demo-parent": "node ./src/cli.mjs --out-parent --format text --limit 5 --out commits-parent.txt",
43
+ "cli:excel-demo": "node ./src/index.mjs --overtime --format excel --stats --limit 5",
44
+ "cli:excel-demo-parent": "node ./src/index.mjs --out-parent --format excel --stats --limit 5 --out commits-parent.xlsx",
45
+ "cli:gerrit-changeid-demo": "node ./src/index.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{changeId}}\" --limit 5 --out commits-gerrit-changeid.txt",
46
+ "cli:gerrit-demo": "node ./src/index.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{hash}}\" --limit 5 --out commits-gerrit.txt",
47
+ "cli:help": "node ./src/index.mjs --help",
48
+ "cli:json-demo": "node ./src/index.mjs --json --limit 5 --out commits.json",
49
+ "cli:overtime-demo": "node ./src/index.mjs --overtime --format text --limit 200",
50
+ "cli:overtime-excel": "node ./src/index.mjs --overtime --limit 50 --format excel",
51
+ "cli:overtime-excel-cn": "node ./src/index.mjs --overtime --limit 50 --format excel --country CN",
52
+ "cli:overtime-excel-cn-outdir": "node ./src/index.mjs --out-dir ../output --overtime --limit 50 --format excel --country CN",
53
+ "cli:overtime-excel-cn-parent": "node ./src/index.mjs --out-parent --overtime --limit 50 --format excel --country CN",
54
+ "cli:overtime-per-period-csv-tab": "node ./src/index.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab",
55
+ "cli:overtime-per-period-only": "node ./src/index.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab,xlsx --per-period-only",
56
+ "cli:overtime-per-period-xlsx-files": "node ./src/index.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats xlsx --per-period-excel-mode files",
57
+ "cli:overtime-per-period-xlsx-sheets": "node ./src/index.mjs --overtime --limit 200 --format text --out commits.txt --per-period-formats csv,tab,xlsx --per-period-excel-mode sheets",
58
+ "cli:overtime-text": "node ./src/index.mjs --overtime --limit 100 --format text --out commits.txt",
59
+ "cli:overtime-text-us": "node ./src/index.mjs --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
60
+ "cli:overtime-text-us-outdir": "node ./src/index.mjs --out-dir ../output --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
61
+ "cli:overtime-text-us-parent": "node ./src/index.mjs --out-parent --overtime --limit 50 --format text --out commits.txt --country US --work-start 10 --work-end 19 --lunch-start 12 --lunch-end 13",
62
+ "cli:serve": "node ./src/index.mjs serve",
63
+ "cli:text-demo": "node ./src/index.mjs export --format text --limit 5 ",
64
+ "cli:text-demo-parent": "node ./src/index.mjs export --out-parent --format text --limit 5 ",
66
65
  "format": "prettier --write \"src/**/*.{js,mjs}\"",
67
66
  "lint": "eslint src --ext .js,.mjs src",
68
67
  "lint:fix": "eslint src --ext .js,.mjs --fix",
@@ -93,8 +92,12 @@
93
92
  "eslint --fix"
94
93
  ]
95
94
  },
95
+ "imports": {
96
+ "#src/*": "./src/*",
97
+ "#utils/*": "./src/utils/*"
98
+ },
96
99
  "dependencies": {
97
- "boxen": "8.0.1",
100
+ "@inquirer/prompts": "^8.1.0",
98
101
  "chalk": "5.6.2",
99
102
  "commander": "12.1.0",
100
103
  "date-fns": "4.1.0",
@@ -102,12 +105,12 @@
102
105
  "dayjs": "1.11.19",
103
106
  "dotenv": "17.2.2",
104
107
  "exceljs": "4.4.0",
105
- "is-online": "12.0.2",
108
+ "fs-extra": "11.3.3",
106
109
  "ora": "9.0.0",
107
- "semver": "6.3.1",
110
+ "os-locale": "^8.0.0",
108
111
  "string-width": "5.1.2",
109
- "wukong-profiler": "^1.0.6",
110
- "zx": "7.2.4"
112
+ "wukong-profiler": "^1.0.11",
113
+ "wukong-progress": "^0.1.2"
111
114
  },
112
115
  "devDependencies": {
113
116
  "@trivago/prettier-plugin-sort-imports": "5.2.2",
@@ -0,0 +1,53 @@
1
+ # ------------------------------
2
+ # .wukonggitlogrc
3
+ # Wukong GitLog 配置示例
4
+ # ------------------------------
5
+
6
+ author:
7
+ include: [] # 只统计这些作者,留空表示全部
8
+ exclude: [] # 排除这些作者
9
+
10
+ git:
11
+ merges: true # 是否排除 merge commit
12
+ limit: 5000 # 最大拉取提交数
13
+
14
+ period:
15
+ groupBy: month # 统计周期: day | week | month
16
+ since: 2024-01-01 # 起始日期
17
+ until: 2024-12-31 # 截止日期
18
+
19
+ gerrit:
20
+ prefix: https://gerrit.xxx.com/c/{{changeNumber}} # change 链接模板
21
+ api: https://gerrit.xxx.com # Gerrit API 地址
22
+ auth: token-or-user:pass # token 或 user:pass
23
+
24
+ worktime:
25
+ country: CN # 国家(影响节假日和周末计算)
26
+ start: 9 # 工作日开始时间(小时)
27
+ end: 18 # 工作日结束时间(小时)
28
+ lunch:
29
+ start: 12 # 午休开始时间
30
+ end: 14 # 午休结束时间
31
+ overnightCutoff: 6 # 凌晨时间计算截止点(0~6点归前一天)
32
+
33
+ output:
34
+ dir: output-wukong # 输出目录
35
+ formats:
36
+ - text
37
+ - json
38
+ - excel
39
+ perPeriod:
40
+ enabled: true # 是否按周期生成单独文件
41
+ formats: [csv, tab, xlsx] # 分周期输出格式
42
+ excelMode: sheets # sheets: 每周期一 sheet
43
+ only: false # true: 仅输出 perPeriod,不生成总统计
44
+
45
+ serve:
46
+ enabled: false # 是否启动 Web 实时服务
47
+ port: 3000 # 服务端口
48
+
49
+ profile:
50
+ enabled: false # 是否启用代码 profile 分析
51
+ hotThreshold: 0.8 # 热点提交阈值
52
+ diffThreshold: 0.2 # 差异阈值
53
+
@@ -0,0 +1,42 @@
1
+ import commits from '../output-wukong/data/commits.mjs';
2
+ import overtimeFromFile from '../output-wukong/data/overtime.mjs';
3
+ import { analyzeOvertime } from '../src/domain/overtime/overtime.mjs';
4
+
5
+ const stats = analyzeOvertime(commits, {
6
+ startHour: overtimeFromFile.startHour ?? 9,
7
+ endHour: overtimeFromFile.endHour ?? 18,
8
+ lunchStart: overtimeFromFile.lunchStart ?? 12,
9
+ lunchEnd: overtimeFromFile.lunchEnd ?? 14,
10
+ country: overtimeFromFile.country ?? 'CN'
11
+ });
12
+
13
+ console.log('Computed stats:');
14
+ console.log(' total:', stats.total);
15
+ console.log(' outsideWorkCount:', stats.outsideWorkCount);
16
+ console.log(' nightOutsideCount:', stats.nightOutsideCount);
17
+ console.log(' hourlyOvertimeCommits sum:', stats.hourlyOvertimeCommits.reduce((a,b)=>a+b,0));
18
+ console.log(' hourlyOvertimeCommits:', stats.hourlyOvertimeCommits);
19
+
20
+ const fileStats = (overtimeFromFile && overtimeFromFile.default) ? overtimeFromFile.default : overtimeFromFile;
21
+ console.log('\nFile stats:');
22
+ console.log(' total:', fileStats.total);
23
+ console.log(' outsideWorkCount:', fileStats.outsideWorkCount);
24
+ console.log(' nightOutsideCount:', fileStats.nightOutsideCount);
25
+ console.log(' hourlyOvertimeCommits sum (if present):', (fileStats.hourlyOvertimeCommits || []).reduce((a,b)=>a+b,0));
26
+ console.log(' hourlyOvertimeCommits:', fileStats.hourlyOvertimeCommits || 'N/A');
27
+
28
+ // Compare
29
+ console.log('\nComparisons:');
30
+ console.log(' outsideWorkCount equal?', stats.outsideWorkCount === fileStats.outsideWorkCount);
31
+ console.log(' nightOutsideCount equal?', stats.nightOutsideCount === fileStats.nightOutsideCount);
32
+ const hourlyEqual = JSON.stringify(stats.hourlyOvertimeCommits) === JSON.stringify(fileStats.hourlyOvertimeCommits || []);
33
+ console.log(' hourlyOvertimeCommits equal?', hourlyEqual);
34
+
35
+ if (!hourlyEqual) {
36
+ console.log(' Differences by hour:');
37
+ const a = stats.hourlyOvertimeCommits;
38
+ const b = fileStats.hourlyOvertimeCommits || Array(24).fill(0);
39
+ for (let i=0;i<24;i++) {
40
+ if ((a[i]||0) !== (b[i]||0)) console.log(` hour ${i}: computed=${a[i]||0} file=${b[i]||0}`);
41
+ }
42
+ }
@@ -0,0 +1,106 @@
1
+ import { fileURLToPath } from 'url'
2
+ import path from 'path'
3
+
4
+ const __filename = fileURLToPath(import.meta.url)
5
+ const __dirname = path.dirname(__filename)
6
+
7
+ const commitsMod = await import(path.join(__dirname, '../output-wukong/data/commits.mjs'))
8
+ const overtimeMod = await import(path.join(__dirname, '../output-wukong/data/overtime.latest.commit.day.mjs'))
9
+ const commits = commitsMod.default || commitsMod
10
+ const overtimeLatest = overtimeMod.default || overtimeMod
11
+
12
+ function formatDateYMD(d) {
13
+ const yyyy = d.getFullYear()
14
+ const mm = String(d.getMonth() + 1).padStart(2, '0')
15
+ const dd = String(d.getDate()).padStart(2, '0')
16
+ return `${yyyy}-${mm}-${dd}`
17
+ }
18
+
19
+ function computeLatestByDayLocal(commits, startHour = 9, endHour = 18, cutoff = 6) {
20
+ const cutoffHour = cutoff || 6
21
+ const dayGroups = {}
22
+ commits.forEach((c) => {
23
+ const d = new Date(c.date)
24
+ if (isNaN(d.getTime())) return
25
+ const dateStr = formatDateYMD(d)
26
+ if (!dayGroups[dateStr]) dayGroups[dateStr] = []
27
+ dayGroups[dateStr].push(c)
28
+ })
29
+
30
+ const dayKeys = Object.keys(dayGroups).sort()
31
+ const virtualPrevDays = new Set()
32
+ commits.forEach((c) => {
33
+ const d = new Date(c.date)
34
+ if (isNaN(d.getTime())) return
35
+ const h = d.getHours()
36
+ if (h < 0 || h >= cutoffHour || h >= startHour) return
37
+ const prevDate = new Date(d)
38
+ prevDate.setDate(prevDate.getDate() - 1)
39
+ const prevDay = formatDateYMD(prevDate)
40
+ if (!dayGroups[prevDay]) virtualPrevDays.add(prevDay)
41
+ })
42
+
43
+ const allDayKeys = Array.from(new Set([...dayKeys, ...virtualPrevDays])).sort()
44
+
45
+ const latestByDay = allDayKeys.map((k) => {
46
+ const list = dayGroups[k] || []
47
+ const sameDayHours = list
48
+ .map((c) => new Date(c.date))
49
+ .filter((d) => !isNaN(d.getTime()))
50
+ .map((d) => d.getHours())
51
+ .filter((h) => h >= endHour && h < 24)
52
+
53
+ const nextDate = new Date(`${k}T00:00:00`)
54
+ nextDate.setDate(nextDate.getDate() + 1)
55
+ const nextKey = formatDateYMD(nextDate)
56
+ const early = dayGroups[nextKey] || []
57
+ const earlyHours = early
58
+ .map((c) => new Date(c.date))
59
+ .filter((d) => !isNaN(d.getTime()))
60
+ .map((d) => d.getHours())
61
+ .filter((h) => h >= 0 && h < cutoffHour && h < startHour)
62
+
63
+ const overtimeValues = [...sameDayHours, ...earlyHours.map((h) => 24 + h)]
64
+ if (overtimeValues.length === 0) return { date: k, latestHour: null, latestHourNormalized: null }
65
+ const latestHourNormalized = Math.max(...overtimeValues)
66
+ const sameDayMax = sameDayHours.length > 0 ? Math.max(...sameDayHours) : null
67
+ return { date: k, latestHour: sameDayMax, latestHourNormalized }
68
+ })
69
+
70
+ return latestByDay
71
+ }
72
+
73
+ async function main() {
74
+ console.log('Computing frontend-style latestByDay from commits...')
75
+ const computed = computeLatestByDayLocal(commits)
76
+ const byDate = {}
77
+ computed.forEach((d) => { byDate[d.date] = d })
78
+
79
+ const mismatches = []
80
+ overtimeLatest.forEach((o) => {
81
+ const comp = byDate[o.date]
82
+ if (!comp) {
83
+ mismatches.push({ date: o.date, reason: 'missing-in-computed', expected: o, actual: null })
84
+ return
85
+ }
86
+ const expNorm = o.latestHourNormalized
87
+ const actNorm = comp.latestHourNormalized
88
+ if (expNorm !== actNorm) {
89
+ mismatches.push({ date: o.date, expected: o, actual: comp })
90
+ }
91
+ })
92
+
93
+ if (mismatches.length === 0) {
94
+ console.log('No mismatches: computed latestByDay matches overtime.latest.commit.day.mjs')
95
+ } else {
96
+ console.log(`Found ${mismatches.length} mismatches:`)
97
+ mismatches.slice(0, 50).forEach((m) => {
98
+ console.log('---')
99
+ console.log(m.date)
100
+ console.log(' expected:', m.expected)
101
+ console.log(' actual :', m.actual)
102
+ })
103
+ }
104
+ }
105
+
106
+ main().catch((e) => { console.error(e); process.exit(1) })
@@ -0,0 +1,120 @@
1
+ import chalk from 'chalk'
2
+ import path from 'path'
3
+ import { createProfiler } from 'wukong-profiler'
4
+ import { createMultiBar } from 'wukong-progress'
5
+
6
+ import { getProfileDirFile } from '#utils/getProfileDirFile.mjs'
7
+
8
+ import { parseOptions } from '../cli/parseOptions.mjs'
9
+ import { getAuthorChangeStats } from '../domain/author/analyze.mjs'
10
+ import { getGitLogsFast } from '../domain/git/getGitLogs.mjs'
11
+ import { resolveGerrit } from '../domain/git/resolveGerrit.mjs'
12
+ import { getWorkOvertimeStats } from '../domain/overtime/analyze.mjs'
13
+ import { t } from '../i18n/index.mjs'
14
+ import { outputAll, outputData } from '../output/index.mjs'
15
+ import {
16
+ getLatestCommitByDay,
17
+ getOvertimeByMonth,
18
+ getOvertimeByWeek,
19
+ getWorkTimeConfig
20
+ } from './helpers.mjs'
21
+
22
+ // 建议将辅助计算函数抽离,保持主逻辑清晰
23
+
24
+ export async function analyzeAction(rawOpts = {}) {
25
+ const opts = await parseOptions(rawOpts)
26
+
27
+ const traceFile = getProfileDirFile('trace.json', opts)
28
+ const profileFile = getProfileDirFile('profile.json', opts)
29
+
30
+ const profiler = createProfiler({ ...opts.profile, traceFile ,profileFile}, opts)
31
+
32
+ // 未来 可考虑将 MultiBar 抽离到更高层,支持所有 action 共用,wukong-progress 需要支持自定义子任务占位符
33
+ // 初始化 MultiBar
34
+ const mb = createMultiBar()
35
+ const bar = mb.create(100, {
36
+ prefix: chalk.cyan(t('analyze.prefix')), // 国际化前缀 'Analyze' / '分析'
37
+ format: ' [:bar] :percent :payload'
38
+ })
39
+
40
+ const result = {}
41
+
42
+ try {
43
+ // 1️⃣ 拉取 Git 记录
44
+ bar.step(5, t('analyze.step_git_fetch')) // '正在提取 Git 提交记录...'
45
+ const { commits, authorMap } = await profiler.stepAsync('getGitLogs', () =>
46
+ getGitLogsFast(opts)
47
+ )
48
+ result.commits = commits
49
+ result.authorMap = authorMap
50
+ bar.step(8, t('analyze.step_git_done')) // 'Git 记录提取完成'
51
+
52
+ const records = result.commits
53
+
54
+ // 2️⃣ Gerrit enrich(公共)
55
+ const enrichedRecords = opts.gerrit
56
+ ? await resolveGerrit(records, opts)
57
+ : records
58
+
59
+ // 2️⃣ 分析作者变更
60
+ bar.step(9, t('analyze.step_author_stats')) // '正在分析作者代码贡献...'
61
+ const authorChanges = await profiler.stepAsync(
62
+ 'analyzeAuthorChanges',
63
+ () => {
64
+ return getAuthorChangeStats(enrichedRecords)
65
+ }
66
+ )
67
+ result.authorChanges = authorChanges
68
+
69
+ // 3️⃣ 加班分析(根据配置可选)
70
+ if (opts.overtime) {
71
+ const worktimeOptions = getWorkTimeConfig(opts)
72
+
73
+ bar.step(10, t('analyze.step_overtime_calc')) // '正在计算加班概况...'
74
+ result.overtime = await profiler.stepAsync('overtime', () => {
75
+ return getWorkOvertimeStats(enrichedRecords, worktimeOptions)
76
+ })
77
+
78
+ bar.step(20, t('analyze.step_trends')) // '正在生成周/月趋势数据...'
79
+ result.overtimeByWeek = await getOvertimeByWeek(enrichedRecords)
80
+ result.overtimeByMonth = await getOvertimeByMonth(enrichedRecords)
81
+
82
+ bar.step(20, t('analyze.step_latest_mark')) // '正在标记每日最晚提交点...'
83
+ result.overtimeLatestCommitByDay = await getLatestCommitByDay({
84
+ commits: enrichedRecords,
85
+ opts: worktimeOptions
86
+ })
87
+ } else {
88
+ bar.step(50, t('analyze.step_skip_overtime')) // '跳过加班数据分析'
89
+ }
90
+
91
+ // 4️⃣ 数据输出
92
+ bar.step(80, t('analyze.step_output')) // '正在持久化分析结果...'
93
+ await profiler.stepAsync('output', async () => {
94
+ const worktimeOptions = getWorkTimeConfig(opts)
95
+
96
+ await outputData(result, {
97
+ dir: opts.output.dir || path.resolve('output-wukong'),
98
+ worktimeOptions,
99
+ period: opts.period || {},
100
+ author: opts.author || null,
101
+ git: opts.git || {}
102
+ })
103
+ })
104
+
105
+ bar.step(100, t('analyze.step_complete')) // '分析任务全部完成!'
106
+ } catch (error) {
107
+ // 异常处理:停止进度条并打印红色错误
108
+ mb.stop()
109
+ console.error(
110
+ `\n${chalk.bgRed(' ERROR ')} ${chalk.red(error.stack || error)}`
111
+ )
112
+ process.exit(1)
113
+ } finally {
114
+ // 正常结束
115
+ profiler.end('analyze')
116
+ mb.stop()
117
+ }
118
+
119
+ return result
120
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * @file: exportAction.mjs
3
+ * @description:
4
+ * @author: King Monkey
5
+ * @created: 2025-12-31 17:27
6
+ */
7
+ import chalk from 'chalk'
8
+ import path from 'path'
9
+ import { createProfiler } from 'wukong-profiler'
10
+ import { createMultiBar } from 'wukong-progress'
11
+
12
+ import { handleExportAuthor } from '#src/domain/export/exportAuthor.mjs'
13
+ import { handleExportAuthorChanges } from '#src/domain/export/exportAuthorChanges.mjs'
14
+ import { handleExportAuthorChangesJson } from '#src/domain/export/exportAuthorChangesJson.mjs'
15
+ import { handleExportByMonth } from '#src/domain/export/exportByMonth.mjs'
16
+ import { handleExportByWeek } from '#src/domain/export/exportByWeek.mjs'
17
+ import { handleExportCommits } from '#src/domain/export/exportCommits.mjs'
18
+ import { handleExportCommitsExcel } from '#src/domain/export/exportCommitsExcel.mjs'
19
+ import { handleExportCommitsJson } from '#src/domain/export/exportCommitsJson.mjs'
20
+ import {
21
+ handleExportOvertimeCsv,
22
+ handleExportOvertimeMain,
23
+ handleExportOvertimeTabTxt,
24
+ handleExportOvertimeTxt
25
+ } from '#src/domain/export/index.mjs'
26
+ import { getProfileDirFile } from '#utils/getProfileDirFile.mjs'
27
+ import { groupRecords } from '#utils/groupRecords.mjs'
28
+
29
+ import { parseOptions } from '../cli/parseOptions.mjs'
30
+ import { getGitLogsFast } from '../domain/git/getGitLogs.mjs'
31
+ import { resolveGerrit } from '../domain/git/resolveGerrit.mjs'
32
+ import { getWorkOvertimeStats } from '../domain/overtime/analyze.mjs'
33
+ import {
34
+ renderOvertimeCsv,
35
+ renderOvertimeTab,
36
+ renderOvertimeText
37
+ } from '../domain/overtime/overtime.mjs'
38
+ import { outputAll, outputData } from '../output/index.mjs'
39
+ import {
40
+ getLatestCommitByDay,
41
+ getOvertimeByMonth,
42
+ getOvertimeByWeek,
43
+ getWorkTimeConfig
44
+ } from './helpers.mjs'
45
+ import { t } from '../i18n/index.mjs'
46
+
47
+
48
+ export async function exportAction(rawOpts = {}) {
49
+ const opts = await parseOptions(rawOpts)
50
+ const traceFile = getProfileDirFile('trace.json', opts)
51
+
52
+
53
+ const profiler = createProfiler({ ...opts.profile, traceFile }, opts)
54
+
55
+ const mb = createMultiBar()
56
+ const bar = mb.create(100, {
57
+ prefix: chalk.cyan(t('export.prefix')),
58
+ format: ' [:bar] :percent :payload'
59
+ })
60
+
61
+ const result = {}
62
+ try {
63
+ // 1️⃣ 拉取 Git 记录
64
+ bar.step(5, t('analyze.step_git_fetch'))
65
+ const { commits, authorMap } = await profiler.stepAsync('getGitLogs', () =>
66
+ getGitLogsFast(opts)
67
+ )
68
+ result.commits = commits
69
+
70
+ bar.step(15, t('analyze.step_git_done'))
71
+
72
+ const records = result.commits
73
+
74
+ // 2️⃣ Gerrit enrich(公共)
75
+ const enrichedRecords = opts.gerrit
76
+ ? await resolveGerrit(records, opts)
77
+ : records
78
+
79
+ // 3️⃣ 加班分析(根据配置可选)
80
+ const worktimeOptions = getWorkTimeConfig(opts)
81
+
82
+ bar.step(10, t('analyze.step_overtime_calc'))
83
+ result.overtime = await profiler.stepAsync('overtime', () => {
84
+ return getWorkOvertimeStats(enrichedRecords, worktimeOptions)
85
+ })
86
+
87
+ if (['all','week'].includes(opts.period.groupBy)) {
88
+ bar.step(20, t('analyze.step_trends'))
89
+ result.overtimeByWeek = await getOvertimeByWeek(enrichedRecords)
90
+ }
91
+
92
+ if (['all','month'].includes(opts.period.groupBy)) {
93
+ result.overtimeByMonth = await getOvertimeByMonth(enrichedRecords)
94
+ }
95
+
96
+ bar.step(20, t('analyze.step_latest_mark'))
97
+ result.overtimeLatestCommitByDay = await getLatestCommitByDay({
98
+ commits: enrichedRecords,
99
+ opts: worktimeOptions
100
+ })
101
+
102
+ // 4️⃣ 数据输出
103
+ bar.step(10, t('analyze.step_output'))
104
+ await profiler.stepAsync('output', async () => {
105
+ await outputData(result, {
106
+ dir: opts.output.dir || path.resolve('output-wukong'),
107
+ worktimeOptions,
108
+ git: opts.git || {},
109
+ })
110
+ })
111
+
112
+ bar.step(100, t('analyze.step_complete'))
113
+ handleExportOvertimeMain({
114
+ fileName: 'overtime.json',
115
+ opts,
116
+ result: result.overtime
117
+ })
118
+
119
+ // 导出 overtime_commits.txt
120
+ const overtimeTxtResult = renderOvertimeText(result.overtime)
121
+ handleExportOvertimeTxt({
122
+ fileName: 'overtime_commits.txt',
123
+ opts,
124
+ result: overtimeTxtResult
125
+ })
126
+
127
+ // 导出 overtime_commits.tab.txt
128
+ const overtimeTabTxtResult = renderOvertimeTab(result.overtime)
129
+ handleExportOvertimeTabTxt({
130
+ fileName: 'overtime_commits.tab.txt',
131
+ opts,
132
+ result: overtimeTabTxtResult
133
+ })
134
+
135
+ // 导出 overtime_commits.csv
136
+ const overtimeCsvResult = renderOvertimeCsv(result.overtime)
137
+ handleExportOvertimeCsv({
138
+ fileName: 'overtime_commits.csv',
139
+ opts,
140
+ result: overtimeCsvResult
141
+ })
142
+
143
+ if (['all','month'].includes(opts.period.groupBy)) {
144
+ // 导出 每月数据
145
+ handleExportByMonth({
146
+ opts,
147
+ records: commits,
148
+ worktimeOptions
149
+ })
150
+ }
151
+
152
+ if (['all','week'].includes(opts.period.groupBy)) {
153
+ // 导出 每周数据
154
+ handleExportByWeek({
155
+ opts,
156
+ records: commits,
157
+ worktimeOptions
158
+ })
159
+ }
160
+
161
+ // 导出 commit logs
162
+ handleExportCommits({
163
+ opts,
164
+ records: commits,
165
+ fileName: 'commits.txt'
166
+ })
167
+
168
+ // 导出 commit logs
169
+ handleExportAuthorChanges({
170
+ opts,
171
+ records: commits,
172
+ fileName: 'author-changes.txt'
173
+ })
174
+
175
+ // --- 分组 ---
176
+ const groups = opts.period.groupBy
177
+ ? groupRecords(records, opts.period.groupBy)
178
+ : null
179
+
180
+
181
+ handleExportCommitsExcel({
182
+ opts,
183
+ records: commits,
184
+ fileName: 'commits.xlsx',
185
+ groups
186
+ })
187
+
188
+ handleExportCommitsJson({
189
+ opts,
190
+ records: commits,
191
+ fileName: 'commits.json',
192
+ groups
193
+ })
194
+
195
+ handleExportAuthorChangesJson({
196
+ opts,
197
+ records: commits,
198
+ fileName: 'author-changes.json'
199
+ })
200
+ handleExportAuthor({ opts, records: authorMap, fileName: 'author.txt' })
201
+ } catch (error) {
202
+ // 异常处理:停止进度条并打印红色错误
203
+ mb.stop()
204
+ console.error(
205
+ `\n${chalk.bgRed(' ERROR ')} ${chalk.red(error.stack || error)}`
206
+ )
207
+ process.exit(1)
208
+ } finally {
209
+ // 正常结束
210
+ profiler.end('analyze')
211
+ mb.stop()
212
+ }
213
+
214
+ return result
215
+ }
@@ -0,0 +1,37 @@
1
+ import chalk from 'chalk'
2
+ import { createMultiBar } from 'wukong-progress'
3
+
4
+ const someAsyncLogTask = () =>
5
+ new Promise((resolve) => {setTimeout(resolve, 1000)})
6
+ const someDataProcessing = () =>
7
+ new Promise((resolve) => {setTimeout(resolve, 1500)})
8
+ const writeToFile = () => new Promise((resolve) => {setTimeout(resolve, 1000)})
9
+
10
+ export async function exportAction(rawOpts = {}) {
11
+ const mb = createMultiBar()
12
+ const bar = mb.create(100, {
13
+ prefix: chalk.cyan('Build'),
14
+ format: 'Build [:bar] :percent :current/:total'
15
+ })
16
+
17
+ console.log('\n🚀', chalk.cyan('Wukong GitLog'), '报告导出中...\n')
18
+
19
+ // --- 模拟业务步骤 ---
20
+
21
+ // 1. 获取日志 (30%)
22
+ await someAsyncLogTask()
23
+ bar.tick(30)
24
+
25
+ // 2. 处理数据 (再加 40%)
26
+ await someDataProcessing()
27
+ bar.tick(40)
28
+
29
+ // 3. 写入文件 (最后 30%)
30
+ await writeToFile()
31
+ bar.tick(30)
32
+
33
+ // --- 业务结束 ---
34
+
35
+ mb.stop()
36
+ console.log(chalk.green('\nDone!\n'))
37
+ }