@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/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];
@@ -265,7 +265,17 @@ export async function commit(): Promise<void> {
265
265
 
266
266
  try {
267
267
  // 提交前再次检查是否有暂存的文件
268
- const finalStatus = parseGitStatus();
268
+ let finalStatus = parseGitStatus();
269
+
270
+ // 如果暂存区为空,但有未暂存的更改,且开启了自动暂存,则重新暂存
271
+ if (finalStatus.staged.length === 0 && finalStatus.unstaged.length > 0) {
272
+ const autoStage = config.autoStage ?? true;
273
+ if (autoStage) {
274
+ execSync("git add -A", { stdio: "pipe" });
275
+ finalStatus = parseGitStatus();
276
+ }
277
+ }
278
+
269
279
  if (finalStatus.staged.length === 0) {
270
280
  spinner.fail("没有暂存的文件可以提交");
271
281
  console.log("");
@@ -277,9 +287,11 @@ export async function commit(): Promise<void> {
277
287
  return;
278
288
  }
279
289
 
280
- // 使用 -m 参数,需要转义引号
281
- const escapedMessage = message.replace(/"/g, '\\"');
282
- execSync(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
290
+ // 处理多行消息:使用 git commit -F - 通过 stdin 传递
291
+ // 这样可以正确处理包含换行符的 commit message
292
+ execSync(`git commit -F -`, {
293
+ input: message,
294
+ });
283
295
  spinner.succeed("提交成功");
284
296
 
285
297
  // 显示提交信息
@@ -300,7 +312,7 @@ export async function commit(): Promise<void> {
300
312
  console.log(colors.yellow("你可以手动执行以下命令:"));
301
313
  console.log(colors.cyan(` git commit -m "${message}"`));
302
314
  console.log("");
303
-
315
+
304
316
  // 重新抛出错误,让调用者知道提交失败了
305
317
  throw error;
306
318
  }
@@ -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")
@@ -28,36 +32,39 @@ export async function listTags(prefix?: string): Promise<void> {
28
32
  return;
29
33
  }
30
34
 
31
- // 4. 如果指定了前缀,直接显示单列(最多 20 个)
35
+ // 5. 如果指定了前缀,直接显示单列(最多 5 个)
32
36
  if (prefix) {
33
- console.log(colors.green(`以 '${prefix}' 开头的 tags:`));
34
- const displayTags = tags.length > 20 ? tags.slice(-20) : tags;
35
- displayTags.forEach((tag) => console.log(` ${tag}`));
36
- if (tags.length > 20) {
37
- console.log(colors.yellow(`\n共 ${tags.length} 个,仅显示最新 20 个`));
37
+ console.log(
38
+ colors.green(`以 '${prefix}' 开头的 tags (共 ${tags.length} 个):`)
39
+ );
40
+ if (tags.length > 5) {
41
+ console.log(colors.dim(" ..."));
38
42
  }
43
+ const displayTags = tags.slice(-5);
44
+ displayTags.forEach((tag) => console.log(` ${tag}`));
39
45
  return;
40
46
  }
41
47
 
42
- // 5. 按前缀分组(提取 tag 名称中数字前的部分作为前缀)
48
+ // 6. 按前缀分组(提取 tag 名称中数字前的部分作为前缀)
49
+ // 由于已过滤无效 tag,所有 tag 都包含数字
43
50
  const grouped = new Map<string, string[]>();
44
51
  tags.forEach((tag) => {
45
- // 提取前缀:去掉数字及之后的部分,如 "v0.1.0" -> "v"
46
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
52
+ // 提取数字之前的字母部分作为前缀(如 "v0.1.0" -> "v"
53
+ const prefix = tag.replace(/\d.*/, "") || "(无前缀)";
47
54
  if (!grouped.has(prefix)) {
48
55
  grouped.set(prefix, []);
49
56
  }
50
57
  grouped.get(prefix)!.push(tag);
51
58
  });
52
59
 
53
- // 6. 如果只有一个前缀,使用单列显示(最多 20 个)
60
+ // 7. 如果只有一个前缀,使用单列显示(最多 5 个)
54
61
  if (grouped.size === 1) {
55
- console.log(colors.green("所有 tags:"));
56
- const displayTags = tags.length > 20 ? tags.slice(-20) : tags;
57
- displayTags.forEach((tag) => console.log(` ${tag}`));
58
- if (tags.length > 20) {
59
- console.log(colors.yellow(`\n共 ${tags.length} 个,仅显示最新 20 个`));
62
+ console.log(colors.green(`所有 tags (共 ${tags.length} 个):`));
63
+ if (tags.length > 5) {
64
+ console.log(colors.dim(" ..."));
60
65
  }
66
+ const displayTags = tags.slice(-5);
67
+ displayTags.forEach((tag) => console.log(` ${tag}`));
61
68
  return;
62
69
  }
63
70
 
@@ -131,11 +138,11 @@ interface TagChoice {
131
138
  value: string;
132
139
  }
133
140
 
134
- // 获取指定前缀的最新 tag(不依赖 shell 管道)
141
+ // 获取指定前缀的最新有效 tag(必须包含数字)
135
142
  function getLatestTag(prefix: string): string {
136
143
  const tags = execOutput(`git tag -l "${prefix}*" --sort=-v:refname`)
137
144
  .split("\n")
138
- .filter(Boolean);
145
+ .filter((tag) => tag && /\d/.test(tag)); // 过滤无效 tag
139
146
  return tags[0] || "";
140
147
  }
141
148
 
@@ -156,7 +163,10 @@ export async function createTag(inputPrefix?: string): Promise<void> {
156
163
  }
157
164
 
158
165
  if (!prefix) {
159
- const allTags = execOutput("git tag -l").split("\n").filter(Boolean);
166
+ // 过滤无效 tag(如 vnull、vundefined 等误操作产生的 tag
167
+ const allTags = execOutput("git tag -l")
168
+ .split("\n")
169
+ .filter((tag) => tag && /\d/.test(tag));
160
170
 
161
171
  // 仓库没有任何 tag 的情况
162
172
  if (allTags.length === 0) {
@@ -209,9 +219,9 @@ export async function createTag(inputPrefix?: string): Promise<void> {
209
219
  return;
210
220
  }
211
221
 
212
- // 从现有 tag 中提取前缀
222
+ // 从现有 tag 中提取前缀(数字之前的字母部分)
213
223
  const prefixes = [
214
- ...new Set(allTags.map((t) => t.replace(/[0-9].*/, "")).filter(Boolean)),
224
+ ...new Set(allTags.map((t) => t.replace(/\d.*/, "")).filter(Boolean)),
215
225
  ];
216
226
 
217
227
  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