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
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file: configStore.mjs
|
|
3
|
+
* @description: 配置存储与加载模块,支持 JS/MJS 动态配置。
|
|
4
|
+
* 配置存储与加载模块。
|
|
5
|
+
命令参数优先级:
|
|
6
|
+
CLI > RC文件 > 内置默认值 (DEFAULT_CONFIG)。
|
|
7
|
+
出厂配置 < 用户全局配置 < 项目配置 < 命令行参数
|
|
8
|
+
* @author: King Monkey
|
|
9
|
+
* @created: 2026-01-01 01:45
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import os from 'os'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
import { pathToFileURL } from 'url'
|
|
15
|
+
import yaml from 'yaml'
|
|
16
|
+
|
|
17
|
+
// 1. 定义出厂默认配置(底座)
|
|
18
|
+
export const DEFAULT_CONFIG = {
|
|
19
|
+
git: { merges: true, limit: undefined, numstat: false },
|
|
20
|
+
period: { groupBy: 'month' },
|
|
21
|
+
worktime: {
|
|
22
|
+
country: 'CN',
|
|
23
|
+
start: 9,
|
|
24
|
+
end: 18,
|
|
25
|
+
lunch: { start: 12, end: 14 },
|
|
26
|
+
overnightCutoff: 6
|
|
27
|
+
},
|
|
28
|
+
output: {
|
|
29
|
+
out: 'commits', // 文件名前缀
|
|
30
|
+
dir: 'output-wukong',
|
|
31
|
+
formats: ['text'],
|
|
32
|
+
perPeriod: { enabled: true, excelMode: 'sheets' }
|
|
33
|
+
},
|
|
34
|
+
// 用户自定义的作者别名映射(key 可以是邮箱或原始作者名,value 为规范化作者名)
|
|
35
|
+
authorAliases: {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 配置文件名列表
|
|
39
|
+
export const RC_NAMES = [
|
|
40
|
+
'.wukonggitlogrc',
|
|
41
|
+
'.wukonggitlogrc.js',
|
|
42
|
+
'.wukonggitlogrc.mjs',
|
|
43
|
+
'.wukonggitlogrc.yml',
|
|
44
|
+
'.wukonggitlogrc.yaml',
|
|
45
|
+
'.wukonggitlogrc.json'
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
function deepMerge(target, source) {
|
|
49
|
+
const result = { ...target }
|
|
50
|
+
if (!source) return result
|
|
51
|
+
|
|
52
|
+
for (const key of Object.keys(source)) {
|
|
53
|
+
if (
|
|
54
|
+
source[key] instanceof Object &&
|
|
55
|
+
!Array.isArray(source[key]) &&
|
|
56
|
+
key in target
|
|
57
|
+
) {
|
|
58
|
+
result[key] = deepMerge(target[key], source[key])
|
|
59
|
+
} else if (source[key] !== undefined) {
|
|
60
|
+
result[key] = source[key]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let cachedConfig = null
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 核心加载逻辑:支持异步 import
|
|
70
|
+
*/
|
|
71
|
+
export async function loadRcConfig(cwd = process.cwd()) {
|
|
72
|
+
if (cachedConfig) return cachedConfig
|
|
73
|
+
|
|
74
|
+
// 优先级队列:家目录(全局) -> 当前项目目录
|
|
75
|
+
const searchDirs = [os.homedir(), cwd]
|
|
76
|
+
let config = { ...DEFAULT_CONFIG }
|
|
77
|
+
|
|
78
|
+
for (const dir of searchDirs) {
|
|
79
|
+
for (const name of RC_NAMES) {
|
|
80
|
+
const filePath = path.join(dir, name)
|
|
81
|
+
// eslint-disable-next-line no-continue
|
|
82
|
+
if (!fs.existsSync(filePath)) continue
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
let parsed = {}
|
|
86
|
+
const ext = path.extname(filePath)
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
ext === '.js' ||
|
|
90
|
+
ext === '.mjs' ||
|
|
91
|
+
(name.endsWith('.wukonggitlogrc') && !ext)
|
|
92
|
+
) {
|
|
93
|
+
// 处理 JS/MJS 或 无后缀但可能是 JS 的文件
|
|
94
|
+
// 必须转为 URL 格式以兼容 Windows 和 ESM 动态导入
|
|
95
|
+
const fileUrl = pathToFileURL(filePath).href
|
|
96
|
+
// eslint-disable-next-line no-await-in-loop
|
|
97
|
+
const module = await import(`${fileUrl}?t=${Date.now()}`) // 加缓存击穿避免热更新问题
|
|
98
|
+
parsed = module.default || module
|
|
99
|
+
} else if (ext === '.json') {
|
|
100
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
101
|
+
} else {
|
|
102
|
+
// 处理 YAML
|
|
103
|
+
parsed = yaml.parse(fs.readFileSync(filePath, 'utf8'))
|
|
104
|
+
}
|
|
105
|
+
config = deepMerge(config, parsed)
|
|
106
|
+
} catch (e) {
|
|
107
|
+
console.warn(
|
|
108
|
+
`[Config] 无法解析配置文件: ${filePath}\n原因: ${e.message}`
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
cachedConfig = config
|
|
115
|
+
|
|
116
|
+
return cachedConfig
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 获取已加载的配置,如果未加载则抛出异常或异步加载
|
|
121
|
+
* 注意:由于支持了 JS,现在建议统一 await loadRcConfig()
|
|
122
|
+
*/
|
|
123
|
+
export function getRcConfig() {
|
|
124
|
+
if (!cachedConfig) {
|
|
125
|
+
throw new Error('Config not loaded. Please await loadRcConfig() first.')
|
|
126
|
+
}
|
|
127
|
+
return cachedConfig
|
|
128
|
+
}
|
package/src/infra/fs.mjs
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { writeText } from '../utils/writeFile.mjs'
|
|
2
|
+
import { outFile } from '../utils/outputPath.mjs'
|
|
3
|
+
import { renderOvertimeCsv } from '../../domain/overtime/render.mjs'
|
|
4
|
+
|
|
5
|
+
export function outputOvertimeCsvByPeriod(map, period, config) {
|
|
6
|
+
for (const key of Object.keys(map)) {
|
|
7
|
+
writeText(
|
|
8
|
+
outFile(config.dir, `${period}/overtime_${config.base}_${key}.csv`),
|
|
9
|
+
renderOvertimeCsv(map[key])
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
|
|
5
|
+
/* ---------------- validation ---------------- */
|
|
6
|
+
|
|
7
|
+
function validateSchema(schema) {
|
|
8
|
+
if (!schema.schemaVersion) {
|
|
9
|
+
throw new Error('Invalid data schema: missing schemaVersion')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!schema.data?.commits) {
|
|
13
|
+
throw new Error('Invalid data schema: commits missing')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export async function readServeData(dir) {
|
|
19
|
+
const dataDir = path.join(dir, 'data');
|
|
20
|
+
const schemaFile = path.join(dataDir, 'data.schema.json');
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(schemaFile)) throw new Error('Missing data.schema.json');
|
|
23
|
+
|
|
24
|
+
// 读取 schema 依然可以用 fs,因为它是配置文件
|
|
25
|
+
const schema = JSON.parse(fs.readFileSync(schemaFile, 'utf8'));
|
|
26
|
+
validateSchema(schema);
|
|
27
|
+
|
|
28
|
+
const result = {};
|
|
29
|
+
|
|
30
|
+
for (const [key, meta] of Object.entries(schema.data)) {
|
|
31
|
+
if (!meta.file) continue;
|
|
32
|
+
|
|
33
|
+
const filePath = path.join(dataDir, meta.file);
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
if (meta.required) throw new Error(`Missing: ${meta.file}`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 关键点:将绝对路径转换为 file:// URL 以支持 Windows 和 dynamic import
|
|
40
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
41
|
+
|
|
42
|
+
if (filePath.endsWith('.mjs')) {
|
|
43
|
+
const module = await import(fileUrl);
|
|
44
|
+
result[key] = module.default;
|
|
45
|
+
} else {
|
|
46
|
+
// JSON 文件处理
|
|
47
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
48
|
+
result[key] = JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
|
|
3
|
+
import { outputExcelDayReport } from '#src/output/excel/outputExcelDayReport.mjs'
|
|
4
|
+
import { outputTxtDayReport } from '#src/output/text/outputTxtDayReport.mjs'
|
|
5
|
+
import { getCurrentTimestampSecond } from '#utils/index.mjs'
|
|
6
|
+
|
|
7
|
+
import { getEsmJs } from '../utils/getEsmJs.mjs'
|
|
8
|
+
import { writeJsonFile, writeTxtFile } from '../utils/index.mjs'
|
|
9
|
+
|
|
10
|
+
const pkg = JSON.parse(
|
|
11
|
+
fs.readFileSync(new URL('../../../package.json', import.meta.url), 'utf-8')
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
function writeSchema(dir, files) {
|
|
15
|
+
const schema = {
|
|
16
|
+
schemaVersion: '1.0.0',
|
|
17
|
+
tool: {
|
|
18
|
+
name: pkg.name,
|
|
19
|
+
version: pkg.version
|
|
20
|
+
},
|
|
21
|
+
generatedAt: getCurrentTimestampSecond(),
|
|
22
|
+
data: {
|
|
23
|
+
commits: { file: files.commits, required: true },
|
|
24
|
+
authorMap: { file: files.authorMap, required: true },
|
|
25
|
+
overtime: { file: files.overtime, required: false },
|
|
26
|
+
overtimeByMonth: {
|
|
27
|
+
file: files.overtimeByMonth,
|
|
28
|
+
required: false
|
|
29
|
+
},
|
|
30
|
+
overtimeByWeek: {
|
|
31
|
+
file: files.overtimeByWeek,
|
|
32
|
+
required: false
|
|
33
|
+
},
|
|
34
|
+
options: { file: files.options, required: false }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
writeJsonFile(dir, 'data.schema.json', schema)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function writeServeData(result, config) {
|
|
42
|
+
const baseDir = `${config.dir}/data`
|
|
43
|
+
|
|
44
|
+
const files = {}
|
|
45
|
+
|
|
46
|
+
files.commits = writeJsonFile(baseDir, 'commits.json', result.commits)
|
|
47
|
+
|
|
48
|
+
files.authorMap = writeJsonFile(baseDir, 'authorMap.json', result.authorMap)
|
|
49
|
+
|
|
50
|
+
if (result.overtime) {
|
|
51
|
+
files.overtime = writeJsonFile(baseDir, 'overtime.json', result.overtime)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (result.overtimeByMonth) {
|
|
55
|
+
files.overtimeByMonth = writeJsonFile(
|
|
56
|
+
baseDir,
|
|
57
|
+
'overtime.month.json',
|
|
58
|
+
result.overtimeByMonth
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.overtimeByWeek) {
|
|
63
|
+
files.overtimeByWeek = writeJsonFile(
|
|
64
|
+
baseDir,
|
|
65
|
+
'overtime.week.json',
|
|
66
|
+
result.overtimeByWeek
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
writeSchema(baseDir, files)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function writeServeDataMjs(result, config) {
|
|
74
|
+
const baseDir = `${config.dir}/data`
|
|
75
|
+
|
|
76
|
+
const files = {}
|
|
77
|
+
|
|
78
|
+
files.config = writeTxtFile(
|
|
79
|
+
baseDir,
|
|
80
|
+
'config.mjs',
|
|
81
|
+
getEsmJs({ ...config.worktimeOptions, git: config.git || {} })
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// 保存 CLI 运行时的 period(since/until/groupBy 等)和筛选条件(author),供前端展示采样信息
|
|
85
|
+
files.options = writeTxtFile(
|
|
86
|
+
baseDir,
|
|
87
|
+
'options.mjs',
|
|
88
|
+
getEsmJs({ period: config.period || {}, author: config.author || null })
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
files.commits = writeTxtFile(baseDir, 'commits.mjs', getEsmJs(result.commits))
|
|
92
|
+
|
|
93
|
+
files.authorMap = writeTxtFile(
|
|
94
|
+
baseDir,
|
|
95
|
+
'authorMap.mjs',
|
|
96
|
+
getEsmJs(result.authorMap)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if (result.authorChanges) {
|
|
100
|
+
files.authorChanges = writeTxtFile(
|
|
101
|
+
baseDir,
|
|
102
|
+
'author.changes.mjs',
|
|
103
|
+
getEsmJs(result.authorChanges)
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (result.overtime) {
|
|
108
|
+
files.overtime = writeTxtFile(
|
|
109
|
+
baseDir,
|
|
110
|
+
'overtime.mjs',
|
|
111
|
+
getEsmJs(result.overtime)
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (result.overtimeByMonth) {
|
|
116
|
+
files.overtimeByMonth = writeTxtFile(
|
|
117
|
+
baseDir,
|
|
118
|
+
'overtime.month.mjs',
|
|
119
|
+
getEsmJs(result.overtimeByMonth)
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (result.overtimeByWeek) {
|
|
124
|
+
files.overtimeByWeek = writeTxtFile(
|
|
125
|
+
baseDir,
|
|
126
|
+
'overtime.week.mjs',
|
|
127
|
+
getEsmJs(result.overtimeByWeek)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
if (result.overtimeLatestCommitByDay) {
|
|
131
|
+
files.overtimeLatestCommitByDay = writeTxtFile(
|
|
132
|
+
baseDir,
|
|
133
|
+
'overtime.latest.commit.day.mjs',
|
|
134
|
+
getEsmJs(result.overtimeLatestCommitByDay)
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
writeSchema(baseDir, files)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 输出 日报 数据excel
|
|
142
|
+
export async function writeDayReportData({ dayReports = [], conf = {} }) {
|
|
143
|
+
await outputExcelDayReport({ dayReports, conf })
|
|
144
|
+
await outputTxtDayReport({ dayReports, conf })
|
|
145
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
import ExcelJS from 'exceljs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
import { DAY_REPORT_EXCEL } from '#src/constants/index.mjs'
|
|
6
|
+
import { outFile } from '#src/output/utils/outputPath.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @function outputExcelDayReport
|
|
10
|
+
* @description
|
|
11
|
+
* 按 author 输出 Excel 日报(每人一个 Excel)
|
|
12
|
+
* @param {Array} dayReports getGitLogsDayReport 的返回结果
|
|
13
|
+
* @param {Object} conf
|
|
14
|
+
* @param {string} conf.dir 输出目录
|
|
15
|
+
*/
|
|
16
|
+
export const outputExcelDayReport = async ({
|
|
17
|
+
dayReports = [],
|
|
18
|
+
conf = {}
|
|
19
|
+
} = {}) => {
|
|
20
|
+
if (!Array.isArray(dayReports) || dayReports.length === 0) {
|
|
21
|
+
console.log('没有日报数据')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const config = { dir: conf.dir || path.resolve('output-wukong') }
|
|
26
|
+
const baseDir = `${config.dir}/${DAY_REPORT_EXCEL}`
|
|
27
|
+
|
|
28
|
+
// 按 author 分组
|
|
29
|
+
const authorMap = {}
|
|
30
|
+
dayReports.forEach((item) => {
|
|
31
|
+
if (!authorMap[item.author]) {
|
|
32
|
+
authorMap[item.author] = []
|
|
33
|
+
}
|
|
34
|
+
authorMap[item.author].push(item)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
for (const [author, records] of Object.entries(authorMap)) {
|
|
38
|
+
records.sort((a, b) => dayjs(a.day).valueOf() - dayjs(b.day).valueOf())
|
|
39
|
+
|
|
40
|
+
const workbook = new ExcelJS.Workbook()
|
|
41
|
+
const sheet = workbook.addWorksheet('日报')
|
|
42
|
+
|
|
43
|
+
sheet.columns = [
|
|
44
|
+
{ header: '日期', key: 'day', width: 15 },
|
|
45
|
+
{ header: '姓名', key: 'author', width: 12 },
|
|
46
|
+
{ header: '打卡时长', key: 'hours', width: 12 },
|
|
47
|
+
{ header: '工作内容', key: 'msg', width: 60 },
|
|
48
|
+
{ header: '偏差说明', key: 'remark', width: 20 },
|
|
49
|
+
{ header: '生成时间', key: 'generatedAt', width: 22 }
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
sheet.getRow(1).font = { bold: true }
|
|
53
|
+
// 🔴 生成时间表头红色(F列)
|
|
54
|
+
sheet.getCell('F1').font = {
|
|
55
|
+
bold: true,
|
|
56
|
+
color: { argb: 'FFFF0000' }
|
|
57
|
+
}
|
|
58
|
+
sheet.getColumn('day').numFmt = 'yyyy-mm-dd'
|
|
59
|
+
sheet.getColumn('msg').alignment = {
|
|
60
|
+
wrapText: true,
|
|
61
|
+
vertical: 'top'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const generatedTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
65
|
+
|
|
66
|
+
records.forEach((item, index) => {
|
|
67
|
+
const row = sheet.addRow({
|
|
68
|
+
day: new Date(item.day),
|
|
69
|
+
author: item.author,
|
|
70
|
+
hours: 8,
|
|
71
|
+
msg: item.msg,
|
|
72
|
+
remark: '',
|
|
73
|
+
generatedAt: index === 0 ? generatedTime : ''
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// ✅ 只给第一行的「生成时间」设红色字体
|
|
77
|
+
if (index === 0) {
|
|
78
|
+
row.getCell('generatedAt').font = {
|
|
79
|
+
color: { argb: 'FFFF0000' } // 红色
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const excelFile = `${author}.xlsx`
|
|
85
|
+
const filePath = outFile(baseDir, excelFile)
|
|
86
|
+
|
|
87
|
+
// eslint-disable-next-line no-await-in-loop
|
|
88
|
+
await workbook.xlsx.writeFile(filePath)
|
|
89
|
+
|
|
90
|
+
console.log(`✅ 已生成 Excel:${filePath}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
import { exportExcelPerPeriodSheets , exportExcel } from '../excel.mjs'
|
|
4
|
+
|
|
5
|
+
export async function outputOvertimeExcelPerPeriod(map, period, config) {
|
|
6
|
+
if (config.mode === 'sheets') {
|
|
7
|
+
await exportExcelPerPeriodSheets(
|
|
8
|
+
map,
|
|
9
|
+
path.join(config.dir, `${period}/overtime_${config.base}.xlsx`)
|
|
10
|
+
)
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// files 模式
|
|
15
|
+
for (const key of Object.keys(map)) {
|
|
16
|
+
// eslint-disable-next-line no-await-in-loop
|
|
17
|
+
await exportExcel(map[key], null, {
|
|
18
|
+
file: path.join(
|
|
19
|
+
config.dir,
|
|
20
|
+
`${period}/overtime_${config.base}_${key}.xlsx`
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import dayjs from 'dayjs'
|
|
2
2
|
import ExcelJS from 'exceljs'
|
|
3
|
-
import { buildAuthorChangeStats } from '
|
|
3
|
+
import { buildAuthorChangeStats } from '#utils/buildAuthorChangeStats.mjs'
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
export async function exportExcel(records, groups, options = {}) {
|
|
@@ -58,7 +58,7 @@ export async function exportExcel(records, groups, options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export async function exportExcelPerPeriodSheets(groups, file, options = {}) {
|
|
61
|
-
const {
|
|
61
|
+
const { gerrit } = options
|
|
62
62
|
|
|
63
63
|
const wb = new ExcelJS.Workbook()
|
|
64
64
|
|
|
@@ -106,6 +106,7 @@ export async function exportExcelPerPeriodFiles(
|
|
|
106
106
|
const keys = Object.keys(groups).sort()
|
|
107
107
|
for (const k of keys) {
|
|
108
108
|
const perFile = `${dir}/overtime_${filePrefix}_${k}.xlsx`
|
|
109
|
+
// eslint-disable-next-line no-await-in-loop
|
|
109
110
|
await exportExcel(groups[k], null, {
|
|
110
111
|
file: perFile,
|
|
111
112
|
stats: options.stats,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getCurrentTimestampSecond } from '#utils/index.mjs'
|
|
2
|
+
|
|
3
|
+
import { outputOvertimeCsvByPeriod } from './csv/overtime.mjs'
|
|
4
|
+
import {
|
|
5
|
+
writeDayReportData,
|
|
6
|
+
writeServeData,
|
|
7
|
+
writeServeDataMjs
|
|
8
|
+
} from './data/writeData.mjs'
|
|
9
|
+
import { outputCommitsExcel } from './excel/commits.mjs'
|
|
10
|
+
import { outputOvertimeExcelPerPeriod } from './excel/perPeriod.mjs'
|
|
11
|
+
import { outputOvertimeJson } from './json/overtime.mjs'
|
|
12
|
+
import { outputOvertimeTabByPeriod } from './tab/overtime.mjs'
|
|
13
|
+
import { outputCommitsText } from './text/commits.mjs'
|
|
14
|
+
import { outputOvertimeText } from './text/overtime.mjs'
|
|
15
|
+
import { resolveOutDir } from './utils/outputPath.mjs'
|
|
16
|
+
|
|
17
|
+
export async function outputAll(result, config) {
|
|
18
|
+
const dir = resolveOutDir(config.dir)
|
|
19
|
+
const base = config.base || 'commits'
|
|
20
|
+
|
|
21
|
+
/* ---------- serve data(永远写) ---------- */
|
|
22
|
+
writeServeData(result, { dir })
|
|
23
|
+
|
|
24
|
+
/* ---------- 人类可读输出 ---------- */
|
|
25
|
+
if (config.formats.includes('text')) {
|
|
26
|
+
outputCommitsText(result, { dir })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.formats.includes('excel')) {
|
|
30
|
+
await outputCommitsExcel(result, { dir })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!result.overtime) return
|
|
34
|
+
|
|
35
|
+
outputOvertimeText(result.overtime, { dir, base })
|
|
36
|
+
|
|
37
|
+
if (config.formats.includes('json')) {
|
|
38
|
+
outputOvertimeJson(result.overtime, { dir, base })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (result.overtimeByMonth) {
|
|
42
|
+
if (config.perPeriod.formats.includes('csv')) {
|
|
43
|
+
outputOvertimeCsvByPeriod(result.overtimeByMonth, 'month', { dir, base })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config.perPeriod.formats.includes('tab')) {
|
|
47
|
+
outputOvertimeTabByPeriod(result.overtimeByMonth, 'month', { dir, base })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (config.perPeriod.formats.includes('xlsx')) {
|
|
51
|
+
await outputOvertimeExcelPerPeriod(result.overtimeByMonth, 'month', {
|
|
52
|
+
dir,
|
|
53
|
+
base,
|
|
54
|
+
mode: config.perPeriod.excelMode
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function outputData(result, config) {
|
|
61
|
+
const dir = resolveOutDir(config.dir)
|
|
62
|
+
|
|
63
|
+
/* ---------- serve data(永远写) ---------- */
|
|
64
|
+
await writeServeDataMjs(result, {
|
|
65
|
+
dir,
|
|
66
|
+
...config,
|
|
67
|
+
worktimeOptions: config.worktimeOptions
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function outputJournalAnalysis(result, config) {
|
|
72
|
+
const dir = resolveOutDir(config.dir)
|
|
73
|
+
|
|
74
|
+
const time = getCurrentTimestampSecond()
|
|
75
|
+
writeDayReportData({
|
|
76
|
+
dayReports: result.authorDayReport,
|
|
77
|
+
conf: { dir, time }
|
|
78
|
+
})
|
|
79
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { writeText } from '../utils/writeFile.mjs'
|
|
2
|
+
import { outFile } from '../utils/outputPath.mjs'
|
|
3
|
+
import { renderOvertimeTab } from '../../domain/overtime/render.mjs'
|
|
4
|
+
|
|
5
|
+
export function outputOvertimeTabByPeriod(map, period, config) {
|
|
6
|
+
for (const key of Object.keys(map)) {
|
|
7
|
+
writeText(
|
|
8
|
+
outFile(config.dir, `${period}/overtime_${config.base}_${key}.tab.txt`),
|
|
9
|
+
renderOvertimeTab(map[key])
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { writeText } from '../utils/writeFile.mjs'
|
|
2
|
+
// import { writeJsonFile, writeTxtFile } from '#src/output/utils/index.mjs'
|
|
3
|
+
import { outFile } from '../utils/outputPath.mjs'
|
|
4
|
+
import { renderText } from '../text.mjs' // 复用你原来的
|
|
5
|
+
|
|
6
|
+
export function outputCommitsText(result, config) {
|
|
7
|
+
const file = outFile(config.dir, config.file || 'commits.txt')
|
|
8
|
+
writeText(file, renderText(result.records))
|
|
9
|
+
}
|