wukong-gitlog-cli 0.0.15 → 1.0.2

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,26 @@
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.2](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.1...v1.0.2) (2025-11-28)
6
+
7
+
8
+ ### Features
9
+
10
+ * 🎸 await zx ([79343d6](https://github.com/tomatobybike/wukong-gitlog-cli/commit/79343d6722c19b8c6381211b1a9b35923443abb5))
11
+
12
+ ### [1.0.1](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v0.0.15...v1.0.1) (2025-11-28)
13
+
14
+
15
+ ### Features
16
+
17
+ * 🎸 show version ([4a81b72](https://github.com/tomatobybike/wukong-gitlog-cli/commit/4a81b7222100521dcacda4d1d8ff96db2d669a3c))
18
+ * 🎸 version ([8fa81d6](https://github.com/tomatobybike/wukong-gitlog-cli/commit/8fa81d64e1a6c44e4b4327fbb6a27559ed2b5372))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * 🐛 eslint ([17701b2](https://github.com/tomatobybike/wukong-gitlog-cli/commit/17701b248802532a63d5b5896cf331de29e07c07))
24
+
5
25
  ### [0.0.15](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v0.0.14...v0.0.15) (2025-11-28)
6
26
 
7
27
 
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import('../src/cli.mjs');
2
+ import('../src/cli.mjs')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "0.0.15",
3
+ "version": "1.0.2",
4
4
  "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
5
  "keywords": [
6
6
  "git",
@@ -90,13 +90,15 @@
90
90
  ]
91
91
  },
92
92
  "dependencies": {
93
- "chalk": "5.6.0",
94
93
  "commander": "12.1.0",
95
94
  "dayjs": "1.11.19",
96
95
  "date-holidays": "2.1.1",
97
96
  "string-width": "5.1.2",
98
97
  "exceljs": "4.4.0",
99
- "zx": "7.2.4"
98
+ "zx": "7.2.4",
99
+ "is-online": "12.0.2",
100
+ "boxen": "8.0.1",
101
+ "chalk": "5.6.2"
100
102
  },
101
103
  "devDependencies": {
102
104
  "@trivago/prettier-plugin-sort-imports": "5.2.2",
package/src/cli.mjs CHANGED
@@ -1,405 +1,629 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import path from 'path';
4
- import { getGitLogs } from './git.mjs';
5
- import { renderText } from './text.mjs';
6
- import { analyzeOvertime, renderOvertimeText, renderOvertimeTab, renderOvertimeCsv } from './overtime.mjs';
7
- import { startServer } from './server.mjs';
8
- import { exportExcel, exportExcelPerPeriodSheets } from './excel.mjs';
9
- import { groupRecords, writeJSON, writeTextFile, outputFilePath } from './utils/index.mjs';
1
+ import chalk from 'chalk'
2
+ import { Command } from 'commander'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import { fileURLToPath } from 'url'
10
6
 
11
- const program = new Command();
7
+ import { CLI_NAME } from './constants/index.mjs'
8
+ import { exportExcel, exportExcelPerPeriodSheets } from './excel.mjs'
9
+ import { getGitLogs } from './git.mjs'
10
+ import {
11
+ analyzeOvertime,
12
+ renderOvertimeCsv,
13
+ renderOvertimeTab,
14
+ renderOvertimeText
15
+ } from './overtime.mjs'
16
+ import { startServer } from './server.mjs'
17
+ import { renderText } from './text.mjs'
18
+ import { checkUpdateWithPatch } from './utils/checkUpdate.mjs'
19
+ import {
20
+ groupRecords,
21
+ outputFilePath,
22
+ writeJSON,
23
+ writeTextFile
24
+ } from './utils/index.mjs'
25
+ import { showVersionInfo } from './utils/showVersionInfo.mjs'
12
26
 
13
- program
14
- .name('git-commits')
15
- .description('Advanced Git commit log exporter.')
16
- .option('--author <name>', '指定 author 名')
17
- .option('--email <email>', '指定 email')
18
- .option('--since <date>', '起始日期')
19
- .option('--until <date>', '结束日期')
20
- .option('--limit <n>', '限制数量', parseInt)
21
- .option('--no-merges', '不包含 merge commit')
22
- .option('--json', '输出 JSON')
23
- .option('--format <type>', '输出格式: text | excel | json', 'text')
24
- .option('--group-by <type>', '按日期分组: day | month | week')
25
- .option('--stats', '输出每日统计数据')
26
- .option('--gerrit <prefix>', '显示 Gerrit 地址,支持在 prefix 中使用 {{hash}} 占位符')
27
- .option('--gerrit-api <url>', '可选:Gerrit REST API 基础地址,用于解析 changeNumber,例如 `https://gerrit.example.com`')
28
- .option('--gerrit-auth <tokenOrUserPass>', '可选:Gerrit API 授权,格式为 `user:pass` 或 `TOKEN`(表示 Bearer token)')
29
- .option('--overtime', '分析公司加班文化(输出下班时间与非工作日提交占比)')
30
- .option('--country <code>', '节假日国家:CN 或 US,默认为 CN', 'CN')
31
- .option('--work-start <hour>', '上班开始小时,默认 9', (v) => parseInt(v, 10), 9)
32
- .option('--work-end <hour>', '下班小时,默认 18', (v) => parseInt(v, 10), 18)
33
- .option('--lunch-start <hour>', '午休开始小时,默认 12', (v) => parseInt(v, 10), 12)
34
- .option('--lunch-end <hour>', '午休结束小时,默认 14', (v) => parseInt(v, 10), 14)
35
- .option('--out <file>', '输出文件名(不含路径)')
36
- .option('--out-dir <dir>', '自定义输出目录,支持相对路径或绝对路径,例如 `--out-dir ../output`')
37
- .option('--out-parent', '将输出目录放到当前工程的父目录的 `output/`(等同于 `--out-dir ../output`)')
38
- .option('--per-period-formats <formats>', '每个周期单独输出的格式,逗号分隔:text,csv,tab,xlsx。默认为空(不输出 CSV/Tab/XLSX)', '')
39
- .option('--per-period-excel-mode <mode>', 'per-period Excel 模式:sheets|files(默认:sheets)', 'sheets')
40
- .option('--per-period-only', '仅输出 per-period(month/week)文件,不输出合并的 monthly/weekly 汇总文件')
41
- .option('--serve', '启动本地 web 服务,查看提交统计(将在 output/data 下生成数据文件)')
42
- .option('--port <n>', '本地 web 服务端口(默认 3000)', (v) => parseInt(v, 10), 3000)
43
- .option('--serve-only', '仅启动 web 服务,不导出或分析数据(使用 output/data 中已有的数据)')
44
- .parse();
27
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
45
28
 
46
- const opts = program.opts();
47
- // compute output directory root early (so serve-only can use it)
48
- const outDir = opts.outParent
49
- ? path.resolve(process.cwd(), '..', 'output')
50
- : opts.outDir || undefined;
29
+ const pkg = JSON.parse(
30
+ fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8')
31
+ )
51
32
 
52
- (async () => {
33
+ const PKG_NAME = pkg.name
34
+ const VERSION = pkg.version
35
+
36
+ const autoCheckUpdate = async () => {
37
+ // === CLI 主逻辑完成后提示更新 ===
38
+ await checkUpdateWithPatch({
39
+ pkg: {
40
+ name: PKG_NAME,
41
+ version: VERSION
42
+ },
43
+ force: true
44
+ })
45
+ }
46
+
47
+ const version = async () => {
48
+ showVersionInfo(VERSION)
49
+ await autoCheckUpdate()
50
+ process.exit(0)
51
+ }
52
+
53
+ const main = async () => {
54
+ const program = new Command()
55
+
56
+ program
57
+ .name('git-commits')
58
+ .description('Advanced Git commit log exporter.')
59
+ .option('--author <name>', '指定 author 名')
60
+ .option('--email <email>', '指定 email')
61
+ .option('--since <date>', '起始日期')
62
+ .option('--until <date>', '结束日期')
63
+ .option('--limit <n>', '限制数量', parseInt)
64
+ .option('--no-merges', '不包含 merge commit')
65
+ .option('--json', '输出 JSON')
66
+ .option('--format <type>', '输出格式: text | excel | json', 'text')
67
+ .option('--group-by <type>', '按日期分组: day | month | week')
68
+ .option('--stats', '输出每日统计数据')
69
+ .option(
70
+ '--gerrit <prefix>',
71
+ '显示 Gerrit 地址,支持在 prefix 中使用 {{hash}} 占位符'
72
+ )
73
+ .option(
74
+ '--gerrit-api <url>',
75
+ '可选:Gerrit REST API 基础地址,用于解析 changeNumber,例如 `https://gerrit.example.com`'
76
+ )
77
+ .option(
78
+ '--gerrit-auth <tokenOrUserPass>',
79
+ '可选:Gerrit API 授权,格式为 `user:pass` 或 `TOKEN`(表示 Bearer token)'
80
+ )
81
+ .option('--overtime', '分析公司加班文化(输出下班时间与非工作日提交占比)')
82
+ .option('--country <code>', '节假日国家:CN 或 US,默认为 CN', 'CN')
83
+ .option(
84
+ '--work-start <hour>',
85
+ '上班开始小时,默认 9',
86
+ (v) => parseInt(v, 10),
87
+ 9
88
+ )
89
+ .option(
90
+ '--work-end <hour>',
91
+ '下班小时,默认 18',
92
+ (v) => parseInt(v, 10),
93
+ 18
94
+ )
95
+ .option(
96
+ '--lunch-start <hour>',
97
+ '午休开始小时,默认 12',
98
+ (v) => parseInt(v, 10),
99
+ 12
100
+ )
101
+ .option(
102
+ '--lunch-end <hour>',
103
+ '午休结束小时,默认 14',
104
+ (v) => parseInt(v, 10),
105
+ 14
106
+ )
107
+ .option('--out <file>', '输出文件名(不含路径)')
108
+ .option(
109
+ '--out-dir <dir>',
110
+ '自定义输出目录,支持相对路径或绝对路径,例如 `--out-dir ../output`'
111
+ )
112
+ .option(
113
+ '--out-parent',
114
+ '将输出目录放到当前工程的父目录的 `output/`(等同于 `--out-dir ../output`)'
115
+ )
116
+ .option(
117
+ '--per-period-formats <formats>',
118
+ '每个周期单独输出的格式,逗号分隔:text,csv,tab,xlsx。默认为空(不输出 CSV/Tab/XLSX)',
119
+ ''
120
+ )
121
+ .option(
122
+ '--per-period-excel-mode <mode>',
123
+ 'per-period Excel 模式:sheets|files(默认:sheets)',
124
+ 'sheets'
125
+ )
126
+ .option(
127
+ '--per-period-only',
128
+ '仅输出 per-period(month/week)文件,不输出合并的 monthly/weekly 汇总文件'
129
+ )
130
+ .option(
131
+ '--serve',
132
+ '启动本地 web 服务,查看提交统计(将在 output/data 下生成数据文件)'
133
+ )
134
+ .option(
135
+ '--port <n>',
136
+ '本地 web 服务端口(默认 3000)',
137
+ (v) => parseInt(v, 10),
138
+ 3000
139
+ )
140
+ .option(
141
+ '--serve-only',
142
+ '仅启动 web 服务,不导出或分析数据(使用 output/data 中已有的数据)'
143
+ )
144
+ .option('--version', 'show version information')
145
+ .parse()
146
+
147
+ const opts = program.opts()
148
+ // compute output directory root early (so serve-only can use it)
149
+ const outDir = opts.outParent
150
+ ? path.resolve(process.cwd(), '..', 'output')
151
+ : opts.outDir || undefined
152
+
153
+ if (opts.version) {
154
+ await version()
155
+ return
156
+ }
53
157
  // if serve-only is requested, start server and exit
54
158
  if (opts.serveOnly) {
55
159
  try {
56
- await startServer(opts.port || 3000, outDir);
160
+ await startServer(opts.port || 3000, outDir)
57
161
  } catch (err) {
58
- console.warn('Start server failed:', err && err.message ? err.message : err);
59
- process.exit(1);
162
+ console.warn(
163
+ 'Start server failed:',
164
+ err && err.message ? err.message : err
165
+ )
166
+ process.exit(1)
60
167
  }
61
- return;
168
+ return
62
169
  }
63
170
 
64
- let records = await getGitLogs(opts);
171
+ let records = await getGitLogs(opts)
65
172
 
66
173
  // compute output directory root if user provided one or wants parent
67
174
 
68
175
  // --- Gerrit 地址处理(若提供) ---
69
176
  if (opts.gerrit) {
70
- const prefix = opts.gerrit;
177
+ const prefix = opts.gerrit
71
178
  // support optional changeNumber resolution via Gerrit REST API
72
- const { gerritApi, gerritAuth } = opts;
179
+ const { gerritApi, gerritAuth } = opts
73
180
  // create new array to avoid mutating function parameters (eslint: no-param-reassign)
74
181
  if (prefix.includes('{{changeNumber}}') && gerritApi) {
75
182
  // async mapping to resolve changeNumber using Gerrit API
76
- const cache = new Map();
77
- const headers = {};
183
+ const cache = new Map()
184
+ const headers = {}
78
185
  if (gerritAuth) {
79
186
  if (gerritAuth.includes(':')) {
80
- headers.Authorization = `Basic ${Buffer.from(gerritAuth).toString('base64')}`;
187
+ headers.Authorization = `Basic ${Buffer.from(gerritAuth).toString('base64')}`
81
188
  } else {
82
- headers.Authorization = `Bearer ${gerritAuth}`;
189
+ headers.Authorization = `Bearer ${gerritAuth}`
83
190
  }
84
191
  }
85
192
  const fetchGerritJson = async (url) => {
86
193
  try {
87
- const res = await fetch(url, { headers });
88
- const txt = await res.text();
194
+ const res = await fetch(url, { headers })
195
+ const txt = await res.text()
89
196
  // Gerrit prepends )]}' to JSON responses — strip it
90
- const jsonText = txt.replace(/^\)\]\}'\n/, '');
91
- return JSON.parse(jsonText);
197
+ const jsonText = txt.replace(/^\)\]\}'\n/, '')
198
+ return JSON.parse(jsonText)
92
199
  } catch (err) {
93
- return null;
200
+ return null
94
201
  }
95
- };
202
+ }
96
203
  const resolveChangeNumber = async (r) => {
97
204
  // try changeId first
98
205
  if (r.changeId) {
99
- if (cache.has(r.changeId)) return cache.get(r.changeId);
206
+ if (cache.has(r.changeId)) return cache.get(r.changeId)
100
207
  // try `changes/{changeId}/detail`
101
- const url = `${gerritApi.replace(/\/$/, '')}/changes/${encodeURIComponent(r.changeId)}/detail`;
102
- let j = await fetchGerritJson(url);
208
+ const url = `${gerritApi.replace(/\/$/, '')}/changes/${encodeURIComponent(r.changeId)}/detail`
209
+ let j = await fetchGerritJson(url)
103
210
  if (j && j._number) {
104
- cache.set(r.changeId, j._number);
105
- return j._number;
211
+ cache.set(r.changeId, j._number)
212
+ return j._number
106
213
  }
107
214
  // fallback: query search
108
- const url2 = `${gerritApi.replace(/\/$/, '')}/changes/?q=change:${encodeURIComponent(r.changeId)}`;
109
- j = await fetchGerritJson(url2);
215
+ const url2 = `${gerritApi.replace(/\/$/, '')}/changes/?q=change:${encodeURIComponent(r.changeId)}`
216
+ j = await fetchGerritJson(url2)
110
217
  if (Array.isArray(j) && j.length > 0 && j[0]._number) {
111
- cache.set(r.changeId, j[0]._number);
112
- return j[0]._number;
218
+ cache.set(r.changeId, j[0]._number)
219
+ return j[0]._number
113
220
  }
114
221
  }
115
222
  // try commit hash
116
223
  if (r.hash) {
117
- if (cache.has(r.hash)) return cache.get(r.hash);
118
- const url3 = `${gerritApi.replace(/\/$/, '')}/changes/?q=commit:${encodeURIComponent(r.hash)}`;
119
- const j = await fetchGerritJson(url3);
224
+ if (cache.has(r.hash)) return cache.get(r.hash)
225
+ const url3 = `${gerritApi.replace(/\/$/, '')}/changes/?q=commit:${encodeURIComponent(r.hash)}`
226
+ const j = await fetchGerritJson(url3)
120
227
  if (Array.isArray(j) && j.length > 0 && j[0]._number) {
121
- cache.set(r.hash, j[0]._number);
122
- return j[0]._number;
228
+ cache.set(r.hash, j[0]._number)
229
+ return j[0]._number
123
230
  }
124
231
  }
125
- return null;
126
- };
232
+ return null
233
+ }
127
234
  records = await Promise.all(
128
235
  records.map(async (r) => {
129
- const changeNumber = await resolveChangeNumber(r);
130
- const changeNumberOrFallback = changeNumber || r.changeId || r.hash;
131
- const gerritUrl = prefix.replace('{{changeNumber}}', changeNumberOrFallback);
132
- return { ...r, gerrit: gerritUrl };
236
+ const changeNumber = await resolveChangeNumber(r)
237
+ const changeNumberOrFallback = changeNumber || r.changeId || r.hash
238
+ const gerritUrl = prefix.replace(
239
+ '{{changeNumber}}',
240
+ changeNumberOrFallback
241
+ )
242
+ return { ...r, gerrit: gerritUrl }
133
243
  })
134
- );
244
+ )
135
245
  } else if (prefix.includes('{{changeNumber}}') && !gerritApi) {
136
- console.warn('prefix contains {{changeNumber}} but no --gerrit-api provided — falling back to changeId/hash');
137
- records = records.map((r) => ({ ...r, gerrit: prefix.replace('{{changeNumber}}', r.changeId || r.hash) }));
246
+ console.warn(
247
+ 'prefix contains {{changeNumber}} but no --gerrit-api provided — falling back to changeId/hash'
248
+ )
249
+ records = records.map((r) => ({
250
+ ...r,
251
+ gerrit: prefix.replace('{{changeNumber}}', r.changeId || r.hash)
252
+ }))
138
253
  } else {
139
254
  records = records.map((r) => {
140
- let gerritUrl;
255
+ let gerritUrl
141
256
  if (prefix.includes('{{changeId}}')) {
142
- const changeId = r.changeId || r.hash;
143
- gerritUrl = prefix.replace('{{changeId}}', changeId);
257
+ const changeId = r.changeId || r.hash
258
+ gerritUrl = prefix.replace('{{changeId}}', changeId)
144
259
  } else if (prefix.includes('{{hash}}')) {
145
- gerritUrl = prefix.replace('{{hash}}', r.hash);
260
+ gerritUrl = prefix.replace('{{hash}}', r.hash)
146
261
  } else {
147
- gerritUrl = prefix.endsWith('/') ? `${prefix}${r.hash}` : `${prefix}/${r.hash}`;
262
+ gerritUrl = prefix.endsWith('/')
263
+ ? `${prefix}${r.hash}`
264
+ : `${prefix}/${r.hash}`
148
265
  }
149
- return { ...r, gerrit: gerritUrl };
150
- });
266
+ return { ...r, gerrit: gerritUrl }
267
+ })
151
268
  }
152
269
  }
153
270
 
154
271
  // --- 分组 ---
155
- const groups = opts.groupBy ? groupRecords(records, opts.groupBy) : null;
272
+ const groups = opts.groupBy ? groupRecords(records, opts.groupBy) : null
156
273
 
157
274
  // --- Overtime analysis ---
158
275
  if (opts.overtime) {
159
276
  const stats = analyzeOvertime(records, {
160
277
  startHour: opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
161
278
  endHour: opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18,
162
- lunchStart: opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
279
+ lunchStart:
280
+ opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
163
281
  lunchEnd: opts.lunchEnd || opts.lunchEnd === 0 ? opts.lunchEnd : 14,
164
- country: opts.country || 'CN',
165
- });
282
+ country: opts.country || 'CN'
283
+ })
166
284
  // Output to console
167
- console.log('\n--- Overtime analysis ---\n');
168
- console.log(renderOvertimeText(stats));
285
+ console.log('\n--- Overtime analysis ---\n')
286
+ console.log(renderOvertimeText(stats))
169
287
  // if user requested json format, write stats to file
170
288
  if (opts.json || opts.format === 'json') {
171
- const file = opts.out || 'overtime.json';
172
- const filepath = outputFilePath(file, outDir);
173
- writeJSON(filepath, stats);
174
- console.log(chalk.green(`overtime JSON 已导出: ${filepath}`));
289
+ const file = opts.out || 'overtime.json'
290
+ const filepath = outputFilePath(file, outDir)
291
+ writeJSON(filepath, stats)
292
+ console.log(chalk.green(`overtime JSON 已导出: ${filepath}`))
175
293
  }
176
294
  // Always write human readable overtime text to file (default: overtime.txt)
177
- const outBase = opts.out ? path.basename(opts.out, path.extname(opts.out)) : 'commits';
178
- const overtimeFileName = `overtime_${outBase}.txt`;
179
- const overtimeFile = outputFilePath(overtimeFileName, outDir);
180
- writeTextFile(overtimeFile, renderOvertimeText(stats));
295
+ const outBase = opts.out
296
+ ? path.basename(opts.out, path.extname(opts.out))
297
+ : 'commits'
298
+ const overtimeFileName = `overtime_${outBase}.txt`
299
+ const overtimeFile = outputFilePath(overtimeFileName, outDir)
300
+ writeTextFile(overtimeFile, renderOvertimeText(stats))
181
301
  // write tab-separated text file for better alignment in editors that use proportional fonts
182
- const overtimeTabFileName = `overtime_${outBase}.tab.txt`;
183
- const overtimeTabFile = outputFilePath(overtimeTabFileName, outDir);
184
- writeTextFile(overtimeTabFile, renderOvertimeTab(stats));
302
+ const overtimeTabFileName = `overtime_${outBase}.tab.txt`
303
+ const overtimeTabFile = outputFilePath(overtimeTabFileName, outDir)
304
+ writeTextFile(overtimeTabFile, renderOvertimeTab(stats))
185
305
  // write CSV for structured data consumption
186
- const overtimeCsvFileName = `overtime_${outBase}.csv`;
187
- const overtimeCsvFile = outputFilePath(overtimeCsvFileName, outDir);
188
- writeTextFile(overtimeCsvFile, renderOvertimeCsv(stats));
189
- console.log(chalk.green(`Overtime text 已导出: ${overtimeFile}`));
190
- console.log(chalk.green(`Overtime table (tabs) 已导出: ${overtimeTabFile}`));
191
- console.log(chalk.green(`Overtime CSV 已导出: ${overtimeCsvFile}`));
306
+ const overtimeCsvFileName = `overtime_${outBase}.csv`
307
+ const overtimeCsvFile = outputFilePath(overtimeCsvFileName, outDir)
308
+ writeTextFile(overtimeCsvFile, renderOvertimeCsv(stats))
309
+ console.log(chalk.green(`Overtime text 已导出: ${overtimeFile}`))
310
+ console.log(chalk.green(`Overtime table (tabs) 已导出: ${overtimeTabFile}`))
311
+ console.log(chalk.green(`Overtime CSV 已导出: ${overtimeCsvFile}`))
192
312
 
193
313
  // If serve mode is enabled, write data modules and launch the web server
194
314
  if (opts.serve) {
195
315
  try {
196
- const dataCommitsFile = outputFilePath('data/commits.mjs', outDir);
197
- const commitsModule = `export default ${JSON.stringify(records, null, 2)};\n`;
198
- writeTextFile(dataCommitsFile, commitsModule);
199
- const dataStatsFile = outputFilePath('data/overtime-stats.mjs', outDir);
200
- const statsModule = `export default ${JSON.stringify(stats, null, 2)};\n`;
201
- writeTextFile(dataStatsFile, statsModule);
316
+ const dataCommitsFile = outputFilePath('data/commits.mjs', outDir)
317
+ const commitsModule = `export default ${JSON.stringify(records, null, 2)};\n`
318
+ writeTextFile(dataCommitsFile, commitsModule)
319
+ const dataStatsFile = outputFilePath('data/overtime-stats.mjs', outDir)
320
+ const statsModule = `export default ${JSON.stringify(stats, null, 2)};\n`
321
+ writeTextFile(dataStatsFile, statsModule)
202
322
 
203
323
  // 新增:每周趋势数据(用于前端图表)
204
- const weekGroups = groupRecords(records, 'week');
205
- const weekKeys = Object.keys(weekGroups).sort();
324
+ const weekGroups = groupRecords(records, 'week')
325
+ const weekKeys = Object.keys(weekGroups).sort()
206
326
  const weeklySeries = weekKeys.map((k) => {
207
327
  const s = analyzeOvertime(weekGroups[k], {
208
- startHour: opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
328
+ startHour:
329
+ opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
209
330
  endHour: opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18,
210
- lunchStart: opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
331
+ lunchStart:
332
+ opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
211
333
  lunchEnd: opts.lunchEnd || opts.lunchEnd === 0 ? opts.lunchEnd : 14,
212
- country: opts.country || 'CN',
213
- });
334
+ country: opts.country || 'CN'
335
+ })
214
336
  return {
215
337
  period: k,
216
338
  total: s.total,
217
339
  outsideWorkCount: s.outsideWorkCount,
218
340
  outsideWorkRate: s.outsideWorkRate,
219
341
  nonWorkdayCount: s.nonWorkdayCount,
220
- nonWorkdayRate: s.nonWorkdayRate,
221
- };
222
- });
223
- const dataWeeklyFile = outputFilePath('data/overtime-weekly.mjs', outDir);
224
- const weeklyModule = `export default ${JSON.stringify(weeklySeries, null, 2)};\n`;
225
- writeTextFile(dataWeeklyFile, weeklyModule);
226
- console.log(chalk.green(`Weekly series 已导出: ${dataWeeklyFile}`));
342
+ nonWorkdayRate: s.nonWorkdayRate
343
+ }
344
+ })
345
+ const dataWeeklyFile = outputFilePath(
346
+ 'data/overtime-weekly.mjs',
347
+ outDir
348
+ )
349
+ const weeklyModule = `export default ${JSON.stringify(weeklySeries, null, 2)};\n`
350
+ writeTextFile(dataWeeklyFile, weeklyModule)
351
+ console.log(chalk.green(`Weekly series 已导出: ${dataWeeklyFile}`))
227
352
 
228
- startServer(opts.port || 3000, outDir).catch(() => {});
353
+ startServer(opts.port || 3000, outDir).catch(() => {})
229
354
  } catch (err) {
230
- console.warn('Export data modules failed:', err && err.message ? err.message : err);
355
+ console.warn(
356
+ 'Export data modules failed:',
357
+ err && err.message ? err.message : err
358
+ )
231
359
  }
232
360
  }
233
361
 
234
362
  // 按月输出 ... 保持原逻辑
235
- const perPeriodFormats = String(opts.perPeriodFormats || '').split(',').map(s => String(s || '').trim().toLowerCase()).filter(Boolean);
363
+ const perPeriodFormats = String(opts.perPeriodFormats || '')
364
+ .split(',')
365
+ .map((s) =>
366
+ String(s || '')
367
+ .trim()
368
+ .toLowerCase()
369
+ )
370
+ .filter(Boolean)
236
371
  try {
237
- const monthGroups = groupRecords(records, 'month');
238
- const monthlyFileName = `overtime_${outBase}_monthly.txt`;
239
- const monthlyFile = outputFilePath(monthlyFileName, outDir);
240
- let monthlyContent = '';
241
- const monthKeys = Object.keys(monthGroups).sort();
372
+ const monthGroups = groupRecords(records, 'month')
373
+ const monthlyFileName = `overtime_${outBase}_monthly.txt`
374
+ const monthlyFile = outputFilePath(monthlyFileName, outDir)
375
+ let monthlyContent = ''
376
+ const monthKeys = Object.keys(monthGroups).sort()
242
377
  monthKeys.forEach((k) => {
243
- const groupRecs = monthGroups[k];
378
+ const groupRecs = monthGroups[k]
244
379
  const s = analyzeOvertime(groupRecs, {
245
- startHour: opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
380
+ startHour:
381
+ opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
246
382
  endHour: opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18,
247
- lunchStart: opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
383
+ lunchStart:
384
+ opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
248
385
  lunchEnd: opts.lunchEnd || opts.lunchEnd === 0 ? opts.lunchEnd : 14,
249
- country: opts.country || 'CN',
250
- });
251
- monthlyContent += `===== ${k} =====\n`;
252
- monthlyContent += `${renderOvertimeText(s)}\n\n`;
386
+ country: opts.country || 'CN'
387
+ })
388
+ monthlyContent += `===== ${k} =====\n`
389
+ monthlyContent += `${renderOvertimeText(s)}\n\n`
253
390
  // Also write a single file per month under 'month/' folder
254
391
  try {
255
- const perMonthFileName = `month/overtime_${outBase}_${k}.txt`;
256
- const perMonthFile = outputFilePath(perMonthFileName, outDir);
257
- writeTextFile(perMonthFile, renderOvertimeText(s));
258
- console.log(chalk.green(`Overtime 月度(${k}) 已导出: ${perMonthFile}`));
392
+ const perMonthFileName = `month/overtime_${outBase}_${k}.txt`
393
+ const perMonthFile = outputFilePath(perMonthFileName, outDir)
394
+ writeTextFile(perMonthFile, renderOvertimeText(s))
395
+ console.log(
396
+ chalk.green(`Overtime 月度(${k}) 已导出: ${perMonthFile}`)
397
+ )
259
398
  // per-period CSV / Tab format (按需生成)
260
399
  if (perPeriodFormats.includes('csv')) {
261
400
  try {
262
- const perMonthCsvName = `month/overtime_${outBase}_${k}.csv`;
263
- writeTextFile(outputFilePath(perMonthCsvName, outDir), renderOvertimeCsv(s));
264
- console.log(chalk.green(`Overtime 月度(CSV)(${k}) 已导出: ${outputFilePath(perMonthCsvName, outDir)}`));
401
+ const perMonthCsvName = `month/overtime_${outBase}_${k}.csv`
402
+ writeTextFile(
403
+ outputFilePath(perMonthCsvName, outDir),
404
+ renderOvertimeCsv(s)
405
+ )
406
+ console.log(
407
+ chalk.green(
408
+ `Overtime 月度(CSV)(${k}) 已导出: ${outputFilePath(perMonthCsvName, outDir)}`
409
+ )
410
+ )
265
411
  } catch (err) {
266
- console.warn(`Write monthly CSV for ${k} failed:`, err && err.message ? err.message : err);
412
+ console.warn(
413
+ `Write monthly CSV for ${k} failed:`,
414
+ err && err.message ? err.message : err
415
+ )
267
416
  }
268
417
  }
269
418
  if (perPeriodFormats.includes('tab')) {
270
419
  try {
271
- const perMonthTabName = `month/overtime_${outBase}_${k}.tab.txt`;
272
- writeTextFile(outputFilePath(perMonthTabName, outDir), renderOvertimeTab(s));
273
- console.log(chalk.green(`Overtime 月度(Tab)(${k}) 已导出: ${outputFilePath(perMonthTabName, outDir)}`));
420
+ const perMonthTabName = `month/overtime_${outBase}_${k}.tab.txt`
421
+ writeTextFile(
422
+ outputFilePath(perMonthTabName, outDir),
423
+ renderOvertimeTab(s)
424
+ )
425
+ console.log(
426
+ chalk.green(
427
+ `Overtime 月度(Tab)(${k}) 已导出: ${outputFilePath(perMonthTabName, outDir)}`
428
+ )
429
+ )
274
430
  } catch (err) {
275
- console.warn(`Write monthly Tab for ${k} failed:`, err && err.message ? err.message : err);
431
+ console.warn(
432
+ `Write monthly Tab for ${k} failed:`,
433
+ err && err.message ? err.message : err
434
+ )
276
435
  }
277
436
  }
278
437
  } catch (err) {
279
- console.warn(`Write monthly file for ${k} failed:`, err && err.message ? err.message : err);
438
+ console.warn(
439
+ `Write monthly file for ${k} failed:`,
440
+ err && err.message ? err.message : err
441
+ )
280
442
  }
281
- });
443
+ })
282
444
  if (!opts.perPeriodOnly) {
283
- writeTextFile(monthlyFile, monthlyContent);
284
- console.log(chalk.green(`Overtime 月度汇总 已导出: ${monthlyFile}`));
445
+ writeTextFile(monthlyFile, monthlyContent)
446
+ console.log(chalk.green(`Overtime 月度汇总 已导出: ${monthlyFile}`))
285
447
  }
286
448
  // per-period Excel (sheets or files)
287
449
  if (perPeriodFormats.includes('xlsx')) {
288
- const perPeriodExcelMode = String(opts.perPeriodExcelMode || 'sheets');
450
+ const perPeriodExcelMode = String(opts.perPeriodExcelMode || 'sheets')
289
451
  if (perPeriodExcelMode === 'sheets') {
290
452
  try {
291
- const monthXlsxName = `month/overtime_${outBase}_monthly.xlsx`;
292
- const monthXlsxFile = outputFilePath(monthXlsxName, outDir);
293
- await exportExcelPerPeriodSheets(monthGroups, monthXlsxFile, { stats: opts.stats, gerrit: opts.gerrit });
294
- console.log(chalk.green(`Overtime 月度(XLSX) 已导出: ${monthXlsxFile}`));
453
+ const monthXlsxName = `month/overtime_${outBase}_monthly.xlsx`
454
+ const monthXlsxFile = outputFilePath(monthXlsxName, outDir)
455
+ await exportExcelPerPeriodSheets(monthGroups, monthXlsxFile, {
456
+ stats: opts.stats,
457
+ gerrit: opts.gerrit
458
+ })
459
+ console.log(
460
+ chalk.green(`Overtime 月度(XLSX) 已导出: ${monthXlsxFile}`)
461
+ )
295
462
  } catch (err) {
296
- console.warn('Export month XLSX (sheets) failed:', err && err.message ? err.message : err);
463
+ console.warn(
464
+ 'Export month XLSX (sheets) failed:',
465
+ err && err.message ? err.message : err
466
+ )
297
467
  }
298
468
  } else {
299
469
  try {
300
- const monthKeys2 = Object.keys(monthGroups).sort();
301
- const tasks = monthKeys2.map(k2 => {
302
- const perMonthXlsxName = `month/overtime_${outBase}_${k2}.xlsx`;
303
- const perMonthXlsxFile = outputFilePath(perMonthXlsxName, outDir);
304
- return exportExcel(monthGroups[k2], null, { file: perMonthXlsxFile, stats: opts.stats, gerrit: opts.gerrit })
305
- .then(() => console.log(chalk.green(`Overtime 月度(XLSX)(${k2}) 已导出: ${perMonthXlsxFile}`)));
306
- });
307
- await Promise.all(tasks);
470
+ const monthKeys2 = Object.keys(monthGroups).sort()
471
+ const tasks = monthKeys2.map((k2) => {
472
+ const perMonthXlsxName = `month/overtime_${outBase}_${k2}.xlsx`
473
+ const perMonthXlsxFile = outputFilePath(perMonthXlsxName, outDir)
474
+ return exportExcel(monthGroups[k2], null, {
475
+ file: perMonthXlsxFile,
476
+ stats: opts.stats,
477
+ gerrit: opts.gerrit
478
+ }).then(() =>
479
+ console.log(
480
+ chalk.green(
481
+ `Overtime 月度(XLSX)(${k2}) 已导出: ${perMonthXlsxFile}`
482
+ )
483
+ )
484
+ )
485
+ })
486
+ await Promise.all(tasks)
308
487
  } catch (err) {
309
- console.warn('Export monthly XLSX files failed:', err && err.message ? err.message : err);
488
+ console.warn(
489
+ 'Export monthly XLSX files failed:',
490
+ err && err.message ? err.message : err
491
+ )
310
492
  }
311
493
  }
312
494
  }
313
495
  } catch (err) {
314
- console.warn('Generate monthly overtime failed:', err && err.message ? err.message : err);
496
+ console.warn(
497
+ 'Generate monthly overtime failed:',
498
+ err && err.message ? err.message : err
499
+ )
315
500
  }
316
501
 
317
502
  // 周度输出保持原逻辑(略)
318
503
  try {
319
- const weekGroups = groupRecords(records, 'week');
320
- const weeklyFileName = `overtime_${outBase}_weekly.txt`;
321
- const weeklyFile = outputFilePath(weeklyFileName, outDir);
322
- let weeklyContent = '';
323
- const weekKeys = Object.keys(weekGroups).sort();
504
+ const weekGroups = groupRecords(records, 'week')
505
+ const weeklyFileName = `overtime_${outBase}_weekly.txt`
506
+ const weeklyFile = outputFilePath(weeklyFileName, outDir)
507
+ let weeklyContent = ''
508
+ const weekKeys = Object.keys(weekGroups).sort()
324
509
  weekKeys.forEach((k) => {
325
- const groupRecs = weekGroups[k];
510
+ const groupRecs = weekGroups[k]
326
511
  const s = analyzeOvertime(groupRecs, {
327
- startHour: opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
512
+ startHour:
513
+ opts.workStart || opts.workStart === 0 ? opts.workStart : 9,
328
514
  endHour: opts.workEnd || opts.workEnd === 0 ? opts.workEnd : 18,
329
- lunchStart: opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
515
+ lunchStart:
516
+ opts.lunchStart || opts.lunchStart === 0 ? opts.lunchStart : 12,
330
517
  lunchEnd: opts.lunchEnd || opts.lunchEnd === 0 ? opts.lunchEnd : 14,
331
- country: opts.country || 'CN',
332
- });
333
- weeklyContent += `===== ${k} =====\n`;
334
- weeklyContent += `${renderOvertimeText(s)}\n\n`;
518
+ country: opts.country || 'CN'
519
+ })
520
+ weeklyContent += `===== ${k} =====\n`
521
+ weeklyContent += `${renderOvertimeText(s)}\n\n`
335
522
  try {
336
- const perWeekFileName = `week/overtime_${outBase}_${k}.txt`;
337
- const perWeekFile = outputFilePath(perWeekFileName, outDir);
338
- writeTextFile(perWeekFile, renderOvertimeText(s));
339
- console.log(chalk.green(`Overtime 周度(${k}) 已导出: ${perWeekFile}`));
523
+ const perWeekFileName = `week/overtime_${outBase}_${k}.txt`
524
+ const perWeekFile = outputFilePath(perWeekFileName, outDir)
525
+ writeTextFile(perWeekFile, renderOvertimeText(s))
526
+ console.log(chalk.green(`Overtime 周度(${k}) 已导出: ${perWeekFile}`))
340
527
  // eslint-disable-next-line no-shadow
341
- const perPeriodFormats = String(opts.perPeriodFormats || '').split(',').map(s => String(s || '').trim().toLowerCase()).filter(Boolean);
528
+ const perPeriodFormats = String(opts.perPeriodFormats || '')
529
+ .split(',')
530
+ // eslint-disable-next-line no-shadow
531
+ .map((s) =>
532
+ String(s || '')
533
+ .trim()
534
+ .toLowerCase()
535
+ )
536
+ .filter(Boolean)
342
537
  if (perPeriodFormats.includes('csv')) {
343
538
  try {
344
- const perWeekCsvName = `week/overtime_${outBase}_${k}.csv`;
345
- writeTextFile(outputFilePath(perWeekCsvName, outDir), renderOvertimeCsv(s));
346
- console.log(chalk.green(`Overtime 周度(CSV)(${k}) 已导出: ${outputFilePath(perWeekCsvName, outDir)}`));
539
+ const perWeekCsvName = `week/overtime_${outBase}_${k}.csv`
540
+ writeTextFile(
541
+ outputFilePath(perWeekCsvName, outDir),
542
+ renderOvertimeCsv(s)
543
+ )
544
+ console.log(
545
+ chalk.green(
546
+ `Overtime 周度(CSV)(${k}) 已导出: ${outputFilePath(perWeekCsvName, outDir)}`
547
+ )
548
+ )
347
549
  } catch (err) {
348
- console.warn(`Write weekly CSV for ${k} failed:`, err && err.message ? err.message : err);
550
+ console.warn(
551
+ `Write weekly CSV for ${k} failed:`,
552
+ err && err.message ? err.message : err
553
+ )
349
554
  }
350
555
  }
351
556
  if (perPeriodFormats.includes('tab')) {
352
557
  try {
353
- const perWeekTabName = `week/overtime_${outBase}_${k}.tab.txt`;
354
- writeTextFile(outputFilePath(perWeekTabName, outDir), renderOvertimeTab(s));
355
- console.log(chalk.green(`Overtime 周度(Tab)(${k}) 已导出: ${outputFilePath(perWeekTabName, outDir)}`));
558
+ const perWeekTabName = `week/overtime_${outBase}_${k}.tab.txt`
559
+ writeTextFile(
560
+ outputFilePath(perWeekTabName, outDir),
561
+ renderOvertimeTab(s)
562
+ )
563
+ console.log(
564
+ chalk.green(
565
+ `Overtime 周度(Tab)(${k}) 已导出: ${outputFilePath(perWeekTabName, outDir)}`
566
+ )
567
+ )
356
568
  } catch (err) {
357
- console.warn(`Write weekly Tab for ${k} failed:`, err && err.message ? err.message : err);
569
+ console.warn(
570
+ `Write weekly Tab for ${k} failed:`,
571
+ err && err.message ? err.message : err
572
+ )
358
573
  }
359
574
  }
360
575
  } catch (err) {
361
- console.warn(`Write weekly file for ${k} failed:`, err && err.message ? err.message : err);
576
+ console.warn(
577
+ `Write weekly file for ${k} failed:`,
578
+ err && err.message ? err.message : err
579
+ )
362
580
  }
363
- });
364
- writeTextFile(weeklyFile, weeklyContent);
365
- console.log(chalk.green(`Overtime 周度汇总 已导出: ${weeklyFile}`));
581
+ })
582
+ writeTextFile(weeklyFile, weeklyContent)
583
+ console.log(chalk.green(`Overtime 周度汇总 已导出: ${weeklyFile}`))
366
584
  } catch (err) {
367
- console.warn('Generate weekly overtime failed:', err && err.message ? err.message : err);
585
+ console.warn(
586
+ 'Generate weekly overtime failed:',
587
+ err && err.message ? err.message : err
588
+ )
368
589
  }
369
590
  }
370
591
 
371
592
  // --- JSON/TEXT/EXCEL(保持原逻辑) ---
372
593
  if (opts.json || opts.format === 'json') {
373
- const file = opts.out || 'commits.json';
374
- const filepath = outputFilePath(file, outDir);
375
- writeJSON(filepath, groups || records);
376
- console.log(chalk.green(`JSON 已导出: ${filepath}`));
377
- return;
594
+ const file = opts.out || 'commits.json'
595
+ const filepath = outputFilePath(file, outDir)
596
+ writeJSON(filepath, groups || records)
597
+ console.log(chalk.green(`JSON 已导出: ${filepath}`))
598
+ return
378
599
  }
379
600
 
380
601
  if (opts.format === 'text') {
381
- const file = opts.out || 'commits.txt';
382
- const filepath = outputFilePath(file, outDir);
383
- const text = renderText(records, groups, { showGerrit: !!opts.gerrit });
384
- writeTextFile(filepath, text);
385
- console.log(text);
386
- console.log(chalk.green(`文本已导出: ${filepath}`));
387
- return;
602
+ const file = opts.out || 'commits.txt'
603
+ const filepath = outputFilePath(file, outDir)
604
+ const text = renderText(records, groups, { showGerrit: !!opts.gerrit })
605
+ writeTextFile(filepath, text)
606
+ console.log(text)
607
+ console.log(chalk.green(`文本已导出: ${filepath}`))
608
+ return
388
609
  }
389
610
 
390
611
  if (opts.format === 'excel') {
391
- const excelFile = opts.out || 'commits.xlsx';
392
- const excelPath = outputFilePath(excelFile, outDir);
393
- const txtFile = excelFile.replace(/\.xlsx$/, '.txt');
394
- const txtPath = outputFilePath(txtFile, outDir);
612
+ const excelFile = opts.out || 'commits.xlsx'
613
+ const excelPath = outputFilePath(excelFile, outDir)
614
+ const txtFile = excelFile.replace(/\.xlsx$/, '.txt')
615
+ const txtPath = outputFilePath(txtFile, outDir)
395
616
  await exportExcel(records, groups, {
396
617
  file: excelPath,
397
618
  stats: opts.stats,
398
619
  gerrit: opts.gerrit
399
- });
400
- const text = renderText(records, groups);
401
- writeTextFile(txtPath, text);
402
- console.log(chalk.green(`Excel 已导出: ${excelPath}`));
403
- console.log(chalk.green(`文本已自动导出: ${txtPath}`));
620
+ })
621
+ const text = renderText(records, groups)
622
+ writeTextFile(txtPath, text)
623
+ console.log(chalk.green(`Excel 已导出: ${excelPath}`))
624
+ console.log(chalk.green(`文本已自动导出: ${txtPath}`))
404
625
  }
405
- })();
626
+ await autoCheckUpdate()
627
+ }
628
+
629
+ main()
@@ -0,0 +1,3 @@
1
+ export const CLI_NAME = 'wukong-gitlog-cli'
2
+
3
+ export const GIT_HUB ='https://www.npmjs.com/package/wukong-gitlog-cli'
package/src/git.mjs CHANGED
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env zx
2
- import 'zx/globals'
1
+ import { $ } from 'zx'
2
+
3
3
 
4
4
  export async function getGitLogs(opts) {
5
5
  const { author, email, since, until, limit, merges } = opts
@@ -0,0 +1,130 @@
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
+ }
@@ -0,0 +1,56 @@
1
+ import { Chalk } from 'chalk'
2
+
3
+ const chalk = new Chalk({ level: 3 }) // 强制开启 truecolor chalk v5
4
+ // 自定义灰色,适合透明和深色背景,避免看不清
5
+ const betterGray = chalk.hex('#BBBBBB')
6
+
7
+ // 统一颜色模块
8
+ export const colors = {
9
+ // 标题、版本号
10
+ title: chalk.bold.cyan,
11
+
12
+ // 命令名
13
+ command: chalk.green,
14
+
15
+ // 选项标记
16
+ option: chalk.magenta,
17
+
18
+ // 描述文字,替代默认灰色
19
+ desc: betterGray,
20
+
21
+ // Usage、Commands、Options、示例等小标题
22
+ header: chalk.yellow,
23
+
24
+ // 示例文字,普通白色
25
+ example: chalk.white,
26
+
27
+ // 警告红色
28
+ error: chalk.bold.red,
29
+
30
+ // 成功绿色
31
+ success: chalk.bold.green,
32
+
33
+ // 信息蓝色
34
+ info: chalk.blue,
35
+
36
+ // 可用更柔和的调暗白色,替代 dim gray
37
+ dim: chalk.white.dim,
38
+ bold: chalk.bold,
39
+ boldBlue: chalk.bold.blue,
40
+ gray: chalk.bold.gray,
41
+ green: chalk.green,
42
+ cyanish: chalk.hex('#57a097'),
43
+ white: chalk.white,
44
+ }
45
+
46
+ export const c = {
47
+ title: chalk.bold.cyan,
48
+ label: chalk.yellow,
49
+ value: chalk.white,
50
+ dim: chalk.hex('#BBBBBB'),
51
+ success: chalk.green,
52
+ green: chalk.green,
53
+ error: chalk.red,
54
+ red: chalk.red,
55
+ link: chalk.blue.underline
56
+ }
@@ -0,0 +1,10 @@
1
+ import os from 'os'
2
+ import process from 'process'
3
+ import { colors } from './colors.mjs'
4
+
5
+
6
+ export function showVersionInfo(VERSION) {
7
+ console.log(colors.title(`\nwukong-deploy v${VERSION}\n`))
8
+ console.log(`${colors.bold('Node.js')}: ${process.version}`)
9
+ console.log(`${colors.bold('Platform')}: ${os.platform()} ${os.arch()}`)
10
+ }