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
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import dayjs from 'dayjs'
|
|
2
|
-
|
|
3
|
-
// eslint-disable-next-line no-unused-vars
|
|
4
|
-
import { renderAuthorChangesJson } from '../json.mjs'
|
|
5
|
-
import { startServer } from '../server.mjs'
|
|
6
|
-
import { getWeekRange } from '../utils/getWeekRange.mjs'
|
|
7
|
-
import { groupRecords, outputFilePath, writeTextFile } from '../utils/index.mjs'
|
|
8
|
-
import { logDev } from '../utils/logDev.mjs'
|
|
9
|
-
|
|
10
|
-
export const handleServe = async ({
|
|
11
|
-
opts,
|
|
12
|
-
outDir,
|
|
13
|
-
records,
|
|
14
|
-
getOvertimeStats
|
|
15
|
-
}) => {
|
|
16
|
-
try {
|
|
17
|
-
const stats = getOvertimeStats(records)
|
|
18
|
-
|
|
19
|
-
const dataCommitsFile = outputFilePath('data/commits.mjs', outDir)
|
|
20
|
-
const commitsModule = `export default ${JSON.stringify(records, null, 2)};\n`
|
|
21
|
-
writeTextFile(dataCommitsFile, commitsModule)
|
|
22
|
-
|
|
23
|
-
const dataCommitsChangedFile = outputFilePath(
|
|
24
|
-
'data/author-changes.mjs',
|
|
25
|
-
outDir
|
|
26
|
-
)
|
|
27
|
-
const jsonText = renderAuthorChangesJson(records)
|
|
28
|
-
|
|
29
|
-
const commitsChangedModule = `export default ${JSON.stringify(jsonText, null, 2)};\n`
|
|
30
|
-
writeTextFile(dataCommitsChangedFile, commitsChangedModule)
|
|
31
|
-
|
|
32
|
-
const dataStatsFile = outputFilePath('data/overtime-stats.mjs', outDir)
|
|
33
|
-
const statsModule = `export default ${JSON.stringify(stats, null, 2)};\n`
|
|
34
|
-
writeTextFile(dataStatsFile, statsModule)
|
|
35
|
-
|
|
36
|
-
// 新增:每周趋势数据(用于前端图表)
|
|
37
|
-
const weekGroups = groupRecords(records, 'week')
|
|
38
|
-
const weekKeys = Object.keys(weekGroups).sort()
|
|
39
|
-
const weeklySeries = weekKeys.map((k) => {
|
|
40
|
-
const s = getOvertimeStats(weekGroups[k])
|
|
41
|
-
return {
|
|
42
|
-
period: k,
|
|
43
|
-
range: getWeekRange(k),
|
|
44
|
-
total: s.total,
|
|
45
|
-
outsideWorkCount: s.outsideWorkCount,
|
|
46
|
-
outsideWorkRate: s.outsideWorkRate,
|
|
47
|
-
nonWorkdayCount: s.nonWorkdayCount,
|
|
48
|
-
nonWorkdayRate: s.nonWorkdayRate
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
const dataWeeklyFile = outputFilePath('data/overtime-weekly.mjs', outDir)
|
|
52
|
-
const weeklyModule = `export default ${JSON.stringify(weeklySeries, null, 2)};\n`
|
|
53
|
-
writeTextFile(dataWeeklyFile, weeklyModule)
|
|
54
|
-
logDev(`Weekly series 已导出: ${dataWeeklyFile}`)
|
|
55
|
-
|
|
56
|
-
// 新增:每月趋势数据(用于前端图表)
|
|
57
|
-
const monthGroups2 = groupRecords(records, 'month')
|
|
58
|
-
const monthKeys2 = Object.keys(monthGroups2).sort()
|
|
59
|
-
const monthlySeries = monthKeys2.map((k) => {
|
|
60
|
-
const s = getOvertimeStats(monthGroups2[k])
|
|
61
|
-
return {
|
|
62
|
-
period: k,
|
|
63
|
-
total: s.total,
|
|
64
|
-
outsideWorkCount: s.outsideWorkCount,
|
|
65
|
-
outsideWorkRate: s.outsideWorkRate,
|
|
66
|
-
nonWorkdayCount: s.nonWorkdayCount,
|
|
67
|
-
nonWorkdayRate: s.nonWorkdayRate
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
const dataMonthlyFile = outputFilePath('data/overtime-monthly.mjs', outDir)
|
|
71
|
-
const monthlyModule = `export default ${JSON.stringify(monthlySeries, null, 2)};\n`
|
|
72
|
-
writeTextFile(dataMonthlyFile, monthlyModule)
|
|
73
|
-
logDev(`Monthly series 已导出: ${dataMonthlyFile}`)
|
|
74
|
-
|
|
75
|
-
// 新增:每日最晚提交小时(用于显著展示加班严重程度)
|
|
76
|
-
const dayGroups2 = groupRecords(records, 'day')
|
|
77
|
-
const dayKeys2 = Object.keys(dayGroups2).sort()
|
|
78
|
-
|
|
79
|
-
// 次日凌晨归并窗口(默认 6 点前仍算前一天的加班)
|
|
80
|
-
const overnightCutoff = Number.isFinite(opts.overnightCutoff)
|
|
81
|
-
? opts.overnightCutoff
|
|
82
|
-
: 6
|
|
83
|
-
// 次日上班时间(默认按 workStart,若未指定则 9 点)
|
|
84
|
-
const workStartHour =
|
|
85
|
-
opts.workStart || opts.workStart === 0 ? opts.workStart : 9
|
|
86
|
-
const workEndHour = opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18
|
|
87
|
-
|
|
88
|
-
// 有些日期「本身没有 commit」,但第二天凌晨有提交要归并到这一天,
|
|
89
|
-
// 需要补出这些“虚拟日期”,否则 latestByDay 会漏掉这天。
|
|
90
|
-
const virtualPrevDays = new Set()
|
|
91
|
-
records.forEach((r) => {
|
|
92
|
-
const d = new Date(r.date)
|
|
93
|
-
if (Number.isNaN(d.valueOf())) return
|
|
94
|
-
const h = d.getHours()
|
|
95
|
-
if (h < 0 || h >= overnightCutoff || h >= workStartHour) return
|
|
96
|
-
const curDay = dayjs(d).format('YYYY-MM-DD')
|
|
97
|
-
const prevDay = dayjs(curDay).subtract(1, 'day').format('YYYY-MM-DD')
|
|
98
|
-
if (!dayGroups2[prevDay]) {
|
|
99
|
-
virtualPrevDays.add(prevDay)
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
const allDayKeys = Array.from(
|
|
104
|
-
new Set([...dayKeys2, ...virtualPrevDays])
|
|
105
|
-
).sort()
|
|
106
|
-
|
|
107
|
-
const latestByDay = allDayKeys.map((k) => {
|
|
108
|
-
const list = dayGroups2[k] || []
|
|
109
|
-
|
|
110
|
-
// 1) 当天「下班后」的提交:只统计 >= workEndHour 的小时
|
|
111
|
-
const sameDayHours = list
|
|
112
|
-
.map((r) => new Date(r.date))
|
|
113
|
-
.filter((d) => !Number.isNaN(d.valueOf()))
|
|
114
|
-
.map((d) => d.getHours())
|
|
115
|
-
.filter((h) => h >= workEndHour && h < 24)
|
|
116
|
-
|
|
117
|
-
// 2) 次日凌晨、但仍算前一日加班的提交
|
|
118
|
-
const nextKey = dayjs(k).add(1, 'day').format('YYYY-MM-DD')
|
|
119
|
-
const early = dayGroups2[nextKey] || []
|
|
120
|
-
const earlyHours = early
|
|
121
|
-
.map((r) => new Date(r.date))
|
|
122
|
-
.filter((d) => !Number.isNaN(d.valueOf()))
|
|
123
|
-
.map((d) => d.getHours())
|
|
124
|
-
// 只看 [0, overnightCutoff) 之间的小时,
|
|
125
|
-
// 并且默认认为 < workStartHour 属于「次日上班前」
|
|
126
|
-
.filter(
|
|
127
|
-
(h) =>
|
|
128
|
-
h >= 0 &&
|
|
129
|
-
h < overnightCutoff &&
|
|
130
|
-
// 保护性判断:若有人把 overnightCutoff 设得大于上班时间,
|
|
131
|
-
// 我们仍然只统计到上班时间为止
|
|
132
|
-
h < workStartHour
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
// 3) 计算「逻辑上的最晚加班时间」
|
|
136
|
-
// - 当天晚上的用原始小时(如 22 点)
|
|
137
|
-
// - 次日凌晨的用 24 + 小时(如 1 点 → 25)
|
|
138
|
-
const overtimeValues = [
|
|
139
|
-
...sameDayHours.map((h) => h),
|
|
140
|
-
...earlyHours.map((h) => 24 + h)
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
if (overtimeValues.length === 0) {
|
|
144
|
-
// 这一天没有任何「下班后到次日上班前」的提交
|
|
145
|
-
return {
|
|
146
|
-
date: k,
|
|
147
|
-
latestHour: null,
|
|
148
|
-
latestHourNormalized: null
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const latestHourNormalized = Math.max(...overtimeValues)
|
|
153
|
-
|
|
154
|
-
// latestHour 保留「当天自然日内」的最晚提交通常小时数,供前端需要时参考
|
|
155
|
-
const sameDayMax =
|
|
156
|
-
sameDayHours.length > 0 ? Math.max(...sameDayHours) : null
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
date: k,
|
|
160
|
-
latestHour: sameDayMax,
|
|
161
|
-
latestHourNormalized
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
const dataLatestByDayFile = outputFilePath(
|
|
165
|
-
'data/overtime-latest-by-day.mjs',
|
|
166
|
-
outDir
|
|
167
|
-
)
|
|
168
|
-
const latestByDayModule = `export default ${JSON.stringify(latestByDay, null, 2)};\n`
|
|
169
|
-
writeTextFile(dataLatestByDayFile, latestByDayModule)
|
|
170
|
-
logDev(`Latest-by-day series 已导出: ${dataLatestByDayFile}`)
|
|
171
|
-
|
|
172
|
-
// 导出配置(供前端显示)
|
|
173
|
-
try {
|
|
174
|
-
const configFile = outputFilePath('data/config.mjs', outDir)
|
|
175
|
-
const cfg = {
|
|
176
|
-
startHour: opts.workStart || 9,
|
|
177
|
-
endHour: opts.workEnd || 18,
|
|
178
|
-
lunchStart: opts.lunchStart || 12,
|
|
179
|
-
lunchEnd: opts.lunchEnd || 14,
|
|
180
|
-
overnightCutoff
|
|
181
|
-
}
|
|
182
|
-
writeTextFile(
|
|
183
|
-
configFile,
|
|
184
|
-
`export default ${JSON.stringify(cfg, null, 2)};\n`
|
|
185
|
-
)
|
|
186
|
-
logDev(`Config 已导出: ${configFile}`)
|
|
187
|
-
} catch (e) {
|
|
188
|
-
console.warn('Export config failed:', e && e.message ? e.message : e)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
startServer(opts.port || 3000, outDir).catch((err) => {
|
|
192
|
-
console.warn(
|
|
193
|
-
'Start server failed.',
|
|
194
|
-
err && err.message ? err.message : err
|
|
195
|
-
)
|
|
196
|
-
})
|
|
197
|
-
} catch (err) {
|
|
198
|
-
console.warn(
|
|
199
|
-
'Export data modules failed:',
|
|
200
|
-
err && err.message ? err.message : err
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
}
|
package/src/lib/configStore.mjs
DELETED
package/src/lib/memoize.mjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// memoize.mjs
|
|
2
|
-
export function memoize(fn) {
|
|
3
|
-
const cache = new Map()
|
|
4
|
-
|
|
5
|
-
return function (...args) {
|
|
6
|
-
const key = JSON.stringify(args)
|
|
7
|
-
if (cache.has(key)) {
|
|
8
|
-
return cache.get(key)
|
|
9
|
-
}
|
|
10
|
-
const result = fn(...args)
|
|
11
|
-
cache.set(key, result)
|
|
12
|
-
return result
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import boxen from 'boxen'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import isOnline from 'is-online'
|
|
4
|
-
import os from 'os'
|
|
5
|
-
import path from 'path'
|
|
6
|
-
import semver from 'semver'
|
|
7
|
-
|
|
8
|
-
import { colors } from './colors.mjs'
|
|
9
|
-
|
|
10
|
-
const CONFIG_DIR = path.join(os.homedir(), '.config', 'configstore')
|
|
11
|
-
|
|
12
|
-
const getCacheFile = (pkg) =>
|
|
13
|
-
path.join(CONFIG_DIR, `update-notifier-${pkg.name}.json`)
|
|
14
|
-
|
|
15
|
-
async function fetchLatestVersion(pkgName, timeout = 1500) {
|
|
16
|
-
const url = `https://registry.npmjs.org/${pkgName}/latest`
|
|
17
|
-
const controller = new AbortController()
|
|
18
|
-
const timer = setTimeout(() => controller.abort(), timeout)
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const res = await fetch(url, { signal: controller.signal })
|
|
22
|
-
clearTimeout(timer)
|
|
23
|
-
if (!res.ok) throw new Error('fetch fail')
|
|
24
|
-
const data = await res.json()
|
|
25
|
-
return data.version
|
|
26
|
-
} catch (e) {
|
|
27
|
-
clearTimeout(timer)
|
|
28
|
-
// 超时或其他错误都返回 null,防止程序卡死
|
|
29
|
-
return null
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 格式化升级提示信息
|
|
35
|
-
*/
|
|
36
|
-
export function formatUpdateMessage(current, latest, name) {
|
|
37
|
-
const arrow = colors.dim('→')
|
|
38
|
-
const currentVer = colors.dim(current)
|
|
39
|
-
const latestVer = colors.green(latest)
|
|
40
|
-
|
|
41
|
-
const message =
|
|
42
|
-
`${colors.dim('Update available')}` +
|
|
43
|
-
` ${currentVer} ${arrow} ${latestVer}\n` +
|
|
44
|
-
`${colors.dim('Run')} ${colors.cyanish(`npm i -g ${name}`)} ${colors.dim(' to update')}`
|
|
45
|
-
|
|
46
|
-
return boxen(message, {
|
|
47
|
-
padding: 1,
|
|
48
|
-
margin: 1,
|
|
49
|
-
borderStyle: 'round',
|
|
50
|
-
borderColor: 'yellow'
|
|
51
|
-
})
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 检查更新(带缓存 + 支持 patch 更新)
|
|
56
|
-
*/
|
|
57
|
-
export async function checkUpdateWithPatch({
|
|
58
|
-
pkg,
|
|
59
|
-
interval = 24 * 60 * 60 * 1000,
|
|
60
|
-
// interval = 6 * 1000,
|
|
61
|
-
force = false
|
|
62
|
-
} = {}) {
|
|
63
|
-
const online = await isOnline()
|
|
64
|
-
// console.log('online', online)
|
|
65
|
-
if (!online) return null
|
|
66
|
-
const now = Date.now()
|
|
67
|
-
const CACHE_FILE = getCacheFile(pkg)
|
|
68
|
-
|
|
69
|
-
let cache = {}
|
|
70
|
-
if (fs.existsSync(CACHE_FILE)) {
|
|
71
|
-
try {
|
|
72
|
-
cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')) || {}
|
|
73
|
-
// eslint-disable-next-line no-empty
|
|
74
|
-
} catch {}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// console.log('now - cache.lastCheck', now - cache.lastCheck, interval)
|
|
78
|
-
if (!force && cache.lastCheck && now - cache.lastCheck < interval) {
|
|
79
|
-
return cache.updateInfo || null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const latest = await fetchLatestVersion(pkg.name)
|
|
83
|
-
if (!latest) {
|
|
84
|
-
// console.log('无法获取最新版本')
|
|
85
|
-
return null
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (semver.lt(pkg.version, latest)) {
|
|
89
|
-
// 构造 update 对象,兼容 update-notifier
|
|
90
|
-
const updateInfo = {
|
|
91
|
-
current: pkg.version,
|
|
92
|
-
latest,
|
|
93
|
-
type: semver.diff(pkg.version, latest) || 'patch',
|
|
94
|
-
name: pkg.name,
|
|
95
|
-
// 这里官方还会有 message 字段
|
|
96
|
-
// 用官方的默认消息格式生成,方便notify打印
|
|
97
|
-
message: `\nUpdate available ${pkg.version} → ${latest}\nRun npm i -g ${pkg.name} to update\n`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 缓存
|
|
101
|
-
try {
|
|
102
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
103
|
-
fs.writeFileSync(
|
|
104
|
-
CACHE_FILE,
|
|
105
|
-
JSON.stringify(
|
|
106
|
-
{
|
|
107
|
-
lastCheck: now,
|
|
108
|
-
updateInfo
|
|
109
|
-
},
|
|
110
|
-
null,
|
|
111
|
-
2
|
|
112
|
-
)
|
|
113
|
-
)
|
|
114
|
-
} catch (e) {
|
|
115
|
-
console.error('缓存写入失败:', e)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const { current } = updateInfo
|
|
119
|
-
console.log(formatUpdateMessage(current, latest, pkg.name))
|
|
120
|
-
return updateInfo
|
|
121
|
-
}
|
|
122
|
-
// 无更新,刷新缓存时间
|
|
123
|
-
try {
|
|
124
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
125
|
-
fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: now }, null, 2))
|
|
126
|
-
} catch (e) {
|
|
127
|
-
console.error('缓存写入失败:', e)
|
|
128
|
-
}
|
|
129
|
-
return null
|
|
130
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Chalk } from 'chalk'
|
|
2
|
-
import { performance } from 'perf_hooks'
|
|
3
|
-
|
|
4
|
-
// 强制开启 truecolor chalk v5
|
|
5
|
-
import { costTimer } from './timer.mjs'
|
|
6
|
-
|
|
7
|
-
const chalk = new Chalk({ level: 3 }) // 强制开启 truecolor chalk v5
|
|
8
|
-
|
|
9
|
-
// 退出前打印耗时并退出
|
|
10
|
-
export const exitWithTime = (start, exitCode = 0, exitNow = false) => {
|
|
11
|
-
const end = performance.now()
|
|
12
|
-
const timeMsg = costTimer(start, end)
|
|
13
|
-
console.log(chalk.green(`\n${timeMsg}\n`))
|
|
14
|
-
if (exitNow) {
|
|
15
|
-
process.exit(exitCode)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Chalk } from 'chalk'
|
|
2
|
-
import { performance } from 'perf_hooks'
|
|
3
|
-
|
|
4
|
-
import { exitWithTime } from './exitWithTime.mjs'
|
|
5
|
-
import { costTimer } from './timer.mjs'
|
|
6
|
-
|
|
7
|
-
export const handleSuccess = ({ message = 'Done', spinner }) => {
|
|
8
|
-
spinner.succeed(message)
|
|
9
|
-
}
|
package/src/utils/logDev.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file: devLog.mjs
|
|
3
|
-
* @description:
|
|
4
|
-
* @author: King Monkey
|
|
5
|
-
* @created: 2025-08-04 16:25
|
|
6
|
-
*/
|
|
7
|
-
import { getConfig } from '../lib/configStore.mjs'
|
|
8
|
-
import { logger } from './logger.mjs'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 开发环境下输出调试信息
|
|
12
|
-
* 推荐用于局部调试
|
|
13
|
-
*/
|
|
14
|
-
export function logDev(...args) {
|
|
15
|
-
if (getConfig('debug')) {
|
|
16
|
-
const msg = args.join(' ')
|
|
17
|
-
logger.debug(msg)
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/utils/output.mjs
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
export function ensureOutputDir(customDir) {
|
|
5
|
-
// If a custom absolute/relative path is provided, resolve relative to cwd as-is
|
|
6
|
-
// Otherwise default to `output-wukong` inside current working directory.
|
|
7
|
-
const dir = customDir
|
|
8
|
-
? path.resolve(process.cwd(), customDir)
|
|
9
|
-
: path.resolve(process.cwd(), 'output-wukong');
|
|
10
|
-
|
|
11
|
-
if (!fs.existsSync(dir)) {
|
|
12
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return dir;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function outputFilePath(filename, customDir) {
|
|
19
|
-
const dir = ensureOutputDir(customDir);
|
|
20
|
-
const fullpath = path.join(dir, filename);
|
|
21
|
-
const parent = path.dirname(fullpath);
|
|
22
|
-
if (!fs.existsSync(parent)) {
|
|
23
|
-
fs.mkdirSync(parent, { recursive: true });
|
|
24
|
-
}
|
|
25
|
-
return fullpath;
|
|
26
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export const diffProfiles = (prev, curr, threshold = 0.2) => {
|
|
2
|
-
const map = new Map()
|
|
3
|
-
|
|
4
|
-
prev.events.forEach(e => {
|
|
5
|
-
map.set(e.name, e.duration)
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
const regressions = []
|
|
9
|
-
|
|
10
|
-
curr.events.forEach(e => {
|
|
11
|
-
const before = map.get(e.name)
|
|
12
|
-
if (!before) return
|
|
13
|
-
|
|
14
|
-
const diff = (e.duration - before) / before
|
|
15
|
-
if (diff >= threshold) {
|
|
16
|
-
regressions.push({
|
|
17
|
-
name: e.name,
|
|
18
|
-
before,
|
|
19
|
-
after: e.duration,
|
|
20
|
-
diff
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
return regressions
|
|
26
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const formatTime = (ms) => {
|
|
2
|
-
if (ms < 1) return `${(ms * 1000).toFixed(2)} μs`
|
|
3
|
-
if (ms < 1000) return `${ms.toFixed(2)} ms`
|
|
4
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(2)} s`
|
|
5
|
-
return `${(ms / 60000).toFixed(2)} min`
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const makeBar = (ratio, width = 32) => {
|
|
9
|
-
const len = Math.max(1, Math.round(ratio * width))
|
|
10
|
-
return '█'.repeat(len)
|
|
11
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import { formatTime, makeBar } from './format.mjs'
|
|
4
|
-
import { exportChromeTrace } from './trace.mjs'
|
|
5
|
-
import { diffProfiles } from './diff.mjs'
|
|
6
|
-
|
|
7
|
-
export const createProfiler = ({
|
|
8
|
-
enabled = false,
|
|
9
|
-
verbose = false,
|
|
10
|
-
flame = false,
|
|
11
|
-
slowThreshold = 500,
|
|
12
|
-
hotThreshold = 0.8,
|
|
13
|
-
traceFile,
|
|
14
|
-
failOnHot = false,
|
|
15
|
-
diffBaseFile,
|
|
16
|
-
diffThreshold = 0.2
|
|
17
|
-
} = {}) => {
|
|
18
|
-
const start = process.hrtime.bigint()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const stack = []
|
|
22
|
-
const events = []
|
|
23
|
-
|
|
24
|
-
const toMs = (a, b) => Number(b - a) / 1e6
|
|
25
|
-
|
|
26
|
-
/* ================= step ================= */
|
|
27
|
-
|
|
28
|
-
const step = (name, fn) => {
|
|
29
|
-
const parent = stack[stack.length - 1]
|
|
30
|
-
const startTime = process.hrtime.bigint()
|
|
31
|
-
|
|
32
|
-
const node = {
|
|
33
|
-
name,
|
|
34
|
-
start: toMs(start, startTime),
|
|
35
|
-
duration: 0,
|
|
36
|
-
depth: stack.length,
|
|
37
|
-
children: []
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (parent) parent.children.push(node)
|
|
41
|
-
stack.push(node)
|
|
42
|
-
|
|
43
|
-
const finish = () => {
|
|
44
|
-
const endTime = process.hrtime.bigint()
|
|
45
|
-
node.duration = toMs(startTime, endTime)
|
|
46
|
-
events.push(node)
|
|
47
|
-
stack.pop()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (typeof fn === 'function') {
|
|
51
|
-
try {
|
|
52
|
-
return fn()
|
|
53
|
-
} finally {
|
|
54
|
-
finish()
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
finish()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* ================= end ================= */
|
|
62
|
-
|
|
63
|
-
const end = (label = 'Total') => {
|
|
64
|
-
const total = toMs(start, process.hrtime.bigint())
|
|
65
|
-
|
|
66
|
-
let hasHot = false
|
|
67
|
-
|
|
68
|
-
if (enabled || verbose) {
|
|
69
|
-
console.log(
|
|
70
|
-
chalk.cyan('⏱'),
|
|
71
|
-
chalk.bold(label),
|
|
72
|
-
chalk.yellow(formatTime(total))
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
if (flame) {
|
|
76
|
-
for (const e of events) {
|
|
77
|
-
const ratio = e.duration / total
|
|
78
|
-
const hot = ratio >= hotThreshold
|
|
79
|
-
const slow = e.duration >= slowThreshold
|
|
80
|
-
|
|
81
|
-
if (hot) hasHot = true
|
|
82
|
-
|
|
83
|
-
console.log(
|
|
84
|
-
chalk.gray(`${' '.repeat(e.depth) }├─`),
|
|
85
|
-
chalk.white(e.name.padEnd(22)),
|
|
86
|
-
chalk.yellow(formatTime(e.duration)),
|
|
87
|
-
chalk.gray(makeBar(ratio)),
|
|
88
|
-
hot
|
|
89
|
-
? chalk.red.bold(' 🔥 HOT')
|
|
90
|
-
: slow
|
|
91
|
-
? chalk.yellow(' ⚠ SLOW')
|
|
92
|
-
: ''
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const profile = { total, events }
|
|
99
|
-
|
|
100
|
-
/* ---------- Chrome Trace ---------- */
|
|
101
|
-
|
|
102
|
-
if (traceFile) {
|
|
103
|
-
exportChromeTrace(events, traceFile)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/* ---------- diff ---------- */
|
|
107
|
-
if (diffBaseFile && fs.existsSync(diffBaseFile)) {
|
|
108
|
-
const base = JSON.parse(fs.readFileSync(diffBaseFile, 'utf8'))
|
|
109
|
-
const regressions = diffProfiles(base, profile, diffThreshold)
|
|
110
|
-
|
|
111
|
-
if (regressions.length) {
|
|
112
|
-
console.log(chalk.red('\n⚠ Performance Regression Detected:\n'))
|
|
113
|
-
regressions.forEach(r => {
|
|
114
|
-
console.log(
|
|
115
|
-
chalk.red(
|
|
116
|
-
` ${r.name}: ${formatTime(r.before)} → ${formatTime(
|
|
117
|
-
r.after
|
|
118
|
-
)} (+${(r.diff * 100).toFixed(1)}%)`
|
|
119
|
-
)
|
|
120
|
-
)
|
|
121
|
-
})
|
|
122
|
-
process.exitCode = 1
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/* ---------- save profile ---------- */
|
|
127
|
-
if (enabled) {
|
|
128
|
-
fs.writeFileSync(
|
|
129
|
-
'profile.json',
|
|
130
|
-
JSON.stringify(profile, null, 2)
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/* ---------- CI HOT fail ---------- */
|
|
135
|
-
if (failOnHot && hasHot) {
|
|
136
|
-
console.log(chalk.red('\n🔥 HOT step detected — failing CI'))
|
|
137
|
-
process.exitCode = 1
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return profile
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { step, end }
|
|
144
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
|
|
3
|
-
export const exportChromeTrace = (events, file) => {
|
|
4
|
-
const traceEvents = []
|
|
5
|
-
|
|
6
|
-
for (const e of events) {
|
|
7
|
-
traceEvents.push({
|
|
8
|
-
name: e.name,
|
|
9
|
-
cat: 'cli',
|
|
10
|
-
ph: 'X', // complete event
|
|
11
|
-
ts: e.start * 1000, // μs
|
|
12
|
-
dur: e.duration * 1000, // μs
|
|
13
|
-
pid: 1,
|
|
14
|
-
tid: e.depth || 0
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const trace = {
|
|
19
|
-
traceEvents,
|
|
20
|
-
displayTimeUnit: 'ms'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
fs.writeFileSync(file, JSON.stringify(trace, null, 2))
|
|
24
|
-
// TODO: remove debug log before production
|
|
25
|
-
console.log('✅', 'file', file);
|
|
26
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// utils/scopeTimer.js
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
const formatTime = (ms) => {
|
|
5
|
-
if (ms < 1) return `${(ms * 1000).toFixed(2)} μs`
|
|
6
|
-
if (ms < 1000) return `${ms.toFixed(2)} ms`
|
|
7
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(2)} s`
|
|
8
|
-
return `${(ms / 60000).toFixed(2)} min`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const createScopeTimer = (scope) => {
|
|
12
|
-
const start = process.hrtime.bigint()
|
|
13
|
-
let last = start
|
|
14
|
-
|
|
15
|
-
const step = (label) => {
|
|
16
|
-
const now = process.hrtime.bigint()
|
|
17
|
-
const ms = Number(now - last) / 1e6
|
|
18
|
-
last = now
|
|
19
|
-
|
|
20
|
-
console.log(
|
|
21
|
-
chalk.gray(' ├─'),
|
|
22
|
-
chalk.white(label),
|
|
23
|
-
chalk.yellow(formatTime(ms))
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const end = () => {
|
|
28
|
-
const total = Number(process.hrtime.bigint() - start) / 1e6
|
|
29
|
-
console.log(
|
|
30
|
-
chalk.cyan('⏱'),
|
|
31
|
-
chalk.bold(scope),
|
|
32
|
-
chalk.yellow(formatTime(total))
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { step, end }
|
|
37
|
-
}
|