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.
Files changed (124) hide show
  1. package/.eslintrc +1 -0
  2. package/.prettierrc +2 -1
  3. package/CHANGELOG.md +103 -0
  4. package/README.md +93 -173
  5. package/README.zh-CN.md +85 -137
  6. package/doc//347/233/256/345/275/225/347/273/223/346/236/204.md +2871 -0
  7. package/package.json +33 -29
  8. package/rc/.wukonggitlogrc +53 -0
  9. package/scripts/compareHourlyCounts.mjs +42 -0
  10. package/scripts/compareLatest.mjs +106 -0
  11. package/src/app/analyzeAction.mjs +120 -0
  12. package/src/app/exportAction.mjs +215 -0
  13. package/src/app/exportActionProgress.mjs +37 -0
  14. package/src/app/helpers.mjs +292 -0
  15. package/src/app/initAction.mjs +110 -0
  16. package/src/app/initActionWithTemp.mjs +192 -0
  17. package/src/app/journalAction.mjs +117 -0
  18. package/src/app/overtimeAction.mjs +100 -0
  19. package/src/app/runProfileEnd.mjs +0 -0
  20. package/src/app/serveAction.mjs +73 -0
  21. package/src/app/versionAction.mjs +7 -0
  22. package/src/cli/defineOptions.mjs +209 -0
  23. package/src/cli/index.mjs +0 -0
  24. package/src/cli/parseOptions.mjs +126 -8
  25. package/src/constants/index.mjs +16 -2
  26. package/src/domain/author/analyze.mjs +6 -0
  27. package/src/domain/author/map.mjs +0 -0
  28. package/src/domain/export/exportAuthor.mjs +28 -0
  29. package/src/domain/export/exportAuthorChanges.mjs +27 -0
  30. package/src/domain/export/exportAuthorChangesJson.mjs +31 -0
  31. package/src/domain/export/exportByMonth.mjs +157 -0
  32. package/src/domain/export/exportByWeek.mjs +121 -0
  33. package/src/domain/export/exportCommits.mjs +26 -0
  34. package/src/domain/export/exportCommitsExcel.mjs +45 -0
  35. package/src/domain/export/exportCommitsJson.mjs +31 -0
  36. package/src/domain/export/index.mjs +91 -0
  37. package/src/domain/git/ensureGitAvailable.mjs +66 -0
  38. package/src/domain/git/ensureGitRepo.mjs +41 -0
  39. package/src/domain/git/getGitFeatures.mjs +59 -0
  40. package/src/domain/git/getGitLogs.mjs +326 -0
  41. package/src/domain/git/getGitUser.mjs +44 -0
  42. package/src/domain/git/getRepoRoot.mjs +32 -0
  43. package/src/domain/git/gitCapability.mjs +119 -0
  44. package/src/domain/git/index.mjs +96 -0
  45. package/src/domain/git/resolveGerrit.mjs +102 -0
  46. package/src/domain/overtime/analyze.mjs +48 -0
  47. package/src/domain/overtime/index.mjs +3 -0
  48. package/src/domain/overtime/perPeriod.mjs +15 -0
  49. package/src/domain/overtime/render.mjs +15 -0
  50. package/src/i18n/index.mjs +38 -0
  51. package/src/i18n/resources.mjs +252 -0
  52. package/src/index.mjs +132 -649
  53. package/src/infra/cache.mjs +0 -0
  54. package/src/infra/configStore.mjs +128 -0
  55. package/src/infra/fs.mjs +0 -0
  56. package/src/infra/path.mjs +0 -0
  57. package/src/output/csv/overtime.mjs +12 -0
  58. package/src/output/csv.mjs +0 -0
  59. package/src/output/data/readData.mjs +54 -0
  60. package/src/output/data/writeData.mjs +145 -0
  61. package/src/output/excel/commits.mjs +9 -0
  62. package/src/output/excel/outputExcelDayReport.mjs +92 -0
  63. package/src/output/excel/perPeriod.mjs +24 -0
  64. package/src/{excel.mjs → output/excel.mjs} +3 -2
  65. package/src/output/index.mjs +79 -0
  66. package/src/output/json/overtime.mjs +9 -0
  67. package/src/output/tab/overtime.mjs +12 -0
  68. package/src/output/tab.mjs +0 -0
  69. package/src/output/text/commits.mjs +9 -0
  70. package/src/output/text/index.mjs +3 -0
  71. package/src/output/text/outputTxtDayReport.mjs +74 -0
  72. package/src/output/text/overtime.mjs +18 -0
  73. package/src/output/utils/getEsmJs.mjs +10 -0
  74. package/src/output/utils/index.mjs +14 -0
  75. package/src/output/utils/outputPath.mjs +19 -0
  76. package/src/output/utils/writeFile.mjs +10 -0
  77. package/src/serve/index.mjs +0 -0
  78. package/src/{server.mjs → serve/startServer.mjs} +21 -3
  79. package/src/serve/writeData.mjs +0 -0
  80. package/src/utils/authorNormalizer.mjs +28 -2
  81. package/src/utils/buildAuthorChangeStats.mjs +44 -0
  82. package/src/utils/deepMerge.mjs +13 -0
  83. package/src/utils/getPackage.mjs +11 -0
  84. package/src/utils/getProfileDirFile.mjs +12 -0
  85. package/src/utils/{file.mjs → groupRecords.mjs} +8 -9
  86. package/src/utils/index.mjs +5 -2
  87. package/src/utils/logger.mjs +28 -17
  88. package/src/utils/profiler.mjs +0 -101
  89. package/src/utils/resolve.mjs +11 -0
  90. package/src/utils/showVersionInfo.mjs +6 -2
  91. package/src/utils/time.mjs +0 -0
  92. package/src/utils/wait.mjs +2 -0
  93. package/web/app.js +3233 -260
  94. package/web/index.html +175 -22
  95. package/web/revoke/alpha1/app.js +4324 -0
  96. package/web/revoke/alpha1/index.html +266 -0
  97. package/web/revoke/app.before.js +3139 -0
  98. package/web/revoke/index-before.html +181 -0
  99. package/web/static/style.css +155 -9
  100. package/src/git.mjs +0 -256
  101. package/src/handlers/handleServe.mjs +0 -203
  102. package/src/lib/configStore.mjs +0 -11
  103. package/src/lib/memoize.mjs +0 -14
  104. package/src/utils/analyzeOvertimeCached.mjs +0 -7
  105. package/src/utils/checkUpdate.mjs +0 -130
  106. package/src/utils/exitWithTime.mjs +0 -17
  107. package/src/utils/handleSuccess.mjs +0 -9
  108. package/src/utils/logDev.mjs +0 -19
  109. package/src/utils/output.mjs +0 -26
  110. package/src/utils/profiler/diff.mjs +0 -26
  111. package/src/utils/profiler/format.mjs +0 -11
  112. package/src/utils/profiler/index.mjs +0 -144
  113. package/src/utils/profiler/trace.mjs +0 -26
  114. package/src/utils/time/scopeTimer.mjs +0 -37
  115. package/src/utils/time/timer.mjs +0 -33
  116. package/src/utils/time/withTimer.mjs +0 -11
  117. package/src/utils/timer.mjs +0 -35
  118. /package/src/{overtime → domain/overtime}/createOvertimeStats.mjs +0 -0
  119. /package/src/{overtime → domain/overtime}/overtime.mjs +0 -0
  120. /package/src/{json.mjs → output/json.mjs} +0 -0
  121. /package/src/{renderAuthorMapText.mjs → output/renderAuthorMapText.mjs} +0 -0
  122. /package/src/{stats-text.mjs → output/stats-text.mjs} +0 -0
  123. /package/src/{stats.mjs → output/stats.mjs} +0 -0
  124. /package/src/{text.mjs → output/text.mjs} +0 -0
@@ -0,0 +1,74 @@
1
+ import dayjs from 'dayjs'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ import { DAY_REPORT_TXT } from '#src/constants/index.mjs'
6
+ import { outFile } from '#src/output/utils/outputPath.mjs'
7
+
8
+ /**
9
+ * @function outputTxtDayReport
10
+ * @description
11
+ * 按 author 输出 TXT 日报(每人一个文件)
12
+ */
13
+ export const outputTxtDayReport = async ({
14
+ dayReports = [],
15
+ conf = {}
16
+ } = {}) => {
17
+ if (!Array.isArray(dayReports) || dayReports.length === 0) {
18
+ return
19
+ }
20
+
21
+ const config = { dir: conf.dir || path.resolve('output-wukong') }
22
+ const baseDir = `${config.dir}/${DAY_REPORT_TXT}`
23
+
24
+ // 生成时间(来自上层 outputData)
25
+ const generateTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
26
+
27
+ // 按 author 分组
28
+ const authorMap = {}
29
+ dayReports.forEach((item) => {
30
+ if (!authorMap[item.author]) {
31
+ authorMap[item.author] = []
32
+ }
33
+ authorMap[item.author].push(item)
34
+ })
35
+
36
+ // 每个人生成一个 txt
37
+ for (const [author, records] of Object.entries(authorMap)) {
38
+ // 日期升序
39
+ records.sort(
40
+ (a, b) =>
41
+ dayjs(a.day).valueOf() - dayjs(b.day).valueOf()
42
+ )
43
+
44
+ const lines = []
45
+
46
+ // ✅ 文件头:生成时间
47
+ lines.push(`生成时间:${generateTime}`)
48
+ lines.push('')
49
+
50
+ // 姓名
51
+ lines.push(`姓名:${author}`)
52
+ lines.push('')
53
+
54
+ // 每一天
55
+ records.forEach((item) => {
56
+ lines.push(item.day)
57
+ lines.push('--------------------------------')
58
+ lines.push('工作时长:8 小时')
59
+ lines.push('工作内容:')
60
+ lines.push(item.msg)
61
+ lines.push('') // 空行分隔
62
+ })
63
+
64
+ const content = lines.join('\n')
65
+
66
+ const txtFile = `${author}.txt`
67
+ const filePath = outFile(baseDir, txtFile)
68
+
69
+ // eslint-disable-next-line no-await-in-loop
70
+ fs.writeFileSync(filePath, content, 'utf-8')
71
+
72
+ console.log(`✅ 已生成 TXT:${filePath}`)
73
+ }
74
+ }
@@ -0,0 +1,18 @@
1
+ import { writeText } from '../utils/writeFile.mjs'
2
+ import { outFile } from '../utils/outputPath.mjs'
3
+ import {
4
+ renderOvertimeText,
5
+ renderOvertimeTab
6
+ } from '../../domain/overtime/render.mjs'
7
+
8
+ export function outputOvertimeText(stats, config) {
9
+ writeText(
10
+ outFile(config.dir, `overtime_${config.base}.txt`),
11
+ renderOvertimeText(stats)
12
+ )
13
+
14
+ writeText(
15
+ outFile(config.dir, `overtime_${config.base}.tab.txt`),
16
+ renderOvertimeTab(stats)
17
+ )
18
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file: getEsmJs.mjs
3
+ * @description:生成ESM文件内容的工具函数
4
+ * @author: King Monkey
5
+ * @created: 2026-01-02 00:03
6
+ */
7
+
8
+ export const getEsmJs = (data) => {
9
+ return `export default ${JSON.stringify(data, null, 2)};\n`
10
+ }
@@ -0,0 +1,14 @@
1
+ import { outFile } from './outputPath.mjs'
2
+ import { writeJson, writeText } from './writeFile.mjs'
3
+
4
+ /* ---------------- helpers ---------------- */
5
+
6
+ export function writeJsonFile(dir, name, data) {
7
+ writeJson(outFile(dir, name), data)
8
+ return name
9
+ }
10
+
11
+ export function writeTxtFile(dir, name, data) {
12
+ writeText(outFile(dir, name), data)
13
+ return name
14
+ }
@@ -0,0 +1,19 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+
4
+ export function resolveOutDir(baseDir = 'output-wukong') {
5
+ const dir = path.isAbsolute(baseDir)
6
+ ? baseDir
7
+ : path.resolve(process.cwd(), baseDir)
8
+
9
+ if (!fs.existsSync(dir)) {
10
+ fs.mkdirSync(dir, { recursive: true })
11
+ }
12
+ return dir
13
+ }
14
+
15
+ export function outFile(dir, filename) {
16
+ const full = path.join(dir, filename)
17
+ fs.mkdirSync(path.dirname(full), { recursive: true })
18
+ return full
19
+ }
@@ -0,0 +1,10 @@
1
+ import fs from 'fs'
2
+
3
+ export function writeText(file, content) {
4
+ fs.writeFileSync(file, content, 'utf8')
5
+ }
6
+
7
+ export function writeJson(file, data) {
8
+ fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8')
9
+ }
10
+
File without changes
@@ -40,16 +40,31 @@ function openBrowser(url) {
40
40
  })
41
41
  }
42
42
 
43
+ // 辅助函数:向上寻找包含 package.json 的根目录
44
+ function findPkgRoot(currentDir) {
45
+ if (fs.existsSync(path.join(currentDir, 'package.json'))) {
46
+ return currentDir
47
+ }
48
+ const parentDir = path.resolve(currentDir, '..')
49
+ if (parentDir === currentDir) return currentDir // 已到系统根目录
50
+ return findPkgRoot(parentDir)
51
+ }
52
+
43
53
  // eslint-disable-next-line default-param-last
44
54
  export function startServer(port = 3000, outputDir) {
45
55
  // 解析包根目录,确保 web 资源在全局安装后也能找到
46
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
47
- const pkgRoot = path.resolve(__dirname, '..')
56
+ const __filename = fileURLToPath(import.meta.url)
57
+ const __dirname = path.dirname(__filename)
58
+
59
+ // 动态寻找根目录
60
+ const pkgRoot = findPkgRoot(__dirname)
61
+
48
62
  const webRoot = path.resolve(pkgRoot, 'web')
49
63
  const dataRoot = outputDir
50
64
  ? path.resolve(outputDir)
51
65
  : path.resolve(process.cwd(), 'output-wukong')
52
66
 
67
+
53
68
  // warn if web directory or data directory doesn't exist
54
69
  if (!fs.existsSync(webRoot)) {
55
70
  console.warn(
@@ -116,7 +131,10 @@ export function startServer(port = 3000, outputDir) {
116
131
  return new Promise((resolve, reject) => {
117
132
  server.on('error', (err) => reject(err))
118
133
  server.listen(port, () => {
119
- const url = `http://localhost:${port}`
134
+ // 监听到的真实端口(当 port 为 0 或系统分配端口时很重要)
135
+ const bound = server.address()
136
+ const actualPort = bound && bound.port ? bound.port : port
137
+ const url = `http://localhost:${actualPort}`
120
138
  console.log(chalk.green(`Server started at ${url}`))
121
139
  console.log(chalk.green(`Serving web/ and output-wukong/data/`))
122
140
 
File without changes
@@ -4,10 +4,20 @@
4
4
  * - 同邮箱多个英文名保持第一个
5
5
  * - 保留原始 author 以便 debug
6
6
  */
7
- export function createAuthorNormalizer() {
7
+ export function createAuthorNormalizer(aliases = {}) {
8
8
  const map = {} // email -> canonical name
9
9
  const originalMap = {} // email -> Set of original author names
10
10
 
11
+ // 预处理别名配置:按 email/name 划分
12
+ const aliasByEmail = {}
13
+ const aliasByName = {}
14
+ for (const [k, v] of Object.entries(aliases || {})) {
15
+ const key = (k || '').trim()
16
+ if (!key) continue
17
+ if (key.includes('@')) aliasByEmail[key] = v
18
+ else aliasByName[key] = v
19
+ }
20
+
11
21
  function isChinese(str) {
12
22
  return /[\u4e00-\u9fa5]/.test(str)
13
23
  }
@@ -25,12 +35,28 @@ export function createAuthorNormalizer() {
25
35
  const n = cleanName(name)
26
36
  const e = cleanEmail(email)
27
37
 
28
- if (!e) return n || 'Unknown'
38
+ if (!e) {
39
+ // 如果没有邮箱,先尝试按名字别名匹配
40
+ if (n && aliasByName[n]) return aliasByName[n]
41
+ return n || 'Unknown'
42
+ }
29
43
 
30
44
  // 记录原始 author
31
45
  if (!originalMap[e]) originalMap[e] = new Set()
32
46
  if (n) originalMap[e].add(n)
33
47
 
48
+ // 先检查 email 别名配置
49
+ if (aliasByEmail[e]) {
50
+ map[e] = aliasByEmail[e]
51
+ return map[e]
52
+ }
53
+
54
+ // 再检查名字别名配置
55
+ if (n && aliasByName[n]) {
56
+ map[e] = aliasByName[n]
57
+ return map[e]
58
+ }
59
+
34
60
  const prev = map[e]
35
61
 
36
62
  // 中文名优先覆盖
@@ -0,0 +1,44 @@
1
+ import dayjs from 'dayjs'
2
+ import isoWeek from 'dayjs/plugin/isoWeek.js'
3
+
4
+ dayjs.extend(isoWeek)
5
+
6
+ /**
7
+ * 自动统计每个作者的日/周/月 changed
8
+ *
9
+ * records: [{
10
+ * author: 'Tom',
11
+ * date: '2025-01-01T12:00:00Z',
12
+ * changed: 123
13
+ * }]
14
+ */
15
+ export function buildAuthorChangeStats(records) {
16
+ const result = {
17
+ daily: {}, // { author: { 'YYYY-MM-DD': x } }
18
+ weekly: {}, // { author: { 'YYYY-WW': x } }
19
+ monthly: {} // { author: { 'YYYY-MM': x } }
20
+ }
21
+
22
+ for (const r of records) {
23
+ const author = r.author || 'Unknown'
24
+ const changed = Number(r.changed || 0)
25
+
26
+ const d = dayjs(r.date)
27
+
28
+ const dayKey = d.format('YYYY-MM-DD')
29
+ const weekKey = `${d.format('GGGG')}-W${d.isoWeek().toString().padStart(2, '0')}`
30
+ const monthKey = d.format('YYYY-MM')
31
+
32
+ if (!result.daily[author]) result.daily[author] = {}
33
+ if (!result.weekly[author]) result.weekly[author] = {}
34
+ if (!result.monthly[author]) result.monthly[author] = {}
35
+
36
+ result.daily[author][dayKey] = (result.daily[author][dayKey] || 0) + changed
37
+ result.weekly[author][weekKey] =
38
+ (result.weekly[author][weekKey] || 0) + changed
39
+ result.monthly[author][monthKey] =
40
+ (result.monthly[author][monthKey] || 0) + changed
41
+ }
42
+
43
+ return result
44
+ }
@@ -0,0 +1,13 @@
1
+ export function deepMerge(target, source) {
2
+ for (const key in source) {
3
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
4
+ // eslint-disable-next-line no-param-reassign
5
+ if (!target[key]) target[key] = {};
6
+ deepMerge(target[key], source[key]);
7
+ } else if (source[key] !== undefined) {
8
+ // eslint-disable-next-line no-param-reassign
9
+ target[key] = source[key];
10
+ }
11
+ }
12
+ return target;
13
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ export const getPackage = () => {
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const pkg = JSON.parse(
8
+ fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8')
9
+ )
10
+ return pkg
11
+ }
@@ -0,0 +1,12 @@
1
+ import path from 'path'
2
+
3
+ import { EXPORT_DIR_PROFILE } from '#src/constants/index.mjs'
4
+ import { outFile } from '#src/output/utils/outputPath.mjs'
5
+
6
+ export const getProfileDirFile = (fileName, opts) => {
7
+ const config = { dir: opts.output.dir || path.resolve('output-wukong') }
8
+ const baseDir = `${config.dir}/${EXPORT_DIR_PROFILE}`
9
+ const filePath = outFile(baseDir, fileName)
10
+
11
+ return filePath
12
+ }
@@ -1,18 +1,17 @@
1
+ /**
2
+ * @file: groupRecords.mjs
3
+ * @description: 按指定周期对提交记录进行分组
4
+ * @author: King Monkey
5
+ * @created: 2026-01-04 00:10
6
+ */
7
+
1
8
  import dayjs from 'dayjs'
2
9
  import isoWeek from 'dayjs/plugin/isoWeek.js'
3
- import fs from 'fs'
10
+
4
11
 
5
12
  // add ISO week plugin to dayjs once when module loaded
6
13
  dayjs.extend(isoWeek)
7
14
 
8
- export function writeJSON(file, data) {
9
- fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8')
10
- }
11
-
12
- export function writeTextFile(file, text) {
13
- fs.writeFileSync(file, text, 'utf8')
14
- }
15
-
16
15
  export function groupRecords(records, mode) {
17
16
  const group = {}
18
17
 
@@ -1,2 +1,5 @@
1
- export { groupRecords, writeJSON, writeTextFile } from './file.mjs'
2
- export * from './output.mjs'
1
+ import dayjs from 'dayjs'
2
+
3
+ export const getCurrentTimestampSecond = () => {
4
+ return dayjs().format('YYYY-MM-DD HH:mm:ss')
5
+ }
@@ -34,15 +34,6 @@ if (!process.stdout.isTTY || chalk.level === 0) {
34
34
  let cachedDay = ''
35
35
  let cachedPath = ''
36
36
 
37
- // // 彩色前缀
38
- // const prefix = {
39
- // info: chalk.cyan('ℹ'),
40
- // success: chalk.green('✔'),
41
- // error: chalk.red('✖'),
42
- // warn: chalk.yellow('⚠'),
43
- // debug: chalk.gray('➤')
44
- // }
45
-
46
37
  // 🔧 1. 修改 prefix:只保留 emoji,不带颜色
47
38
  const prefix = {
48
39
  info: e('➤', '[i]'),
@@ -89,6 +80,20 @@ const getLogFilePath = () => {
89
80
  return cachedPath
90
81
  }
91
82
 
83
+ // 格式化参数为字符串
84
+ const formatArgs = (args) => {
85
+ return args.map(arg => {
86
+ if (typeof arg === 'object' && arg !== null) {
87
+ try {
88
+ return JSON.stringify(arg, null, 2)
89
+ } catch (err) {
90
+ return String(arg)
91
+ }
92
+ }
93
+ return String(arg)
94
+ }).join(' ')
95
+ }
96
+
92
97
  // 写入日志(同步 + 追加)
93
98
  const writeToFile = (level, msg, newline) => {
94
99
  const logPath = getLogFilePath()
@@ -105,6 +110,8 @@ const writeToFile = (level, msg, newline) => {
105
110
  function createLogger(level, colorFn, outFn = console.log) {
106
111
  return (...args) => {
107
112
  let options = {}
113
+
114
+ // 检查最后一个参数是否是选项对象
108
115
  if (
109
116
  args.length &&
110
117
  typeof args[args.length - 1] === 'object' &&
@@ -114,22 +121,26 @@ function createLogger(level, colorFn, outFn = console.log) {
114
121
  options = args.pop()
115
122
  }
116
123
 
117
- const msg = args
118
- .map((arg) =>
119
- typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
120
- )
121
- .join(' ')
124
+ // 处理剩余的参数
125
+ const formattedArgs = formatArgs(args)
126
+ const displayMsg = args.length === 1 && typeof args[0] === 'string'
127
+ ? args[0]
128
+ : formattedArgs
122
129
 
123
130
  // 🔧 2. 修改拼接方式,colorFn 统一处理 prefix + msg
124
- const line = `${shortTimestamp()} ${colorFn(`${prefix[level]} ${msg}`)}`
131
+ const timestamp = shortTimestamp()
132
+ const prefixStr = prefix[level]
125
133
 
126
134
  if (options.newline) {
127
135
  outFn('')
128
136
  }
129
- outFn(line)
130
137
 
138
+ // 输出到控制台,保留参数的原格式
139
+ outFn(timestamp, colorFn(prefixStr), ...args)
140
+
141
+ // 写入文件时使用格式化的字符串
131
142
  if (options.write) {
132
- writeToFile(level, msg, options.newline)
143
+ writeToFile(level, displayMsg, options.newline)
133
144
  }
134
145
  }
135
146
  }
@@ -1,101 +0,0 @@
1
- import chalk from 'chalk'
2
-
3
- /* ================= utils ================= */
4
-
5
- const formatTime = (ms) => {
6
- if (ms < 1) return `${(ms * 1000).toFixed(2)} μs`
7
- if (ms < 1000) return `${ms.toFixed(2)} ms`
8
- if (ms < 60000) return `${(ms / 1000).toFixed(2)} s`
9
- return `${(ms / 60000).toFixed(2)} min`
10
- }
11
-
12
- const makeBar = (ratio, width = 32) => {
13
- const len = Math.max(1, Math.round(ratio * width))
14
- return '█'.repeat(len)
15
- }
16
-
17
- /* ================= profiler ================= */
18
-
19
- export const createProfiler = ({
20
- enabled = false,
21
- verbose = false,
22
- flame = false,
23
- slowThreshold = 500, // ms
24
- hotThreshold = 0.8 // ratio (0~1)
25
- } = {}) => {
26
- const start = process.hrtime.bigint()
27
- let last = start
28
- const events = []
29
-
30
- const toMs = (a, b) => Number(b - a) / 1e6
31
-
32
- /* -------- step -------- */
33
- const step = (name, meta = {}) => {
34
- const now = process.hrtime.bigint()
35
- const duration = toMs(last, now)
36
- last = now
37
-
38
- events.push({
39
- name,
40
- duration,
41
- sinceStart: toMs(start, now),
42
- meta
43
- })
44
-
45
- if (!enabled && !verbose) return
46
-
47
- const isSlow = duration >= slowThreshold
48
-
49
- console.log(
50
- chalk.gray(' ├─'),
51
- isSlow ? chalk.red.bold(name) : chalk.white(name),
52
- chalk.yellow(formatTime(duration))
53
- )
54
- }
55
-
56
- /* -------- end -------- */
57
- const end = (label = 'Total') => {
58
- const endTime = process.hrtime.bigint()
59
- const total = toMs(start, endTime)
60
-
61
- // 计算 HOT
62
- for (const e of events) {
63
- e.ratio = total > 0 ? e.duration / total : 0
64
- e.hot = e.ratio >= hotThreshold
65
- }
66
-
67
- if (enabled || verbose) {
68
- console.log(
69
- chalk.cyan('⏱'),
70
- chalk.bold(label),
71
- chalk.yellow(formatTime(total))
72
- )
73
-
74
- if (flame) {
75
- for (const e of events) {
76
- const bar = makeBar(e.ratio)
77
- const hotMark = e.hot ? chalk.red.bold(' 🔥 HOT') : ''
78
- const slowMark =
79
- !e.hot && e.duration >= slowThreshold
80
- ? chalk.yellow(' ⚠ SLOW')
81
- : ''
82
-
83
- console.log(
84
- chalk.gray(' ├─'),
85
- chalk.white(e.name.padEnd(24)),
86
- chalk.yellow(formatTime(e.duration)),
87
- chalk.gray(bar),
88
- hotMark || slowMark
89
- )
90
- }
91
- }
92
- }
93
-
94
- return {
95
- total,
96
- events
97
- }
98
- }
99
-
100
- return { step, end }
101
- }
@@ -0,0 +1,11 @@
1
+ export function resolveBool(cli, config, def = false) {
2
+ if (cli !== undefined) return cli
3
+ if (config !== undefined) return config
4
+ return def
5
+ }
6
+
7
+ export function resolveValue(cli, config, def) {
8
+ if (cli !== undefined) return cli
9
+ if (config !== undefined) return config
10
+ return def
11
+ }
@@ -1,10 +1,14 @@
1
1
  import os from 'os'
2
2
  import process from 'process'
3
+
3
4
  import { colors } from './colors.mjs'
4
5
 
5
6
 
6
- export function showVersionInfo(VERSION) {
7
- console.log(colors.title(`\nwukong-deploy v${VERSION}\n`))
7
+
8
+ export function showVersionInfo(pkg) {
9
+ const { name, version } = pkg
10
+
11
+ console.log(colors.title(`\n${name} v${version}\n`))
8
12
  console.log(`${colors.bold('Node.js')}: ${process.version}`)
9
13
  console.log(`${colors.bold('Platform')}: ${os.platform()} ${os.arch()}`)
10
14
  }
File without changes
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line no-promise-executor-return
2
+ export const wait = (time) => new Promise((res) => setTimeout(res, time))