@zjex/git-workflow 0.3.2 → 0.3.3
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 +100 -23
- 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 +6 -4
- package/src/commands/init.ts +23 -0
- package/src/commands/tag.ts +18 -10
- package/src/config.ts +1 -0
- package/tests/commit-format.test.ts +535 -0
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();
|
package/src/ai-service.ts
CHANGED
|
@@ -60,7 +60,7 @@ function getGitDiff(): string {
|
|
|
60
60
|
/**
|
|
61
61
|
* 构建 AI prompt
|
|
62
62
|
*/
|
|
63
|
-
function buildPrompt(diff: string, language: string, detailedDescription: boolean = false): string {
|
|
63
|
+
function buildPrompt(diff: string, language: string, detailedDescription: boolean = false, useEmoji: boolean = true): string {
|
|
64
64
|
const isZh = language === "zh-CN";
|
|
65
65
|
|
|
66
66
|
if (detailedDescription) {
|
|
@@ -69,7 +69,7 @@ function buildPrompt(diff: string, language: string, detailedDescription: boolea
|
|
|
69
69
|
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的详细 commit message。
|
|
70
70
|
|
|
71
71
|
格式要求:
|
|
72
|
-
1.
|
|
72
|
+
1. 第一行:${useEmoji ? '<emoji> ' : ''}<type>(<scope>): <subject>
|
|
73
73
|
2. 空行
|
|
74
74
|
3. 详细描述:列出主要修改点,每个修改点一行,以 "- " 开头
|
|
75
75
|
|
|
@@ -80,9 +80,22 @@ function buildPrompt(diff: string, language: string, detailedDescription: boolea
|
|
|
80
80
|
- 详细描述要列出 3-6 个主要修改点,每个修改点简洁明了
|
|
81
81
|
- 如果修改较少,可以只列出 2-3 个修改点
|
|
82
82
|
- 不要有其他解释或多余内容
|
|
83
|
+
${useEmoji ? `
|
|
84
|
+
Emoji 映射规则:
|
|
85
|
+
- feat: ✨ (新功能)
|
|
86
|
+
- fix: 🐛 (修复Bug)
|
|
87
|
+
- docs: 📝 (文档)
|
|
88
|
+
- style: 💄 (代码格式)
|
|
89
|
+
- refactor: ♻️ (重构)
|
|
90
|
+
- perf: ⚡️ (性能优化)
|
|
91
|
+
- test: ✅ (测试)
|
|
92
|
+
- build: 📦 (构建)
|
|
93
|
+
- ci: 👷 (CI/CD)
|
|
94
|
+
- chore: 🔧 (其他杂项)
|
|
95
|
+
- revert: ⏪ (回滚)` : ''}
|
|
83
96
|
|
|
84
97
|
示例:
|
|
85
|
-
feat(auth): 添加用户登录功能
|
|
98
|
+
${useEmoji ? '✨ ' : ''}feat(auth): 添加用户登录功能
|
|
86
99
|
|
|
87
100
|
- 实现用户名密码登录接口
|
|
88
101
|
- 添加登录状态验证中间件
|
|
@@ -91,7 +104,7 @@ feat(auth): 添加用户登录功能
|
|
|
91
104
|
: `You are a professional Git commit message generator. Generate a detailed commit message following Conventional Commits specification based on the provided git diff.
|
|
92
105
|
|
|
93
106
|
Format requirements:
|
|
94
|
-
1. First line: <type>(<scope>): <subject>
|
|
107
|
+
1. First line: ${useEmoji ? '<emoji> ' : ''}<type>(<scope>): <subject>
|
|
95
108
|
2. Empty line
|
|
96
109
|
3. Detailed description: List main changes, one per line, starting with "- "
|
|
97
110
|
|
|
@@ -102,9 +115,22 @@ Rules:
|
|
|
102
115
|
- Detailed description should list 3-6 main changes, each change should be concise
|
|
103
116
|
- If changes are minimal, list 2-3 changes
|
|
104
117
|
- No explanations or extra content
|
|
118
|
+
${useEmoji ? `
|
|
119
|
+
Emoji mapping rules:
|
|
120
|
+
- feat: ✨ (new feature)
|
|
121
|
+
- fix: 🐛 (bug fix)
|
|
122
|
+
- docs: 📝 (documentation)
|
|
123
|
+
- style: 💄 (code style)
|
|
124
|
+
- refactor: ♻️ (refactoring)
|
|
125
|
+
- perf: ⚡️ (performance)
|
|
126
|
+
- test: ✅ (testing)
|
|
127
|
+
- build: 📦 (build)
|
|
128
|
+
- ci: 👷 (CI/CD)
|
|
129
|
+
- chore: 🔧 (chore)
|
|
130
|
+
- revert: ⏪ (revert)` : ''}
|
|
105
131
|
|
|
106
132
|
Example:
|
|
107
|
-
feat(auth): add user login functionality
|
|
133
|
+
${useEmoji ? '✨ ' : ''}feat(auth): add user login functionality
|
|
108
134
|
|
|
109
135
|
- Implement username/password login API
|
|
110
136
|
- Add login status validation middleware
|
|
@@ -122,31 +148,57 @@ feat(auth): add user login functionality
|
|
|
122
148
|
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的 commit message。
|
|
123
149
|
|
|
124
150
|
规则:
|
|
125
|
-
1.
|
|
151
|
+
1. 格式:${useEmoji ? '<emoji> ' : ''}<type>(<scope>): <subject>
|
|
126
152
|
2. type 必须是以下之一:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
127
153
|
3. scope 是可选的,表示影响范围
|
|
128
154
|
4. subject 用中文描述,简洁明了,不超过 50 字
|
|
129
155
|
5. 只返回一条 commit message,即使有多个文件改动也要总结成一条
|
|
130
156
|
6. 不要有其他解释或多余内容
|
|
157
|
+
${useEmoji ? `
|
|
158
|
+
Emoji 映射规则:
|
|
159
|
+
- feat: ✨ (新功能)
|
|
160
|
+
- fix: 🐛 (修复Bug)
|
|
161
|
+
- docs: 📝 (文档)
|
|
162
|
+
- style: 💄 (代码格式)
|
|
163
|
+
- refactor: ♻️ (重构)
|
|
164
|
+
- perf: ⚡️ (性能优化)
|
|
165
|
+
- test: ✅ (测试)
|
|
166
|
+
- build: 📦 (构建)
|
|
167
|
+
- ci: 👷 (CI/CD)
|
|
168
|
+
- chore: 🔧 (其他杂项)
|
|
169
|
+
- revert: ⏪ (回滚)` : ''}
|
|
131
170
|
|
|
132
171
|
示例:
|
|
133
|
-
- feat(auth): 添加用户登录功能
|
|
134
|
-
- fix(api): 修复数据获取失败的问题
|
|
135
|
-
- docs(readme): 更新安装说明`
|
|
172
|
+
- ${useEmoji ? '✨ ' : ''}feat(auth): 添加用户登录功能
|
|
173
|
+
- ${useEmoji ? '🐛 ' : ''}fix(api): 修复数据获取失败的问题
|
|
174
|
+
- ${useEmoji ? '📝 ' : ''}docs(readme): 更新安装说明`
|
|
136
175
|
: `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
|
|
137
176
|
|
|
138
177
|
Rules:
|
|
139
|
-
1. Format: <type>(<scope>): <subject>
|
|
178
|
+
1. Format: ${useEmoji ? '<emoji> ' : ''}<type>(<scope>): <subject>
|
|
140
179
|
2. type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
141
180
|
3. scope is optional, indicates the affected area
|
|
142
181
|
4. subject should be concise, no more than 50 characters
|
|
143
182
|
5. Return only ONE commit message, even if multiple files are changed, summarize into one message
|
|
144
183
|
6. No explanations or extra content
|
|
184
|
+
${useEmoji ? `
|
|
185
|
+
Emoji mapping rules:
|
|
186
|
+
- feat: ✨ (new feature)
|
|
187
|
+
- fix: 🐛 (bug fix)
|
|
188
|
+
- docs: 📝 (documentation)
|
|
189
|
+
- style: 💄 (code style)
|
|
190
|
+
- refactor: ♻️ (refactoring)
|
|
191
|
+
- perf: ⚡️ (performance)
|
|
192
|
+
- test: ✅ (testing)
|
|
193
|
+
- build: 📦 (build)
|
|
194
|
+
- ci: 👷 (CI/CD)
|
|
195
|
+
- chore: 🔧 (chore)
|
|
196
|
+
- revert: ⏪ (revert)` : ''}
|
|
145
197
|
|
|
146
198
|
Examples:
|
|
147
|
-
- feat(auth): add user login functionality
|
|
148
|
-
- fix(api): resolve data fetching failure
|
|
149
|
-
- docs(readme): update installation guide`;
|
|
199
|
+
- ${useEmoji ? '✨ ' : ''}feat(auth): add user login functionality
|
|
200
|
+
- ${useEmoji ? '🐛 ' : ''}fix(api): resolve data fetching failure
|
|
201
|
+
- ${useEmoji ? '📝 ' : ''}docs(readme): update installation guide`;
|
|
150
202
|
|
|
151
203
|
const userPrompt = isZh
|
|
152
204
|
? `请根据以下 git diff 生成 commit message:\n\n${diff}`
|
|
@@ -327,6 +379,9 @@ export async function generateAICommitMessage(
|
|
|
327
379
|
const language = aiConfig.language || "zh-CN";
|
|
328
380
|
const detailedDescription = aiConfig.detailedDescription !== false; // 默认启用详细描述
|
|
329
381
|
const maxTokens = aiConfig.maxTokens || (detailedDescription ? 400 : 200);
|
|
382
|
+
|
|
383
|
+
// AI emoji配置:优先使用aiCommit.useEmoji,如果未设置则使用全局useEmoji,默认true
|
|
384
|
+
const useEmoji = aiConfig.useEmoji !== undefined ? aiConfig.useEmoji : (config.useEmoji !== false);
|
|
330
385
|
|
|
331
386
|
// 获取 git diff
|
|
332
387
|
const diff = getGitDiff();
|
|
@@ -340,7 +395,7 @@ export async function generateAICommitMessage(
|
|
|
340
395
|
diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
341
396
|
|
|
342
397
|
// 构建 prompt
|
|
343
|
-
const prompt = buildPrompt(truncatedDiff, language, detailedDescription);
|
|
398
|
+
const prompt = buildPrompt(truncatedDiff, language, detailedDescription, useEmoji);
|
|
344
399
|
|
|
345
400
|
// 根据提供商调用对应的 API
|
|
346
401
|
const providerInfo = AI_PROVIDERS[provider];
|
package/src/commands/commit.ts
CHANGED
|
@@ -277,9 +277,11 @@ export async function commit(): Promise<void> {
|
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
execSync(`git commit -
|
|
280
|
+
// 处理多行消息:使用 git commit -F - 通过 stdin 传递
|
|
281
|
+
// 这样可以正确处理包含换行符的 commit message
|
|
282
|
+
execSync(`git commit -F -`, {
|
|
283
|
+
input: message,
|
|
284
|
+
});
|
|
283
285
|
spinner.succeed("提交成功");
|
|
284
286
|
|
|
285
287
|
// 显示提交信息
|
|
@@ -300,7 +302,7 @@ export async function commit(): Promise<void> {
|
|
|
300
302
|
console.log(colors.yellow("你可以手动执行以下命令:"));
|
|
301
303
|
console.log(colors.cyan(` git commit -m "${message}"`));
|
|
302
304
|
console.log("");
|
|
303
|
-
|
|
305
|
+
|
|
304
306
|
// 重新抛出错误,让调用者知道提交失败了
|
|
305
307
|
throw error;
|
|
306
308
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -283,12 +283,35 @@ export async function init(): Promise<void> {
|
|
|
283
283
|
theme,
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
const aiUseEmoji = await select({
|
|
287
|
+
message: "AI 生成的 commit message 是否包含 emoji?",
|
|
288
|
+
choices: [
|
|
289
|
+
{
|
|
290
|
+
name: "是(推荐)",
|
|
291
|
+
value: true,
|
|
292
|
+
description: "如:✨ feat(auth): 添加用户登录功能"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "否",
|
|
296
|
+
value: false,
|
|
297
|
+
description: "如:feat(auth): 添加用户登录功能"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "跟随全局设置",
|
|
301
|
+
value: undefined,
|
|
302
|
+
description: `当前全局设置:${useEmoji ? '启用' : '禁用'} emoji`
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
theme,
|
|
306
|
+
});
|
|
307
|
+
|
|
286
308
|
config.aiCommit = {
|
|
287
309
|
enabled: true,
|
|
288
310
|
provider: aiProvider as "github" | "openai" | "claude" | "ollama",
|
|
289
311
|
apiKey: apiKey || undefined,
|
|
290
312
|
language: language as "zh-CN" | "en-US",
|
|
291
313
|
detailedDescription,
|
|
314
|
+
useEmoji: aiUseEmoji,
|
|
292
315
|
};
|
|
293
316
|
|
|
294
317
|
// 根据提供商设置默认模型
|
package/src/commands/tag.ts
CHANGED
|
@@ -16,11 +16,15 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
16
16
|
|
|
17
17
|
// 2. 获取 tags 列表(按版本号升序排序,最新的在最后)
|
|
18
18
|
const pattern = prefix ? `${prefix}*` : "";
|
|
19
|
-
const
|
|
19
|
+
const allTags = execOutput(`git tag -l ${pattern} --sort=v:refname`)
|
|
20
20
|
.split("\n")
|
|
21
21
|
.filter(Boolean);
|
|
22
22
|
|
|
23
|
-
// 3.
|
|
23
|
+
// 3. 过滤无效 tag(如 vnull、vundefined 等误操作产生的 tag)
|
|
24
|
+
// 有效 tag 必须包含数字(版本号)
|
|
25
|
+
const tags = allTags.filter((tag) => /\d/.test(tag));
|
|
26
|
+
|
|
27
|
+
// 4. 如果没有 tags,提示并返回
|
|
24
28
|
if (tags.length === 0) {
|
|
25
29
|
console.log(
|
|
26
30
|
colors.yellow(prefix ? `没有 '${prefix}' 开头的 tag` : "没有 tag")
|
|
@@ -39,11 +43,12 @@ export async function listTags(prefix?: string): Promise<void> {
|
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
//
|
|
46
|
+
// 6. 按前缀分组(提取 tag 名称中数字前的部分作为前缀)
|
|
47
|
+
// 由于已过滤无效 tag,所有 tag 都包含数字
|
|
43
48
|
const grouped = new Map<string, string[]>();
|
|
44
49
|
tags.forEach((tag) => {
|
|
45
|
-
//
|
|
46
|
-
const prefix = tag.replace(
|
|
50
|
+
// 提取数字之前的字母部分作为前缀(如 "v0.1.0" -> "v")
|
|
51
|
+
const prefix = tag.replace(/\d.*/, "") || "(无前缀)";
|
|
47
52
|
if (!grouped.has(prefix)) {
|
|
48
53
|
grouped.set(prefix, []);
|
|
49
54
|
}
|
|
@@ -131,11 +136,11 @@ interface TagChoice {
|
|
|
131
136
|
value: string;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
//
|
|
139
|
+
// 获取指定前缀的最新有效 tag(必须包含数字)
|
|
135
140
|
function getLatestTag(prefix: string): string {
|
|
136
141
|
const tags = execOutput(`git tag -l "${prefix}*" --sort=-v:refname`)
|
|
137
142
|
.split("\n")
|
|
138
|
-
.filter(
|
|
143
|
+
.filter((tag) => tag && /\d/.test(tag)); // 过滤无效 tag
|
|
139
144
|
return tags[0] || "";
|
|
140
145
|
}
|
|
141
146
|
|
|
@@ -156,7 +161,10 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
if (!prefix) {
|
|
159
|
-
|
|
164
|
+
// 过滤无效 tag(如 vnull、vundefined 等误操作产生的 tag)
|
|
165
|
+
const allTags = execOutput("git tag -l")
|
|
166
|
+
.split("\n")
|
|
167
|
+
.filter((tag) => tag && /\d/.test(tag));
|
|
160
168
|
|
|
161
169
|
// 仓库没有任何 tag 的情况
|
|
162
170
|
if (allTags.length === 0) {
|
|
@@ -209,9 +217,9 @@ export async function createTag(inputPrefix?: string): Promise<void> {
|
|
|
209
217
|
return;
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
// 从现有 tag
|
|
220
|
+
// 从现有 tag 中提取前缀(数字之前的字母部分)
|
|
213
221
|
const prefixes = [
|
|
214
|
-
...new Set(allTags.map((t) => t.replace(
|
|
222
|
+
...new Set(allTags.map((t) => t.replace(/\d.*/, "")).filter(Boolean)),
|
|
215
223
|
];
|
|
216
224
|
|
|
217
225
|
if (prefixes.length === 0) {
|
package/src/config.ts
CHANGED
|
@@ -46,6 +46,7 @@ export interface GwConfig {
|
|
|
46
46
|
language?: "zh-CN" | "en-US"; // 生成语言,默认 zh-CN
|
|
47
47
|
maxTokens?: number; // 最大 token 数,默认 200
|
|
48
48
|
detailedDescription?: boolean; // 是否生成详细的修改点描述,默认 false
|
|
49
|
+
useEmoji?: boolean; // AI生成的commit message是否包含emoji,默认继承全局useEmoji设置
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|