@zjex/git-workflow 0.4.5 → 0.4.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.0.0](https://github.com/iamzjt-front-end/git-workflow/compare/v0.4.5...v1.0.0) (2026-01-19)
4
+
5
+
6
+ ## [v0.4.5](https://github.com/iamzjt-front-end/git-workflow/compare/v0.4.4...v0.4.5) (2026-01-19)
7
+
8
+ ### ✨ Features
9
+
10
+ - 更新日期格式说明,支持完整时间输入 ([25f2b39](https://github.com/iamzjt-front-end/git-workflow/commit/25f2b39))
11
+
12
+ ### 📖 Documentation
13
+
14
+ - 📝 docs: 自动更新测试数量徽章 [skip ci] ([c49a4bd](https://github.com/iamzjt-front-end/git-workflow/commit/c49a4bd))
15
+
16
+ ### 🔧 Chore
17
+
18
+ - 🔖 chore(release): 发布 v0.4.5 ([8abd7b4](https://github.com/iamzjt-front-end/git-workflow/commit/8abd7b4))
19
+
20
+
3
21
  ## [v0.4.4](https://github.com/iamzjt-front-end/git-workflow/compare/v0.4.3...v0.4.4) (2026-01-19)
4
22
 
5
23
  ### ✨ Features
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://github.com/iamzjt-front-end/git-workflow"><img src="https://img.shields.io/github/stars/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=F59E0B" alt="github stars"></a>
13
13
  <a href="https://github.com/iamzjt-front-end/git-workflow/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@zjex/git-workflow?style=flat&colorA=18181B&colorB=10B981" alt="license"></a>
14
14
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-339933?style=flat&logo=node.js&logoColor=white&colorA=18181B" alt="node version"></a>
15
- <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-435%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
15
+ <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-474%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
16
16
  <a href="https://github.com/iamzjt-front-end/git-workflow/issues"><img src="https://img.shields.io/github/issues/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=EC4899" alt="issues"></a>
17
17
  </p>
18
18
 
package/dist/index.js CHANGED
@@ -11,6 +11,9 @@ var __export = (target, all) => {
11
11
 
12
12
  // src/utils.ts
13
13
  import { execSync, spawn } from "child_process";
14
+ function setDebugMode(enabled) {
15
+ debugMode = enabled;
16
+ }
14
17
  function exec(cmd, silent = false) {
15
18
  try {
16
19
  const options = {
@@ -53,21 +56,57 @@ function divider() {
53
56
  }
54
57
  function execAsync(command, spinner) {
55
58
  return new Promise((resolve) => {
56
- const [cmd, ...args] = command.split(" ");
57
- const process2 = spawn(cmd, args, {
58
- stdio: spinner ? "pipe" : "inherit"
59
+ if (debugMode) {
60
+ console.log(colors.dim(`
61
+ [DEBUG] \u6267\u884C\u547D\u4EE4: ${colors.cyan(command)}`));
62
+ }
63
+ const process2 = spawn(command, {
64
+ stdio: spinner ? "pipe" : "inherit",
65
+ shell: true
59
66
  });
67
+ let errorOutput = "";
68
+ let stdoutOutput = "";
69
+ if (debugMode && process2.stdout) {
70
+ process2.stdout.on("data", (data) => {
71
+ stdoutOutput += data.toString();
72
+ });
73
+ }
74
+ if (process2.stderr) {
75
+ process2.stderr.on("data", (data) => {
76
+ errorOutput += data.toString();
77
+ });
78
+ }
60
79
  process2.on("close", (code) => {
61
- resolve(code === 0);
80
+ if (debugMode) {
81
+ console.log(colors.dim(`[DEBUG] \u9000\u51FA\u7801: ${code}`));
82
+ if (stdoutOutput) {
83
+ console.log(colors.dim(`[DEBUG] \u6807\u51C6\u8F93\u51FA:
84
+ ${stdoutOutput}`));
85
+ }
86
+ if (errorOutput) {
87
+ console.log(colors.dim(`[DEBUG] \u9519\u8BEF\u8F93\u51FA:
88
+ ${errorOutput}`));
89
+ }
90
+ }
91
+ if (code === 0) {
92
+ resolve({ success: true });
93
+ } else {
94
+ resolve({ success: false, error: errorOutput.trim() });
95
+ }
62
96
  });
63
- process2.on("error", () => {
64
- resolve(false);
97
+ process2.on("error", (err) => {
98
+ if (debugMode) {
99
+ console.log(colors.dim(`[DEBUG] \u8FDB\u7A0B\u9519\u8BEF: ${err.message}`));
100
+ console.log(colors.dim(`[DEBUG] \u9519\u8BEF\u5806\u6808:
101
+ ${err.stack}`));
102
+ }
103
+ resolve({ success: false, error: err.message });
65
104
  });
66
105
  });
67
106
  }
68
107
  async function execWithSpinner(command, spinner, successMessage, errorMessage) {
69
- const success = await execAsync(command, spinner);
70
- if (success) {
108
+ const result = await execAsync(command, spinner);
109
+ if (result.success) {
71
110
  if (successMessage) {
72
111
  spinner.succeed(successMessage);
73
112
  } else {
@@ -79,13 +118,27 @@ async function execWithSpinner(command, spinner, successMessage, errorMessage) {
79
118
  } else {
80
119
  spinner.fail();
81
120
  }
121
+ if (result.error) {
122
+ console.log(colors.dim(` ${result.error}`));
123
+ }
124
+ if (debugMode) {
125
+ console.log(colors.yellow("\n[DEBUG] \u6545\u969C\u6392\u67E5\u4FE1\u606F:"));
126
+ console.log(colors.dim(` \u547D\u4EE4: ${command}`));
127
+ console.log(colors.dim(` \u5DE5\u4F5C\u76EE\u5F55: ${process.cwd()}`));
128
+ console.log(colors.dim(` Shell: ${process.env.SHELL || "unknown"}`));
129
+ console.log(
130
+ colors.dim(` \u5EFA\u8BAE: \u5C1D\u8BD5\u5728\u7EC8\u7AEF\u4E2D\u76F4\u63A5\u8FD0\u884C\u4E0A\u8FF0\u547D\u4EE4\u4EE5\u83B7\u53D6\u66F4\u591A\u4FE1\u606F
131
+ `)
132
+ );
133
+ }
82
134
  }
83
- return success;
135
+ return result.success;
84
136
  }
85
- var colors, TODAY, theme;
137
+ var debugMode, colors, TODAY, theme;
86
138
  var init_utils = __esm({
87
139
  "src/utils.ts"() {
88
140
  "use strict";
141
+ debugMode = false;
89
142
  colors = {
90
143
  red: (s) => `\x1B[31m${s}\x1B[0m`,
91
144
  green: (s) => `\x1B[32m${s}\x1B[0m`,
@@ -3533,7 +3586,7 @@ process.on("SIGTERM", () => {
3533
3586
  console.log("");
3534
3587
  process.exit(0);
3535
3588
  });
3536
- var version = true ? "0.4.5" : "0.0.0-dev";
3589
+ var version = true ? "0.4.6" : "0.0.0-dev";
3537
3590
  async function mainMenu() {
3538
3591
  console.log(
3539
3592
  colors.green(`
@@ -3813,7 +3866,12 @@ cli.command("clean", "\u6E05\u7406\u7F13\u5B58\u548C\u4E34\u65F6\u6587\u4EF6").a
3813
3866
  });
3814
3867
  cli.option("-v, --version", "\u663E\u793A\u7248\u672C\u53F7");
3815
3868
  cli.option("-h, --help", "\u663E\u793A\u5E2E\u52A9\u4FE1\u606F");
3869
+ cli.option("-d, --debug", "\u542F\u7528\u8C03\u8BD5\u6A21\u5F0F\uFF0C\u663E\u793A\u8BE6\u7EC6\u7684\u547D\u4EE4\u548C\u9519\u8BEF\u4FE1\u606F");
3816
3870
  var processArgs = process.argv.slice(2);
3871
+ if (processArgs.includes("-d") || processArgs.includes("--debug")) {
3872
+ setDebugMode(true);
3873
+ console.log(colors.yellow("\u{1F41B} Debug \u6A21\u5F0F\u5DF2\u542F\u7528\n"));
3874
+ }
3817
3875
  if (processArgs.includes("-v") || processArgs.includes("--version")) {
3818
3876
  console.log(colors.yellow(`v${version}`));
3819
3877
  process.exit(0);
@@ -0,0 +1,279 @@
1
+ # 命令参数引号处理
2
+
3
+ 本文档说明了 git-workflow 如何正确处理带引号和特殊字符的命令参数。
4
+
5
+ ## 问题背景
6
+
7
+ 在早期版本中,使用简单的字符串分割方式处理命令参数:
8
+
9
+ ```typescript
10
+ // ❌ 错误的方式
11
+ const [cmd, ...args] = command.split(" ");
12
+ spawn(cmd, args);
13
+ ```
14
+
15
+ 这种方式无法正确处理引号:
16
+
17
+ ```javascript
18
+ 'git tag -a "v1.5.3" -m "Release v1.5.3"'.split(" ");
19
+ // 结果: ["git", "tag", "-a", '"v1.5.3"', "-m", '"Release', 'v1.5.3"']
20
+ // 引号被当作参数的一部分!
21
+ ```
22
+
23
+ 导致的问题:
24
+
25
+ - Tag 名称包含引号:`"v1.5.3"` 而不是 `v1.5.3`
26
+ - Git 报错:`fatal: Failed to resolve '"v1.5.3"' as a valid ref.`
27
+
28
+ ## 解决方案
29
+
30
+ 使用 `shell: true` 选项让 spawn 通过 shell 执行命令:
31
+
32
+ ```typescript
33
+ // ✅ 正确的方式
34
+ spawn(command, {
35
+ stdio: spinner ? "pipe" : "inherit",
36
+ shell: true, // 使用 shell 模式
37
+ });
38
+ ```
39
+
40
+ ### 优势
41
+
42
+ 1. **正确处理引号**:Shell 会自动解析和移除引号
43
+ 2. **支持特殊字符**:emoji、中文、空格等都能正确处理
44
+ 3. **支持转义**:`\"` 等转义字符正常工作
45
+
46
+ ## 支持的场景
47
+
48
+ ### 1. Tag 命令
49
+
50
+ ```bash
51
+ # 基本版本号
52
+ gw tag # 创建 v1.5.3
53
+
54
+ # 预发布版本
55
+ gw tag # 创建 v1.0.0-beta.1
56
+
57
+ # 带特殊字符
58
+ git tag -a "v1.0.0-🎉" -m "Release 🎉"
59
+ ```
60
+
61
+ ### 2. Branch 命令
62
+
63
+ ```bash
64
+ # 带日期和描述的分支
65
+ gw b feature # 创建 feature/20240120-123-add-feature
66
+
67
+ # 删除带特殊字符的分支
68
+ git branch -D "feature/20240120-123-add-feature"
69
+ git push origin --delete "feature/20240120-123-add-feature"
70
+ ```
71
+
72
+ ### 3. Stash 命令
73
+
74
+ ```bash
75
+ # 带中文的 stash 消息
76
+ git stash push -m "临时保存:修复登录bug"
77
+
78
+ # 带引号的消息
79
+ git stash push -m "WIP: 添加\"新功能\""
80
+
81
+ # 从 stash 创建分支
82
+ git stash branch "feature/from-stash" stash@{0}
83
+ ```
84
+
85
+ ### 4. Commit 命令
86
+
87
+ ```bash
88
+ # 带特殊字符的提交消息
89
+ git commit -m "feat: add \"quotes\" support"
90
+ git commit -m "fix: 修复登录问题 🐛"
91
+ ```
92
+
93
+ ## 测试覆盖
94
+
95
+ 我们创建了全面的测试用例来确保引号处理的正确性:
96
+
97
+ ### 测试场景
98
+
99
+ 1. **基本引号处理**
100
+ - 带引号的 tag 名称
101
+ - 带空格的分支名称
102
+ - 带特殊字符的 commit message
103
+
104
+ 2. **特殊字符支持**
105
+ - Emoji:`v1.0.0-🎉`
106
+ - 中文:`临时保存:修复bug`
107
+ - 转义引号:`feat: add \"quotes\" support`
108
+
109
+ 3. **错误处理**
110
+ - 捕获 stderr 错误信息
111
+ - 显示详细的失败原因
112
+ - 提供解决建议
113
+
114
+ ### 运行测试
115
+
116
+ ```bash
117
+ # 运行引号处理测试
118
+ npm test -- tests/command-with-quotes.test.ts
119
+
120
+ # 运行所有测试
121
+ npm test
122
+ ```
123
+
124
+ ## 错误信息改进
125
+
126
+ 现在当命令失败时,会显示详细的错误信息:
127
+
128
+ ### Tag 已存在
129
+
130
+ ```
131
+ ✗ Tag v1.5.3 已存在
132
+
133
+ 提示: 如需重新创建,请先删除旧 tag:
134
+ git tag -d v1.5.3
135
+ git push origin --delete v1.5.3
136
+ ```
137
+
138
+ ### 没有提交
139
+
140
+ ```
141
+ ✗ 当前仓库没有任何提交
142
+
143
+ 提示: 需要先创建至少一个提交才能打 tag:
144
+ git add .
145
+ git commit -m "Initial commit"
146
+ gw tag
147
+ ```
148
+
149
+ ### Git 命令错误
150
+
151
+ ```
152
+ ✗ tag 创建失败
153
+ fatal: Failed to resolve 'HEAD' as a valid ref.
154
+ ```
155
+
156
+ ## 实现细节
157
+
158
+ ### execAsync 函数
159
+
160
+ ```typescript
161
+ export function execAsync(
162
+ command: string,
163
+ spinner?: Ora,
164
+ ): Promise<{ success: boolean; error?: string }> {
165
+ return new Promise((resolve) => {
166
+ const process = spawn(command, {
167
+ stdio: spinner ? "pipe" : "inherit",
168
+ shell: true, // 关键:使用 shell 模式
169
+ });
170
+
171
+ let errorOutput = "";
172
+
173
+ // 捕获错误输出
174
+ if (process.stderr) {
175
+ process.stderr.on("data", (data) => {
176
+ errorOutput += data.toString();
177
+ });
178
+ }
179
+
180
+ process.on("close", (code) => {
181
+ if (code === 0) {
182
+ resolve({ success: true });
183
+ } else {
184
+ resolve({ success: false, error: errorOutput.trim() });
185
+ }
186
+ });
187
+
188
+ process.on("error", (err) => {
189
+ resolve({ success: false, error: err.message });
190
+ });
191
+ });
192
+ }
193
+ ```
194
+
195
+ ### execWithSpinner 函数
196
+
197
+ ```typescript
198
+ export async function execWithSpinner(
199
+ command: string,
200
+ spinner: Ora,
201
+ successMessage?: string,
202
+ errorMessage?: string,
203
+ ): Promise<boolean> {
204
+ const result = await execAsync(command, spinner);
205
+
206
+ if (result.success) {
207
+ if (successMessage) {
208
+ spinner.succeed(successMessage);
209
+ } else {
210
+ spinner.succeed();
211
+ }
212
+ } else {
213
+ if (errorMessage) {
214
+ spinner.fail(errorMessage);
215
+ } else {
216
+ spinner.fail();
217
+ }
218
+
219
+ // 显示具体的错误信息
220
+ if (result.error) {
221
+ console.log(colors.dim(` ${result.error}`));
222
+ }
223
+ }
224
+
225
+ return result.success;
226
+ }
227
+ ```
228
+
229
+ ## 最佳实践
230
+
231
+ ### 1. 始终使用引号包裹可能包含特殊字符的参数
232
+
233
+ ```typescript
234
+ // ✅ 推荐
235
+ await execAsync(`git tag -a "${tagName}" -m "Release ${tagName}"`);
236
+
237
+ // ❌ 不推荐(如果 tagName 包含空格会失败)
238
+ await execAsync(`git tag -a ${tagName} -m Release ${tagName}`);
239
+ ```
240
+
241
+ ### 2. 转义用户输入中的引号
242
+
243
+ ```typescript
244
+ // ✅ 正确处理用户输入
245
+ const message = userInput.replace(/"/g, '\\"');
246
+ await execAsync(`git commit -m "${message}"`);
247
+ ```
248
+
249
+ ### 3. 使用 execWithSpinner 显示进度
250
+
251
+ ```typescript
252
+ // ✅ 推荐:显示进度和错误信息
253
+ const spinner = ora("正在创建 tag...").start();
254
+ const success = await execWithSpinner(
255
+ `git tag -a "${tagName}" -m "Release ${tagName}"`,
256
+ spinner,
257
+ "Tag 创建成功",
258
+ "Tag 创建失败",
259
+ );
260
+
261
+ if (!success) {
262
+ // 错误信息已自动显示
263
+ return;
264
+ }
265
+ ```
266
+
267
+ ## 相关文件
268
+
269
+ - `src/utils.ts` - execAsync 和 execWithSpinner 实现
270
+ - `tests/command-with-quotes.test.ts` - 引号处理测试
271
+ - `src/commands/tag.ts` - Tag 命令实现
272
+ - `src/commands/branch.ts` - Branch 命令实现
273
+ - `src/commands/stash.ts` - Stash 命令实现
274
+
275
+ ## 版本历史
276
+
277
+ - **v0.4.5** - 修复引号处理问题,添加详细错误信息
278
+ - **v0.4.4** - 修复 spinner 阻塞问题
279
+ - **v0.4.3** - 添加 execAsync 和 execWithSpinner 函数