wukong-gitlog-cli 1.0.42 → 1.0.43
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/CHANGELOG.md +12 -0
- package/package.json +3 -1
- package/src/cli/createCommand.mjs +30 -0
- package/src/index.mjs +95 -69
- package/src/utils/checkUpdate.mjs +135 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.0.43](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.42...v1.0.43) (2026-02-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* 🎸 autoCheckUpdate ([d377012](https://github.com/tomatobybike/wukong-gitlog-cli/commit/d3770123f10c574f0f5ea592ef58e71bfdd8edc1))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* 🐛 update version ([840216d](https://github.com/tomatobybike/wukong-gitlog-cli/commit/840216d4f553e5d78f84e42f76386056553d1282))
|
|
16
|
+
|
|
5
17
|
### [1.0.42](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.41...v1.0.42) (2026-02-13)
|
|
6
18
|
|
|
7
19
|
### [1.0.41](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.40...v1.0.41) (2026-02-12)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wukong-gitlog-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git",
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
100
|
"@inquirer/prompts": "^8.1.0",
|
|
101
|
+
"boxen": "^8.0.1",
|
|
101
102
|
"chalk": "5.6.2",
|
|
102
103
|
"commander": "12.1.0",
|
|
103
104
|
"date-fns": "4.1.0",
|
|
@@ -106,6 +107,7 @@
|
|
|
106
107
|
"dotenv": "17.2.2",
|
|
107
108
|
"exceljs": "4.4.0",
|
|
108
109
|
"fs-extra": "11.3.3",
|
|
110
|
+
"is-online": "^12.0.2",
|
|
109
111
|
"ora": "9.0.0",
|
|
110
112
|
"os-locale": "^8.0.0",
|
|
111
113
|
"string-width": "5.1.2",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function createCommand(program, {
|
|
2
|
+
name,
|
|
3
|
+
description,
|
|
4
|
+
optionsBuilder,
|
|
5
|
+
handler,
|
|
6
|
+
autoUpdate
|
|
7
|
+
}) {
|
|
8
|
+
const cmd = program.command(name).description(description)
|
|
9
|
+
|
|
10
|
+
// 注入 options
|
|
11
|
+
if (optionsBuilder) {
|
|
12
|
+
optionsBuilder(cmd)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
cmd.action(async (cmdOpts, command) => {
|
|
16
|
+
const globalOpts = command.parent?.opts?.() || {}
|
|
17
|
+
const finalOpts = { ...globalOpts, ...cmdOpts }
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await handler(finalOpts)
|
|
21
|
+
} finally {
|
|
22
|
+
if (autoUpdate) {
|
|
23
|
+
// 不阻塞 CLI
|
|
24
|
+
autoUpdate().catch(() => {})
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return cmd
|
|
30
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
3
6
|
|
|
4
7
|
import { runGitPreflight, showGitInfo } from '#src/domain/git/index.mjs'
|
|
5
8
|
|
|
@@ -11,6 +14,7 @@ import { journalAction } from './app/journalAction.mjs'
|
|
|
11
14
|
import { overtimeAction } from './app/overtimeAction.mjs'
|
|
12
15
|
import { serveAction } from './app/serveAction.mjs'
|
|
13
16
|
import { versionAction } from './app/versionAction.mjs'
|
|
17
|
+
import { createCommand } from './cli/createCommand.mjs'
|
|
14
18
|
import {
|
|
15
19
|
addAnalysisOptions,
|
|
16
20
|
addGitSourceOptions,
|
|
@@ -21,9 +25,38 @@ import {
|
|
|
21
25
|
} from './cli/defineOptions.mjs'
|
|
22
26
|
import { initI18n, t } from './i18n/index.mjs'
|
|
23
27
|
import { loadRcConfig } from './infra/configStore.mjs'
|
|
28
|
+
import { checkUpdateWithPatch } from './utils/checkUpdate.mjs'
|
|
24
29
|
|
|
25
30
|
// 引入加载器
|
|
26
31
|
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
33
|
+
const __dirname = path.dirname(__filename)
|
|
34
|
+
|
|
35
|
+
const pkgPath = path.resolve(__dirname, '../package.json')
|
|
36
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
37
|
+
|
|
38
|
+
const autoCheckUpdate = async () => {
|
|
39
|
+
// 避免 CI 打印更新提示
|
|
40
|
+
if (process.env.CI) return
|
|
41
|
+
// CLI 更新提示只在 global 安装时出现
|
|
42
|
+
if (process.env.npm_config_global !== 'true') return
|
|
43
|
+
if (!process.stdout.isTTY) return
|
|
44
|
+
|
|
45
|
+
// === CLI 主逻辑完成后提示更新 ===
|
|
46
|
+
// await checkUpdateWithPatch({
|
|
47
|
+
// pkg: {
|
|
48
|
+
// name: pkg.name,
|
|
49
|
+
// version: pkg.version
|
|
50
|
+
// },
|
|
51
|
+
// // force:true
|
|
52
|
+
// })
|
|
53
|
+
|
|
54
|
+
// 放到下一个 tick
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
checkUpdateWithPatch({ pkg }).catch(() => {})
|
|
57
|
+
}, 0)
|
|
58
|
+
}
|
|
59
|
+
|
|
27
60
|
const main = async () => {
|
|
28
61
|
// --- 第一步:参数预检 (Pre-flight) ---
|
|
29
62
|
const args = process.argv.slice(2)
|
|
@@ -67,85 +100,78 @@ const main = async () => {
|
|
|
67
100
|
|
|
68
101
|
// === 命令: Init ===
|
|
69
102
|
|
|
70
|
-
program
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
createCommand(program, {
|
|
104
|
+
name: 'init',
|
|
105
|
+
description: t('cmds.init'),
|
|
106
|
+
optionsBuilder: (cmd) => {
|
|
107
|
+
cmd.option('-f, --force', t('options.force'))
|
|
108
|
+
},
|
|
109
|
+
handler: initAction,
|
|
110
|
+
autoUpdate: autoCheckUpdate
|
|
111
|
+
})
|
|
79
112
|
|
|
80
113
|
// # 命令: Analyze 核心分析(默认) 分析 git 提交记录 (最全参数)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
addOutputOptions(analyzeCmd)
|
|
94
|
-
addPerformanceOptions(analyzeCmd)
|
|
114
|
+
createCommand(program, {
|
|
115
|
+
name: 'analyze',
|
|
116
|
+
description: t('cmds.analyze'),
|
|
117
|
+
optionsBuilder: (cmd) => {
|
|
118
|
+
addGitSourceOptions(cmd)
|
|
119
|
+
addAnalysisOptions(cmd)
|
|
120
|
+
addOutputOptions(cmd)
|
|
121
|
+
addPerformanceOptions(cmd)
|
|
122
|
+
},
|
|
123
|
+
handler: analyzeAction,
|
|
124
|
+
autoUpdate: autoCheckUpdate
|
|
125
|
+
})
|
|
95
126
|
|
|
96
127
|
// # 命令: Overtime 加班文化分析
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
createCommand(program, {
|
|
129
|
+
name: 'overtime',
|
|
130
|
+
description: t('cmds.overtime'),
|
|
131
|
+
optionsBuilder: (cmd) => {
|
|
132
|
+
addGitSourceOptions(cmd)
|
|
133
|
+
addAnalysisOptions(cmd)
|
|
134
|
+
},
|
|
135
|
+
handler: overtimeAction,
|
|
136
|
+
autoUpdate: autoCheckUpdate
|
|
137
|
+
})
|
|
107
138
|
|
|
108
139
|
// # 命令: Export (专注导出) 导出(excel / csv / json)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
})
|
|
120
|
-
addGitSourceOptions(exportCmd)
|
|
121
|
-
addOutputOptions(exportCmd)
|
|
140
|
+
createCommand(program, {
|
|
141
|
+
name: 'export',
|
|
142
|
+
description: t('cmds.export'),
|
|
143
|
+
optionsBuilder: (cmd) => {
|
|
144
|
+
addGitSourceOptions(cmd)
|
|
145
|
+
addOutputOptions(cmd)
|
|
146
|
+
},
|
|
147
|
+
handler: exportAction,
|
|
148
|
+
autoUpdate: autoCheckUpdate
|
|
149
|
+
})
|
|
122
150
|
|
|
123
151
|
// === 命令: Journal (日报) ===
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
addPerformanceOptions(journalCmd)
|
|
152
|
+
createCommand(program, {
|
|
153
|
+
name: 'journal',
|
|
154
|
+
description: t('cmds.journal'),
|
|
155
|
+
optionsBuilder: (cmd) => {
|
|
156
|
+
addGitSourceOptions(cmd)
|
|
157
|
+
addPerformanceOptions(cmd)
|
|
158
|
+
},
|
|
159
|
+
handler: journalAction,
|
|
160
|
+
autoUpdate: autoCheckUpdate
|
|
161
|
+
})
|
|
136
162
|
|
|
137
163
|
// === 命令: Serve (Web服务) ===
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
program.
|
|
164
|
+
createCommand(program, {
|
|
165
|
+
name: 'serve',
|
|
166
|
+
description: 'Start web server',
|
|
167
|
+
optionsBuilder: (cmd) => {
|
|
168
|
+
addServeOptions(cmd)
|
|
169
|
+
},
|
|
170
|
+
handler: serveAction,
|
|
171
|
+
autoUpdate: autoCheckUpdate
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
await program.parseAsync(process.argv)
|
|
149
175
|
|
|
150
176
|
// const opts = program.opts()
|
|
151
177
|
// console.log('✅ Cli Opts:', opts)
|
|
@@ -0,0 +1,135 @@
|
|
|
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.valid(pkg.version) || !semver.valid(latest)) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if (semver.lt(pkg.version, latest)) {
|
|
94
|
+
// 构造 update 对象,兼容 update-notifier
|
|
95
|
+
const updateInfo = {
|
|
96
|
+
current: pkg.version,
|
|
97
|
+
latest,
|
|
98
|
+
type: semver.diff(pkg.version, latest) || 'patch',
|
|
99
|
+
name: pkg.name,
|
|
100
|
+
// 这里官方还会有 message 字段
|
|
101
|
+
// 用官方的默认消息格式生成,方便notify打印
|
|
102
|
+
message: `\nUpdate available ${pkg.version} → ${latest}\nRun npm i -g ${pkg.name} to update\n`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 缓存
|
|
106
|
+
try {
|
|
107
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
108
|
+
fs.writeFileSync(
|
|
109
|
+
CACHE_FILE,
|
|
110
|
+
JSON.stringify(
|
|
111
|
+
{
|
|
112
|
+
lastCheck: now,
|
|
113
|
+
updateInfo
|
|
114
|
+
},
|
|
115
|
+
null,
|
|
116
|
+
2
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.error('缓存写入失败:', e)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { current } = updateInfo
|
|
124
|
+
console.log(formatUpdateMessage(current, latest, pkg.name))
|
|
125
|
+
return updateInfo
|
|
126
|
+
}
|
|
127
|
+
// 无更新,刷新缓存时间
|
|
128
|
+
try {
|
|
129
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
130
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: now }, null, 2))
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error('缓存写入失败:', e)
|
|
133
|
+
}
|
|
134
|
+
return null
|
|
135
|
+
}
|