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.
- package/.eslintrc +1 -0
- package/.prettierrc +2 -1
- package/CHANGELOG.md +97 -0
- package/README.md +97 -172
- package/README.zh-CN.md +88 -137
- package/bin/wukong-gitlog-cli +0 -0
- package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +2871 -0
- package/package.json +32 -29
- package/rc/.wukonggitlogrc +53 -0
- package/scripts/compareHourlyCounts.mjs +42 -0
- package/scripts/compareLatest.mjs +106 -0
- package/src/app/analyzeAction.mjs +120 -0
- package/src/app/exportAction.mjs +215 -0
- package/src/app/exportActionProgress.mjs +37 -0
- package/src/app/helpers.mjs +292 -0
- package/src/app/initAction.mjs +110 -0
- package/src/app/initActionWithTemp.mjs +192 -0
- package/src/app/journalAction.mjs +117 -0
- package/src/app/overtimeAction.mjs +100 -0
- package/src/app/runProfileEnd.mjs +0 -0
- package/src/app/serveAction.mjs +73 -0
- package/src/app/versionAction.mjs +7 -0
- package/src/cli/defineOptions.mjs +209 -0
- package/src/cli/index.mjs +0 -0
- package/src/cli/parseOptions.mjs +126 -8
- package/src/constants/index.mjs +16 -2
- package/src/domain/author/analyze.mjs +6 -0
- package/src/domain/author/map.mjs +0 -0
- package/src/domain/export/exportAuthor.mjs +28 -0
- package/src/domain/export/exportAuthorChanges.mjs +27 -0
- package/src/domain/export/exportAuthorChangesJson.mjs +31 -0
- package/src/domain/export/exportByMonth.mjs +157 -0
- package/src/domain/export/exportByWeek.mjs +121 -0
- package/src/domain/export/exportCommits.mjs +26 -0
- package/src/domain/export/exportCommitsExcel.mjs +45 -0
- package/src/domain/export/exportCommitsJson.mjs +31 -0
- package/src/domain/export/index.mjs +91 -0
- package/src/domain/git/ensureGitAvailable.mjs +66 -0
- package/src/domain/git/ensureGitRepo.mjs +41 -0
- package/src/domain/git/getGitFeatures.mjs +59 -0
- package/src/domain/git/getGitLogs.mjs +326 -0
- package/src/domain/git/getGitUser.mjs +44 -0
- package/src/domain/git/getRepoRoot.mjs +32 -0
- package/src/domain/git/gitCapability.mjs +119 -0
- package/src/domain/git/index.mjs +96 -0
- package/src/domain/git/resolveGerrit.mjs +102 -0
- package/src/domain/overtime/analyze.mjs +48 -0
- package/src/domain/overtime/index.mjs +3 -0
- package/src/domain/overtime/perPeriod.mjs +15 -0
- package/src/domain/overtime/render.mjs +15 -0
- package/src/i18n/index.mjs +38 -0
- package/src/i18n/resources.mjs +252 -0
- package/src/index.mjs +132 -649
- package/src/infra/cache.mjs +0 -0
- package/src/infra/configStore.mjs +128 -0
- package/src/infra/fs.mjs +0 -0
- package/src/infra/path.mjs +0 -0
- package/src/output/csv/overtime.mjs +12 -0
- package/src/output/csv.mjs +0 -0
- package/src/output/data/readData.mjs +54 -0
- package/src/output/data/writeData.mjs +145 -0
- package/src/output/excel/commits.mjs +9 -0
- package/src/output/excel/outputExcelDayReport.mjs +92 -0
- package/src/output/excel/perPeriod.mjs +24 -0
- package/src/{excel.mjs → output/excel.mjs} +3 -2
- package/src/output/index.mjs +79 -0
- package/src/output/json/overtime.mjs +9 -0
- package/src/output/tab/overtime.mjs +12 -0
- package/src/output/tab.mjs +0 -0
- package/src/output/text/commits.mjs +9 -0
- package/src/output/text/index.mjs +3 -0
- package/src/output/text/outputTxtDayReport.mjs +74 -0
- package/src/output/text/overtime.mjs +18 -0
- package/src/output/utils/getEsmJs.mjs +10 -0
- package/src/output/utils/index.mjs +14 -0
- package/src/output/utils/outputPath.mjs +19 -0
- package/src/output/utils/writeFile.mjs +10 -0
- package/src/serve/index.mjs +0 -0
- package/src/{server.mjs → serve/startServer.mjs} +21 -3
- package/src/serve/writeData.mjs +0 -0
- package/src/utils/authorNormalizer.mjs +28 -2
- package/src/utils/buildAuthorChangeStats.mjs +44 -0
- package/src/utils/deepMerge.mjs +13 -0
- package/src/utils/getPackage.mjs +11 -0
- package/src/utils/getProfileDirFile.mjs +12 -0
- package/src/utils/{file.mjs → groupRecords.mjs} +8 -9
- package/src/utils/index.mjs +5 -2
- package/src/utils/logger.mjs +28 -17
- package/src/utils/profiler.mjs +0 -101
- package/src/utils/resolve.mjs +11 -0
- package/src/utils/showVersionInfo.mjs +6 -2
- package/src/utils/time.mjs +0 -0
- package/src/utils/wait.mjs +2 -0
- package/web/app.js +3197 -257
- package/web/index.html +171 -22
- package/web/revoke/alpha1/app.js +4324 -0
- package/web/revoke/alpha1/index.html +266 -0
- package/web/revoke/app.before.js +3139 -0
- package/web/revoke/index-before.html +181 -0
- package/web/static/style.css +116 -9
- package/src/git.mjs +0 -256
- package/src/handlers/handleServe.mjs +0 -203
- package/src/lib/configStore.mjs +0 -11
- package/src/lib/memoize.mjs +0 -14
- package/src/utils/analyzeOvertimeCached.mjs +0 -7
- package/src/utils/checkUpdate.mjs +0 -130
- package/src/utils/exitWithTime.mjs +0 -17
- package/src/utils/handleSuccess.mjs +0 -9
- package/src/utils/logDev.mjs +0 -19
- package/src/utils/output.mjs +0 -26
- package/src/utils/profiler/diff.mjs +0 -26
- package/src/utils/profiler/format.mjs +0 -11
- package/src/utils/profiler/index.mjs +0 -144
- package/src/utils/profiler/trace.mjs +0 -26
- package/src/utils/time/scopeTimer.mjs +0 -37
- package/src/utils/time/timer.mjs +0 -33
- package/src/utils/time/withTimer.mjs +0 -11
- package/src/utils/timer.mjs +0 -35
- /package/src/{overtime → domain/overtime}/createOvertimeStats.mjs +0 -0
- /package/src/{overtime → domain/overtime}/overtime.mjs +0 -0
- /package/src/{json.mjs → output/json.mjs} +0 -0
- /package/src/{renderAuthorMapText.mjs → output/renderAuthorMapText.mjs} +0 -0
- /package/src/{stats-text.mjs → output/stats-text.mjs} +0 -0
- /package/src/{stats.mjs → output/stats.mjs} +0 -0
- /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,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
|
package/src/cli/parseOptions.mjs
CHANGED
|
@@ -1,9 +1,127 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
}
|
package/src/constants/index.mjs
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
// 输出目录
|
|
2
|
+
export const EXPORT_DIR = "export"
|
|
2
3
|
|
|
3
|
-
|
|
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"
|
|
File without changes
|