wukong-gitlog-cli 0.0.1

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/.editorconfig ADDED
@@ -0,0 +1,10 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ indent_size = 2
7
+ indent_style = space
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+ max_line_length = 100
package/.eslintignore ADDED
@@ -0,0 +1,8 @@
1
+ /node_modules/**
2
+ /build
3
+ /dist
4
+ /Doc
5
+ /bin/
6
+ /src/lib
7
+ /backup
8
+ #/src/**/*.zx.mjs
package/.eslintrc ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es6": true,
5
+ "node": true
6
+ },
7
+ "extends": ["airbnb-base", "plugin:prettier/recommended"],
8
+ "parserOptions": {
9
+ "ecmaVersion": 2022,
10
+ "sourceType": "module"
11
+ },
12
+ "globals": {
13
+ "GLOBAL": "writable",
14
+ "$": "readonly",
15
+ "cd": "readonly",
16
+ "fetch": "readonly",
17
+ "question": "readonly",
18
+ "chalk": "readonly",
19
+ "argv": "readonly"
20
+ },
21
+ "plugins": ["prettier"],
22
+ "rules": {
23
+ "array-callback-return": "off",
24
+ "consistent-return": "off",
25
+ "no-await-in-loop": "warn",
26
+ "no-unused-vars": "warn",
27
+ "no-console": "off",
28
+ "no-plusplus": "off",
29
+ "no-unresolved": "off",
30
+ "import/no-unresolved": "off",
31
+ "import/no-named-as-default": "off",
32
+ "no-restricted-syntax": "off",
33
+ "no-underscore-dangle": "off",
34
+ "no-unused-expressions": "off",
35
+ "no-useless-constructor": "warn",
36
+ "no-useless-escape": "off",
37
+ "prettier/prettier": "off",
38
+ "import/prefer-default-export": "off",
39
+ "import/no-named-as-default-member": "off",
40
+ "import/extensions": "off",
41
+ "import/no-extraneous-dependencies": [
42
+ "off",
43
+ {
44
+ "devDependencies": true,
45
+ "peerDependencies": true
46
+ // optionalDependencies: true,
47
+ // bundledDependencies: true
48
+ }
49
+ ]
50
+ },
51
+ "settings": {
52
+ "import/resolver": {
53
+ "alias": [["@", "./src"]]
54
+ }
55
+ },
56
+ "overrides": [
57
+ {
58
+ "files": ["*.js", "*.mjs"]
59
+ // ... 其他规则配置
60
+ }
61
+ ]
62
+ }
@@ -0,0 +1,13 @@
1
+ # Dependencies
2
+ node_modules
3
+
4
+
5
+ build
6
+
7
+ # Lock files
8
+ package-lock.json
9
+
10
+ # Misc
11
+ dist
12
+ public
13
+ src/lib
package/.prettierrc ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "bracketSpacing": true,
3
+ "importOrder": [
4
+ "^./conf(.*)(?<!.css)$",
5
+ "^(#|#company|#command|#ui|components|#utils|#constants||#lib|utils|config|vendored-lib)(/(.*)(?<!.css)$)",
6
+ "^(src|@)/(.*)(?<!.css)$",
7
+ "^[./]",
8
+ "^api/(.*)(?<!.css)$",
9
+ "^.+\\.s?css$"
10
+ ],
11
+ "importOrderSeparation": true,
12
+ "importOrderSortSpecifiers": true,
13
+ "jsxBracketSameLine": true,
14
+ "plugins": ["@trivago/prettier-plugin-sort-imports"],
15
+ "printWidth": 80,
16
+ "semi": false,
17
+ "singleQuote": true,
18
+ "tabWidth": 2,
19
+ "trailingComma": "none",
20
+ "useTabs": false
21
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
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
+
5
+ ### 0.0.1 (2025-11-26)
6
+
7
+
8
+ ### Features
9
+
10
+ * 🎸 文件结构优化 ([2bcedad](https://github.com/tomatobybike/wukong-gitlog-cli/commit/2bcedad8c8df7f41e0c8a03cb951c783343037b7))
11
+ * 🎸 增加output目录 ([c5bdf9d](https://github.com/tomatobybike/wukong-gitlog-cli/commit/c5bdf9d4f52f39bd7d580318bafc8ba4b6c129bc))
12
+ * 🎸 init ([ea82531](https://github.com/tomatobybike/wukong-gitlog-cli/commit/ea8253186a2fbf968c04b501ce180afc788e7b0f))
13
+ * 🎸 package.json ([5b6e1d4](https://github.com/tomatobybike/wukong-gitlog-cli/commit/5b6e1d49cf61a4254584a4e1e737099db2e4adba))
14
+ * 🎸 script ([94ea5e1](https://github.com/tomatobybike/wukong-gitlog-cli/commit/94ea5e1c5c6338f5da51d3e59aae059a98a333ac))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * 🐛 eslint ([9edf3cb](https://github.com/tomatobybike/wukong-gitlog-cli/commit/9edf3cb94bafe65c2eb94a22bb68f9b3fb9a9b9b))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2025] [杨琼]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # wukong-gitlog-cli
2
+
3
+ Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Export commit logs to JSON / text or Excel (XLSX)
10
+ - Group commits by date (day / month)
11
+ - Include a daily stats sheet in Excel
12
+ - Optional Gerrit links per commit (custom template or prefix)
13
+ - Small, dependency-friendly CLI using ZX and ExcelJS
14
+
15
+ ---
16
+
17
+ ## What's new
18
+
19
+ - `--gerrit` option to show Gerrit links in text/excel/json output
20
+ - `--out-dir` / `--out-parent` to control where output files are written (useful to avoid committing generated files)
21
+ - `npm` demo scripts for quickly running examples (`cli:text-demo`, `cli:excel-demo`, `cli:json-demo`, `cli:gerrit-demo` and parent variants)
22
+ - `src/utils` restructured with `src/utils/index.mjs` barrel to simplify imports
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ Clone and install dependencies:
29
+
30
+ ```bash
31
+ git clone https://github.com/tomatoboy/wukong-gitlog-cli.git
32
+ cd wukong-gitlog-cli
33
+ npm install
34
+ ```
35
+
36
+ Run from the repo using Node:
37
+
38
+ ```bash
39
+ node ./src/cli.mjs --help
40
+ ```
41
+
42
+ (Optional) Install globally to run with a short command:
43
+
44
+ ```bash
45
+ npm link
46
+ # then you can run
47
+ wukong-gitlog-cli --help
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ node ./src/cli.mjs [options]
56
+ ```
57
+
58
+ Command-line options:
59
+
60
+ - `--author <name>` Filter commits by author name
61
+ - `--email <email>` Filter commits by author email
62
+ - `--since <date>` Start date (e.g., 2025-01-01)
63
+ - `--until <date>` End date
64
+ - `--limit <n>` Limit number of commits
65
+ - `--no-merges` Exclude merge commits
66
+ - `--json` Output JSON
67
+ - `--format <type>` Output format: `text` | `excel` | `json` (default: `text`)
68
+ - `--group-by <type>` Group commits by date: `day` | `month`
69
+ - `--stats` Include a `Stats` sheet in the Excel export
70
+ - `--gerrit <prefix>` Show Gerrit URL for each commit (supports templates `{{hash}}` and `{{changeId}}`; `{{changeId}}` falls back to `hash` when absent)
71
+ - `--out <file>` Output file name (without path). Defaults: `commits.json` / `commits.txt` / `commits.xlsx`
72
+ - `--out-dir <dir>` Output directory path — supports relative or absolute path, e.g., `--out-dir ../output`
73
+ - `--out-parent` Place output in the parent directory's `output/` folder (same as `--out-dir ../output`)
74
+
75
+ > Output files are written to an `output/` directory in the current working directory.
76
+ >
77
+ > Tip: Use `--out-parent` or `--out-dir ../output` to write outputs into the parent folder's `output/` to avoid accidentally committing generated files to your repository.
78
+
79
+ ---
80
+
81
+ ## Gerrit support
82
+
83
+ Use the `--gerrit` option to include a Gerrit link for each commit. You can provide a template containing `{{hash}}` to place the full commit hash into the URL, for example:
84
+
85
+ ```bash
86
+ node ./src/cli.mjs --gerrit "https://gerrit.example.com/c/project/+/{{hash}}" --limit 5 --format text
87
+ ```
88
+
89
+ If `{{hash}}` is not present, the CLI will append the commit hash to the prefix with a `/` separator.
90
+
91
+ You can also use `{{changeId}}` in the template to reference Gerrit change id. The tool will try to extract a `Change-Id: I...` value from the commit body and replace `{{changeId}}` with it. If it can't find a `Change-Id`, the CLI will fall back to using the commit `hash`.
92
+
93
+ The Gerrit link will show up in:
94
+
95
+ - The text output if `--format text` (as a new `Gerrit` column)
96
+ - The Excel export as a `Gerrit` column if `--format excel`
97
+ - JSON output will include a `gerrit` field for each record when `--gerrit` is used
98
+ - JSON output will include a `gerrit` field for each record when `--gerrit` is used
99
+ - When `--gerrit` uses `{{changeId}}`, the CLI will try to extract `Change-Id:` from the commit body and include `changeId` and `body` in the JSON record. If no `Change-Id` is present, the CLI falls back to `hash` when forming the Gerrit URL.
100
+
101
+ Note: `--out <file>` is the filename only and the directory used to store that file depends on:
102
+
103
+ - The default directory `./output/` in the current working directory
104
+ - `--out-dir <dir>` to override the target folder (relative or absolute)
105
+ - `--out-parent` to write to the parent repository folder `../output/` (same as `--out-dir ../output`)
106
+
107
+ For example:
108
+
109
+ ```bash
110
+ node ./src/cli.mjs --out parent.json --out-parent
111
+ node ./src/cli.mjs --out demo.txt --out-dir ../temp
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Examples
117
+
118
+ Export as text, grouped by month, with Gerrit links:
119
+
120
+ ```bash
121
+ node ./src/cli.mjs --format text --group-by month --gerrit "https://gerrit.example.com/c/project/+/{{hash}}"
122
+ ```
123
+
124
+ Export to Excel with stats and Gerrit URLs:
125
+
126
+ ```bash
127
+ node ./src/cli.mjs --format excel --stats --gerrit "https://gerrit.example.com/c/project/+/{{hash}}"
128
+ ```
129
+
130
+ Export raw JSON:
131
+
132
+ ```bash
133
+ node ./src/cli.mjs --json --out commits.json
134
+ ```
135
+
136
+ Export text to a custom directory (parent output folder):
137
+
138
+ ```bash
139
+ node ./src/cli.mjs --out-dir ../output --format text --limit 5 --out custom1.txt
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Quick demo (npm scripts)
145
+
146
+ We provided a few convenient npm scripts to quickly run common scenarios. Run them from the project root:
147
+
148
+ ```bash
149
+ # show help
150
+ npm run cli:help
151
+
152
+ # simple text export (commits.txt in ./output)
153
+ npm run cli:text-demo
154
+
155
+ # Excel export with stats (commits.xlsx + commits.txt in ./output)
156
+ npm run cli:excel-demo
157
+
158
+ # JSON export (commits.json in ./output)
159
+ npm run cli:json-demo
160
+
161
+ # Gerrit text export demo
162
+ npm run cli:gerrit-demo
163
+ # Gerrit Change-Id demo (use commit Change-Id to build Gerrit URLs when present)
164
+ npm run cli:gerrit-changeid-demo
165
+ ```
166
+
167
+ If you prefer to write output outside the project (e.g., a parent `output/` folder), we also provide `npm` scripts that run with `--out-parent`:
168
+
169
+ ```bash
170
+ # text export to parent `output/`
171
+ npm run cli:text-demo-parent
172
+
173
+ # excel export to parent `output/`
174
+ npm run cli:excel-demo-parent
175
+ ```
176
+
177
+ Example text output (from `npm run cli:text-demo`):
178
+
179
+ ```text
180
+ Hash | Author | Date | Message
181
+ ---------------------------------------------------------------------------------------------------------------------
182
+ c5bdf9d4 | tom | 2025-11-25 | feat: 🎸 增加output目录
183
+
184
+ ea82531 | tom | 2025-11-25 | feat: 🎸 init
185
+
186
+ 741de50 | tom | 2025-11-25 | first commit
187
+ ```
188
+
189
+ Example JSON output (from `npm run cli:json-demo`):
190
+
191
+ ```json
192
+ [
193
+ {
194
+ "hash": "c5bdf9d4f52f39bd7d580318bafc8ba4b6c129bc",
195
+ "author": "tom",
196
+ "email": "",
197
+ "date": "2025-11-25 17:24:32 +0800",
198
+ "message": "feat: 🎸 增加output目录"
199
+ }
200
+ /* truncated... */
201
+ ]
202
+ ```
203
+
204
+ Example JSON output including `changeId`/`gerrit` when `--gerrit` uses `{{changeId}}` (if present in commit):
205
+
206
+ ```json
207
+ [
208
+ {
209
+ "hash": "Iabc...",
210
+ "author": "tom",
211
+ "email": "",
212
+ "date": "2025-11-25 17:24:32 +0800",
213
+ "message": "feat: add feature",
214
+ "body": "feat: add feature\n\nChange-Id: Iabcd123456789",
215
+ "changeId": "Iabcd123456789",
216
+ "gerrit": "https://gerrit.example.com/c/project/+/Iabcd123456789"
217
+ }
218
+ ]
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Notes & Developer Info
224
+
225
+ - The CLI prints helpful messages after exporting files and writes outputs to the `output/` folder in the repo root.
226
+ - Internally `src/utils/index.mjs` acts as a barrel that re-exports helper functions located in `src/utils/`.
227
+ - If you plan to reuse the helpers in other modules, import from `./src/utils/index.mjs` explicitly.
228
+ - The Excel export uses `exceljs` and adds an ``autoFilter`` to the sheet header.
229
+
230
+ Suggested `.gitignore` snippet (to avoid accidentally committing generated files):
231
+
232
+ ```gitignore
233
+ # ignore commit exports
234
+ output/
235
+ custom-output/
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Contributing
241
+
242
+ PRs are welcome — add tests and keep changes modular. If you add new CLI flags or new fields in commit records, please update this README accordingly.
243
+
244
+ ---
245
+
246
+ ## License
247
+
248
+ MIT
249
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../src/cli.mjs');
package/jsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "checkJs": false,
5
+ "allowJs": true,
6
+ "module": "esnext",
7
+ "target": "esnext",
8
+ "moduleResolution": "Node",
9
+
10
+ "allowSyntheticDefaultImports": true,
11
+ "paths": {
12
+ "#src/*": ["src/*"],
13
+ "#utils/*": ["src/utils/*"]
14
+ }
15
+ },
16
+
17
+ "include": ["src", "bin"],
18
+
19
+ "exclude": ["node_modules", "dist", "build", "**/*.test.js"]
20
+ }
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "wukong-gitlog-cli",
3
+ "version": "0.0.1",
4
+ "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
+ "keywords": [
6
+ "git",
7
+ "gitlog",
8
+ "commit-log",
9
+ "commits",
10
+ "gerrit",
11
+ "excel",
12
+ "json",
13
+ "text",
14
+ "cli",
15
+ "export",
16
+ "commit-logs",
17
+ "wukong-gitlog",
18
+ "提交",
19
+ "提交记录",
20
+ "提交日志",
21
+ "导出",
22
+ "命令行工具",
23
+ "Excel导出",
24
+ "JSON导出",
25
+ "文本导出",
26
+ "Gerrit链接"
27
+ ],
28
+ "homepage": "https://tomatobybike.github.io/wukong-gitlog-cli/",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/tomatobybike/wukong-gitlog-cli"
32
+ },
33
+ "license": "MIT",
34
+ "author": "Tom <tomatobybike@gmail.com>",
35
+ "type": "module",
36
+ "bin": {
37
+ "wukong-gitlog-cli": "./bin/wukong-gitlog-cli"
38
+ },
39
+ "scripts": {
40
+ "cli:excel-demo": "node ./src/cli.mjs --format excel --stats --limit 5 --out commits.xlsx",
41
+ "cli:excel-demo-parent": "node ./src/cli.mjs --out-parent --format excel --stats --limit 5 --out commits-parent.xlsx",
42
+ "cli:gerrit-changeid-demo": "node ./src/cli.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{changeId}}\" --limit 5 --out commits-gerrit-changeid.txt",
43
+ "cli:gerrit-demo": "node ./src/cli.mjs --format text --gerrit \"https://gerrit.example.com/c/project/+/{{hash}}\" --limit 5 --out commits-gerrit.txt",
44
+ "cli:help": "node ./src/cli.mjs --help",
45
+ "cli:json-demo": "node ./src/cli.mjs --json --limit 5 --out commits.json",
46
+ "cli:text-demo": "node ./src/cli.mjs --format text --limit 5 --out commits.txt",
47
+ "cli:text-demo-parent": "node ./src/cli.mjs --out-parent --format text --limit 5 --out commits-parent.txt",
48
+ "format": "prettier --write \"src/**/*.{js,mjs}\"",
49
+ "lint": "eslint src --ext .js,.mjs src",
50
+ "lint:fix": "eslint src --ext .js,.mjs --fix",
51
+ "pack": "npm pack",
52
+ "build": "node scripts/esbuild.config.mjs",
53
+ "prepare": "husky install",
54
+ "prettierall": "npx prettier --write 'src/**/*.{js,jsx}'",
55
+ "prepublishOnly": "yarn lint && yarn build",
56
+ "release": "yarn release:patch && yarn push:tags && npm publish",
57
+ "push:tags": "git push origin main --follow-tags",
58
+ "release:major": "yarn lint && standard-version --release-as major",
59
+ "release:minor": "yarn lint && standard-version --release-as minor",
60
+ "release:patch": "yarn lint && standard-version --release-as patch",
61
+ "sort": "sort-package-json"
62
+ },
63
+ "husky": {
64
+ "hooks": {
65
+ "pre-commit": "lint-staged"
66
+ }
67
+ },
68
+ "lint-staged": {
69
+ "package.json": [
70
+ "sort-package-json"
71
+ ],
72
+ "src/**/*.js": [
73
+ "prettier --write",
74
+ "eslint --fix"
75
+ ]
76
+ },
77
+ "dependencies": {
78
+ "chalk": "^5.3.0",
79
+ "commander": "^12.0.0",
80
+ "dayjs": "^1.11.10",
81
+ "exceljs": "^4.4.0",
82
+ "zx": "^7.2.3"
83
+ },
84
+ "devDependencies": {
85
+ "@trivago/prettier-plugin-sort-imports": "5.2.2",
86
+ "husky": "^9.1.7",
87
+ "lint-staged": "^16.2.7",
88
+ "eslint": "8.57.1",
89
+ "eslint-config-airbnb-base": "15.0.0",
90
+ "eslint-config-prettier": "10.1.8",
91
+ "eslint-import-resolver-alias": "1.1.2",
92
+ "eslint-plugin-import": "2.32.0",
93
+ "eslint-plugin-prettier": "5.5.4",
94
+ "eslint-plugin-simple-import-sort": "12.1.1",
95
+ "prettier": "3.6.2",
96
+ "prettier-plugin-packagejson": "2.5.19",
97
+ "sort-package-json": "3.4.0",
98
+ "standard-version": "9.5.0"
99
+ }
100
+ }
@@ -0,0 +1 @@
1
+ console.log('✅ Build finished')
package/src/cli.mjs ADDED
@@ -0,0 +1,112 @@
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 { exportExcel } from './excel.mjs';
7
+ import { groupRecords, writeJSON, writeTextFile, outputFilePath } from './utils/index.mjs';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('git-commits')
13
+ .description('Advanced Git commit log exporter.')
14
+ .option('--author <name>', '指定 author 名')
15
+ .option('--email <email>', '指定 email')
16
+ .option('--since <date>', '起始日期')
17
+ .option('--until <date>', '结束日期')
18
+ .option('--limit <n>', '限制数量', parseInt)
19
+ .option('--no-merges', '不包含 merge commit')
20
+ .option('--json', '输出 JSON')
21
+ .option('--format <type>', '输出格式: text | excel | json', 'text')
22
+ .option('--group-by <type>', '按日期分组: day | month')
23
+ .option('--stats', '输出每日统计数据')
24
+ .option('--gerrit <prefix>', '显示 Gerrit 地址,支持在 prefix 中使用 {{hash}} 占位符')
25
+ .option('--out <file>', '输出文件名(不含路径)')
26
+ .option('--out-dir <dir>', '自定义输出目录,支持相对路径或绝对路径,例如 `--out-dir ../output`')
27
+ .option('--out-parent', '将输出目录放到当前工程的父目录的 `output/`(等同于 `--out-dir ../output`)')
28
+ .parse();
29
+
30
+ const opts = program.opts();
31
+
32
+ (async () => {
33
+ let records = await getGitLogs(opts);
34
+
35
+ // compute output directory root if user provided one or wants parent
36
+ const outDir = opts.outParent
37
+ ? path.resolve(process.cwd(), '..', 'output')
38
+ : opts.outDir || undefined;
39
+
40
+ // --- Gerrit 地址处理(若提供) ---
41
+ if (opts.gerrit) {
42
+ const prefix = opts.gerrit;
43
+ // create new array to avoid mutating function parameters (eslint: no-param-reassign)
44
+ records = records.map(r => {
45
+ let gerritUrl;
46
+ if (prefix.includes('{{changeId}}')) {
47
+ const changeId = r.changeId || r.hash;
48
+ gerritUrl = prefix.replace('{{changeId}}', changeId);
49
+ } else if (prefix.includes('{{hash}}')) {
50
+ gerritUrl = prefix.replace('{{hash}}', r.hash);
51
+ } else {
52
+ // append hash to prefix, ensure slash handling
53
+ gerritUrl = prefix.endsWith('/') ? `${prefix}${r.hash}` : `${prefix}/${r.hash}`;
54
+ }
55
+
56
+ return { ...r, gerrit: gerritUrl };
57
+ });
58
+ }
59
+
60
+ // --- 分组 ---
61
+ const groups = opts.groupBy ? groupRecords(records, opts.groupBy) : null;
62
+
63
+ // --- JSON ---
64
+ if (opts.json || opts.format === 'json') {
65
+ const file = opts.out || 'commits.json';
66
+ const filepath = outputFilePath(file, outDir);
67
+
68
+ writeJSON(filepath, groups || records);
69
+ console.log(chalk.green(`JSON 已导出: ${filepath}`));
70
+ return;
71
+ }
72
+
73
+ // --- TEXT ---
74
+ if (opts.format === 'text') {
75
+ const file = opts.out || 'commits.txt';
76
+ const filepath = outputFilePath(file, outDir);
77
+
78
+ const text = renderText(records, groups, { showGerrit: !!opts.gerrit });
79
+ writeTextFile(filepath, text);
80
+
81
+ console.log(text);
82
+ console.log(chalk.green(`文本已导出: ${filepath}`));
83
+ return;
84
+ }
85
+
86
+ // --- EXCEL(强制同时输出 TXT) ---
87
+ if (opts.format === 'excel') {
88
+ // Excel
89
+ const excelFile = opts.out || 'commits.xlsx';
90
+ const excelPath = outputFilePath(excelFile, outDir);
91
+
92
+ // TXT(自动附带)
93
+ const txtFile = excelFile.replace(/\.xlsx$/, '.txt');
94
+ const txtPath = outputFilePath(txtFile, outDir);
95
+
96
+ // 导出 Excel 文件
97
+ await exportExcel(records, groups, {
98
+ file: excelPath,
99
+ stats: opts.stats,
100
+ gerrit: opts.gerrit
101
+ });
102
+
103
+ // 导出文本
104
+ const text = renderText(records, groups);
105
+ writeTextFile(txtPath, text);
106
+
107
+ console.log(chalk.green(`Excel 已导出: ${excelPath}`));
108
+ console.log(chalk.green(`文本已自动导出: ${txtPath}`));
109
+
110
+
111
+ }
112
+ })();
package/src/excel.mjs ADDED
@@ -0,0 +1,52 @@
1
+ import ExcelJS from 'exceljs';
2
+ import dayjs from 'dayjs';
3
+
4
+ export async function exportExcel(records, groups, options = {}) {
5
+ const { file, stats, gerrit } = options;
6
+
7
+ const wb = new ExcelJS.Workbook();
8
+ const ws = wb.addWorksheet('Commits');
9
+
10
+ const cols = [
11
+ { header: 'Hash', key: 'hash', width: 12 },
12
+ { header: 'Author', key: 'author', width: 20 },
13
+ { header: 'Email', key: 'email', width: 30 },
14
+ { header: 'Date', key: 'date', width: 20 },
15
+ { header: 'Message', key: 'message', width: 80 }
16
+ ];
17
+
18
+ if (gerrit) {
19
+ cols.push({ header: 'Gerrit', key: 'gerrit', width: 50 });
20
+ }
21
+
22
+ ws.columns = cols;
23
+
24
+ (groups ? Object.values(groups).flat() : records).forEach(r =>
25
+ ws.addRow(r)
26
+ );
27
+
28
+ ws.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: cols.length } };
29
+
30
+ // --- stats sheet ---
31
+ if (stats) {
32
+ const statWs = wb.addWorksheet('Stats');
33
+
34
+ const map = {};
35
+
36
+ records.forEach(r => {
37
+ const d = dayjs(r.date).format('YYYY-MM-DD');
38
+ map[d] = (map[d] || 0) + 1;
39
+ });
40
+
41
+ statWs.columns = [
42
+ { header: 'Date', key: 'date', width: 15 },
43
+ { header: 'Commits', key: 'count', width: 15 }
44
+ ];
45
+
46
+ Object.entries(map).forEach(([d, cnt]) =>
47
+ statWs.addRow({ date: d, count: cnt })
48
+ );
49
+ }
50
+
51
+ await wb.xlsx.writeFile(file);
52
+ }
package/src/git.mjs ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env zx
2
+ import 'zx/globals'
3
+
4
+ export async function getGitLogs(opts) {
5
+ const { author, email, since, until, limit, merges } = opts
6
+
7
+ // include subject and full body so we can extract Change-Id from commit message
8
+ const pretty = '%H%x1f%an%x1f%ae%x1f%ad%x1f%s%x1f%B%x1e'
9
+
10
+ const args = ['log', `--pretty=format:${pretty}`, '--date=iso']
11
+
12
+ if (author) args.push(`--author=${author}`)
13
+ if (email) args.push(`--author=${email}`)
14
+ if (since) args.push(`--since=${since}`)
15
+ if (until) args.push(`--until=${until}`)
16
+ if (merges === false) args.push(`--no-merges`)
17
+ if (limit) args.push(`-n`, `${limit}`)
18
+
19
+ // 使用 spread 形式传参,ZX 才会正确处理
20
+ const { stdout } = await $`git ${args}`.quiet()
21
+
22
+ return stdout
23
+ .split('\x1e')
24
+ .filter(Boolean)
25
+ .map((r) => {
26
+ const f = r.split('\x1f').map((s) => (s || '').trim())
27
+
28
+ const hash = f[0]
29
+ const authorName = f[1]
30
+ const emailAddr = f[2]
31
+ const date = f[3]
32
+ const subject = f[4]
33
+ const body = f[5] || ''
34
+
35
+ // extract Change-Id from commit body (line like "Change-Id: Iabc123...")
36
+ const [, changeId] = body.match(/Change-Id:\s*(I[0-9a-fA-F]+)/) || []
37
+
38
+ return {
39
+ hash,
40
+ author: authorName,
41
+ email: emailAddr,
42
+ date,
43
+ message: subject,
44
+ body,
45
+ changeId
46
+ }
47
+ })
48
+ }
package/src/text.mjs ADDED
@@ -0,0 +1,56 @@
1
+ export function renderText(records, groups = null, opts = {}) {
2
+ const { showGerrit = false } = opts;
3
+ const pad = (s, n) =>
4
+ s.length >= n ? `${s.slice(0, n - 1) }…` : s + ' '.repeat(n - s.length);
5
+
6
+ const baseHeader =
7
+ `${pad('Hash', 10)
8
+ } | ${
9
+ pad('Author', 18)
10
+ } | ${
11
+ pad('Date', 20)
12
+ } | ${
13
+ pad('Message', 60)}`;
14
+
15
+ const gerritHeader = showGerrit ? ` | ${ pad('Gerrit', 50)}` : '';
16
+ const header = baseHeader + gerritHeader;
17
+
18
+ const line = '-'.repeat(header.length);
19
+
20
+ const rows = [];
21
+
22
+ if (groups) {
23
+ for (const [g, list] of Object.entries(groups)) {
24
+ rows.push(`\n=== ${g} ===\n`);
25
+ list.forEach(r => {
26
+ rows.push(
27
+ (
28
+ [
29
+ pad(r.hash.slice(0, 8), 10),
30
+ pad(r.author, 18),
31
+ pad(r.date.replace(/ .+/, ''), 20),
32
+ pad(r.message, 60)
33
+ ].join(' | ') +
34
+ (showGerrit ? ` | ${ pad(r.gerrit || '', 50)}` : '')
35
+ )
36
+ );
37
+ });
38
+ }
39
+ } else {
40
+ records.forEach(r => {
41
+ rows.push(
42
+ (
43
+ [
44
+ pad(r.hash.slice(0, 8), 10),
45
+ pad(r.author, 18),
46
+ pad(r.date.replace(/ .+/, ''), 20),
47
+ pad(r.message, 60)
48
+ ].join(' | ') +
49
+ (showGerrit ? ` | ${ pad(r.gerrit || '', 50)}` : '')
50
+ )
51
+ );
52
+ });
53
+ }
54
+
55
+ return [header, line, ...rows].join('\n');
56
+ }
@@ -0,0 +1,28 @@
1
+ import fs from 'fs';
2
+ import dayjs from 'dayjs';
3
+
4
+ export function writeJSON(file, data) {
5
+ fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');
6
+ }
7
+
8
+ export function writeTextFile(file, text) {
9
+ fs.writeFileSync(file, text, 'utf8');
10
+ }
11
+
12
+ export function groupRecords(records, mode) {
13
+ const group = {};
14
+
15
+ records.forEach(r => {
16
+ const date = dayjs(r.date);
17
+
18
+ const key =
19
+ mode === 'day'
20
+ ? date.format('YYYY-MM-DD')
21
+ : date.format('YYYY-MM');
22
+
23
+ if (!group[key]) group[key] = [];
24
+ group[key].push(r);
25
+ });
26
+
27
+ return group;
28
+ }
@@ -0,0 +1,2 @@
1
+ export { groupRecords, writeJSON, writeTextFile } from './file.mjs';
2
+ export * from './output.mjs';
@@ -0,0 +1,21 @@
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` inside current working directory.
7
+ const dir = customDir
8
+ ? path.resolve(process.cwd(), customDir)
9
+ : path.resolve(process.cwd(), 'output');
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
+ return path.join(dir, filename);
21
+ }