@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/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] 🏷️ 创建 tag gw t
220
+ [5] 📋 查看提交历史 gw log
221
+ [6] 🏷️ 创建 tag gw t
191
222
  ...
192
223
  ```
193
224
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zjex/git-workflow",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "🚀 极简的 Git 工作流 CLI 工具,让分支管理和版本发布变得轻松愉快",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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. 第一行:<type>(<scope>): <subject>
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. 格式:<type>(<scope>): <subject>
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];
@@ -277,9 +277,11 @@ export async function commit(): Promise<void> {
277
277
  return;
278
278
  }
279
279
 
280
- // 使用 -m 参数,需要转义引号
281
- const escapedMessage = message.replace(/"/g, '\\"');
282
- execSync(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
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
  }
@@ -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
  // 根据提供商设置默认模型
@@ -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 tags = execOutput(`git tag -l ${pattern} --sort=v:refname`)
19
+ const allTags = execOutput(`git tag -l ${pattern} --sort=v:refname`)
20
20
  .split("\n")
21
21
  .filter(Boolean);
22
22
 
23
- // 3. 如果没有 tags,提示并返回
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
- // 5. 按前缀分组(提取 tag 名称中数字前的部分作为前缀)
46
+ // 6. 按前缀分组(提取 tag 名称中数字前的部分作为前缀)
47
+ // 由于已过滤无效 tag,所有 tag 都包含数字
43
48
  const grouped = new Map<string, string[]>();
44
49
  tags.forEach((tag) => {
45
- // 提取前缀:去掉数字及之后的部分,如 "v0.1.0" -> "v"
46
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
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
- // 获取指定前缀的最新 tag(不依赖 shell 管道)
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(Boolean);
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
- const allTags = execOutput("git tag -l").split("\n").filter(Boolean);
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(/[0-9].*/, "")).filter(Boolean)),
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