wukong-gitlog-cli 1.0.4 → 1.0.6
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 +9 -0
- package/README.zh-CN.md +7 -7
- package/package.json +1 -1
- package/src/server.mjs +86 -43
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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.6](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.5...v1.0.6) (2025-11-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* 🎸 auto open ([380e013](https://github.com/tomatobybike/wukong-gitlog-cli/commit/380e01386300342b5b4009f4ddf5463a19cf1be8))
|
|
11
|
+
|
|
12
|
+
### [1.0.5](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.4...v1.0.5) (2025-11-29)
|
|
13
|
+
|
|
5
14
|
### [1.0.4](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.3...v1.0.4) (2025-11-29)
|
|
6
15
|
|
|
7
16
|
### [1.0.3](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.2...v1.0.3) (2025-11-29)
|
package/README.zh-CN.md
CHANGED
|
@@ -62,7 +62,7 @@ wukong-gitlog-cli [options]
|
|
|
62
62
|
### 常用参数
|
|
63
63
|
|
|
64
64
|
| 参数 | 描述 |
|
|
65
|
-
| -------------------- | ------------------------------------------------------------------ |
|
|
65
|
+
| -------------------- | ------------------------------------------------------------------ |
|
|
66
66
|
| `--author <name>` | 按作者过滤 |
|
|
67
67
|
| `--email <email>` | 按邮箱过滤 |
|
|
68
68
|
| `--since <date>` | 起始日期(如 2025-01-01) |
|
|
@@ -70,8 +70,8 @@ wukong-gitlog-cli [options]
|
|
|
70
70
|
| `--limit <n>` | 限制提交数量 |
|
|
71
71
|
| `--no-merges` | 排除 merge 提交 |
|
|
72
72
|
| `--json` | 输出 JSON |
|
|
73
|
-
| `--format <type>` | 输出格式:text / excel / json(默认 text)
|
|
74
|
-
| `--group-by <type>` | 分组:day / month
|
|
73
|
+
| `--format <type>` | 输出格式: text / excel / json(默认 text) |
|
|
74
|
+
| `--group-by <type>` | 分组: day / month |
|
|
75
75
|
| `--overtime` | 启用加班文化分析 |
|
|
76
76
|
| `--country <code>` | 假期:CN 或 US(默认 CN) |
|
|
77
77
|
| `--stats` | Excel 中包含统计 sheet |
|
|
@@ -79,10 +79,10 @@ wukong-gitlog-cli [options]
|
|
|
79
79
|
| `--gerrit-api <url>` | Gerrit API 地址(用于 changeNumber) |
|
|
80
80
|
| `--out <file>` | 输出文件名 |
|
|
81
81
|
| `--out-dir <dir>` | 输出目录 |
|
|
82
|
-
| `--out-parent` | 输出到父目录的 output
|
|
83
|
-
| `--serve` | 启动本地
|
|
84
|
-
| `--port <n>` |
|
|
85
|
-
| `--serve-only` | 仅启动
|
|
82
|
+
| `--out-parent` | 输出到父目录的 `output/` |
|
|
83
|
+
| `--serve` | 启动本地 Web 服务查看提交统计(会生成 output/data 下的数据) |
|
|
84
|
+
| `--port <n>` | Web 服务端口(默认 3000) |
|
|
85
|
+
| `--serve-only` | 仅启动 Web 服务,不导出或分析数据(使用现有 output/data) |
|
|
86
86
|
| `--version` | 显示版本号 |
|
|
87
87
|
|
|
88
88
|
---
|
package/package.json
CHANGED
package/src/server.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { exec } from 'child_process'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import http from 'http'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
6
7
|
|
|
7
8
|
const mime = new Map([
|
|
8
9
|
['.html', 'text/html; charset=utf-8'],
|
|
@@ -14,74 +15,116 @@ const mime = new Map([
|
|
|
14
15
|
['.png', 'image/png'],
|
|
15
16
|
['.jpg', 'image/jpeg'],
|
|
16
17
|
['.jpeg', 'image/jpeg'],
|
|
17
|
-
['.map', 'application/json; charset=utf-8']
|
|
18
|
-
])
|
|
18
|
+
['.map', 'application/json; charset=utf-8']
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
// --------------------------------
|
|
22
|
+
// 打开浏览器(跨平台)
|
|
23
|
+
// --------------------------------
|
|
24
|
+
function openBrowser(url) {
|
|
25
|
+
const { platform } = process
|
|
26
|
+
|
|
27
|
+
let cmd
|
|
28
|
+
if (platform === 'win32') {
|
|
29
|
+
cmd = `start "" "${url}"`
|
|
30
|
+
} else if (platform === 'darwin') {
|
|
31
|
+
cmd = `open "${url}"`
|
|
32
|
+
} else {
|
|
33
|
+
cmd = `xdg-open "${url}"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exec(cmd, (err) => {
|
|
37
|
+
if (err) {
|
|
38
|
+
console.log(chalk.yellow(`Failed to auto-open browser: ${err.message}`))
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
// eslint-disable-next-line default-param-last
|
|
21
44
|
export function startServer(port = 3000, outputDir) {
|
|
22
45
|
// 解析包根目录,确保 web 资源在全局安装后也能找到
|
|
23
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
24
|
-
const pkgRoot = path.resolve(__dirname, '..')
|
|
25
|
-
const webRoot = path.resolve(pkgRoot, 'web')
|
|
26
|
-
const dataRoot = outputDir
|
|
46
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
47
|
+
const pkgRoot = path.resolve(__dirname, '..')
|
|
48
|
+
const webRoot = path.resolve(pkgRoot, 'web')
|
|
49
|
+
const dataRoot = outputDir
|
|
50
|
+
? path.resolve(outputDir)
|
|
51
|
+
: path.resolve(process.cwd(), 'output')
|
|
27
52
|
|
|
28
53
|
// warn if web directory or data directory doesn't exist
|
|
29
54
|
if (!fs.existsSync(webRoot)) {
|
|
30
|
-
console.warn(
|
|
55
|
+
console.warn(
|
|
56
|
+
chalk.yellow(
|
|
57
|
+
`Warning: web/ directory not found at ${webRoot}. Server will still run but no UI will be available.`
|
|
58
|
+
)
|
|
59
|
+
)
|
|
31
60
|
}
|
|
32
61
|
if (!fs.existsSync(dataRoot)) {
|
|
33
|
-
console.warn(
|
|
62
|
+
console.warn(
|
|
63
|
+
chalk.yellow(
|
|
64
|
+
`Warning: output data directory not found at ${dataRoot}. Server will still run but data endpoints (/data/) may 404.`
|
|
65
|
+
)
|
|
66
|
+
)
|
|
34
67
|
}
|
|
35
68
|
|
|
36
69
|
const server = http.createServer((req, res) => {
|
|
37
70
|
try {
|
|
38
71
|
// Normalize URL path
|
|
39
|
-
const u = new URL(req.url, `http://localhost`)
|
|
40
|
-
let pathname = decodeURIComponent(u.pathname)
|
|
72
|
+
const u = new URL(req.url, `http://localhost`)
|
|
73
|
+
let pathname = decodeURIComponent(u.pathname)
|
|
41
74
|
|
|
42
75
|
// Serve data files under /data/* mapped to dataRoot/data/*
|
|
43
76
|
if (pathname.startsWith('/data/')) {
|
|
44
|
-
const relative = pathname.replace(/^\/data\//, '')
|
|
45
|
-
const fileLocal = path.join(dataRoot, 'data', relative)
|
|
77
|
+
const relative = pathname.replace(/^\/data\//, '')
|
|
78
|
+
const fileLocal = path.join(dataRoot, 'data', relative)
|
|
46
79
|
if (fs.existsSync(fileLocal) && fs.statSync(fileLocal).isFile()) {
|
|
47
|
-
const ext = path.extname(fileLocal).toLowerCase()
|
|
48
|
-
res.setHeader(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
const ext = path.extname(fileLocal).toLowerCase()
|
|
81
|
+
res.setHeader(
|
|
82
|
+
'Content-Type',
|
|
83
|
+
mime.get(ext) || 'application/octet-stream'
|
|
84
|
+
)
|
|
85
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
86
|
+
const stream = fs.createReadStream(fileLocal)
|
|
87
|
+
stream.pipe(res)
|
|
88
|
+
return
|
|
53
89
|
}
|
|
54
90
|
}
|
|
55
91
|
|
|
56
92
|
// Resolve web assets
|
|
57
|
-
if (pathname === '/') pathname = '/index.html'
|
|
58
|
-
const fileLocal = path.join(webRoot, pathname)
|
|
93
|
+
if (pathname === '/') pathname = '/index.html'
|
|
94
|
+
const fileLocal = path.join(webRoot, pathname)
|
|
59
95
|
if (fs.existsSync(fileLocal) && fs.statSync(fileLocal).isFile()) {
|
|
60
|
-
const ext = path.extname(fileLocal).toLowerCase()
|
|
61
|
-
res.setHeader(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
const ext = path.extname(fileLocal).toLowerCase()
|
|
97
|
+
res.setHeader(
|
|
98
|
+
'Content-Type',
|
|
99
|
+
mime.get(ext) || 'application/octet-stream'
|
|
100
|
+
)
|
|
101
|
+
const stream = fs.createReadStream(fileLocal)
|
|
102
|
+
stream.pipe(res)
|
|
103
|
+
return
|
|
65
104
|
}
|
|
66
105
|
|
|
67
106
|
// file not found
|
|
68
|
-
res.statusCode = 404
|
|
69
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
70
|
-
res.end('Not Found')
|
|
107
|
+
res.statusCode = 404
|
|
108
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
|
|
109
|
+
res.end('Not Found')
|
|
71
110
|
} catch (err) {
|
|
72
|
-
res.statusCode = 500
|
|
73
|
-
res.end('Server error')
|
|
111
|
+
res.statusCode = 500
|
|
112
|
+
res.end('Server error')
|
|
74
113
|
}
|
|
75
|
-
})
|
|
114
|
+
})
|
|
76
115
|
|
|
77
116
|
return new Promise((resolve, reject) => {
|
|
78
|
-
server.on('error', (err) => reject(err))
|
|
117
|
+
server.on('error', (err) => reject(err))
|
|
79
118
|
server.listen(port, () => {
|
|
80
|
-
|
|
81
|
-
console.log(chalk.green(`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
const url = `http://localhost:${port}`
|
|
120
|
+
console.log(chalk.green(`Server started at ${url}`))
|
|
121
|
+
console.log(chalk.green(`Serving web/ and output/data/`))
|
|
122
|
+
|
|
123
|
+
// ====== 自动打开浏览器 ======
|
|
124
|
+
openBrowser(url)
|
|
125
|
+
resolve(server)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
85
128
|
}
|
|
86
129
|
|
|
87
|
-
export default startServer
|
|
130
|
+
export default startServer
|