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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wukong-gitlog-cli",
|
|
3
|
-
"version": "1.0.
|
|
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/
|
|
44
|
-
"cli:excel-demo-parent": "node ./src/
|
|
45
|
-
"cli:gerrit-changeid-demo": "node ./src/
|
|
46
|
-
"cli:gerrit-demo": "node ./src/
|
|
47
|
-
"cli:help": "node ./src/
|
|
48
|
-
"cli:json-demo": "node ./src/
|
|
49
|
-
"cli:overtime-demo": "node ./src/
|
|
50
|
-
"cli:overtime-excel": "node ./src/
|
|
51
|
-
"cli:overtime-excel-cn": "node ./src/
|
|
52
|
-
"cli:overtime-excel-cn-outdir": "node ./src/
|
|
53
|
-
"cli:overtime-excel-cn-parent": "node ./src/
|
|
54
|
-
"cli:overtime-per-period-csv-tab": "node ./src/
|
|
55
|
-
"cli:overtime-per-period-only": "node ./src/
|
|
56
|
-
"cli:overtime-per-period-xlsx-files": "node ./src/
|
|
57
|
-
"cli:overtime-per-period-xlsx-sheets": "node ./src/
|
|
58
|
-
"cli:overtime-text": "node ./src/
|
|
59
|
-
"cli:overtime-text-us": "node ./src/
|
|
60
|
-
"cli:overtime-text-us-outdir": "node ./src/
|
|
61
|
-
"cli:overtime-text-us-parent": "node ./src/
|
|
62
|
-
"cli:serve": "node ./src/
|
|
63
|
-
"cli:
|
|
64
|
-
"cli:text-demo": "node ./src/
|
|
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
|
-
"
|
|
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
|
-
"
|
|
108
|
+
"fs-extra": "11.3.3",
|
|
106
109
|
"ora": "9.0.0",
|
|
107
|
-
"
|
|
110
|
+
"os-locale": "^8.0.0",
|
|
108
111
|
"string-width": "5.1.2",
|
|
109
|
-
"wukong-profiler": "^1.0.
|
|
110
|
-
"
|
|
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
|
+
}
|