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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { renderAuthorMapText } from '#src/output/renderAuthorMapText.mjs'
|
|
5
|
+
import { getEsmJs } from '#src/output/utils/getEsmJs.mjs'
|
|
6
|
+
import { writeTxtFile } from '#src/output/utils/index.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @function handleExportAuthor
|
|
10
|
+
* @description 按周导出
|
|
11
|
+
* @param {type}
|
|
12
|
+
* @returns {type}
|
|
13
|
+
*/
|
|
14
|
+
export const handleExportAuthor = async ({ opts, records, fileName }) => {
|
|
15
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
16
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const commitFileName = fileName || `authors.txt`
|
|
20
|
+
const rawResult = renderAuthorMapText(records)
|
|
21
|
+
writeTxtFile(baseDir, commitFileName, rawResult)
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.warn(
|
|
24
|
+
'handleExportAuthor failed:',
|
|
25
|
+
err && err.message ? err.message : err
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { renderChangedLinesText } from '#src/output/text.mjs'
|
|
5
|
+
import { writeTxtFile } from '#src/output/utils/index.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function handleExportAuthorChanges
|
|
9
|
+
* @description 按周导出
|
|
10
|
+
* @param {type}
|
|
11
|
+
* @returns {type}
|
|
12
|
+
*/
|
|
13
|
+
export const handleExportAuthorChanges = async ({ opts, records, fileName }) => {
|
|
14
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
15
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const commitFileName = fileName || `author-changes.txt`
|
|
19
|
+
const rawResult = renderChangedLinesText(records)
|
|
20
|
+
writeTxtFile(baseDir, commitFileName, rawResult)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.warn(
|
|
23
|
+
'handleExportAuthorChanges failed:',
|
|
24
|
+
err && err.message ? err.message : err
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { renderAuthorChangesJson } from '#src/output/json.mjs'
|
|
5
|
+
import { writeJsonFile } from '#src/output/utils/index.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function handleExportAuthorChangesJson
|
|
9
|
+
* @description 按周导出
|
|
10
|
+
* @param {type}
|
|
11
|
+
* @returns {type}
|
|
12
|
+
*/
|
|
13
|
+
export const handleExportAuthorChangesJson = async ({
|
|
14
|
+
opts,
|
|
15
|
+
records,
|
|
16
|
+
fileName
|
|
17
|
+
}) => {
|
|
18
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
19
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const commitFileName = fileName || `author-changes.json`
|
|
23
|
+
const rawResult = renderAuthorChangesJson(records)
|
|
24
|
+
writeJsonFile(baseDir, commitFileName, rawResult)
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.warn(
|
|
27
|
+
'handleExportAuthorChangesJson failed:',
|
|
28
|
+
err && err.message ? err.message : err
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR_MONTH } from '#src/constants/index.mjs'
|
|
4
|
+
import {
|
|
5
|
+
renderOvertimeCsv,
|
|
6
|
+
renderOvertimeTab,
|
|
7
|
+
renderOvertimeText
|
|
8
|
+
} from '#src/domain/overtime/overtime.mjs'
|
|
9
|
+
import {
|
|
10
|
+
exportExcel,
|
|
11
|
+
exportExcelAuthorChangeStats,
|
|
12
|
+
exportExcelPerPeriodFiles,
|
|
13
|
+
exportExcelPerPeriodSheets
|
|
14
|
+
} from '#src/output/excel.mjs'
|
|
15
|
+
import { writeJsonFile, writeTxtFile } from '#src/output/utils/index.mjs'
|
|
16
|
+
import { outFile } from '#src/output/utils/outputPath.mjs'
|
|
17
|
+
import { groupRecords } from '#utils/groupRecords.mjs'
|
|
18
|
+
import { logger } from '#utils/logger.mjs'
|
|
19
|
+
|
|
20
|
+
import { getWorkOvertimeStats } from '../overtime/analyze.mjs'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @function handleExportByMonth
|
|
24
|
+
* @description 按月导出
|
|
25
|
+
* @param {type}
|
|
26
|
+
* @returns {type}
|
|
27
|
+
*/
|
|
28
|
+
export const handleExportByMonth = async ({
|
|
29
|
+
opts,
|
|
30
|
+
records,
|
|
31
|
+
worktimeOptions
|
|
32
|
+
}) => {
|
|
33
|
+
// Always write human readable overtime text to file (default: overtime.txt)
|
|
34
|
+
const outBase = opts.output.out || 'commits'
|
|
35
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
36
|
+
const baseDir = `${config.dir}/${EXPORT_DIR_MONTH}`
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// 按月输出 ... 保持原逻辑
|
|
40
|
+
const perPeriodFormats = opts.output.perPeriod.formats
|
|
41
|
+
try {
|
|
42
|
+
const monthGroups = groupRecords(records, 'month')
|
|
43
|
+
const monthlyFileName = `overtime_${outBase}_monthly.txt`
|
|
44
|
+
|
|
45
|
+
let monthlyContent = ''
|
|
46
|
+
const monthKeys = Object.keys(monthGroups).sort()
|
|
47
|
+
monthKeys.forEach((k) => {
|
|
48
|
+
const groupRecs = monthGroups[k]
|
|
49
|
+
const s = getWorkOvertimeStats(groupRecs, worktimeOptions)
|
|
50
|
+
monthlyContent += `===== ${k} =====\n`
|
|
51
|
+
monthlyContent += `${renderOvertimeText(s)}\n\n`
|
|
52
|
+
// Also write a single file per month under 'month/' folder
|
|
53
|
+
try {
|
|
54
|
+
const perMonthFileName = `month/overtime_${outBase}_${k}.txt`
|
|
55
|
+
// const perMonthFile = outFile(perMonthFileName, outDir)
|
|
56
|
+
// writeTextFile(perMonthFile, renderOvertimeText(s))
|
|
57
|
+
writeTxtFile(baseDir, perMonthFileName, renderOvertimeText(s))
|
|
58
|
+
logger.info(`Overtime 月度(${k}) 已导出: ${perMonthFileName}`)
|
|
59
|
+
// per-period CSV / Tab format (按需生成)
|
|
60
|
+
if (perPeriodFormats.includes('csv')) {
|
|
61
|
+
try {
|
|
62
|
+
const perMonthCsvName = `month/overtime_${outBase}_${k}.csv`
|
|
63
|
+
// writeTextFile(
|
|
64
|
+
// outFile(perMonthCsvName, outDir),
|
|
65
|
+
// renderOvertimeCsv(s)
|
|
66
|
+
// )
|
|
67
|
+
writeTxtFile(baseDir, perMonthCsvName, renderOvertimeCsv(s))
|
|
68
|
+
|
|
69
|
+
logger.info(`Overtime 月度(CSV)(${k}) 已导出: ${perMonthCsvName}`)
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn(
|
|
72
|
+
`Write monthly CSV for ${k} failed:`,
|
|
73
|
+
err && err.message ? err.message : err
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (perPeriodFormats.includes('tab')) {
|
|
78
|
+
try {
|
|
79
|
+
const perMonthTabName = `month/overtime_${outBase}_${k}.tab.txt`
|
|
80
|
+
// writeTextFile(
|
|
81
|
+
// outFile(perMonthTabName, outDir),
|
|
82
|
+
// renderOvertimeTab(s)
|
|
83
|
+
// )
|
|
84
|
+
writeTxtFile(baseDir, perMonthTabName, renderOvertimeTab(s))
|
|
85
|
+
|
|
86
|
+
logger.info(`Overtime 月度(Tab)(${k}) 已导出: ${perMonthTabName}`)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`Write monthly Tab for ${k} failed:`,
|
|
90
|
+
err && err.message ? err.message : err
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.warn(
|
|
96
|
+
`Write monthly file for ${k} failed:`,
|
|
97
|
+
err && err.message ? err.message : err
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
if (!opts.perPeriodOnly) {
|
|
102
|
+
// writeTextFile(monthlyFile, monthlyContent)
|
|
103
|
+
writeTxtFile(baseDir, monthlyFileName, monthlyContent)
|
|
104
|
+
logger.info(`Overtime 月度汇总 已导出: ${monthlyFileName}`)
|
|
105
|
+
}
|
|
106
|
+
// per-period Excel (sheets or files)
|
|
107
|
+
if (perPeriodFormats.includes('xlsx')) {
|
|
108
|
+
const perPeriodExcelMode = String(opts.perPeriodExcelMode || 'sheets')
|
|
109
|
+
if (perPeriodExcelMode === 'sheets') {
|
|
110
|
+
try {
|
|
111
|
+
const monthXlsxName = `overtime_${outBase}_monthly.xlsx`
|
|
112
|
+
const monthXlsxFile = outFile(baseDir, monthXlsxName)
|
|
113
|
+
await exportExcelPerPeriodSheets(monthGroups, monthXlsxFile, {
|
|
114
|
+
stats: opts.stats,
|
|
115
|
+
gerrit: opts.gerrit
|
|
116
|
+
})
|
|
117
|
+
logger.info(`Overtime 月度(XLSX) 已导出: ${monthXlsxFile}`)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.warn(
|
|
120
|
+
'Export month XLSX (sheets) failed:',
|
|
121
|
+
err && err.message ? err.message : err
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
try {
|
|
126
|
+
const monthKeys2 = Object.keys(monthGroups).sort()
|
|
127
|
+
const tasks = monthKeys2.map((k2) => {
|
|
128
|
+
const perMonthXlsxName = `overtime_${outBase}_${k2}.xlsx`
|
|
129
|
+
const perMonthXlsxFile = outFile(baseDir, perMonthXlsxName)
|
|
130
|
+
return exportExcel(monthGroups[k2], null, {
|
|
131
|
+
file: perMonthXlsxFile,
|
|
132
|
+
stats: opts.stats,
|
|
133
|
+
gerrit: opts.gerrit
|
|
134
|
+
}).then(() =>
|
|
135
|
+
console.log(
|
|
136
|
+
chalk.green(
|
|
137
|
+
`Overtime 月度(XLSX)(${k2}) 已导出: ${perMonthXlsxFile}`
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
await Promise.all(tasks)
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.warn(
|
|
145
|
+
'Export monthly XLSX files failed:',
|
|
146
|
+
err && err.message ? err.message : err
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.warn(
|
|
153
|
+
'Generate monthly overtime failed:',
|
|
154
|
+
err && err.message ? err.message : err
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR_WEEK } from '#src/constants/index.mjs'
|
|
4
|
+
import {
|
|
5
|
+
renderOvertimeCsv,
|
|
6
|
+
renderOvertimeTab,
|
|
7
|
+
renderOvertimeText
|
|
8
|
+
} from '#src/domain/overtime/overtime.mjs'
|
|
9
|
+
import {
|
|
10
|
+
exportExcel,
|
|
11
|
+
exportExcelAuthorChangeStats,
|
|
12
|
+
exportExcelPerPeriodFiles,
|
|
13
|
+
exportExcelPerPeriodSheets
|
|
14
|
+
} from '#src/output/excel.mjs'
|
|
15
|
+
import { writeJsonFile, writeTxtFile } from '#src/output/utils/index.mjs'
|
|
16
|
+
import { outFile } from '#src/output/utils/outputPath.mjs'
|
|
17
|
+
import { groupRecords } from '#utils/groupRecords.mjs'
|
|
18
|
+
import { logger } from '#utils/logger.mjs'
|
|
19
|
+
|
|
20
|
+
import { getWorkOvertimeStats } from '../overtime/analyze.mjs'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @function handleExportByWeek
|
|
24
|
+
* @description 按周导出
|
|
25
|
+
* @param {type}
|
|
26
|
+
* @returns {type}
|
|
27
|
+
*/
|
|
28
|
+
export const handleExportByWeek = async ({
|
|
29
|
+
opts,
|
|
30
|
+
records,
|
|
31
|
+
worktimeOptions
|
|
32
|
+
}) => {
|
|
33
|
+
// Always write human readable overtime text to file (default: overtime.txt)
|
|
34
|
+
const outBase = opts.output.out || 'commits'
|
|
35
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
36
|
+
const baseDir = `${config.dir}/${EXPORT_DIR_WEEK}`
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const weekGroups = groupRecords(records, 'week')
|
|
42
|
+
const weeklyFileName = `overtime_${outBase}_weekly.txt`
|
|
43
|
+
// const weeklyFile = outputFilePath(weeklyFileName, outDir)
|
|
44
|
+
let weeklyContent = ''
|
|
45
|
+
const weekKeys = Object.keys(weekGroups).sort()
|
|
46
|
+
weekKeys.forEach((k) => {
|
|
47
|
+
const groupRecs = weekGroups[k]
|
|
48
|
+
const s = getWorkOvertimeStats(groupRecs, worktimeOptions)
|
|
49
|
+
weeklyContent += `===== ${k} =====\n`
|
|
50
|
+
weeklyContent += `${renderOvertimeText(s)}\n\n`
|
|
51
|
+
try {
|
|
52
|
+
const perWeekFileName = `week/overtime_${outBase}_${k}.txt`
|
|
53
|
+
// const perWeekFile = outputFilePath(perWeekFileName, outDir)
|
|
54
|
+
// writeTextFile(perWeekFile, renderOvertimeText(s))
|
|
55
|
+
writeTxtFile(baseDir, perWeekFileName, renderOvertimeText(s))
|
|
56
|
+
|
|
57
|
+
// console.log(chalk.green(`Overtime 周度(${k}) 已导出: ${perWeekFile}`))
|
|
58
|
+
logger.info(`Overtime 周度(${k}) 已导出: ${perWeekFileName}`)
|
|
59
|
+
|
|
60
|
+
// eslint-disable-next-line no-shadow
|
|
61
|
+
const perPeriodFormats = String(opts.perPeriodFormats || '')
|
|
62
|
+
.split(',')
|
|
63
|
+
// eslint-disable-next-line no-shadow
|
|
64
|
+
.map((s) =>
|
|
65
|
+
String(s || '')
|
|
66
|
+
.trim()
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
)
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
if (perPeriodFormats.includes('csv')) {
|
|
71
|
+
try {
|
|
72
|
+
const perWeekCsvName = `week/overtime_${outBase}_${k}.csv`
|
|
73
|
+
// writeTextFile(
|
|
74
|
+
// outputFilePath(perWeekCsvName, outDir),
|
|
75
|
+
// renderOvertimeCsv(s)
|
|
76
|
+
// )
|
|
77
|
+
writeTxtFile(baseDir, perWeekCsvName, renderOvertimeCsv(s))
|
|
78
|
+
|
|
79
|
+
logger.info(`Overtime 周度(CSV)(${k}) 已导出: ${perWeekCsvName}`)
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`Write weekly CSV for ${k} failed:`,
|
|
83
|
+
err && err.message ? err.message : err
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (perPeriodFormats.includes('tab')) {
|
|
88
|
+
try {
|
|
89
|
+
const perWeekTabName = `week/overtime_${outBase}_${k}.tab.txt`
|
|
90
|
+
// writeTextFile(
|
|
91
|
+
// outputFilePath(perWeekTabName, outDir),
|
|
92
|
+
// renderOvertimeTab(s)
|
|
93
|
+
// )
|
|
94
|
+
writeTxtFile(baseDir, perWeekTabName, renderOvertimeTab(s))
|
|
95
|
+
|
|
96
|
+
logger.info(`Overtime 周度(Tab)(${k}) 已导出: ${perWeekTabName}`)
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`Write weekly Tab for ${k} failed:`,
|
|
100
|
+
err && err.message ? err.message : err
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
console.warn(
|
|
106
|
+
`Write weekly file for ${k} failed:`,
|
|
107
|
+
err && err.message ? err.message : err
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
// writeTextFile(weeklyFile, weeklyContent)
|
|
112
|
+
writeTxtFile(baseDir, weeklyFileName, weeklyContent)
|
|
113
|
+
|
|
114
|
+
logger.info(`Overtime 周度汇总 已导出: ${weeklyFileName}`)
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.warn(
|
|
117
|
+
'Generate weekly overtime failed:',
|
|
118
|
+
err && err.message ? err.message : err
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { renderText } from '#src/output/text.mjs'
|
|
5
|
+
import { writeTxtFile } from '#src/output/utils/index.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function handleExportCommits
|
|
9
|
+
* @description 按周导出
|
|
10
|
+
* @param {type}
|
|
11
|
+
* @returns {type}
|
|
12
|
+
*/
|
|
13
|
+
export const handleExportCommits = async ({ opts, records, fileName }) => {
|
|
14
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
15
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const commitFileName = fileName || `commits.txt`
|
|
19
|
+
writeTxtFile(baseDir, commitFileName, renderText(records))
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.warn(
|
|
22
|
+
'handleExportCommits failed:',
|
|
23
|
+
err && err.message ? err.message : err
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import {
|
|
5
|
+
exportExcel,
|
|
6
|
+
exportExcelAuthorChangeStats,
|
|
7
|
+
exportExcelPerPeriodFiles,
|
|
8
|
+
exportExcelPerPeriodSheets
|
|
9
|
+
} from '#src/output/excel.mjs'
|
|
10
|
+
import { renderText } from '#src/output/text.mjs'
|
|
11
|
+
import { writeTxtFile } from '#src/output/utils/index.mjs'
|
|
12
|
+
import { outFile } from '#src/output/utils/outputPath.mjs'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @function handleExportCommitsExcel
|
|
16
|
+
* @description 按周导出
|
|
17
|
+
* @param {type}
|
|
18
|
+
* @returns {type}
|
|
19
|
+
*/
|
|
20
|
+
export const handleExportCommitsExcel = async ({
|
|
21
|
+
opts,
|
|
22
|
+
records,
|
|
23
|
+
fileName,
|
|
24
|
+
groups
|
|
25
|
+
}) => {
|
|
26
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
27
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const excelFile = fileName || 'commits.xlsx'
|
|
31
|
+
|
|
32
|
+
const excelPath = outFile(baseDir, excelFile)
|
|
33
|
+
|
|
34
|
+
await exportExcel(records, groups, {
|
|
35
|
+
file: excelPath,
|
|
36
|
+
stats: opts.stats,
|
|
37
|
+
gerrit: opts.gerrit
|
|
38
|
+
})
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.warn(
|
|
41
|
+
'handleExportCommitsExcel failed:',
|
|
42
|
+
err && err.message ? err.message : err
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { renderText } from '#src/output/text.mjs'
|
|
5
|
+
import { writeJsonFile } from '#src/output/utils/index.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @function handleExportCommitsJson
|
|
9
|
+
* @description 按周导出
|
|
10
|
+
* @param {type}
|
|
11
|
+
* @returns {type}
|
|
12
|
+
*/
|
|
13
|
+
export const handleExportCommitsJson = async ({
|
|
14
|
+
opts,
|
|
15
|
+
records,
|
|
16
|
+
fileName,
|
|
17
|
+
groups
|
|
18
|
+
}) => {
|
|
19
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
20
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const commitFileName = fileName || `commits.json`
|
|
24
|
+
writeJsonFile(baseDir, commitFileName, groups || records)
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.warn(
|
|
27
|
+
'handleExportCommitsJson failed:',
|
|
28
|
+
err && err.message ? err.message : err
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { EXPORT_DIR } from '#src/constants/index.mjs'
|
|
4
|
+
import { writeJsonFile, writeTxtFile } from '#src/output/utils/index.mjs'
|
|
5
|
+
import { logger } from '#utils/logger.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @handleExport
|
|
9
|
+
* @description
|
|
10
|
+
* @param {type} param - suffix: json,txt,csv
|
|
11
|
+
* @returns {type}
|
|
12
|
+
*/
|
|
13
|
+
export const handleExport = ({
|
|
14
|
+
fileName,
|
|
15
|
+
suffix,
|
|
16
|
+
opts,
|
|
17
|
+
result,
|
|
18
|
+
msg = '已导出'
|
|
19
|
+
}) => {
|
|
20
|
+
const config = { dir: opts.output.dir || path.resolve('output-wukong') }
|
|
21
|
+
const baseDir = `${config.dir}/${EXPORT_DIR}`
|
|
22
|
+
if (suffix === 'json') {
|
|
23
|
+
writeJsonFile(baseDir, fileName, result)
|
|
24
|
+
} else {
|
|
25
|
+
writeTxtFile(baseDir, fileName, result)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const file = `${baseDir}/${fileName}`
|
|
29
|
+
logger.success(`${fileName}`, file, msg)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const handleExportOvertimeMain = ({
|
|
33
|
+
fileName,
|
|
34
|
+
suffix = 'json',
|
|
35
|
+
opts,
|
|
36
|
+
result
|
|
37
|
+
}) => {
|
|
38
|
+
handleExport({
|
|
39
|
+
fileName,
|
|
40
|
+
suffix,
|
|
41
|
+
opts,
|
|
42
|
+
result,
|
|
43
|
+
msg: 'overtime JSON 已导出'
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const handleExportOvertimeTxt = ({
|
|
48
|
+
fileName,
|
|
49
|
+
suffix = 'txt',
|
|
50
|
+
opts,
|
|
51
|
+
result
|
|
52
|
+
}) => {
|
|
53
|
+
handleExport({
|
|
54
|
+
fileName,
|
|
55
|
+
suffix,
|
|
56
|
+
opts,
|
|
57
|
+
result,
|
|
58
|
+
msg: '已导出(Overtime text)'
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const handleExportOvertimeTabTxt = ({
|
|
63
|
+
fileName,
|
|
64
|
+
suffix = 'txt',
|
|
65
|
+
opts,
|
|
66
|
+
result
|
|
67
|
+
}) => {
|
|
68
|
+
handleExport({
|
|
69
|
+
fileName,
|
|
70
|
+
suffix,
|
|
71
|
+
opts,
|
|
72
|
+
result,
|
|
73
|
+
msg: '已导出(Overtime table (tabs))'
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const handleExportOvertimeCsv = ({
|
|
78
|
+
fileName,
|
|
79
|
+
suffix = 'csv',
|
|
80
|
+
opts,
|
|
81
|
+
result
|
|
82
|
+
}) => {
|
|
83
|
+
handleExport({
|
|
84
|
+
fileName,
|
|
85
|
+
suffix,
|
|
86
|
+
opts,
|
|
87
|
+
result,
|
|
88
|
+
msg: '已导出(Overtime CSV )'
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 启动时检测 git 是否可用
|
|
3
|
+
*
|
|
4
|
+
* - 不使用 shell
|
|
5
|
+
* - 不触发 WSL
|
|
6
|
+
* - Windows / macOS / Linux 通用
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'node:child_process'
|
|
9
|
+
import { promisify } from 'node:util'
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile)
|
|
12
|
+
|
|
13
|
+
export async function ensureGitAvailable() {
|
|
14
|
+
try {
|
|
15
|
+
const { stdout } = await execFileAsync('git', ['--version'], {
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
timeout: 5000,
|
|
18
|
+
maxBuffer: 1024 * 1024
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// 示例输出:git version 2.45.1.windows.1
|
|
22
|
+
if (!stdout || !stdout.toLowerCase().includes('git version')) {
|
|
23
|
+
throw new Error(`Unexpected git output: ${stdout}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return stdout.trim()
|
|
27
|
+
} catch (err) {
|
|
28
|
+
/**
|
|
29
|
+
* 统一抛出人类可读错误
|
|
30
|
+
*/
|
|
31
|
+
const { platform } = process
|
|
32
|
+
|
|
33
|
+
let hint = ''
|
|
34
|
+
|
|
35
|
+
if (platform === 'win32') {
|
|
36
|
+
hint = `
|
|
37
|
+
请确认:
|
|
38
|
+
1️⃣ 已安装 Git for Windows
|
|
39
|
+
https://git-scm.com/download/win
|
|
40
|
+
|
|
41
|
+
2️⃣ 安装时勾选:
|
|
42
|
+
✔ "Add Git to PATH"
|
|
43
|
+
|
|
44
|
+
3️⃣ 关闭并重新打开终端后再试
|
|
45
|
+
`
|
|
46
|
+
} else if (platform === 'darwin') {
|
|
47
|
+
hint = `
|
|
48
|
+
可通过以下方式安装 git:
|
|
49
|
+
xcode-select --install
|
|
50
|
+
或
|
|
51
|
+
brew install git
|
|
52
|
+
`
|
|
53
|
+
} else {
|
|
54
|
+
hint = `
|
|
55
|
+
请使用系统包管理器安装 git,例如:
|
|
56
|
+
apt install git
|
|
57
|
+
yum install git
|
|
58
|
+
`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const error = new Error(`❌ Git 不可用,CLI 无法继续运行\n\n${hint}`)
|
|
62
|
+
|
|
63
|
+
error.cause = err
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 确保当前目录在 git 仓库内
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
import { execFile } from 'node:child_process'
|
|
7
|
+
import { promisify } from 'node:util'
|
|
8
|
+
import { getGitCapability } from './gitCapability.mjs'
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @returns {Promise<boolean>}
|
|
14
|
+
*/
|
|
15
|
+
export async function ensureGitRepo() {
|
|
16
|
+
await getGitCapability()
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await execFileAsync(
|
|
20
|
+
'git',
|
|
21
|
+
['rev-parse', '--is-inside-work-tree'],
|
|
22
|
+
{
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
timeout: 5000,
|
|
25
|
+
maxBuffer: 1024 * 1024
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if (stdout.trim() !== 'true') {
|
|
30
|
+
throw new Error('Not a git repository')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true
|
|
34
|
+
} catch {
|
|
35
|
+
// throw new Error(
|
|
36
|
+
// `❌ 当前目录不是 Git 仓库\n\n请在 Git 项目根目录下运行该命令`
|
|
37
|
+
// )
|
|
38
|
+
console.log(`${chalk.red("❌")} 当前目录不是 Git 仓库\n\n请在 Git 项目根目录下运行该命令`)
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
}
|