wukong-gitlog-cli 1.0.41 → 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 CHANGED
@@ -2,6 +2,20 @@
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
+
17
+ ### [1.0.42](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.41...v1.0.42) (2026-02-13)
18
+
5
19
  ### [1.0.41](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.40...v1.0.41) (2026-02-12)
6
20
 
7
21
  ### [1.0.40](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.39...v1.0.40) (2026-02-12)
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  An enhanced Git commit log analysis and export tool. Supports **Excel / JSON / TXT** output, grouped statistics, overtime culture analysis, Gerrit link integration, and a visual Web Dashboard.
6
6
 
7
- ## [中文](./README.zh-CN.md) | English
7
+ ## English | [简体中文](./README.zh-CN.md)
8
8
 
9
9
  ---
10
10
 
@@ -42,7 +42,7 @@ wukong-gitlog-cli --help
42
42
  wukong-gitlog-cli init
43
43
 
44
44
  # Analyze and start web dashboard
45
- wukong-gitlog-cli serve --overtime
45
+ wukong-gitlog-cli serve
46
46
  ```
47
47
 
48
48
  ---
package/README.zh-CN.md CHANGED
@@ -42,7 +42,7 @@ wukong-gitlog-cli --help
42
42
  wukong-gitlog-cli init
43
43
 
44
44
  # 执行分析并启动 Web Dashboard
45
- wukong-gitlog-cli serve --overtime
45
+ wukong-gitlog-cli serve
46
46
  ```
47
47
 
48
48
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "1.0.41",
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
- .command('init')
72
- .description(t('cmds.init'))
73
- .option('-f, --force', t('options.force'))
74
- .action(async (options) => {
75
- // 如果你不想抽离到 app 层,也可以直接写这里
76
- // 但推荐抽离以保持架构一致性
77
- await initAction(options)
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
- const analyzeCmd = program
82
- .command('analyze')
83
- .description(t('cmds.analyze'))
84
- .action((cmdOpts) => {
85
- const globalOpts = program.opts()
86
- const finalOpts = { ...globalOpts, ...cmdOpts }
87
- analyzeAction(finalOpts)
88
- })
89
-
90
- // 挂载 analyze 需要的参数组
91
- addGitSourceOptions(analyzeCmd)
92
- addAnalysisOptions(analyzeCmd)
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
- const overtimeCmd = program
98
- .command('overtime')
99
- .description(t('cmds.overtime'))
100
- .action((cmdOpts) => {
101
- const globalOpts = program.opts()
102
- const finalOpts = { ...globalOpts, ...cmdOpts }
103
- overtimeAction(finalOpts)
104
- })
105
- addGitSourceOptions(overtimeCmd)
106
- addAnalysisOptions(overtimeCmd) // 加班分析肯定需要上班时间配置
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
- const exportCmd = program
110
- .command('export')
111
- .description(t('cmds.export'))
112
- // .option('-f, --format <type>', '导出格式') // 局部参数
113
- .action((cmdOpts, command) => {
114
- // globalOpts 拿到 author, since 等
115
- const globalOpts = command.parent.opts()
116
- // 合并全局和局部参数
117
- const finalOpts = { ...globalOpts, ...cmdOpts }
118
- exportAction(finalOpts)
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
- const journalCmd = program
125
- .command('journal')
126
- .description(t('cmds.journal'))
127
- .action((cmdOpts) => {
128
- const globalOpts = program.opts()
129
- const finalOpts = { ...globalOpts, ...cmdOpts }
130
- journalAction(finalOpts)
131
- })
132
-
133
- addGitSourceOptions(journalCmd)
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
- const serveCmd = program
139
- .command('serve')
140
- .description('Start web server')
141
- .action((cmdOpts) => {
142
- const globalOpts = program.opts()
143
- const finalOpts = { ...globalOpts, ...cmdOpts }
144
- serveAction(finalOpts)
145
- })
146
- // Serve 命令只需要端口,不需要 Git 作者之类的参数
147
- addServeOptions(serveCmd)
148
- program.parse(process.argv)
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
+ }