@zjex/git-workflow 0.0.1 → 0.1.0
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/README.md +220 -4
- package/dist/index.js +68 -35
- package/package.json +6 -3
- package/scripts/README.md +57 -0
- package/scripts/release.sh +363 -0
- package/scripts/version.js +138 -0
- package/scripts/version.sh +133 -0
- package/src/commands/branch.ts +123 -28
- package/src/commands/commit.ts +263 -0
- package/src/commands/help.ts +26 -17
- package/src/commands/init.ts +40 -0
- package/src/commands/tag.ts +66 -7
- package/src/config.ts +20 -0
- package/src/index.ts +140 -3
package/src/commands/branch.ts
CHANGED
|
@@ -49,6 +49,38 @@ export async function createBranch(
|
|
|
49
49
|
): Promise<void> {
|
|
50
50
|
const config = getConfig();
|
|
51
51
|
|
|
52
|
+
// 检查是否有未提交的更改
|
|
53
|
+
const hasChanges = execOutput("git status --porcelain");
|
|
54
|
+
if (hasChanges) {
|
|
55
|
+
console.log(colors.yellow("检测到未提交的更改:"));
|
|
56
|
+
console.log(colors.dim(hasChanges));
|
|
57
|
+
divider();
|
|
58
|
+
|
|
59
|
+
const shouldStash = await select({
|
|
60
|
+
message: "是否暂存 (stash) 这些更改后继续?",
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: "是", value: true },
|
|
63
|
+
{ name: "否,取消操作", value: false },
|
|
64
|
+
],
|
|
65
|
+
theme,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!shouldStash) {
|
|
69
|
+
console.log(colors.yellow("已取消"));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const stashSpinner = ora("正在暂存更改...").start();
|
|
74
|
+
try {
|
|
75
|
+
exec('git stash push -m "auto stash before branch switch"', true);
|
|
76
|
+
stashSpinner.succeed("更改已暂存,切换分支后可用 gw s 恢复");
|
|
77
|
+
} catch {
|
|
78
|
+
stashSpinner.fail("暂存失败");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
divider();
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
const branchName = await getBranchName(type);
|
|
53
85
|
if (!branchName) return;
|
|
54
86
|
|
|
@@ -118,15 +150,32 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
118
150
|
|
|
119
151
|
let branch = branchArg;
|
|
120
152
|
|
|
153
|
+
// 如果传入的是 origin/xxx 格式,提取分支名
|
|
154
|
+
if (branch?.startsWith("origin/")) {
|
|
155
|
+
branch = branch.replace("origin/", "");
|
|
156
|
+
}
|
|
157
|
+
|
|
121
158
|
if (!branch) {
|
|
122
|
-
|
|
159
|
+
// 获取本地分支
|
|
160
|
+
const localBranches = execOutput(
|
|
123
161
|
"git for-each-ref --sort=-committerdate refs/heads/ --format='%(refname:short)'"
|
|
124
162
|
)
|
|
125
163
|
.split("\n")
|
|
126
164
|
.filter((b) => b && b !== currentBranch);
|
|
127
165
|
|
|
128
|
-
|
|
129
|
-
|
|
166
|
+
// 获取远程分支(排除 HEAD 和已有本地分支的)
|
|
167
|
+
const remoteBranches = execOutput(
|
|
168
|
+
"git for-each-ref --sort=-committerdate refs/remotes/origin/ --format='%(refname:short)'"
|
|
169
|
+
)
|
|
170
|
+
.split("\n")
|
|
171
|
+
.map((b) => b.replace("origin/", ""))
|
|
172
|
+
.filter(
|
|
173
|
+
(b) =>
|
|
174
|
+
b && b !== "HEAD" && b !== currentBranch && !localBranches.includes(b)
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (localBranches.length === 0 && remoteBranches.length === 0) {
|
|
178
|
+
console.log(colors.yellow("没有可删除的分支"));
|
|
130
179
|
return;
|
|
131
180
|
}
|
|
132
181
|
|
|
@@ -135,13 +184,25 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
135
184
|
value: string;
|
|
136
185
|
}
|
|
137
186
|
|
|
138
|
-
const choices: BranchChoice[] =
|
|
139
|
-
|
|
140
|
-
|
|
187
|
+
const choices: BranchChoice[] = [];
|
|
188
|
+
|
|
189
|
+
// 本地分支
|
|
190
|
+
localBranches.forEach((b) => {
|
|
191
|
+
const hasRemote = execOutput(`git branch -r --list "origin/${b}"`);
|
|
192
|
+
choices.push({
|
|
141
193
|
name: hasRemote ? `${b} (本地+远程)` : `${b} (仅本地)`,
|
|
142
194
|
value: b,
|
|
143
|
-
};
|
|
195
|
+
});
|
|
144
196
|
});
|
|
197
|
+
|
|
198
|
+
// 仅远程分支
|
|
199
|
+
remoteBranches.forEach((b) => {
|
|
200
|
+
choices.push({
|
|
201
|
+
name: `${b} (仅远程)`,
|
|
202
|
+
value: `__remote__${b}`,
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
145
206
|
choices.push({ name: "取消", value: "__cancel__" });
|
|
146
207
|
|
|
147
208
|
branch = await select({
|
|
@@ -154,6 +215,36 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
154
215
|
console.log(colors.yellow("已取消"));
|
|
155
216
|
return;
|
|
156
217
|
}
|
|
218
|
+
|
|
219
|
+
// 处理仅远程分支的情况
|
|
220
|
+
if (branch.startsWith("__remote__")) {
|
|
221
|
+
const remoteBranch = branch.replace("__remote__", "");
|
|
222
|
+
|
|
223
|
+
const confirm = await select({
|
|
224
|
+
message: `确认删除远程分支 origin/${remoteBranch}?`,
|
|
225
|
+
choices: [
|
|
226
|
+
{ name: "是", value: true },
|
|
227
|
+
{ name: "否", value: false },
|
|
228
|
+
],
|
|
229
|
+
theme,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (!confirm) {
|
|
233
|
+
console.log(colors.yellow("已取消"));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const spinner = ora(`正在删除远程分支: origin/${remoteBranch}`).start();
|
|
238
|
+
try {
|
|
239
|
+
execSync(`git push origin --delete "${remoteBranch}"`, {
|
|
240
|
+
stdio: "pipe",
|
|
241
|
+
});
|
|
242
|
+
spinner.succeed(`远程分支已删除: origin/${remoteBranch}`);
|
|
243
|
+
} catch {
|
|
244
|
+
spinner.fail("远程分支删除失败");
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
157
248
|
}
|
|
158
249
|
|
|
159
250
|
if (branch === currentBranch) {
|
|
@@ -162,7 +253,7 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
162
253
|
}
|
|
163
254
|
|
|
164
255
|
const localExists = execOutput(`git branch --list "${branch}"`);
|
|
165
|
-
const hasRemote = execOutput(`git branch -r
|
|
256
|
+
const hasRemote = execOutput(`git branch -r --list "origin/${branch}"`);
|
|
166
257
|
|
|
167
258
|
if (!localExists) {
|
|
168
259
|
if (hasRemote) {
|
|
@@ -170,7 +261,7 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
170
261
|
colors.yellow(`本地分支不存在,但远程分支存在: origin/${branch}`)
|
|
171
262
|
);
|
|
172
263
|
const deleteRemote = await select({
|
|
173
|
-
message:
|
|
264
|
+
message: `确认删除远程分支 origin/${branch}?`,
|
|
174
265
|
choices: [
|
|
175
266
|
{ name: "是", value: true },
|
|
176
267
|
{ name: "否", value: false },
|
|
@@ -193,6 +284,23 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
193
284
|
return;
|
|
194
285
|
}
|
|
195
286
|
|
|
287
|
+
// 删除本地分支前确认
|
|
288
|
+
const confirmDelete = await select({
|
|
289
|
+
message: `确认删除分支 ${branch}?${
|
|
290
|
+
hasRemote ? " (本地+远程)" : " (仅本地)"
|
|
291
|
+
}`,
|
|
292
|
+
choices: [
|
|
293
|
+
{ name: "是", value: true },
|
|
294
|
+
{ name: "否", value: false },
|
|
295
|
+
],
|
|
296
|
+
theme,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (!confirmDelete) {
|
|
300
|
+
console.log(colors.yellow("已取消"));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
196
304
|
const localSpinner = ora(`正在删除本地分支: ${branch}`).start();
|
|
197
305
|
try {
|
|
198
306
|
execSync(`git branch -D "${branch}"`, { stdio: "pipe" });
|
|
@@ -203,25 +311,12 @@ export async function deleteBranch(branchArg?: string): Promise<void> {
|
|
|
203
311
|
}
|
|
204
312
|
|
|
205
313
|
if (hasRemote) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{ name: "否", value: false },
|
|
213
|
-
],
|
|
214
|
-
theme,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (deleteRemote) {
|
|
218
|
-
const remoteSpinner = ora(`正在删除远程分支: origin/${branch}`).start();
|
|
219
|
-
try {
|
|
220
|
-
execSync(`git push origin --delete "${branch}"`, { stdio: "pipe" });
|
|
221
|
-
remoteSpinner.succeed(`远程分支已删除: origin/${branch}`);
|
|
222
|
-
} catch {
|
|
223
|
-
remoteSpinner.fail("远程分支删除失败");
|
|
224
|
-
}
|
|
314
|
+
const remoteSpinner = ora(`正在删除远程分支: origin/${branch}`).start();
|
|
315
|
+
try {
|
|
316
|
+
execSync(`git push origin --delete "${branch}"`, { stdio: "pipe" });
|
|
317
|
+
remoteSpinner.succeed(`远程分支已删除: origin/${branch}`);
|
|
318
|
+
} catch {
|
|
319
|
+
remoteSpinner.fail("远程分支删除失败");
|
|
225
320
|
}
|
|
226
321
|
}
|
|
227
322
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { select, input, confirm, checkbox } from "@inquirer/prompts";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { colors, theme, execOutput, divider } from "../utils.js";
|
|
5
|
+
import { getConfig } from "../config.js";
|
|
6
|
+
|
|
7
|
+
// Conventional Commits 类型 + Gitmoji
|
|
8
|
+
const DEFAULT_COMMIT_TYPES = [
|
|
9
|
+
{ type: "feat", emoji: "✨", description: "新功能" },
|
|
10
|
+
{ type: "fix", emoji: "🐛", description: "修复 Bug" },
|
|
11
|
+
{ type: "docs", emoji: "📝", description: "文档更新" },
|
|
12
|
+
{ type: "style", emoji: "💄", description: "代码格式 (不影响功能)" },
|
|
13
|
+
{ type: "refactor", emoji: "♻️", description: "重构 (非新功能/修复)" },
|
|
14
|
+
{ type: "perf", emoji: "⚡️", description: "性能优化" },
|
|
15
|
+
{ type: "test", emoji: "✅", description: "测试相关" },
|
|
16
|
+
{ type: "build", emoji: "📦", description: "构建/依赖相关" },
|
|
17
|
+
{ type: "ci", emoji: "👷", description: "CI/CD 相关" },
|
|
18
|
+
{ type: "chore", emoji: "🔧", description: "其他杂项" },
|
|
19
|
+
{ type: "revert", emoji: "⏪", description: "回退提交" },
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
type CommitType = (typeof DEFAULT_COMMIT_TYPES)[number]["type"];
|
|
23
|
+
|
|
24
|
+
function getCommitTypes(config: ReturnType<typeof getConfig>) {
|
|
25
|
+
const customEmojis = config.commitEmojis || {};
|
|
26
|
+
return DEFAULT_COMMIT_TYPES.map((item) => ({
|
|
27
|
+
...item,
|
|
28
|
+
emoji: customEmojis[item.type as CommitType] || item.emoji,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FileStatus {
|
|
33
|
+
status: string;
|
|
34
|
+
file: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseGitStatus(): { staged: FileStatus[]; unstaged: FileStatus[] } {
|
|
38
|
+
const output = execOutput("git status --porcelain");
|
|
39
|
+
if (!output) return { staged: [], unstaged: [] };
|
|
40
|
+
|
|
41
|
+
const staged: FileStatus[] = [];
|
|
42
|
+
const unstaged: FileStatus[] = [];
|
|
43
|
+
|
|
44
|
+
for (const line of output.split("\n")) {
|
|
45
|
+
if (!line) continue;
|
|
46
|
+
const indexStatus = line[0];
|
|
47
|
+
const workTreeStatus = line[1];
|
|
48
|
+
const file = line.slice(3);
|
|
49
|
+
|
|
50
|
+
// 已暂存的更改 (index 有状态)
|
|
51
|
+
if (indexStatus !== " " && indexStatus !== "?") {
|
|
52
|
+
staged.push({ status: indexStatus, file });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 未暂存的更改 (work tree 有状态,或者是未跟踪文件)
|
|
56
|
+
if (workTreeStatus !== " " || indexStatus === "?") {
|
|
57
|
+
const status = indexStatus === "?" ? "?" : workTreeStatus;
|
|
58
|
+
unstaged.push({ status, file });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { staged, unstaged };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatFileStatus(status: string): string {
|
|
66
|
+
const statusMap: Record<string, string> = {
|
|
67
|
+
M: colors.yellow("M"),
|
|
68
|
+
A: colors.green("A"),
|
|
69
|
+
D: colors.red("D"),
|
|
70
|
+
R: colors.yellow("R"),
|
|
71
|
+
C: colors.yellow("C"),
|
|
72
|
+
"?": colors.green("?"),
|
|
73
|
+
};
|
|
74
|
+
return statusMap[status] || status;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function commit(): Promise<void> {
|
|
78
|
+
const config = getConfig();
|
|
79
|
+
let { staged, unstaged } = parseGitStatus();
|
|
80
|
+
|
|
81
|
+
// 没有暂存的更改
|
|
82
|
+
if (staged.length === 0) {
|
|
83
|
+
if (unstaged.length === 0) {
|
|
84
|
+
console.log(colors.yellow("工作区干净,没有需要提交的更改"));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(colors.yellow("没有暂存的更改"));
|
|
89
|
+
divider();
|
|
90
|
+
console.log("未暂存的文件:");
|
|
91
|
+
for (const { status, file } of unstaged) {
|
|
92
|
+
console.log(` ${formatFileStatus(status)} ${file}`);
|
|
93
|
+
}
|
|
94
|
+
divider();
|
|
95
|
+
|
|
96
|
+
// 根据配置决定是否自动暂存
|
|
97
|
+
const autoStage = config.autoStage ?? true;
|
|
98
|
+
|
|
99
|
+
if (autoStage) {
|
|
100
|
+
// 自动暂存所有文件
|
|
101
|
+
execSync("git add -A", { stdio: "pipe" });
|
|
102
|
+
console.log(colors.green("✔ 已自动暂存所有更改"));
|
|
103
|
+
divider();
|
|
104
|
+
// 重新获取状态
|
|
105
|
+
const newStatus = parseGitStatus();
|
|
106
|
+
staged = newStatus.staged;
|
|
107
|
+
} else {
|
|
108
|
+
// 让用户选择要暂存的文件
|
|
109
|
+
const filesToStage = await checkbox({
|
|
110
|
+
message: "选择要暂存的文件:",
|
|
111
|
+
choices: unstaged.map(({ status, file }) => ({
|
|
112
|
+
name: `${formatFileStatus(status)} ${file}`,
|
|
113
|
+
value: file,
|
|
114
|
+
checked: true,
|
|
115
|
+
})),
|
|
116
|
+
theme,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (filesToStage.length === 0) {
|
|
120
|
+
console.log(colors.yellow("没有选择任何文件,已取消"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 暂存选中的文件
|
|
125
|
+
for (const file of filesToStage) {
|
|
126
|
+
execSync(`git add "${file}"`, { stdio: "pipe" });
|
|
127
|
+
}
|
|
128
|
+
console.log(colors.green(`✔ 已暂存 ${filesToStage.length} 个文件`));
|
|
129
|
+
divider();
|
|
130
|
+
|
|
131
|
+
// 重新获取状态
|
|
132
|
+
const newStatus = parseGitStatus();
|
|
133
|
+
staged = newStatus.staged;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
console.log("已暂存的文件:");
|
|
137
|
+
for (const { status, file } of staged) {
|
|
138
|
+
console.log(` ${formatFileStatus(status)} ${file}`);
|
|
139
|
+
}
|
|
140
|
+
divider();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 获取提交类型(支持自定义 emoji)
|
|
144
|
+
const commitTypes = getCommitTypes(config);
|
|
145
|
+
|
|
146
|
+
// 选择提交类型
|
|
147
|
+
const typeChoice = await select({
|
|
148
|
+
message: "选择提交类型:",
|
|
149
|
+
choices: commitTypes.map((t) => ({
|
|
150
|
+
name: `${t.emoji} ${t.type.padEnd(10)} ${colors.dim(t.description)}`,
|
|
151
|
+
value: t,
|
|
152
|
+
})),
|
|
153
|
+
theme,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// 输入 scope (可选)
|
|
157
|
+
const scope = await input({
|
|
158
|
+
message: "输入影响范围 scope (可跳过):",
|
|
159
|
+
theme,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 输入简短描述
|
|
163
|
+
const subject = await input({
|
|
164
|
+
message: "输入简短描述:",
|
|
165
|
+
validate: (value) => {
|
|
166
|
+
if (!value.trim()) return "描述不能为空";
|
|
167
|
+
if (value.length > 72) return "描述不能超过 72 个字符";
|
|
168
|
+
return true;
|
|
169
|
+
},
|
|
170
|
+
theme,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 输入详细描述 (可选)
|
|
174
|
+
const body = await input({
|
|
175
|
+
message: "输入详细描述 (可跳过):",
|
|
176
|
+
theme,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 是否有破坏性变更
|
|
180
|
+
const hasBreaking = await confirm({
|
|
181
|
+
message: "是否包含破坏性变更 (BREAKING CHANGE)?",
|
|
182
|
+
default: false,
|
|
183
|
+
theme,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
let breakingDesc = "";
|
|
187
|
+
if (hasBreaking) {
|
|
188
|
+
breakingDesc = await input({
|
|
189
|
+
message: "描述破坏性变更:",
|
|
190
|
+
validate: (value) => (value.trim() ? true : "请描述破坏性变更"),
|
|
191
|
+
theme,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 关联 Issue (可选)
|
|
196
|
+
const issues = await input({
|
|
197
|
+
message: "关联 Issue (如 #123, 可跳过):",
|
|
198
|
+
theme,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// 构建 commit message
|
|
202
|
+
const { type, emoji } = typeChoice;
|
|
203
|
+
const scopePart = scope ? `(${scope})` : "";
|
|
204
|
+
const breakingMark = hasBreaking ? "!" : "";
|
|
205
|
+
|
|
206
|
+
// 根据配置决定是否使用 emoji
|
|
207
|
+
const useEmoji = config.useEmoji ?? true;
|
|
208
|
+
const emojiPrefix = useEmoji ? `${emoji} ` : "";
|
|
209
|
+
|
|
210
|
+
// Header: [emoji] type(scope)!: subject
|
|
211
|
+
let message = `${emojiPrefix}${type}${scopePart}${breakingMark}: ${subject}`;
|
|
212
|
+
|
|
213
|
+
// Body
|
|
214
|
+
if (body || hasBreaking || issues) {
|
|
215
|
+
message += "\n";
|
|
216
|
+
|
|
217
|
+
if (body) {
|
|
218
|
+
message += `\n${body}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (hasBreaking) {
|
|
222
|
+
message += `\n\nBREAKING CHANGE: ${breakingDesc}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (issues) {
|
|
226
|
+
message += `\n\n${issues}`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
divider();
|
|
231
|
+
console.log("提交信息预览:");
|
|
232
|
+
console.log(colors.green(message));
|
|
233
|
+
divider();
|
|
234
|
+
|
|
235
|
+
const shouldCommit = await confirm({
|
|
236
|
+
message: "确认提交?",
|
|
237
|
+
default: true,
|
|
238
|
+
theme,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!shouldCommit) {
|
|
242
|
+
console.log(colors.yellow("已取消"));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const spinner = ora("正在提交...").start();
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
// 使用 -m 参数,需要转义引号
|
|
250
|
+
const escapedMessage = message.replace(/"/g, '\\"');
|
|
251
|
+
execSync(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
|
|
252
|
+
spinner.succeed("提交成功");
|
|
253
|
+
|
|
254
|
+
// 显示提交信息
|
|
255
|
+
const commitHash = execOutput("git rev-parse --short HEAD");
|
|
256
|
+
console.log(colors.dim(`commit: ${commitHash}`));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
spinner.fail("提交失败");
|
|
259
|
+
if (error instanceof Error) {
|
|
260
|
+
console.log(colors.red(error.message));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
package/src/commands/help.ts
CHANGED
|
@@ -4,43 +4,52 @@ export function showHelp(): string {
|
|
|
4
4
|
return `
|
|
5
5
|
分支命令:
|
|
6
6
|
gw feature [--base <branch>] 创建 feature 分支
|
|
7
|
-
gw feat [--base <branch>] 同上 (
|
|
8
|
-
gw f [--base <branch>] 同上 (
|
|
7
|
+
gw feat [--base <branch>] 同上 (别名)
|
|
8
|
+
gw f [--base <branch>] 同上 (别名)
|
|
9
9
|
|
|
10
10
|
gw hotfix [--base <branch>] 创建 hotfix 分支
|
|
11
|
-
gw fix [--base <branch>] 同上 (
|
|
12
|
-
gw h [--base <branch>] 同上 (
|
|
11
|
+
gw fix [--base <branch>] 同上 (别名)
|
|
12
|
+
gw h [--base <branch>] 同上 (别名)
|
|
13
13
|
|
|
14
14
|
gw delete [branch] 删除本地/远程分支
|
|
15
|
-
gw del [branch] 同上 (
|
|
16
|
-
gw d [branch] 同上 (
|
|
15
|
+
gw del [branch] 同上 (别名)
|
|
16
|
+
gw d [branch] 同上 (别名)
|
|
17
17
|
|
|
18
18
|
Tag 命令:
|
|
19
19
|
gw tags [prefix] 列出所有 tag,可按前缀过滤
|
|
20
|
-
gw ts [prefix] 同上 (
|
|
20
|
+
gw ts [prefix] 同上 (别名)
|
|
21
21
|
|
|
22
22
|
gw tag [prefix] 交互式选择版本类型并创建 tag
|
|
23
|
-
gw t [prefix] 同上 (
|
|
23
|
+
gw t [prefix] 同上 (别名)
|
|
24
24
|
|
|
25
25
|
发布命令:
|
|
26
26
|
gw release 交互式选择版本号并更新 package.json
|
|
27
|
-
gw r 同上 (
|
|
27
|
+
gw r 同上 (别名)
|
|
28
28
|
|
|
29
29
|
配置命令:
|
|
30
30
|
gw init 初始化配置文件 .gwrc.json
|
|
31
31
|
|
|
32
32
|
Stash 命令:
|
|
33
33
|
gw stash 交互式管理 stash
|
|
34
|
-
gw s 同上 (
|
|
34
|
+
gw s 同上 (别名)
|
|
35
|
+
gw st 同上 (别名)
|
|
36
|
+
|
|
37
|
+
Commit 命令:
|
|
38
|
+
gw commit 交互式提交 (Conventional Commits + Gitmoji)
|
|
39
|
+
gw c 同上 (别名)
|
|
40
|
+
gw cm 同上 (别名)
|
|
35
41
|
|
|
36
42
|
示例:
|
|
37
|
-
gw
|
|
38
|
-
gw
|
|
39
|
-
gw
|
|
40
|
-
gw
|
|
41
|
-
gw
|
|
42
|
-
gw
|
|
43
|
-
gw
|
|
43
|
+
gw f 基于 main/master 创建 feature 分支
|
|
44
|
+
gw f --base develop 基于 develop 分支创建 feature 分支
|
|
45
|
+
gw h --base release 基于 release 分支创建 hotfix 分支
|
|
46
|
+
gw d 交互式选择并删除分支
|
|
47
|
+
gw d feature/xxx 直接删除指定分支
|
|
48
|
+
gw ts v 列出所有 v 开头的 tag
|
|
49
|
+
gw t 交互式创建 tag
|
|
50
|
+
gw r 交互式发布版本
|
|
51
|
+
gw s 交互式管理 stash
|
|
52
|
+
gw c 交互式提交代码
|
|
44
53
|
|
|
45
54
|
分支命名格式:
|
|
46
55
|
feature/${TODAY}-<Story ID>-<描述>
|
package/src/commands/init.ts
CHANGED
|
@@ -5,6 +5,21 @@ import type { GwConfig } from "../config.js";
|
|
|
5
5
|
|
|
6
6
|
const CONFIG_FILE = ".gwrc.json";
|
|
7
7
|
|
|
8
|
+
// 默认的 commit emoji 配置
|
|
9
|
+
const DEFAULT_COMMIT_EMOJIS = {
|
|
10
|
+
feat: "✨",
|
|
11
|
+
fix: "🐛",
|
|
12
|
+
docs: "📝",
|
|
13
|
+
style: "💄",
|
|
14
|
+
refactor: "♻️",
|
|
15
|
+
perf: "⚡️",
|
|
16
|
+
test: "✅",
|
|
17
|
+
build: "📦",
|
|
18
|
+
ci: "👷",
|
|
19
|
+
chore: "🔧",
|
|
20
|
+
revert: "⏪",
|
|
21
|
+
};
|
|
22
|
+
|
|
8
23
|
export async function init(): Promise<void> {
|
|
9
24
|
if (existsSync(CONFIG_FILE)) {
|
|
10
25
|
const overwrite = await confirm({
|
|
@@ -94,10 +109,35 @@ export async function init(): Promise<void> {
|
|
|
94
109
|
|
|
95
110
|
divider();
|
|
96
111
|
|
|
112
|
+
// Commit 配置
|
|
113
|
+
const autoStage = await confirm({
|
|
114
|
+
message: "Commit 时是否自动暂存所有更改?",
|
|
115
|
+
default: true,
|
|
116
|
+
theme,
|
|
117
|
+
});
|
|
118
|
+
if (!autoStage) config.autoStage = false;
|
|
119
|
+
|
|
120
|
+
const useEmoji = await confirm({
|
|
121
|
+
message: "Commit 时是否使用 emoji?",
|
|
122
|
+
default: true,
|
|
123
|
+
theme,
|
|
124
|
+
});
|
|
125
|
+
if (!useEmoji) config.useEmoji = false;
|
|
126
|
+
|
|
127
|
+
// 始终写入默认的 commitEmojis 配置,方便用户修改
|
|
128
|
+
config.commitEmojis = DEFAULT_COMMIT_EMOJIS;
|
|
129
|
+
|
|
130
|
+
divider();
|
|
131
|
+
|
|
97
132
|
// 写入配置
|
|
98
133
|
const content = JSON.stringify(config, null, 2);
|
|
99
134
|
writeFileSync(CONFIG_FILE, content + "\n");
|
|
100
135
|
|
|
101
136
|
console.log(colors.green(`✓ 配置已保存到 ${CONFIG_FILE}`));
|
|
137
|
+
console.log(
|
|
138
|
+
colors.dim(
|
|
139
|
+
"\n提示: 可以在配置文件中修改 commitEmojis 来自定义各类型的 emoji"
|
|
140
|
+
)
|
|
141
|
+
);
|
|
102
142
|
console.log(colors.dim("\n" + content));
|
|
103
143
|
}
|