wukong-gitlog-cli 1.0.38 → 1.0.40
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 +103 -0
- package/README.md +93 -173
- package/README.zh-CN.md +85 -137
- package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +2871 -0
- package/package.json +33 -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 +3233 -260
- package/web/index.html +175 -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 +155 -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
package/src/index.mjs
CHANGED
|
@@ -1,658 +1,154 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander'
|
|
3
|
-
import dayjs from 'dayjs'
|
|
4
|
-
import isoWeek from 'dayjs/plugin/isoWeek.js'
|
|
5
|
-
import fs from 'fs'
|
|
6
|
-
import ora from 'ora'
|
|
7
|
-
import path from 'path'
|
|
8
|
-
import { fileURLToPath } from 'url'
|
|
9
|
-
import { createProfiler } from 'wukong-profiler'
|
|
10
3
|
|
|
11
|
-
import {
|
|
12
|
-
// eslint-disable-next-line no-unused-vars
|
|
13
|
-
import { CLI_NAME } from './constants/index.mjs'
|
|
14
|
-
import {
|
|
15
|
-
exportExcel,
|
|
16
|
-
exportExcelAuthorChangeStats,
|
|
17
|
-
exportExcelPerPeriodSheets
|
|
18
|
-
} from './excel.mjs'
|
|
19
|
-
import { getGitLogsFast } from './git.mjs'
|
|
20
|
-
import { handleServe } from './handlers/handleServe.mjs'
|
|
21
|
-
import { renderAuthorChangesJson } from './json.mjs'
|
|
22
|
-
import { setConfig } from './lib/configStore.mjs'
|
|
23
|
-
import { createOvertimeStats } from './overtime/createOvertimeStats.mjs'
|
|
24
|
-
import {
|
|
25
|
-
renderOvertimeCsv,
|
|
26
|
-
renderOvertimeTab,
|
|
27
|
-
renderOvertimeText
|
|
28
|
-
} from './overtime/overtime.mjs'
|
|
29
|
-
import { renderAuthorMapText } from './renderAuthorMapText.mjs'
|
|
30
|
-
import { startServer } from './server.mjs'
|
|
31
|
-
import { renderChangedLinesText, renderText } from './text.mjs'
|
|
32
|
-
import { checkUpdateWithPatch } from './utils/checkUpdate.mjs'
|
|
33
|
-
import { handleSuccess } from './utils/handleSuccess.mjs'
|
|
34
|
-
import {
|
|
35
|
-
groupRecords,
|
|
36
|
-
outputFilePath,
|
|
37
|
-
writeJSON,
|
|
38
|
-
writeTextFile
|
|
39
|
-
} from './utils/index.mjs'
|
|
40
|
-
import { logDev } from './utils/logDev.mjs'
|
|
41
|
-
import { showVersionInfo } from './utils/showVersionInfo.mjs'
|
|
42
|
-
|
|
43
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
44
|
-
|
|
45
|
-
const pkg = JSON.parse(
|
|
46
|
-
fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8')
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
dayjs.extend(isoWeek)
|
|
50
|
-
|
|
51
|
-
const PKG_NAME = pkg.name
|
|
52
|
-
const VERSION = pkg.version
|
|
53
|
-
|
|
54
|
-
let profiler
|
|
55
|
-
|
|
56
|
-
const autoCheckUpdate = async () => {
|
|
57
|
-
// === CLI 主逻辑完成后提示更新 ===
|
|
58
|
-
await checkUpdateWithPatch({
|
|
59
|
-
pkg: {
|
|
60
|
-
name: PKG_NAME,
|
|
61
|
-
version: VERSION
|
|
62
|
-
},
|
|
63
|
-
force: true
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const version = async () => {
|
|
68
|
-
showVersionInfo(VERSION)
|
|
69
|
-
await autoCheckUpdate()
|
|
70
|
-
process.exit(0)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** 将 "2025-W48" → { start: '2025-11-24', end: '2025-11-30' } */
|
|
74
|
-
export function getWeekRange(periodStr) {
|
|
75
|
-
// periodStr = "2025-W48"
|
|
76
|
-
const [year, w] = periodStr.split('-W')
|
|
77
|
-
const week = parseInt(w, 10)
|
|
4
|
+
import { runGitPreflight, showGitInfo } from '#src/domain/git/index.mjs'
|
|
78
5
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
6
|
+
import { analyzeAction } from './app/analyzeAction.mjs'
|
|
7
|
+
import { exportAction } from './app/exportAction.mjs'
|
|
8
|
+
// import { initAction } from './app/initAction.mjs'
|
|
9
|
+
import { initActionWithTemp as initAction } from './app/initActionWithTemp.mjs'
|
|
10
|
+
import { journalAction } from './app/journalAction.mjs'
|
|
11
|
+
import { overtimeAction } from './app/overtimeAction.mjs'
|
|
12
|
+
import { serveAction } from './app/serveAction.mjs'
|
|
13
|
+
import { versionAction } from './app/versionAction.mjs'
|
|
14
|
+
import {
|
|
15
|
+
addAnalysisOptions,
|
|
16
|
+
addGitSourceOptions,
|
|
17
|
+
addOutputOptions,
|
|
18
|
+
addPerformanceOptions,
|
|
19
|
+
addServeOptions,
|
|
20
|
+
setupBaseProgram
|
|
21
|
+
} from './cli/defineOptions.mjs'
|
|
22
|
+
import { initI18n, t } from './i18n/index.mjs'
|
|
23
|
+
import { loadRcConfig } from './infra/configStore.mjs'
|
|
24
|
+
|
|
25
|
+
// 引入加载器
|
|
87
26
|
|
|
88
27
|
const main = async () => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
'显示 Gerrit 地址,支持在 prefix 中使用 {{hash}} 占位符'
|
|
109
|
-
)
|
|
110
|
-
.option(
|
|
111
|
-
'--gerrit-api <url>',
|
|
112
|
-
'可选:Gerrit REST API 基础地址,用于解析 changeNumber,例如 `https://gerrit.example.com`'
|
|
113
|
-
)
|
|
114
|
-
.option(
|
|
115
|
-
'--gerrit-auth <tokenOrUserPass>',
|
|
116
|
-
'可选:Gerrit API 授权,格式为 `user:pass` 或 `TOKEN`(表示 Bearer token)'
|
|
117
|
-
)
|
|
118
|
-
.option('--overtime', '分析公司加班文化(输出下班时间与非工作日提交占比)')
|
|
119
|
-
.option('--country <code>', '节假日国家:CN 或 US,默认为 CN', 'CN')
|
|
120
|
-
.option(
|
|
121
|
-
'--work-start <hour>',
|
|
122
|
-
'上班开始小时,默认 9',
|
|
123
|
-
(v) => parseInt(v, 10),
|
|
124
|
-
9
|
|
125
|
-
)
|
|
126
|
-
.option(
|
|
127
|
-
'--work-end <hour>',
|
|
128
|
-
'下班小时,默认 18',
|
|
129
|
-
(v) => parseInt(v, 10),
|
|
130
|
-
18
|
|
131
|
-
)
|
|
132
|
-
.option(
|
|
133
|
-
'--lunch-start <hour>',
|
|
134
|
-
'午休开始小时,默认 12',
|
|
135
|
-
(v) => parseInt(v, 10),
|
|
136
|
-
12
|
|
137
|
-
)
|
|
138
|
-
.option(
|
|
139
|
-
'--lunch-end <hour>',
|
|
140
|
-
'午休结束小时,默认 14',
|
|
141
|
-
(v) => parseInt(v, 10),
|
|
142
|
-
14
|
|
143
|
-
)
|
|
144
|
-
.option(
|
|
145
|
-
'--overnight-cutoff <hour>',
|
|
146
|
-
'次日凌晨归并窗口(小时),默认 6',
|
|
147
|
-
(v) => parseInt(v, 10),
|
|
148
|
-
6
|
|
149
|
-
)
|
|
150
|
-
.option('--out <file>', '输出文件名(不含路径)')
|
|
151
|
-
.option(
|
|
152
|
-
'--out-dir <dir>',
|
|
153
|
-
'自定义输出目录,支持相对路径或绝对路径,例如 `--out-dir ../output-wukong`'
|
|
154
|
-
)
|
|
155
|
-
.option(
|
|
156
|
-
'--out-parent',
|
|
157
|
-
'将输出目录放到当前工程的父目录的 `output-wukong/`(等同于 `--out-dir ../output-wukong`)'
|
|
158
|
-
)
|
|
159
|
-
.option(
|
|
160
|
-
'--per-period-formats <formats>',
|
|
161
|
-
'每个周期单独输出的格式,逗号分隔:text,csv,tab,xlsx。默认为空(不输出 CSV/Tab/XLSX)',
|
|
162
|
-
''
|
|
163
|
-
)
|
|
164
|
-
.option(
|
|
165
|
-
'--per-period-excel-mode <mode>',
|
|
166
|
-
'per-period Excel 模式:sheets|files(默认:sheets)',
|
|
167
|
-
'sheets'
|
|
168
|
-
)
|
|
169
|
-
.option(
|
|
170
|
-
'--per-period-only',
|
|
171
|
-
'仅输出 per-period(month/week)文件,不输出合并的 monthly/weekly 汇总文件'
|
|
172
|
-
)
|
|
173
|
-
.option(
|
|
174
|
-
'--serve',
|
|
175
|
-
'启动本地 web 服务,查看提交统计(将在 output-wukong/data 下生成数据文件)'
|
|
176
|
-
)
|
|
177
|
-
.option(
|
|
178
|
-
'--port <n>',
|
|
179
|
-
'本地 web 服务端口(默认 3000)',
|
|
180
|
-
(v) => parseInt(v, 10),
|
|
181
|
-
3000
|
|
182
|
-
)
|
|
183
|
-
.option('--debug', 'enable debug logs')
|
|
184
|
-
.option(
|
|
185
|
-
'--serve-only',
|
|
186
|
-
'仅启动 web 服务,不导出或分析数据(使用 output-wukong/data 中已有的数据)'
|
|
187
|
-
)
|
|
188
|
-
.option('--version', 'show version information')
|
|
189
|
-
.option('--profile', '输出性能分析 JSON')
|
|
190
|
-
.option('--verbose', '显示详细性能日志')
|
|
191
|
-
.option('--flame', '显示 flame-like 日志')
|
|
192
|
-
.option('--trace <file>', '生成 Chrome Trace')
|
|
193
|
-
.option('--hot-threshold <n>', 'HOT 比例阈值', parseFloat, 0.8)
|
|
194
|
-
.option('--fail-on-hot', 'HOT 时 CI 失败')
|
|
195
|
-
.option('--diff-base <file>', '基线 profile.json')
|
|
196
|
-
.option('--diff-threshold <n>', '回归阈值', parseFloat, 0.2)
|
|
197
|
-
.parse()
|
|
198
|
-
|
|
199
|
-
const opts = program.opts()
|
|
200
|
-
|
|
201
|
-
const config = parseOptions(opts)
|
|
202
|
-
|
|
203
|
-
profiler = createProfiler({
|
|
204
|
-
enabled: opts.profile,
|
|
205
|
-
verbose: opts.verbose,
|
|
206
|
-
flame: opts.flame,
|
|
207
|
-
traceFile: opts.trace,
|
|
208
|
-
hotThreshold: opts.hotThreshold,
|
|
209
|
-
failOnHot: opts.failOnHot,
|
|
210
|
-
diffBaseFile: opts.diffBase,
|
|
211
|
-
diffThreshold: opts.diffThreshold
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
// ❗只创建一次缓存实例
|
|
215
|
-
const getOvertimeStats = createOvertimeStats(config)
|
|
216
|
-
|
|
217
|
-
setConfig('debug', opts.debug === true)
|
|
218
|
-
|
|
219
|
-
// compute output directory root early (so serve-only can use it)
|
|
220
|
-
const outDir = opts.outParent
|
|
221
|
-
? path.resolve(process.cwd(), '..', 'output-wukong')
|
|
222
|
-
: opts.outDir || undefined
|
|
223
|
-
|
|
224
|
-
if (opts.version) {
|
|
225
|
-
await version()
|
|
226
|
-
return
|
|
28
|
+
// --- 第一步:参数预检 (Pre-flight) ---
|
|
29
|
+
const args = process.argv.slice(2)
|
|
30
|
+
|
|
31
|
+
// 1. 快速提取 --lang 参数(不依赖 Commander,避免混乱)
|
|
32
|
+
const langArgIndex = args.findIndex((a) => a === '--lang' || a === '-l')
|
|
33
|
+
const userLang = langArgIndex !== -1 ? args[langArgIndex + 1] : null
|
|
34
|
+
|
|
35
|
+
// 初始化字典
|
|
36
|
+
// 2. 初始化 i18n(如果有 --lang 用 --lang,没有则内部调 osLocale)
|
|
37
|
+
// 这一步必须在定义子命令描述之前完成!
|
|
38
|
+
const finalLang = await initI18n(userLang)
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------
|
|
41
|
+
// 2. 环境准备
|
|
42
|
+
// ---------------------------------------------------------
|
|
43
|
+
const gitPreflightResult = await runGitPreflight()
|
|
44
|
+
if (args.includes('--info') || args.includes('-i')) {
|
|
45
|
+
console.log(`✔ Language: ${finalLang}`)
|
|
46
|
+
await showGitInfo(gitPreflightResult)
|
|
227
47
|
}
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
process.exit(1)
|
|
238
|
-
}
|
|
239
|
-
return
|
|
48
|
+
// 【关键优化】在一切开始前,先异步加载 RC 配置
|
|
49
|
+
// 这样后续 parseOptions 内部的 cachedConfig 就有值了
|
|
50
|
+
await loadRcConfig()
|
|
51
|
+
|
|
52
|
+
// 拦截 --version 自定义逻辑
|
|
53
|
+
if (args.includes('--version')) {
|
|
54
|
+
await versionAction()
|
|
55
|
+
process.exit(0) // 彻底拦截,不走后面的子命令逻辑
|
|
240
56
|
}
|
|
241
57
|
|
|
242
|
-
|
|
58
|
+
// ---------------------------------------------------------
|
|
59
|
+
// 3. 定义 CLI 结构
|
|
60
|
+
// ---------------------------------------------------------
|
|
61
|
+
const program = new Command()
|
|
243
62
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const { authorMap } = gitCommits
|
|
247
|
-
// compute output directory root if user provided one or wants parent
|
|
63
|
+
// A. 挂载基础信息 (name, version, --lang, --debug)
|
|
64
|
+
setupBaseProgram(program)
|
|
248
65
|
|
|
249
|
-
//
|
|
250
|
-
if (opts.gerrit) {
|
|
251
|
-
const prefix = opts.gerrit
|
|
252
|
-
// support optional changeNumber resolution via Gerrit REST API
|
|
253
|
-
const { gerritApi, gerritAuth } = opts
|
|
254
|
-
// create new array to avoid mutating function parameters (eslint: no-param-reassign)
|
|
255
|
-
if (prefix.includes('{{changeNumber}}') && gerritApi) {
|
|
256
|
-
// async mapping to resolve changeNumber using Gerrit API
|
|
257
|
-
const cache = new Map()
|
|
258
|
-
const headers = {}
|
|
259
|
-
if (gerritAuth) {
|
|
260
|
-
if (gerritAuth.includes(':')) {
|
|
261
|
-
headers.Authorization = `Basic ${Buffer.from(gerritAuth).toString('base64')}`
|
|
262
|
-
} else {
|
|
263
|
-
headers.Authorization = `Bearer ${gerritAuth}`
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const fetchGerritJson = async (url) => {
|
|
267
|
-
try {
|
|
268
|
-
const res = await fetch(url, { headers })
|
|
269
|
-
const txt = await res.text()
|
|
270
|
-
// Gerrit prepends )]}' to JSON responses — strip it
|
|
271
|
-
const jsonText = txt.replace(/^\)\]\}'\n/, '')
|
|
272
|
-
return JSON.parse(jsonText)
|
|
273
|
-
} catch (err) {
|
|
274
|
-
return null
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
const resolveChangeNumber = async (r) => {
|
|
278
|
-
// try changeId first
|
|
279
|
-
if (r.changeId) {
|
|
280
|
-
if (cache.has(r.changeId)) return cache.get(r.changeId)
|
|
281
|
-
// try `changes/{changeId}/detail`
|
|
282
|
-
const url = `${gerritApi.replace(/\/$/, '')}/changes/${encodeURIComponent(r.changeId)}/detail`
|
|
283
|
-
let j = await fetchGerritJson(url)
|
|
284
|
-
if (j && j._number) {
|
|
285
|
-
cache.set(r.changeId, j._number)
|
|
286
|
-
return j._number
|
|
287
|
-
}
|
|
288
|
-
// fallback: query search
|
|
289
|
-
const url2 = `${gerritApi.replace(/\/$/, '')}/changes/?q=change:${encodeURIComponent(r.changeId)}`
|
|
290
|
-
j = await fetchGerritJson(url2)
|
|
291
|
-
if (Array.isArray(j) && j.length > 0 && j[0]._number) {
|
|
292
|
-
cache.set(r.changeId, j[0]._number)
|
|
293
|
-
return j[0]._number
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// try commit hash
|
|
297
|
-
if (r.hash) {
|
|
298
|
-
if (cache.has(r.hash)) return cache.get(r.hash)
|
|
299
|
-
const url3 = `${gerritApi.replace(/\/$/, '')}/changes/?q=commit:${encodeURIComponent(r.hash)}`
|
|
300
|
-
const j = await fetchGerritJson(url3)
|
|
301
|
-
if (Array.isArray(j) && j.length > 0 && j[0]._number) {
|
|
302
|
-
cache.set(r.hash, j[0]._number)
|
|
303
|
-
return j[0]._number
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return null
|
|
307
|
-
}
|
|
308
|
-
records = await Promise.all(
|
|
309
|
-
records.map(async (r) => {
|
|
310
|
-
const changeNumber = await resolveChangeNumber(r)
|
|
311
|
-
const changeNumberOrFallback = changeNumber || r.changeId || r.hash
|
|
312
|
-
const gerritUrl = prefix.replace(
|
|
313
|
-
'{{changeNumber}}',
|
|
314
|
-
changeNumberOrFallback
|
|
315
|
-
)
|
|
316
|
-
return { ...r, gerrit: gerritUrl }
|
|
317
|
-
})
|
|
318
|
-
)
|
|
319
|
-
} else if (prefix.includes('{{changeNumber}}') && !gerritApi) {
|
|
320
|
-
console.warn(
|
|
321
|
-
'prefix contains {{changeNumber}} but no --gerrit-api provided — falling back to changeId/hash'
|
|
322
|
-
)
|
|
323
|
-
records = records.map((r) => ({
|
|
324
|
-
...r,
|
|
325
|
-
gerrit: prefix.replace('{{changeNumber}}', r.changeId || r.hash)
|
|
326
|
-
}))
|
|
327
|
-
} else {
|
|
328
|
-
records = records.map((r) => {
|
|
329
|
-
let gerritUrl
|
|
330
|
-
if (prefix.includes('{{changeId}}')) {
|
|
331
|
-
const changeId = r.changeId || r.hash
|
|
332
|
-
gerritUrl = prefix.replace('{{changeId}}', changeId)
|
|
333
|
-
} else if (prefix.includes('{{hash}}')) {
|
|
334
|
-
gerritUrl = prefix.replace('{{hash}}', r.hash)
|
|
335
|
-
} else {
|
|
336
|
-
gerritUrl = prefix.endsWith('/')
|
|
337
|
-
? `${prefix}${r.hash}`
|
|
338
|
-
: `${prefix}/${r.hash}`
|
|
339
|
-
}
|
|
340
|
-
return { ...r, gerrit: gerritUrl }
|
|
341
|
-
})
|
|
342
|
-
}
|
|
343
|
-
}
|
|
66
|
+
// B. 注册子命令:按需组合参数模块
|
|
344
67
|
|
|
345
|
-
//
|
|
346
|
-
const groups = opts.groupBy ? groupRecords(records, opts.groupBy) : null
|
|
347
|
-
profiler.step('load config')
|
|
68
|
+
// === 命令: Init ===
|
|
348
69
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
await getOvertimeStats(records)
|
|
70
|
+
program
|
|
71
|
+
.command('init')
|
|
72
|
+
.description(t('cmds.init'))
|
|
73
|
+
.option('-f, --force', t('options.force'))
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
// 如果你不想抽离到 app 层,也可以直接写这里
|
|
76
|
+
// 但推荐抽离以保持架构一致性
|
|
77
|
+
await initAction(options)
|
|
358
78
|
})
|
|
359
|
-
const stats = getOvertimeStats(records)
|
|
360
|
-
|
|
361
|
-
profiler.step('load getOvertimeStats')
|
|
362
|
-
// Output to console
|
|
363
|
-
console.log('\n--- Overtime analysis ---\n')
|
|
364
|
-
console.log(renderOvertimeText(stats))
|
|
365
|
-
|
|
366
|
-
const authorMapText = renderAuthorMapText(authorMap)
|
|
367
|
-
console.log('\n Developers:\n', authorMapText, '\n')
|
|
368
|
-
writeTextFile(outputFilePath('authors.text', outDir), authorMapText)
|
|
369
|
-
|
|
370
|
-
// if user requested json format, write stats to file
|
|
371
|
-
if (opts.json || opts.format === 'json') {
|
|
372
|
-
const file = opts.out || 'overtime.json'
|
|
373
|
-
const filepath = outputFilePath(file, outDir)
|
|
374
|
-
writeJSON(filepath, stats)
|
|
375
|
-
logDev(`overtime JSON 已导出: ${filepath}`)
|
|
376
|
-
}
|
|
377
|
-
// Always write human readable overtime text to file (default: overtime.txt)
|
|
378
|
-
const outBase = opts.out
|
|
379
|
-
? path.basename(opts.out, path.extname(opts.out))
|
|
380
|
-
: 'commits'
|
|
381
|
-
const overtimeFileName = `overtime_${outBase}.txt`
|
|
382
|
-
const overtimeFile = outputFilePath(overtimeFileName, outDir)
|
|
383
|
-
writeTextFile(overtimeFile, renderOvertimeText(stats))
|
|
384
|
-
// write tab-separated text file for better alignment in editors that use proportional fonts
|
|
385
|
-
const overtimeTabFileName = `overtime_${outBase}.tab.txt`
|
|
386
|
-
const overtimeTabFile = outputFilePath(overtimeTabFileName, outDir)
|
|
387
|
-
writeTextFile(overtimeTabFile, renderOvertimeTab(stats))
|
|
388
|
-
// write CSV for structured data consumption
|
|
389
|
-
const overtimeCsvFileName = `overtime_${outBase}.csv`
|
|
390
|
-
const overtimeCsvFile = outputFilePath(overtimeCsvFileName, outDir)
|
|
391
|
-
writeTextFile(overtimeCsvFile, renderOvertimeCsv(stats))
|
|
392
|
-
logDev(`Overtime text 已导出: ${overtimeFile}`)
|
|
393
|
-
logDev(`Overtime table (tabs) 已导出: ${overtimeTabFile}`)
|
|
394
|
-
logDev(`Overtime CSV 已导出: ${overtimeCsvFile}`)
|
|
395
|
-
|
|
396
|
-
// 按月输出 ... 保持原逻辑
|
|
397
|
-
const perPeriodFormats = String(opts.perPeriodFormats || '')
|
|
398
|
-
.split(',')
|
|
399
|
-
.map((s) =>
|
|
400
|
-
String(s || '')
|
|
401
|
-
.trim()
|
|
402
|
-
.toLowerCase()
|
|
403
|
-
)
|
|
404
|
-
.filter(Boolean)
|
|
405
|
-
try {
|
|
406
|
-
const monthGroups = groupRecords(records, 'month')
|
|
407
|
-
const monthlyFileName = `overtime_${outBase}_monthly.txt`
|
|
408
|
-
const monthlyFile = outputFilePath(monthlyFileName, outDir)
|
|
409
|
-
let monthlyContent = ''
|
|
410
|
-
const monthKeys = Object.keys(monthGroups).sort()
|
|
411
|
-
monthKeys.forEach((k) => {
|
|
412
|
-
const groupRecs = monthGroups[k]
|
|
413
|
-
const s = getOvertimeStats(groupRecs)
|
|
414
|
-
monthlyContent += `===== ${k} =====\n`
|
|
415
|
-
monthlyContent += `${renderOvertimeText(s)}\n\n`
|
|
416
|
-
// Also write a single file per month under 'month/' folder
|
|
417
|
-
try {
|
|
418
|
-
const perMonthFileName = `month/overtime_${outBase}_${k}.txt`
|
|
419
|
-
const perMonthFile = outputFilePath(perMonthFileName, outDir)
|
|
420
|
-
writeTextFile(perMonthFile, renderOvertimeText(s))
|
|
421
|
-
logDev(`Overtime 月度(${k}) 已导出: ${perMonthFile}`)
|
|
422
|
-
// per-period CSV / Tab format (按需生成)
|
|
423
|
-
if (perPeriodFormats.includes('csv')) {
|
|
424
|
-
try {
|
|
425
|
-
const perMonthCsvName = `month/overtime_${outBase}_${k}.csv`
|
|
426
|
-
writeTextFile(
|
|
427
|
-
outputFilePath(perMonthCsvName, outDir),
|
|
428
|
-
renderOvertimeCsv(s)
|
|
429
|
-
)
|
|
430
|
-
logDev(
|
|
431
|
-
`Overtime 月度(CSV)(${k}) 已导出: ${outputFilePath(perMonthCsvName, outDir)}`
|
|
432
|
-
)
|
|
433
|
-
} catch (err) {
|
|
434
|
-
console.warn(
|
|
435
|
-
`Write monthly CSV for ${k} failed:`,
|
|
436
|
-
err && err.message ? err.message : err
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (perPeriodFormats.includes('tab')) {
|
|
441
|
-
try {
|
|
442
|
-
const perMonthTabName = `month/overtime_${outBase}_${k}.tab.txt`
|
|
443
|
-
writeTextFile(
|
|
444
|
-
outputFilePath(perMonthTabName, outDir),
|
|
445
|
-
renderOvertimeTab(s)
|
|
446
|
-
)
|
|
447
|
-
logDev(
|
|
448
|
-
`Overtime 月度(Tab)(${k}) 已导出: ${outputFilePath(perMonthTabName, outDir)}`
|
|
449
|
-
)
|
|
450
|
-
} catch (err) {
|
|
451
|
-
console.warn(
|
|
452
|
-
`Write monthly Tab for ${k} failed:`,
|
|
453
|
-
err && err.message ? err.message : err
|
|
454
|
-
)
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
} catch (err) {
|
|
458
|
-
console.warn(
|
|
459
|
-
`Write monthly file for ${k} failed:`,
|
|
460
|
-
err && err.message ? err.message : err
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
})
|
|
464
|
-
if (!opts.perPeriodOnly) {
|
|
465
|
-
writeTextFile(monthlyFile, monthlyContent)
|
|
466
|
-
logDev(`Overtime 月度汇总 已导出: ${monthlyFile}`)
|
|
467
|
-
}
|
|
468
|
-
// per-period Excel (sheets or files)
|
|
469
|
-
if (perPeriodFormats.includes('xlsx')) {
|
|
470
|
-
const perPeriodExcelMode = String(opts.perPeriodExcelMode || 'sheets')
|
|
471
|
-
if (perPeriodExcelMode === 'sheets') {
|
|
472
|
-
try {
|
|
473
|
-
const monthXlsxName = `month/overtime_${outBase}_monthly.xlsx`
|
|
474
|
-
const monthXlsxFile = outputFilePath(monthXlsxName, outDir)
|
|
475
|
-
await exportExcelPerPeriodSheets(monthGroups, monthXlsxFile, {
|
|
476
|
-
stats: opts.stats,
|
|
477
|
-
gerrit: opts.gerrit
|
|
478
|
-
})
|
|
479
|
-
logDev(`Overtime 月度(XLSX) 已导出: ${monthXlsxFile}`)
|
|
480
|
-
} catch (err) {
|
|
481
|
-
console.warn(
|
|
482
|
-
'Export month XLSX (sheets) failed:',
|
|
483
|
-
err && err.message ? err.message : err
|
|
484
|
-
)
|
|
485
|
-
}
|
|
486
|
-
} else {
|
|
487
|
-
try {
|
|
488
|
-
const monthKeys2 = Object.keys(monthGroups).sort()
|
|
489
|
-
const tasks = monthKeys2.map((k2) => {
|
|
490
|
-
const perMonthXlsxName = `month/overtime_${outBase}_${k2}.xlsx`
|
|
491
|
-
const perMonthXlsxFile = outputFilePath(perMonthXlsxName, outDir)
|
|
492
|
-
return exportExcel(monthGroups[k2], null, {
|
|
493
|
-
file: perMonthXlsxFile,
|
|
494
|
-
stats: opts.stats,
|
|
495
|
-
gerrit: opts.gerrit
|
|
496
|
-
}).then(() =>
|
|
497
|
-
console.log(
|
|
498
|
-
chalk.green(
|
|
499
|
-
`Overtime 月度(XLSX)(${k2}) 已导出: ${perMonthXlsxFile}`
|
|
500
|
-
)
|
|
501
|
-
)
|
|
502
|
-
)
|
|
503
|
-
})
|
|
504
|
-
await Promise.all(tasks)
|
|
505
|
-
} catch (err) {
|
|
506
|
-
console.warn(
|
|
507
|
-
'Export monthly XLSX files failed:',
|
|
508
|
-
err && err.message ? err.message : err
|
|
509
|
-
)
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
} catch (err) {
|
|
514
|
-
console.warn(
|
|
515
|
-
'Generate monthly overtime failed:',
|
|
516
|
-
err && err.message ? err.message : err
|
|
517
|
-
)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// 周度输出保持原逻辑(略)
|
|
521
|
-
try {
|
|
522
|
-
const weekGroups = groupRecords(records, 'week')
|
|
523
|
-
const weeklyFileName = `overtime_${outBase}_weekly.txt`
|
|
524
|
-
const weeklyFile = outputFilePath(weeklyFileName, outDir)
|
|
525
|
-
let weeklyContent = ''
|
|
526
|
-
const weekKeys = Object.keys(weekGroups).sort()
|
|
527
|
-
weekKeys.forEach((k) => {
|
|
528
|
-
const groupRecs = weekGroups[k]
|
|
529
|
-
const s = getOvertimeStats(groupRecs)
|
|
530
|
-
weeklyContent += `===== ${k} =====\n`
|
|
531
|
-
weeklyContent += `${renderOvertimeText(s)}\n\n`
|
|
532
|
-
try {
|
|
533
|
-
const perWeekFileName = `week/overtime_${outBase}_${k}.txt`
|
|
534
|
-
const perWeekFile = outputFilePath(perWeekFileName, outDir)
|
|
535
|
-
writeTextFile(perWeekFile, renderOvertimeText(s))
|
|
536
|
-
// console.log(chalk.green(`Overtime 周度(${k}) 已导出: ${perWeekFile}`))
|
|
537
|
-
logDev(`Overtime 周度(${k}) 已导出: ${perWeekFile}`)
|
|
538
|
-
|
|
539
|
-
// eslint-disable-next-line no-shadow
|
|
540
|
-
const perPeriodFormats = String(opts.perPeriodFormats || '')
|
|
541
|
-
.split(',')
|
|
542
|
-
// eslint-disable-next-line no-shadow
|
|
543
|
-
.map((s) =>
|
|
544
|
-
String(s || '')
|
|
545
|
-
.trim()
|
|
546
|
-
.toLowerCase()
|
|
547
|
-
)
|
|
548
|
-
.filter(Boolean)
|
|
549
|
-
if (perPeriodFormats.includes('csv')) {
|
|
550
|
-
try {
|
|
551
|
-
const perWeekCsvName = `week/overtime_${outBase}_${k}.csv`
|
|
552
|
-
writeTextFile(
|
|
553
|
-
outputFilePath(perWeekCsvName, outDir),
|
|
554
|
-
renderOvertimeCsv(s)
|
|
555
|
-
)
|
|
556
|
-
logDev(
|
|
557
|
-
`Overtime 周度(CSV)(${k}) 已导出: ${outputFilePath(perWeekCsvName, outDir)}`
|
|
558
|
-
)
|
|
559
|
-
} catch (err) {
|
|
560
|
-
console.warn(
|
|
561
|
-
`Write weekly CSV for ${k} failed:`,
|
|
562
|
-
err && err.message ? err.message : err
|
|
563
|
-
)
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (perPeriodFormats.includes('tab')) {
|
|
567
|
-
try {
|
|
568
|
-
const perWeekTabName = `week/overtime_${outBase}_${k}.tab.txt`
|
|
569
|
-
writeTextFile(
|
|
570
|
-
outputFilePath(perWeekTabName, outDir),
|
|
571
|
-
renderOvertimeTab(s)
|
|
572
|
-
)
|
|
573
|
-
logDev(
|
|
574
|
-
`Overtime 周度(Tab)(${k}) 已导出: ${outputFilePath(perWeekTabName, outDir)}`
|
|
575
|
-
)
|
|
576
|
-
} catch (err) {
|
|
577
|
-
console.warn(
|
|
578
|
-
`Write weekly Tab for ${k} failed:`,
|
|
579
|
-
err && err.message ? err.message : err
|
|
580
|
-
)
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
} catch (err) {
|
|
584
|
-
console.warn(
|
|
585
|
-
`Write weekly file for ${k} failed:`,
|
|
586
|
-
err && err.message ? err.message : err
|
|
587
|
-
)
|
|
588
|
-
}
|
|
589
|
-
})
|
|
590
|
-
writeTextFile(weeklyFile, weeklyContent)
|
|
591
|
-
logDev(`Overtime 周度汇总 已导出: ${weeklyFile}`)
|
|
592
|
-
} catch (err) {
|
|
593
|
-
console.warn(
|
|
594
|
-
'Generate weekly overtime failed:',
|
|
595
|
-
err && err.message ? err.message : err
|
|
596
|
-
)
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
79
|
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (opts.format === 'text') {
|
|
613
|
-
const file = opts.out || 'commits.txt'
|
|
614
|
-
const filepath = outputFilePath(file, outDir)
|
|
615
|
-
const text = renderText(records, groups, { showGerrit: !!opts.gerrit })
|
|
616
|
-
writeTextFile(filepath, text)
|
|
617
|
-
writeTextFile(
|
|
618
|
-
outputFilePath('author-changes.txt', outDir),
|
|
619
|
-
renderChangedLinesText(records)
|
|
620
|
-
)
|
|
80
|
+
// # 命令: Analyze 核心分析(默认) 分析 git 提交记录 (最全参数)
|
|
81
|
+
const analyzeCmd = program
|
|
82
|
+
.command('analyze')
|
|
83
|
+
.description(t('cmds.analyze'))
|
|
84
|
+
.action((cmdOpts) => {
|
|
85
|
+
const globalOpts = program.opts()
|
|
86
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
87
|
+
analyzeAction(finalOpts)
|
|
88
|
+
})
|
|
621
89
|
|
|
622
|
-
|
|
90
|
+
// 挂载 analyze 需要的参数组
|
|
91
|
+
addGitSourceOptions(analyzeCmd)
|
|
92
|
+
addAnalysisOptions(analyzeCmd)
|
|
93
|
+
addOutputOptions(analyzeCmd)
|
|
94
|
+
addPerformanceOptions(analyzeCmd)
|
|
95
|
+
|
|
96
|
+
// # 命令: Overtime 加班文化分析
|
|
97
|
+
const overtimeCmd = program
|
|
98
|
+
.command('overtime')
|
|
99
|
+
.description(t('cmds.overtime'))
|
|
100
|
+
.action((cmdOpts) => {
|
|
101
|
+
const globalOpts = program.opts()
|
|
102
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
103
|
+
overtimeAction(finalOpts)
|
|
104
|
+
})
|
|
105
|
+
addGitSourceOptions(overtimeCmd)
|
|
106
|
+
addAnalysisOptions(overtimeCmd) // 加班分析肯定需要上班时间配置
|
|
107
|
+
|
|
108
|
+
// # 命令: Export (专注导出) 导出(excel / csv / json)
|
|
109
|
+
const exportCmd = program
|
|
110
|
+
.command('export')
|
|
111
|
+
.description(t('cmds.export'))
|
|
112
|
+
// .option('-f, --format <type>', '导出格式') // 局部参数
|
|
113
|
+
.action((cmdOpts, command) => {
|
|
114
|
+
// globalOpts 拿到 author, since 等
|
|
115
|
+
const globalOpts = command.parent.opts()
|
|
116
|
+
// 合并全局和局部参数
|
|
117
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
118
|
+
exportAction(finalOpts)
|
|
119
|
+
})
|
|
120
|
+
addGitSourceOptions(exportCmd)
|
|
121
|
+
addOutputOptions(exportCmd)
|
|
122
|
+
|
|
123
|
+
// === 命令: Journal (日报) ===
|
|
124
|
+
const journalCmd = program
|
|
125
|
+
.command('journal')
|
|
126
|
+
.description(t('cmds.journal'))
|
|
127
|
+
.action((cmdOpts) => {
|
|
128
|
+
const globalOpts = program.opts()
|
|
129
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
130
|
+
journalAction(finalOpts)
|
|
131
|
+
})
|
|
623
132
|
|
|
624
|
-
|
|
133
|
+
addGitSourceOptions(journalCmd)
|
|
625
134
|
|
|
626
|
-
|
|
135
|
+
addPerformanceOptions(journalCmd)
|
|
627
136
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
await exportExcel(records, groups, {
|
|
637
|
-
file: excelPath,
|
|
638
|
-
stats: opts.stats,
|
|
639
|
-
gerrit: opts.gerrit
|
|
137
|
+
// === 命令: Serve (Web服务) ===
|
|
138
|
+
const serveCmd = program
|
|
139
|
+
.command('serve')
|
|
140
|
+
.description('Start web server')
|
|
141
|
+
.action((cmdOpts) => {
|
|
142
|
+
const globalOpts = program.opts()
|
|
143
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
144
|
+
serveAction(finalOpts)
|
|
640
145
|
})
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
const text = renderText(records, groups)
|
|
646
|
-
writeTextFile(txtPath, text)
|
|
647
|
-
logDev(`Excel 已导出: ${excelPath}`)
|
|
648
|
-
logDev(`文本已自动导出: ${txtPath}`)
|
|
649
|
-
|
|
650
|
-
handleSuccess({ spinner })
|
|
651
|
-
}
|
|
146
|
+
// Serve 命令只需要端口,不需要 Git 作者之类的参数
|
|
147
|
+
addServeOptions(serveCmd)
|
|
148
|
+
program.parse(process.argv)
|
|
652
149
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
handleSuccess({ spinner })
|
|
150
|
+
// const opts = program.opts()
|
|
151
|
+
// console.log('✅ Cli Opts:', opts)
|
|
656
152
|
}
|
|
657
153
|
|
|
658
154
|
try {
|
|
@@ -661,18 +157,5 @@ try {
|
|
|
661
157
|
console.error(err)
|
|
662
158
|
process.exitCode = 1
|
|
663
159
|
} finally {
|
|
664
|
-
|
|
665
|
-
const result = profiler.end('git-commits')
|
|
666
|
-
|
|
667
|
-
// --profile 时输出 JSON
|
|
668
|
-
if (process.argv.includes('--profile')) {
|
|
669
|
-
const json = {
|
|
670
|
-
command: 'git-commits',
|
|
671
|
-
version: VERSION,
|
|
672
|
-
timestamp: Date.now(),
|
|
673
|
-
profile: result
|
|
674
|
-
}
|
|
675
|
-
console.log(JSON.stringify(json, null, 2))
|
|
676
|
-
}
|
|
677
|
-
}
|
|
160
|
+
/* empty */
|
|
678
161
|
}
|