@zjex/git-workflow 0.3.2 → 0.3.4
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/.husky/commit-msg +5 -0
- package/.husky/pre-commit +0 -6
- package/README.md +1 -1
- package/ROADMAP.md +142 -61
- package/dist/index.js +120 -36
- package/docs/.vitepress/cache/deps/_metadata.json +9 -9
- package/docs/.vitepress/config.ts +2 -1
- package/docs/commands/help.md +248 -0
- package/docs/commands/index.md +15 -8
- package/docs/commands/log.md +328 -0
- package/docs/index.md +32 -1
- package/package.json +1 -1
- package/scripts/format-commit-msg.js +258 -0
- package/src/ai-service.ts +69 -14
- package/src/commands/commit.ts +17 -5
- package/src/commands/init.ts +23 -0
- package/src/commands/tag.ts +32 -22
- package/src/config.ts +1 -0
- package/tests/commit-format.test.ts +535 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# gw log - Git 日志查看
|
|
2
|
+
|
|
3
|
+
`gw log` 命令提供了一个美观、易读的 Git 提交历史查看界面,采用时间线设计和 boxen 边框装饰。
|
|
4
|
+
|
|
5
|
+
## 🎯 功能特性
|
|
6
|
+
|
|
7
|
+
- 📅 **按日期分组** - 按提交日期自动分组显示
|
|
8
|
+
- 🎨 **Boxen 边框** - 每个提交都有独立的圆角边框
|
|
9
|
+
- 🔍 **交互式浏览** - 默认使用 `less` 分页器查看
|
|
10
|
+
- 🏷️ **标签和分支** - 清晰展示 Git tags 和分支信息
|
|
11
|
+
- ⚡️ **智能图标** - 根据提交类型自动显示对应图标
|
|
12
|
+
- 🌐 **中文时间** - 本地化的相对时间显示
|
|
13
|
+
|
|
14
|
+
## 📋 命令格式
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 基础用法(交互式分页查看)
|
|
18
|
+
gw log
|
|
19
|
+
|
|
20
|
+
# 命令别名
|
|
21
|
+
gw ls
|
|
22
|
+
gw l
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🎨 界面展示
|
|
26
|
+
|
|
27
|
+
### 实际显示效果
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ 📊 共显示 50 个提交 │
|
|
32
|
+
└─────────────────────────────────────────────────────────────┘
|
|
33
|
+
|
|
34
|
+
📅 Commits on 2024-01-12
|
|
35
|
+
|
|
36
|
+
╭─────────────────────────────────────────────────────────────╮
|
|
37
|
+
│ ✅ test: 完善commit message格式化测试用例 │
|
|
38
|
+
│ │
|
|
39
|
+
│ 👤 zjex committed 2小时前 │
|
|
40
|
+
│ 🔗 #8d74ffa │
|
|
41
|
+
│ 🌿 HEAD -> main, origin/main │
|
|
42
|
+
╰─────────────────────────────────────────────────────────────╯
|
|
43
|
+
|
|
44
|
+
╭─────────────────────────────────────────────────────────────╮
|
|
45
|
+
│ 🔧 chore: 删除重复的测试文件 │
|
|
46
|
+
│ │
|
|
47
|
+
│ 👤 zjex committed 3小时前 │
|
|
48
|
+
│ 🔗 #746aa87 │
|
|
49
|
+
╰─────────────────────────────────────────────────────────────╯
|
|
50
|
+
|
|
51
|
+
📅 Commits on 2024-01-11
|
|
52
|
+
|
|
53
|
+
╭─────────────────────────────────────────────────────────────╮
|
|
54
|
+
│ ✨ feat(log): 实现GitHub风格的提交历史查看 │
|
|
55
|
+
│ │
|
|
56
|
+
│ 👤 zjex committed 1天前 │
|
|
57
|
+
│ 🔗 #a1b2c3d │
|
|
58
|
+
│ 🔖 tag v0.3.0 │
|
|
59
|
+
╰─────────────────────────────────────────────────────────────╯
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 🎯 界面元素说明
|
|
63
|
+
|
|
64
|
+
### 日期分组
|
|
65
|
+
|
|
66
|
+
提交历史按日期自动分组:
|
|
67
|
+
- **📅 Commits on YYYY-MM-DD** - 按日期分组的标题
|
|
68
|
+
|
|
69
|
+
### 提交信息格式
|
|
70
|
+
|
|
71
|
+
每个提交显示在独立的圆角边框内:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
╭─────────────────────────────────────────────────────────────╮
|
|
75
|
+
│ [icon] [commit-subject] │
|
|
76
|
+
│ │
|
|
77
|
+
│ 👤 [author] committed [relative-time] │
|
|
78
|
+
│ 🔗 #[short-hash] │
|
|
79
|
+
│ 🌿 [branches] │
|
|
80
|
+
│ 🔖 tag [tags] │
|
|
81
|
+
╰─────────────────────────────────────────────────────────────╯
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**字段说明:**
|
|
85
|
+
- **icon** - 根据提交类型自动选择的图标
|
|
86
|
+
- **commit-subject** - 提交主题(支持子任务解析)
|
|
87
|
+
- **author** - 提交作者
|
|
88
|
+
- **relative-time** - 相对时间(中文格式)
|
|
89
|
+
- **short-hash** - 短提交哈希(7位)
|
|
90
|
+
- **branches** - 关联的分支信息
|
|
91
|
+
- **tags** - 关联的 Git 标签
|
|
92
|
+
|
|
93
|
+
### 提交类型图标
|
|
94
|
+
|
|
95
|
+
| 关键词 | 图标 | 说明 |
|
|
96
|
+
|--------|------|------|
|
|
97
|
+
| feat, feature | ✨ | 新功能 |
|
|
98
|
+
| fix, bug | 🐛 | Bug修复 |
|
|
99
|
+
| docs, doc | 📚 | 文档更新 |
|
|
100
|
+
| style | 💄 | 代码格式 |
|
|
101
|
+
| refactor | ♻️ | 代码重构 |
|
|
102
|
+
| test | 🧪 | 测试相关 |
|
|
103
|
+
| chore | 🔧 | 杂项任务 |
|
|
104
|
+
| perf | ⚡ | 性能优化 |
|
|
105
|
+
| ci | 👷 | CI配置 |
|
|
106
|
+
| build | 📦 | 构建相关 |
|
|
107
|
+
| revert | ⏪ | 回滚提交 |
|
|
108
|
+
| merge | 🔀 | 合并分支 |
|
|
109
|
+
| release, version | 🔖 | 版本发布 |
|
|
110
|
+
| 其他 | 📝 | 默认图标 |
|
|
111
|
+
|
|
112
|
+
## ⌨️ 交互式操作
|
|
113
|
+
|
|
114
|
+
默认使用系统的 `less` 分页器,支持以下操作:
|
|
115
|
+
|
|
116
|
+
### 导航控制
|
|
117
|
+
|
|
118
|
+
| 按键 | 功能 |
|
|
119
|
+
|------|------|
|
|
120
|
+
| `↑` / `k` | 向上滚动一行 |
|
|
121
|
+
| `↓` / `j` | 向下滚动一行 |
|
|
122
|
+
| `Page Up` / `b` | 向上翻页 |
|
|
123
|
+
| `Page Down` / `Space` | 向下翻页 |
|
|
124
|
+
| `Home` / `g` | 跳到顶部 |
|
|
125
|
+
| `End` / `G` | 跳到底部 |
|
|
126
|
+
|
|
127
|
+
### 搜索功能
|
|
128
|
+
|
|
129
|
+
| 按键 | 功能 |
|
|
130
|
+
|------|------|
|
|
131
|
+
| `/` | 向前搜索 |
|
|
132
|
+
| `?` | 向后搜索 |
|
|
133
|
+
| `n` | 下一个搜索结果 |
|
|
134
|
+
| `N` | 上一个搜索结果 |
|
|
135
|
+
|
|
136
|
+
### 退出控制
|
|
137
|
+
|
|
138
|
+
| 按键 | 功能 |
|
|
139
|
+
|------|------|
|
|
140
|
+
| `q` | 退出查看器 |
|
|
141
|
+
| `Ctrl+C` | 强制退出 |
|
|
142
|
+
|
|
143
|
+
## 🕒 时间格式
|
|
144
|
+
|
|
145
|
+
时间显示采用中文本地化的相对时间格式:
|
|
146
|
+
|
|
147
|
+
### 智能时间转换
|
|
148
|
+
|
|
149
|
+
- **1分钟内** - 显示原始相对时间
|
|
150
|
+
- **60分钟以上** - 自动转换为小时(如:90分钟 → 1小时前)
|
|
151
|
+
- **24小时以上** - 自动转换为天数(如:30小时 → 1天前)
|
|
152
|
+
- **7天以上** - 转换为周数(如:10天 → 1周前)
|
|
153
|
+
- **30天以上** - 转换为月数(如:35天 → 1个月前)
|
|
154
|
+
- **12个月以上** - 转换为年数(如:15个月 → 1年前)
|
|
155
|
+
|
|
156
|
+
### 格式化规则
|
|
157
|
+
|
|
158
|
+
原始 Git 输出会被自动转换:
|
|
159
|
+
- `22 minutes ago` → `22分钟前`
|
|
160
|
+
- `3 hours ago` → `3小时前`
|
|
161
|
+
- `2 days ago` → `2天前`
|
|
162
|
+
- `1 week ago` → `1周前`
|
|
163
|
+
|
|
164
|
+
## 🎨 颜色和样式
|
|
165
|
+
|
|
166
|
+
### 颜色方案
|
|
167
|
+
|
|
168
|
+
- **日期标题** - 黄色加粗
|
|
169
|
+
- **提交主题** - 白色加粗
|
|
170
|
+
- **作者名称** - 蓝色
|
|
171
|
+
- **时间信息** - 绿色
|
|
172
|
+
- **哈希值** - 橙色
|
|
173
|
+
- **分支信息** - 浅紫色
|
|
174
|
+
- **标签信息** - 黄色
|
|
175
|
+
- **边框** - 灰色圆角
|
|
176
|
+
|
|
177
|
+
### 边框样式
|
|
178
|
+
|
|
179
|
+
使用 `boxen` 库提供的圆角边框:
|
|
180
|
+
- **边框样式** - `round`(圆角)
|
|
181
|
+
- **边框颜色** - `gray`(灰色)
|
|
182
|
+
- **内边距** - 左右各1个字符
|
|
183
|
+
- **外边距** - 底部1行间距
|
|
184
|
+
|
|
185
|
+
## 🔧 高级功能
|
|
186
|
+
|
|
187
|
+
### 子任务解析
|
|
188
|
+
|
|
189
|
+
支持解析包含子任务的提交信息:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# 原始提交信息
|
|
193
|
+
feat: 添加用户认证功能 - 实现登录接口 - 添加JWT验证 - 完善错误处理
|
|
194
|
+
|
|
195
|
+
# 解析后显示
|
|
196
|
+
✨ feat: 添加用户认证功能
|
|
197
|
+
|
|
198
|
+
– 实现登录接口
|
|
199
|
+
– 添加JWT验证
|
|
200
|
+
– 完善错误处理
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 分支和标签分离显示
|
|
204
|
+
|
|
205
|
+
智能解析 Git refs 信息:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# 分支信息
|
|
209
|
+
🌿 HEAD -> main, origin/main
|
|
210
|
+
|
|
211
|
+
# 标签信息
|
|
212
|
+
🔖 tag v1.0.0, tag v1.0.0-beta
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 默认显示数量
|
|
216
|
+
|
|
217
|
+
- **交互式模式** - 默认显示50个提交
|
|
218
|
+
- **非交互式模式** - 默认显示10个提交
|
|
219
|
+
|
|
220
|
+
## 🛠️ 技术实现
|
|
221
|
+
|
|
222
|
+
### Git 命令
|
|
223
|
+
|
|
224
|
+
使用以下 Git 命令获取提交信息:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git log --pretty=format:"%H|%h|%s|%an|%ad|%ar|%D" --date=short -50
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**格式说明:**
|
|
231
|
+
- `%H` - 完整哈希
|
|
232
|
+
- `%h` - 短哈希
|
|
233
|
+
- `%s` - 提交主题
|
|
234
|
+
- `%an` - 作者名称
|
|
235
|
+
- `%ad` - 作者日期
|
|
236
|
+
- `%ar` - 相对时间
|
|
237
|
+
- `%D` - 引用信息(分支、标签)
|
|
238
|
+
|
|
239
|
+
### 分页器配置
|
|
240
|
+
|
|
241
|
+
使用系统 `less` 命令,配置参数:
|
|
242
|
+
- `-R` - 支持ANSI颜色代码
|
|
243
|
+
- `-S` - 不换行长行
|
|
244
|
+
- `-F` - 内容少于一屏时直接退出
|
|
245
|
+
- `-X` - 不清屏
|
|
246
|
+
- `-i` - 忽略大小写搜索
|
|
247
|
+
|
|
248
|
+
## 🚀 使用场景
|
|
249
|
+
|
|
250
|
+
### 日常开发
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# 查看最近的提交历史
|
|
254
|
+
gw log
|
|
255
|
+
|
|
256
|
+
# 快速浏览(别名)
|
|
257
|
+
gw ls
|
|
258
|
+
gw l
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 代码审查
|
|
262
|
+
|
|
263
|
+
在分页器中使用搜索功能:
|
|
264
|
+
```bash
|
|
265
|
+
# 启动 gw log 后
|
|
266
|
+
/feat # 搜索所有新功能提交
|
|
267
|
+
/fix # 搜索所有修复提交
|
|
268
|
+
/author # 搜索特定作者
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 版本追踪
|
|
272
|
+
|
|
273
|
+
查看标签和版本信息:
|
|
274
|
+
```bash
|
|
275
|
+
# 查看包含标签的提交
|
|
276
|
+
gw log
|
|
277
|
+
# 在分页器中搜索: /tag
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 🔍 故障排除
|
|
281
|
+
|
|
282
|
+
### 常见问题
|
|
283
|
+
|
|
284
|
+
**Q: 分页器无法启动?**
|
|
285
|
+
```bash
|
|
286
|
+
# 检查 less 是否安装
|
|
287
|
+
which less
|
|
288
|
+
|
|
289
|
+
# 或设置其他分页器
|
|
290
|
+
export PAGER=more
|
|
291
|
+
gw log
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Q: 颜色显示异常?**
|
|
295
|
+
```bash
|
|
296
|
+
# 强制启用颜色
|
|
297
|
+
FORCE_COLOR=1 gw log
|
|
298
|
+
|
|
299
|
+
# 或检查终端支持
|
|
300
|
+
echo $TERM
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Q: 中文显示乱码?**
|
|
304
|
+
```bash
|
|
305
|
+
# 设置正确的编码
|
|
306
|
+
export LANG=zh_CN.UTF-8
|
|
307
|
+
export LC_ALL=zh_CN.UTF-8
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Q: 没有提交记录?**
|
|
311
|
+
```bash
|
|
312
|
+
# 检查是否在 Git 仓库中
|
|
313
|
+
git status
|
|
314
|
+
|
|
315
|
+
# 检查是否有提交
|
|
316
|
+
git log --oneline -1
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## 📚 相关命令
|
|
320
|
+
|
|
321
|
+
- [`gw c`](/commands/commit) - 提交代码
|
|
322
|
+
- [`gw t`](/commands/tag) - 管理标签
|
|
323
|
+
- [`gw r`](/commands/release) - 发布版本
|
|
324
|
+
- [`gw s`](/commands/stash) - 管理 stash
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
`gw log` 提供了一个美观且实用的 Git 提交历史查看体验。通过 boxen 边框装饰和智能的时间格式化,让查看代码历史变得更加直观和愉快。
|
package/docs/index.md
CHANGED
|
@@ -23,6 +23,9 @@ features:
|
|
|
23
23
|
- icon: 🎯
|
|
24
24
|
title: 规范命名
|
|
25
25
|
details: 自动生成带日期的规范分支名,如 feature/20260109-PROJ-123-add-login
|
|
26
|
+
- icon: 📋
|
|
27
|
+
title: 优雅日志
|
|
28
|
+
details: GitHub 风格的提交历史查看,时间线分组,交互式浏览,支持搜索和过滤
|
|
26
29
|
- icon: 🏷️
|
|
27
30
|
title: 智能版本
|
|
28
31
|
details: 自动识别当前版本,交互式选择下一版本,支持 semver + 预发布版本
|
|
@@ -126,6 +129,32 @@ gw f
|
|
|
126
129
|
# ✔ 分支创建成功: feature/20260109-PROJ-123-add-user-login
|
|
127
130
|
```
|
|
128
131
|
|
|
132
|
+
### 📋 优雅的提交历史
|
|
133
|
+
|
|
134
|
+
GitHub 风格的提交历史查看,让代码历史一目了然:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
gw log
|
|
138
|
+
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
|
139
|
+
# │ Git 提交历史 │
|
|
140
|
+
# ├─────────────────────────────────────────────────────────────────────────────┤
|
|
141
|
+
# │ │
|
|
142
|
+
# │ 📅 今天 │
|
|
143
|
+
# │ │
|
|
144
|
+
# │ ✅ test: 完善commit message格式化测试用例 │
|
|
145
|
+
# │ 🔗 #8d74ffa • 2小时前 • zjex │
|
|
146
|
+
# │ │
|
|
147
|
+
# │ 🔧 chore: 删除重复的测试文件 │
|
|
148
|
+
# │ 🔗 #746aa87 • 3小时前 • zjex │
|
|
149
|
+
# │ │
|
|
150
|
+
# │ 📅 昨天 │
|
|
151
|
+
# │ │
|
|
152
|
+
# │ ✨ feat(log): 实现GitHub风格的提交历史查看 │
|
|
153
|
+
# │ 🔗 #a1b2c3d • 1天前 • zjex │
|
|
154
|
+
# │ 🔖 v0.3.0 │
|
|
155
|
+
# └─────────────────────────────────────────────────────────────────────────────┘
|
|
156
|
+
```
|
|
157
|
+
|
|
129
158
|
### 🏷️ 智能版本管理
|
|
130
159
|
|
|
131
160
|
自动检测现有 tag 前缀,智能递增版本号:
|
|
@@ -164,6 +193,7 @@ gw s
|
|
|
164
193
|
| 分支命名规范 | ✅ 自动生成 | ❌ 需手动 | ❌ 需手动 |
|
|
165
194
|
| 版本号管理 | ✅ 智能递增 | ❌ 需手动 | ❌ 需手动 |
|
|
166
195
|
| AI Commit | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
|
|
196
|
+
| 提交历史查看 | ✅ GitHub风格 | ❌ 不支持 | ⚠️ 原生命令 |
|
|
167
197
|
| Stash 可视化管理 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
|
|
168
198
|
| 交互式操作 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
|
|
169
199
|
| 配置灵活性 | ✅ 项目级配置 | ⚠️ 有限 | - |
|
|
@@ -187,7 +217,8 @@ Git Workflow 提供优雅的命令行界面,支持键盘快捷操作:
|
|
|
187
217
|
[2] 🐛 创建 hotfix 分支 gw h
|
|
188
218
|
[3] 🗑️ 删除分支 gw d
|
|
189
219
|
[4] 📝 提交代码 gw c
|
|
190
|
-
[5]
|
|
220
|
+
[5] 📋 查看提交历史 gw log
|
|
221
|
+
[6] 🏷️ 创建 tag gw t
|
|
191
222
|
...
|
|
192
223
|
```
|
|
193
224
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Commit Message Emoji 格式化工具
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* 1. 检测 commit message 是否包含 emoji
|
|
8
|
+
* 2. 如果没有 emoji,根据 type 自动添加
|
|
9
|
+
* 3. 如果有 emoji 但与 type 不匹配,自动替换
|
|
10
|
+
* 4. 支持 Conventional Commits 规范
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
import { homedir } from 'os';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
|
|
17
|
+
// Emoji 映射表
|
|
18
|
+
const EMOJI_MAP = {
|
|
19
|
+
feat: '✨',
|
|
20
|
+
fix: '🐛',
|
|
21
|
+
docs: '📝',
|
|
22
|
+
style: '💄',
|
|
23
|
+
refactor: '♻️',
|
|
24
|
+
perf: '⚡️',
|
|
25
|
+
test: '✅',
|
|
26
|
+
build: '📦',
|
|
27
|
+
ci: '👷',
|
|
28
|
+
chore: '🔧',
|
|
29
|
+
revert: '⏪',
|
|
30
|
+
merge: '🔀',
|
|
31
|
+
release: '🔖',
|
|
32
|
+
hotfix: '🚑',
|
|
33
|
+
security: '🔒',
|
|
34
|
+
breaking: '💥'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 解析 commit message
|
|
39
|
+
* @param {string} message - commit message
|
|
40
|
+
* @returns {object} 解析结果
|
|
41
|
+
*/
|
|
42
|
+
function parseCommitMessage(message) {
|
|
43
|
+
// 移除开头和结尾的空白字符
|
|
44
|
+
const cleanMessage = message.trim();
|
|
45
|
+
|
|
46
|
+
// 检测是否以已知的emoji开头
|
|
47
|
+
const allEmojis = Object.values(EMOJI_MAP);
|
|
48
|
+
let hasEmoji = false;
|
|
49
|
+
let currentEmoji = null;
|
|
50
|
+
let messageWithoutEmoji = cleanMessage;
|
|
51
|
+
|
|
52
|
+
for (const emoji of allEmojis) {
|
|
53
|
+
if (cleanMessage.startsWith(emoji)) {
|
|
54
|
+
hasEmoji = true;
|
|
55
|
+
currentEmoji = emoji;
|
|
56
|
+
messageWithoutEmoji = cleanMessage.substring(emoji.length).trim();
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 如果没有找到已知emoji,检查是否以其他emoji开头
|
|
62
|
+
if (!hasEmoji) {
|
|
63
|
+
const emojiMatch = cleanMessage.match(/^(\p{Emoji})\s*/u);
|
|
64
|
+
if (emojiMatch) {
|
|
65
|
+
hasEmoji = true;
|
|
66
|
+
currentEmoji = emojiMatch[1];
|
|
67
|
+
messageWithoutEmoji = cleanMessage.replace(/^(\p{Emoji})\s*/u, '').trim();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 解析 Conventional Commits 格式: type(scope): subject
|
|
72
|
+
const conventionalMatch = messageWithoutEmoji.match(/^(\w+)(\([^)]+\))?(!)?:\s*(.+)/);
|
|
73
|
+
|
|
74
|
+
if (!conventionalMatch) {
|
|
75
|
+
return {
|
|
76
|
+
isConventional: false,
|
|
77
|
+
hasEmoji,
|
|
78
|
+
currentEmoji,
|
|
79
|
+
originalMessage: message,
|
|
80
|
+
cleanMessage: messageWithoutEmoji
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [, type, scope, breaking, subject] = conventionalMatch;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
isConventional: true,
|
|
88
|
+
hasEmoji,
|
|
89
|
+
currentEmoji,
|
|
90
|
+
type: type.toLowerCase(),
|
|
91
|
+
scope: scope || '',
|
|
92
|
+
breaking: breaking === '!',
|
|
93
|
+
subject,
|
|
94
|
+
originalMessage: message,
|
|
95
|
+
cleanMessage: messageWithoutEmoji,
|
|
96
|
+
messageWithoutEmoji
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 获取正确的 emoji
|
|
102
|
+
* @param {string} type - commit type
|
|
103
|
+
* @returns {string} emoji
|
|
104
|
+
*/
|
|
105
|
+
function getCorrectEmoji(type) {
|
|
106
|
+
return EMOJI_MAP[type] || EMOJI_MAP.chore;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 检查 emoji 是否匹配 type
|
|
111
|
+
* @param {string} emoji - 当前 emoji
|
|
112
|
+
* @param {string} type - commit type
|
|
113
|
+
* @returns {boolean} 是否匹配
|
|
114
|
+
*/
|
|
115
|
+
function isEmojiCorrect(emoji, type) {
|
|
116
|
+
return emoji === getCorrectEmoji(type);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 格式化 commit message
|
|
121
|
+
* @param {string} message - 原始 commit message
|
|
122
|
+
* @returns {object} 格式化结果
|
|
123
|
+
*/
|
|
124
|
+
function formatCommitMessage(message) {
|
|
125
|
+
const parsed = parseCommitMessage(message);
|
|
126
|
+
|
|
127
|
+
// 如果不是 Conventional Commits 格式,不处理
|
|
128
|
+
if (!parsed.isConventional) {
|
|
129
|
+
return {
|
|
130
|
+
needsUpdate: false,
|
|
131
|
+
originalMessage: message,
|
|
132
|
+
formattedMessage: message,
|
|
133
|
+
reason: 'Not a conventional commit format'
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const correctEmoji = getCorrectEmoji(parsed.type);
|
|
138
|
+
let needsUpdate = false;
|
|
139
|
+
let reason = '';
|
|
140
|
+
|
|
141
|
+
// 检查是否需要更新
|
|
142
|
+
if (!parsed.hasEmoji) {
|
|
143
|
+
needsUpdate = true;
|
|
144
|
+
reason = `Added missing emoji for type '${parsed.type}'`;
|
|
145
|
+
} else if (!isEmojiCorrect(parsed.currentEmoji, parsed.type)) {
|
|
146
|
+
needsUpdate = true;
|
|
147
|
+
reason = `Replaced incorrect emoji '${parsed.currentEmoji}' with '${correctEmoji}' for type '${parsed.type}'`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 构建格式化后的消息
|
|
151
|
+
const formattedMessage = needsUpdate
|
|
152
|
+
? `${correctEmoji} ${parsed.messageWithoutEmoji}`
|
|
153
|
+
: message;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
needsUpdate,
|
|
157
|
+
originalMessage: message,
|
|
158
|
+
formattedMessage,
|
|
159
|
+
reason,
|
|
160
|
+
type: parsed.type,
|
|
161
|
+
currentEmoji: parsed.currentEmoji,
|
|
162
|
+
correctEmoji
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 检查用户配置是否启用 emoji
|
|
168
|
+
* @returns {boolean} 是否启用 emoji
|
|
169
|
+
*/
|
|
170
|
+
function isEmojiEnabled() {
|
|
171
|
+
try {
|
|
172
|
+
// 尝试读取配置文件
|
|
173
|
+
const configPaths = ['.gwrc.json', '.gwrc', 'gw.config.json'];
|
|
174
|
+
|
|
175
|
+
for (const configPath of configPaths) {
|
|
176
|
+
try {
|
|
177
|
+
if (existsSync(configPath)) {
|
|
178
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
179
|
+
if (config.useEmoji !== undefined) {
|
|
180
|
+
return config.useEmoji;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// 继续尝试下一个配置文件
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 尝试读取全局配置
|
|
189
|
+
try {
|
|
190
|
+
const globalConfigPath = path.join(homedir(), '.gwrc.json');
|
|
191
|
+
if (existsSync(globalConfigPath)) {
|
|
192
|
+
const globalConfig = JSON.parse(readFileSync(globalConfigPath, 'utf-8'));
|
|
193
|
+
if (globalConfig.useEmoji !== undefined) {
|
|
194
|
+
return globalConfig.useEmoji;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch {
|
|
198
|
+
// 忽略全局配置错误
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 默认启用 emoji
|
|
202
|
+
return true;
|
|
203
|
+
} catch {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 主函数
|
|
210
|
+
*/
|
|
211
|
+
function main() {
|
|
212
|
+
// 获取 commit message 文件路径
|
|
213
|
+
const commitMsgFile = process.argv[2];
|
|
214
|
+
|
|
215
|
+
if (!commitMsgFile) {
|
|
216
|
+
console.error('Error: No commit message file provided');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// 检查是否启用 emoji
|
|
222
|
+
if (!isEmojiEnabled()) {
|
|
223
|
+
// 如果禁用了 emoji,直接退出
|
|
224
|
+
process.exit(0);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 读取 commit message
|
|
228
|
+
const originalMessage = readFileSync(commitMsgFile, 'utf-8').trim();
|
|
229
|
+
|
|
230
|
+
// 跳过空消息或合并消息
|
|
231
|
+
if (!originalMessage || originalMessage.startsWith('Merge ')) {
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 格式化消息
|
|
236
|
+
const result = formatCommitMessage(originalMessage);
|
|
237
|
+
|
|
238
|
+
// 如果需要更新
|
|
239
|
+
if (result.needsUpdate) {
|
|
240
|
+
// 写入格式化后的消息
|
|
241
|
+
writeFileSync(commitMsgFile, result.formattedMessage + '\n');
|
|
242
|
+
|
|
243
|
+
// 输出提示信息
|
|
244
|
+
console.log(`\n🎨 Commit message formatted:`);
|
|
245
|
+
console.log(` ${result.reason}`);
|
|
246
|
+
console.log(` Before: ${result.originalMessage}`);
|
|
247
|
+
console.log(` After: ${result.formattedMessage}\n`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
process.exit(0);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error('Error formatting commit message:', error.message);
|
|
253
|
+
process.exit(0); // 不阻止提交,只是跳过格式化
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 运行主函数
|
|
258
|
+
main();
|