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
@@ -0,0 +1,117 @@
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 { dedupeCommits, getGitLogsFast } from '../domain/git/getGitLogs.mjs'
11
+ import { resolveGerrit } from '../domain/git/resolveGerrit.mjs'
12
+ import { getWorkOvertimeStats } from '../domain/overtime/analyze.mjs'
13
+ import { outputAll, outputJournalAnalysis } from '../output/index.mjs'
14
+ import {
15
+ getGitLogsDayReport,
16
+ getLatestCommitByDay,
17
+ getOvertimeByMonth,
18
+ getOvertimeByWeek,
19
+ getWorkTimeConfig
20
+ } from './helpers.mjs'
21
+ import { t } from '../i18n/index.mjs'
22
+
23
+
24
+ // 日报,把每天提交记录合并后,按天输出
25
+
26
+ export async function journalAction(rawOpts = {}) {
27
+ const opts = await parseOptions(rawOpts)
28
+
29
+ const traceFile = getProfileDirFile('trace.json', opts)
30
+ const profileFile = getProfileDirFile('profile.json', opts)
31
+
32
+ const profiler = createProfiler(
33
+ { ...opts.profile, traceFile, profileFile },
34
+ opts
35
+ )
36
+
37
+ // 未来 可考虑将 MultiBar 抽离到更高层,支持所有 action 共用,wukong-progress 需要支持自定义子任务占位符
38
+ // 初始化 MultiBar
39
+ const mb = createMultiBar()
40
+ const bar = mb.create(100, {
41
+ prefix: chalk.cyan(t('journal.prefix')),
42
+ format: ' [:bar] :percent :payload'
43
+ })
44
+
45
+ const result = {}
46
+
47
+ try {
48
+ // 1️⃣ 拉取 Git 记录
49
+ bar.step(5, t('analyze.step_git_fetch'))
50
+ const { commits, authorMap } = await profiler.stepAsync('getGitLogs', () =>
51
+ getGitLogsFast(opts)
52
+ )
53
+
54
+ // 去重 commit(基于 Change-Id),过滤掉 Gerrit 产生的重复提交例如cherry-pick
55
+ const cleanCommits = dedupeCommits(commits, {
56
+ by: 'changeId',
57
+ prefer: 'original'
58
+ })
59
+
60
+ result.cleanCommits = cleanCommits
61
+
62
+ result.commits = commits
63
+ result.authorMap = authorMap
64
+ bar.step(15, t('analyze.step_git_done')) // 'Git 记录提取完成'
65
+
66
+ const records = result.commits
67
+
68
+ // 2️⃣ Gerrit enrich(公共)
69
+ const enrichedRecords = opts.gerrit
70
+ ? await resolveGerrit(records, opts)
71
+ : records
72
+
73
+ // 2️⃣ 分析作者变更
74
+ bar.step(10, t('analyze.step_author_stats')) // '正在分析作者代码贡献...'
75
+ const authorChanges = await profiler.stepAsync(
76
+ 'analyzeAuthorChanges',
77
+ () => {
78
+ return getAuthorChangeStats(enrichedRecords)
79
+ }
80
+ )
81
+ result.authorChanges = authorChanges
82
+
83
+ const authorDayReport = await profiler.stepAsync(
84
+ 'analyzeAuthorDayReport',
85
+ () => {
86
+ return getGitLogsDayReport(cleanCommits, opts)
87
+ }
88
+ )
89
+
90
+ result.authorDayReport = authorDayReport
91
+
92
+ // 4️⃣ 数据输出
93
+ bar.step(10, t('analyze.step_output')) // '正在持久化分析结果...'
94
+ await profiler.stepAsync('output', async () => {
95
+ const worktimeOptions = getWorkTimeConfig(opts)
96
+ await outputJournalAnalysis(result, {
97
+ dir: opts.output.dir || path.resolve('output-wukong'),
98
+ worktimeOptions
99
+ })
100
+ })
101
+
102
+ bar.step(100, t('journal.complete')) // '分析任务全部完成!'
103
+ } catch (error) {
104
+ // 异常处理:停止进度条并打印红色错误
105
+ mb.stop()
106
+ console.error(
107
+ `\n${chalk.bgRed(' ERROR ')} ${chalk.red(error.stack || error)}`
108
+ )
109
+ process.exit(1)
110
+ } finally {
111
+ // 正常结束
112
+ profiler.end('journal')
113
+ mb.stop()
114
+ }
115
+
116
+ return result
117
+ }
@@ -0,0 +1,100 @@
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 { getGitLogsFast } from '../domain/git/getGitLogs.mjs'
10
+ import { resolveGerrit } from '../domain/git/resolveGerrit.mjs'
11
+ import { getWorkOvertimeStats } from '../domain/overtime/analyze.mjs'
12
+ import { t } from '../i18n/index.mjs'
13
+ import { outputAll, outputData } from '../output/index.mjs'
14
+ import { resolveOutDir } from '../output/utils/outputPath.mjs'
15
+ import {
16
+ getLatestCommitByDay,
17
+ getOvertimeByMonth,
18
+ getOvertimeByWeek,
19
+ getWorkTimeConfig
20
+ } from './helpers.mjs'
21
+
22
+ export async function overtimeAction(rawOpts = {}) {
23
+ const opts = await parseOptions(rawOpts)
24
+ const traceFile = getProfileDirFile('trace.json', opts)
25
+
26
+ const profiler = createProfiler({ ...opts.profile, traceFile })
27
+
28
+ // 未来 可考虑将 MultiBar 抽离到更高层,支持所有 action 共用,wukong-progress 需要支持自定义子任务占位符
29
+ // 初始化 MultiBar
30
+ const mb = createMultiBar()
31
+ const bar = mb.create(100, {
32
+ prefix: chalk.cyan(t('overtime.prefix')), // '加班分析' / 'Overtime'
33
+ format: ' [:bar] :percent :payload'
34
+ })
35
+
36
+ const result = {}
37
+ try {
38
+ // 1️⃣ 拉取 Git 记录
39
+ bar.step(5, t('analyze.step_git_fetch'))
40
+ const { commits } = await profiler.stepAsync('getGitLogs', () =>
41
+ getGitLogsFast(opts)
42
+ )
43
+ result.commits = commits
44
+
45
+ bar.step(5, t('analyze.step_git_done'))
46
+
47
+ const records = result.commits
48
+
49
+ // 2️⃣ Gerrit enrich(公共)
50
+ const enrichedRecords = opts.gerrit
51
+ ? await resolveGerrit(records, opts)
52
+ : records
53
+
54
+ // 3️⃣ 加班分析(根据配置可选)
55
+ const worktimeOptions = getWorkTimeConfig(opts)
56
+
57
+ bar.step(5, t('analyze.step_overtime_calc'))
58
+ result.overtime = await profiler.stepAsync('overtime', () => {
59
+ return getWorkOvertimeStats(enrichedRecords, worktimeOptions)
60
+ })
61
+
62
+ bar.step(5, t('analyze.step_trends'))
63
+ result.overtimeByWeek = await getOvertimeByWeek(enrichedRecords)
64
+ result.overtimeByMonth = await getOvertimeByMonth(enrichedRecords)
65
+ bar.step(10, t('analyze.step_latest_mark'))
66
+ result.overtimeLatestCommitByDay = await getLatestCommitByDay({
67
+ commits: enrichedRecords,
68
+ opts: worktimeOptions
69
+ })
70
+
71
+ // 4️⃣ 数据输出
72
+ bar.step(1, t('analyze.step_output'))
73
+ const dir = opts.output.dir || path.resolve('output-wukong')
74
+ await profiler.stepAsync('output', async () => {
75
+ return outputData(result, {
76
+ dir,
77
+ worktimeOptions,
78
+ git: opts.git || {}
79
+ })
80
+ })
81
+
82
+ bar.step(100, t('analyze.step_complete'))
83
+ console.log(
84
+ chalk.green(`\n${t('overtime.complete')} ${resolveOutDir(dir)}\n`)
85
+ )
86
+ } catch (error) {
87
+ // 异常处理:停止进度条并打印红色错误
88
+ mb.stop()
89
+ console.error(
90
+ `\n${chalk.bgRed(' ERROR ')} ${chalk.red(error.stack || error)}`
91
+ )
92
+ process.exit(1)
93
+ } finally {
94
+ // 正常结束
95
+ profiler.end('analyze')
96
+ mb.stop()
97
+ }
98
+
99
+ return result
100
+ }
File without changes
@@ -0,0 +1,73 @@
1
+ import { logger } from '#utils/logger.mjs'
2
+
3
+ import { parseOptions } from '../cli/parseOptions.mjs'
4
+ import { readServeData } from '../output/data/readData.mjs'
5
+ import { startServer } from '../serve/startServer.mjs'
6
+ import { analyzeAction } from './analyzeAction.mjs'
7
+
8
+ export async function serveAction(rawOpts = {}) {
9
+ const opts = await parseOptions(rawOpts)
10
+
11
+ const dir = opts.output.dir || 'output-wukong'
12
+
13
+ // 🚀 在启动服务前,自动运行 analyze 以更新基础数据
14
+ try {
15
+ logger.info('⚡ Auto-running analyze to ensure latest data...')
16
+ await analyzeAction(rawOpts)
17
+ logger.info('✅ Data refreshed successfully')
18
+ } catch (error) {
19
+ logger.warn(
20
+ '⚠️ Auto-analyze failed, but continuing with cached data:',
21
+ error.message
22
+ )
23
+ }
24
+
25
+ let data = null
26
+
27
+ try {
28
+ data = await readServeData(dir)
29
+
30
+ if (!data) {
31
+ logger.error(
32
+ 'No serve data found. Please run `wukong-gitlog analyze` first.'
33
+ )
34
+ process.exit(1)
35
+ }
36
+ } catch (error) {
37
+ logger.error(
38
+ 'Failed to read serve data. Please run `wukong-gitlog analyze` first.'
39
+ )
40
+ process.exit(1)
41
+ }
42
+
43
+ const initialPort = Number(opts.serve.port || 3000)
44
+ let port = initialPort
45
+ let server = null
46
+ const maxTries = 50 // 尝试的端口数量上限
47
+
48
+ for (let i = 0; i < maxTries; i++) {
49
+ try {
50
+ // eslint-disable-next-line no-await-in-loop
51
+ server = await startServer(port, dir, data)
52
+ break
53
+ } catch (err) {
54
+ // 端口被占用,尝试下一个端口;其它错误抛出
55
+ if (err && err.code === 'EADDRINUSE') {
56
+ console.warn(`Port ${port} in use, trying ${port + 1}...`)
57
+ port += 1
58
+ continue
59
+ }
60
+ throw err
61
+ }
62
+ }
63
+
64
+ if (!server) {
65
+ throw new Error(`Failed to start server on ports ${initialPort} - ${port}.`)
66
+ }
67
+
68
+ if (port !== initialPort) {
69
+ console.log(`Port ${initialPort} occupied, server started on ${port}.`)
70
+ }
71
+
72
+ return server
73
+ }
@@ -0,0 +1,7 @@
1
+ import { getPackage } from '../utils/getPackage.mjs'
2
+ import { showVersionInfo } from '../utils/showVersionInfo.mjs'
3
+
4
+ export const versionAction = () => {
5
+ const pkg = getPackage()
6
+ showVersionInfo(pkg)
7
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @file: defineOptions.mjs
3
+ * @description:
4
+ * @author: King Monkey
5
+ * @created: 2025-12-31 17:30
6
+ */
7
+ import { t } from '../i18n/index.mjs'
8
+ import { getPackage } from '../utils/getPackage.mjs'
9
+
10
+ // 辅助函数:统一 Int 解析
11
+ const toInt = (v, def) => (v ? parseInt(v, 10) : def)
12
+ const toFloat = (v) => parseFloat(v)
13
+
14
+ export function defineOptions(program) {
15
+ const pkg = getPackage()
16
+ program
17
+ .name('wukong-gitlog')
18
+ .version(pkg.version, '-v')
19
+ .description('Advanced Git commit log exporter.')
20
+ .option(
21
+ '--author <name>',
22
+ '指定 author,注意如果用户Git的user.name和user.email不规范,例如配置过不同的邮箱和name,应该通过--author-aliases 进行别名映射,并且不传递 --author 参数,以免过滤掉不规范的提交记录'
23
+ )
24
+ .option('--email <email>', '指定 email')
25
+ .option('--since <date>', '起始日期')
26
+ .option('--until <date>', '结束日期')
27
+ .option('--limit <n>', '限制数量', parseInt)
28
+ .option('--no-merges', '不包含 merge commit')
29
+ .option('--numstat', '显示每次提交中更改的文件以及增删的行数统计')
30
+
31
+ .option('--group-by <type>', '按日期分组: day | month | week | all ')
32
+ .option('--format <type>', '输出格式: text | excel | json', 'text')
33
+
34
+ .option('--stats', '输出每日统计数据')
35
+ .option(
36
+ '--gerrit <prefix>',
37
+ '显示 Gerrit 地址,支持在 prefix 中使用 {{hash}} 占位符'
38
+ )
39
+ .option(
40
+ '--gerrit-api <url>',
41
+ '可选:Gerrit REST API 基础地址,用于解析 changeNumber,例如 `https://gerrit.example.com`'
42
+ )
43
+ .option(
44
+ '--gerrit-auth <tokenOrUserPass>',
45
+ '可选:Gerrit API 授权,格式为 `user:pass` 或 `TOKEN`(表示 Bearer token)'
46
+ )
47
+
48
+ .option('--overtime', '启用加班文化分析')
49
+
50
+ .option('--country <code>', '节假日国家:CN 或 US,默认为 CN')
51
+ .option(
52
+ '--work-start <hour>',
53
+ '上班开始小时,默认 9',
54
+ (v) => parseInt(v, 10),
55
+ 9
56
+ )
57
+ .option(
58
+ '--work-end <hour>',
59
+ '下班小时,默认 18',
60
+ (v) => parseInt(v, 10),
61
+ 18
62
+ )
63
+ .option(
64
+ '--lunch-start <hour>',
65
+ '午休开始小时,默认 12',
66
+ (v) => parseInt(v, 10),
67
+ 12
68
+ )
69
+ .option(
70
+ '--lunch-end <hour>',
71
+ '午休结束小时,默认 14',
72
+ (v) => parseInt(v, 10),
73
+ 14
74
+ )
75
+ .option(
76
+ '--overnight-cutoff <hour>',
77
+ '次日凌晨归并窗口(小时),默认 6',
78
+ (v) => parseInt(v, 10),
79
+ 6
80
+ )
81
+
82
+
83
+ .option(
84
+ '--out-dir <dir>',
85
+ '自定义输出目录,支持相对路径或绝对路径,例如 `--out-dir ../output-wukong`'
86
+ )
87
+ .option(
88
+ '--out-parent',
89
+ '将输出目录放到当前工程的父目录的 `output-wukong/`(等同于 `--out-dir ../output-wukong`)'
90
+ )
91
+ .option(
92
+ '--per-period-formats <formats>',
93
+ '每个周期单独输出的格式,逗号分隔:text,csv,tab,xlsx。默认为空(不输出 CSV/Tab/XLSX)',
94
+ ''
95
+ )
96
+ .option(
97
+ '--per-period-excel-mode <mode>',
98
+ 'per-period Excel 模式:sheets|files(默认:sheets)',
99
+ 'sheets'
100
+ )
101
+ .option(
102
+ '--per-period-only',
103
+ '仅输出 per-period(month/week)文件,不输出合并的 monthly/weekly 汇总文件'
104
+ )
105
+ .option(
106
+ '--port <n>',
107
+ '本地 web 服务端口(默认 3000)',
108
+ (v) => parseInt(v, 10),
109
+ 3000
110
+ )
111
+ .option('--debug', 'enable debug logs')
112
+
113
+ .option('--profile', '输出性能分析 JSON')
114
+ .option('--verbose', '显示详细性能日志')
115
+ .option('--flame', '显示 flame-like 日志')
116
+ .option('--trace <file>', '生成 Chrome Trace')
117
+ .option('--hot-threshold <n>', 'HOT 比例阈值', parseFloat)
118
+ .option('--fail-on-hot', 'HOT 时 CI 失败')
119
+ .option('--diff-base <file>', '基线 profile.json')
120
+ .option('--diff-threshold <n>', '回归阈值', parseFloat)
121
+
122
+ .option('--version', 'show version information')
123
+ }
124
+
125
+
126
+ /**
127
+ * 1. 基础程序信息(全局通用)
128
+ */
129
+ export function setupBaseProgram(program) {
130
+ const pkg = getPackage()
131
+ program
132
+ .name('wukong-gitlog')
133
+ .version(pkg.version, '-v')
134
+ .description(t('cli.desc'))
135
+ .option('-l, --lang <type>', t('options.lang')) // 放在这里,让所有命令都能看到 help
136
+ .option('-i,--info', t('options.info'))
137
+ .option('--debug', t('options.debug'))
138
+ }
139
+
140
+ /**
141
+ * 2. Git 数据源过滤参数 (Analyze, Export, Journal, Overtime 通用)
142
+ */
143
+ export function addGitSourceOptions(cmd) {
144
+ return cmd
145
+ .option('--author <name>', t('options.author'))
146
+ .option('--email <email>', t('options.email'))
147
+ .option('--since <date>', t('options.since'))
148
+ .option('--until <date>', t('options.until'))
149
+ .option('--limit <n>', t('options.limit'), toInt)
150
+ .option('--no-merges', t('options.no_merges'))
151
+ .option('--numstat', t('options.numstat'))
152
+ // FIXME: 暂时没有Gerrit需求,先注释掉相关参数,等需要了再放开
153
+ // .option('--gerrit <prefix>', t('options.gerrit_prefix'))
154
+ // .option('--gerrit-api <url>', t('options.gerrit_api'))
155
+ // .option('--gerrit-auth <token>', t('options.gerrit_auth'))
156
+ }
157
+
158
+ /**
159
+ * 3. 核心算法与统计参数 (Analyze, Overtime 通用)
160
+ */
161
+ export function addAnalysisOptions(cmd) {
162
+ return cmd
163
+ .option('--country <code>', t('options.country'), 'CN')
164
+ .option('--work-start <hour>', t('options.work_start'), toInt, 9)
165
+ .option('--work-end <hour>', t('options.work_end'), toInt, 18)
166
+ .option('--lunch-start <hour>', t('options.lunch_start'), toInt, 12)
167
+ .option('--lunch-end <hour>', t('options.lunch_end'), toInt, 14)
168
+ .option('--overnight-cutoff <hour>', t('options.overnight_cutoff'), toInt, 6)
169
+ .option('--group-by <type>', t('options.group_by'), 'day') // 默认 day
170
+ .option('--stats', t('options.stats'))
171
+ .option('--overtime', t('options.overtime_mode'))
172
+ }
173
+
174
+ /**
175
+ * 4. 文件导出与格式参数 (Export, Journal, Analyze)
176
+ */
177
+ export function addOutputOptions(cmd) {
178
+ return cmd
179
+ .option('--format <type>', t('options.format'), 'text')
180
+ .option('--out-dir <dir>', t('options.out_dir'))
181
+ .option('--out-parent', t('options.out_parent'))
182
+ .option('--per-period-formats <formats>', t('options.per_period_formats'), '')
183
+ .option('--per-period-excel-mode <mode>', t('options.per_period_mode'), 'sheets')
184
+ .option('--per-period-only', t('options.per_period_only'))
185
+ }
186
+
187
+ /**
188
+ * 5. Web 服务专用参数
189
+ */
190
+ export function addServeOptions(cmd) {
191
+ return cmd
192
+ .option('--port <n>', t('options.port'), toInt, 3000)
193
+ .option('--numstat', t('options.numstat'))
194
+ }
195
+
196
+ /**
197
+ * 6. 性能追踪与调试 (通常用于 analyze)
198
+ */
199
+ export function addPerformanceOptions(cmd) {
200
+ return cmd
201
+ .option('--profile', t('options.profile'))
202
+ .option('--verbose', t('options.verbose'))
203
+ .option('--flame', t('options.flame'))
204
+ .option('--trace <file>', t('options.trace'))
205
+ // .option('--hot-threshold <n>', t('options.hot_threshold'), toFloat)
206
+ // .option('--fail-on-hot', t('options.fail_on_hot'))
207
+ // .option('--diff-base <file>', t('options.diff_base'))
208
+ // .option('--diff-threshold <n>', t('options.diff_threshold'), toFloat)
209
+ }
File without changes
@@ -1,9 +1,127 @@
1
- export function parseOptions(opts) {
2
- return {
3
- startHour: opts.workStart ?? 9,
4
- endHour: opts.workEnd ?? 18,
5
- lunchStart: opts.lunchStart ?? 12,
6
- lunchEnd: opts.lunchEnd ?? 14,
7
- country: opts.country ?? 'CN'
8
- };
1
+ /**
2
+ * @file: parseOptions.mjs
3
+ * @description:
4
+ * @author: King Monkey
5
+ * @created: 2025-12-31 17:24
6
+ */
7
+ import { resolveBool, resolveValue } from '#src/utils/resolve.mjs'
8
+
9
+ import { loadRcConfig } from '../infra/configStore.mjs'
10
+
11
+ /**
12
+ * 深度合并辅助函数 (如果你的项目里没引入 lodash,可以直接用这个)
13
+ */
14
+ function deepMerge(target, source) {
15
+ const result = { ...target }
16
+ for (const key in source) {
17
+ if (source[key] instanceof Object && key in target) {
18
+ result[key] = deepMerge(target[key], source[key])
19
+ } else if (source[key] !== undefined) {
20
+ result[key] = source[key]
21
+ }
22
+ }
23
+ return result
24
+ }
25
+
26
+ export async function parseOptions(cliOpts) {
27
+ // 1. 加载“底座”配置(此时已包含:出厂默认 + RC文件)
28
+ // 确保拿到 baseConfig
29
+ const baseConfig = await loadRcConfig()
30
+
31
+ const overtime = resolveBool(cliOpts.overtime, baseConfig?.overtime, false)
32
+
33
+ const country = resolveValue(
34
+ cliOpts.country,
35
+ baseConfig?.worktime?.country,
36
+ 'CN'
37
+ )
38
+
39
+ // 2. 将扁平的 CLI 参数映射为嵌套结构 (与 RC 结构对齐)
40
+ // 注意:只有当 CLI 确实传了值时,才映射到对象里,否则保持 undefined
41
+ const mappedCli = {
42
+ author: cliOpts.author,
43
+ email: cliOpts.email,
44
+ git: {
45
+ merges: cliOpts.merges,
46
+ limit: cliOpts.limit,
47
+ numstat: cliOpts.numstat
48
+ },
49
+ period: {
50
+ groupBy: cliOpts.groupBy,
51
+ since: cliOpts.since,
52
+ until: cliOpts.until
53
+ },
54
+ overtime,
55
+ gerrit: {
56
+ prefix: cliOpts.gerrit,
57
+ api: cliOpts.gerritApi,
58
+ auth: cliOpts.gerritAuth
59
+ },
60
+ worktime: {
61
+ country,
62
+ start: cliOpts.workStart,
63
+ end: cliOpts.workEnd,
64
+ overnightCutoff: cliOpts.overnightCutoff,
65
+ // 只有当传了任何一个午休参数时才生成 lunch 对象
66
+ lunch:
67
+ cliOpts.lunchStart || cliOpts.lunchEnd
68
+ ? {
69
+ start: cliOpts.lunchStart,
70
+ end: cliOpts.lunchEnd
71
+ }
72
+ : undefined
73
+ },
74
+ output: {
75
+ dir: cliOpts.outParent ? '../output-wukong' : cliOpts.outDir,
76
+ formats: cliOpts.format ? cliOpts.format : undefined,
77
+ perPeriod: {
78
+ // formats: cliOpts.perPeriodFormats?.split(','),
79
+ formats: String(cliOpts.perPeriodFormats || '')
80
+ .split(',')
81
+ .map((s) =>
82
+ String(s || '')
83
+ .trim()
84
+ .toLowerCase()
85
+ )
86
+ .filter(Boolean),
87
+ excelMode: cliOpts.perPeriodExcelMode,
88
+ only: cliOpts.perPeriodOnly
89
+ }
90
+ },
91
+ serve: {
92
+ port: cliOpts.port
93
+ },
94
+ profile: {
95
+ enabled: cliOpts.profile,
96
+ verbose: cliOpts.verbose ,
97
+ flame: cliOpts.flame || true,
98
+ traceFile: cliOpts.traceFile || 'trace.json',
99
+ hotThreshold: cliOpts.hotThreshold || 0.8,
100
+ diffThreshold: cliOpts.diffThreshold || 0.2,
101
+ failOnHot: cliOpts.failOnHot || false,
102
+ diffBaseFile: cliOpts.diffBaseFile || 'baseline.json'
103
+ /*
104
+ enabled: true,
105
+ flame: true,
106
+ traceFile: 'trace.json',
107
+ hotThreshold: 0.8,
108
+ failOnHot: true,
109
+ diffBaseFile: 'baseline.json',
110
+ diffThreshold: 0.2
111
+ */
112
+ }
113
+ }
114
+
115
+ // 3. 终极合并:用映射后的 CLI 配置 覆盖 底座配置
116
+ const finalConfig = deepMerge(baseConfig, mappedCli)
117
+
118
+ // 4. 最后做一点“路径标准化”或“格式转换”
119
+ // 例如:如果 format 选了 json,自动勾选 cliOpts.json
120
+ if (cliOpts.json) {
121
+ finalConfig.output.formats = Array.from(
122
+ new Set([...finalConfig.output.formats, 'json'])
123
+ )
124
+ }
125
+
126
+ return finalConfig
9
127
  }
@@ -1,3 +1,17 @@
1
- export const CLI_NAME = 'wukong-gitlog-cli'
1
+ // 输出目录
2
+ export const EXPORT_DIR = "export"
2
3
 
3
- export const GIT_HUB ='https://www.npmjs.com/package/wukong-gitlog-cli'
4
+ // 日报相关
5
+ export const DAY_REPORT = "dayReport"
6
+ export const DAY_REPORT_TXT = "dayReport/txt"
7
+ export const DAY_REPORT_EXCEL = "dayReport/excel"
8
+
9
+ // 按月导出相关
10
+ export const EXPORT_DIR_MONTH = "export/month"
11
+ export const EXPORT_DIR_WEEK = "export/week"
12
+
13
+ // profile
14
+ export const EXPORT_DIR_PROFILE = "export/profile"
15
+
16
+ // 配置文件名
17
+ export const WUKONG_GITLOG_RC = ".wukonggitlogrc"
@@ -0,0 +1,6 @@
1
+ import { buildAuthorChangeStats } from '#utils/buildAuthorChangeStats.mjs'
2
+
3
+ // 分析作者变更统计
4
+ export const getAuthorChangeStats = (records) => {
5
+ return buildAuthorChangeStats(records)
6
+ }
File without changes