@alejandrochaves/devflow-cli 1.3.2 → 1.4.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/dist/commands/amend.d.ts.map +1 -1
- package/dist/commands/amend.js +139 -68
- package/dist/commands/amend.js.map +1 -1
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +201 -80
- package/dist/commands/branch.js.map +1 -1
- package/dist/commands/changelog.d.ts.map +1 -1
- package/dist/commands/changelog.js +62 -32
- package/dist/commands/changelog.js.map +1 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +10 -3
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +343 -175
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/fixup.d.ts.map +1 -1
- package/dist/commands/fixup.js +129 -81
- package/dist/commands/fixup.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +223 -110
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts.map +1 -1
- package/dist/commands/issue.js +341 -83
- package/dist/commands/issue.js.map +1 -1
- package/dist/commands/issues.d.ts.map +1 -1
- package/dist/commands/issues.js +166 -77
- package/dist/commands/issues.js.map +1 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +81 -46
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +78 -38
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +110 -42
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/release.d.ts.map +1 -1
- package/dist/commands/release.js +70 -37
- package/dist/commands/release.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +144 -84
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/stash.d.ts.map +1 -1
- package/dist/commands/stash.js +164 -94
- package/dist/commands/stash.js.map +1 -1
- package/dist/commands/test-plan.d.ts.map +1 -1
- package/dist/commands/test-plan.js +86 -42
- package/dist/commands/test-plan.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/worktree.d.ts.map +1 -1
- package/dist/commands/worktree.js +168 -96
- package/dist/commands/worktree.js.map +1 -1
- package/dist/prompts.d.ts +62 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +130 -0
- package/dist/prompts.js.map +1 -0
- package/package.json +3 -2
package/dist/commands/commit.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { select, search, confirm, input, checkbox } from "@inquirer/prompts";
|
|
2
1
|
import { execSync } from "child_process";
|
|
3
2
|
import { loadConfig } from "../config.js";
|
|
4
3
|
import { inferTicket, inferScope, getBranch, isProtectedBranch } from "../git.js";
|
|
5
4
|
import { bold, cyan, dim, green, yellow } from "../colors.js";
|
|
5
|
+
import { selectWithBack, inputWithBack, confirmWithBack, searchWithBack, checkboxWithBack, BACK_VALUE, } from "../prompts.js";
|
|
6
6
|
export function inferScopeFromPaths(stagedFiles, scopes) {
|
|
7
7
|
const scopesWithPaths = scopes.filter((s) => s.paths && s.paths.length > 0);
|
|
8
8
|
if (scopesWithPaths.length === 0)
|
|
@@ -53,16 +53,17 @@ export async function commitCommand(options = {}) {
|
|
|
53
53
|
if (isProtectedBranch() && !options.yes) {
|
|
54
54
|
const branch = getBranch();
|
|
55
55
|
console.log(yellow(`⚠ You are on ${bold(branch)}. Committing directly to protected branches is not recommended.`));
|
|
56
|
-
const proceed = await
|
|
56
|
+
const proceed = await confirmWithBack({
|
|
57
57
|
message: `Continue committing to ${branch}?`,
|
|
58
58
|
default: false,
|
|
59
|
+
showBack: false,
|
|
59
60
|
});
|
|
60
|
-
if (
|
|
61
|
+
if (proceed !== true) {
|
|
61
62
|
console.log("Use: devflow branch");
|
|
62
63
|
process.exit(0);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
|
-
// Stage files if --all or --files provided
|
|
66
|
+
// Stage files if --all or --files provided (non-interactive)
|
|
66
67
|
if (options.all) {
|
|
67
68
|
if (!options.dryRun) {
|
|
68
69
|
execSync("git add -A");
|
|
@@ -76,198 +77,354 @@ export async function commitCommand(options = {}) {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
//
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
// Initialize state
|
|
81
|
+
const state = {
|
|
82
|
+
stagedFiles: [],
|
|
83
|
+
type: options.type || "",
|
|
84
|
+
scope: options.scope ?? "",
|
|
85
|
+
message: options.message || "",
|
|
86
|
+
isBreaking: options.breaking ?? false,
|
|
87
|
+
body: options.body || "",
|
|
88
|
+
breakingDesc: options.breakingDesc || "",
|
|
89
|
+
};
|
|
90
|
+
let currentStep = "stageFiles";
|
|
91
|
+
const stepOrder = ["stageFiles", "type", "scope", "message", "breaking", "body", "breakingDesc"];
|
|
92
|
+
// Determine starting step based on flags
|
|
93
|
+
if (options.all || options.files) {
|
|
94
|
+
// Files already staged via flags, skip to type
|
|
95
|
+
const staged = execSync("git diff --cached --name-only", { encoding: "utf-8" }).trim();
|
|
96
|
+
state.stagedFiles = staged ? staged.split("\n") : [];
|
|
97
|
+
currentStep = "type";
|
|
90
98
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
const goBack = () => {
|
|
100
|
+
const currentIndex = stepOrder.indexOf(currentStep);
|
|
101
|
+
if (currentIndex > 0) {
|
|
102
|
+
currentStep = stepOrder[currentIndex - 1];
|
|
103
|
+
// Skip breakingDesc if not breaking
|
|
104
|
+
if (currentStep === "breakingDesc" && !state.isBreaking) {
|
|
105
|
+
const prevIndex = stepOrder.indexOf(currentStep) - 1;
|
|
106
|
+
if (prevIndex >= 0)
|
|
107
|
+
currentStep = stepOrder[prevIndex];
|
|
108
|
+
}
|
|
109
|
+
// Skip stageFiles if files were staged via flags
|
|
110
|
+
if (currentStep === "stageFiles" && (options.all || options.files)) {
|
|
111
|
+
// Can't go back further
|
|
112
|
+
currentStep = "type";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const goNext = () => {
|
|
117
|
+
const currentIndex = stepOrder.indexOf(currentStep);
|
|
118
|
+
if (currentIndex < stepOrder.length - 1) {
|
|
119
|
+
currentStep = stepOrder[currentIndex + 1];
|
|
120
|
+
// Skip breakingDesc if not breaking
|
|
121
|
+
if (currentStep === "breakingDesc" && !state.isBreaking) {
|
|
122
|
+
currentStep = stepOrder[stepOrder.indexOf(currentStep) + 1] || currentStep;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
while (currentStep !== undefined) {
|
|
127
|
+
const canGoBackToStageFiles = !options.all && !options.files && !options.yes;
|
|
128
|
+
const isFirstStep = (currentStep === "stageFiles") || (currentStep === "type" && !canGoBackToStageFiles);
|
|
129
|
+
switch (currentStep) {
|
|
130
|
+
case "stageFiles": {
|
|
131
|
+
// Check for staged files
|
|
132
|
+
const staged = execSync("git diff --cached --name-only", { encoding: "utf-8" }).trim();
|
|
133
|
+
const unstaged = execSync("git diff --name-only", { encoding: "utf-8" }).trim();
|
|
134
|
+
const untracked = execSync("git ls-files --others --exclude-standard", { encoding: "utf-8" }).trim();
|
|
135
|
+
const allChanges = [
|
|
136
|
+
...unstaged.split("\n").filter(Boolean).map((f) => ({ file: f, label: `M ${f}` })),
|
|
137
|
+
...untracked.split("\n").filter(Boolean).map((f) => ({ file: f, label: `? ${f}` })),
|
|
138
|
+
];
|
|
139
|
+
// If already have staged changes, skip to type
|
|
140
|
+
if (staged) {
|
|
141
|
+
state.stagedFiles = staged.split("\n");
|
|
142
|
+
console.log(dim("Staged files:"));
|
|
143
|
+
staged.split("\n").forEach((f) => console.log(dim(` ${f}`)));
|
|
144
|
+
console.log("");
|
|
145
|
+
goNext();
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
if (allChanges.length === 0) {
|
|
149
|
+
console.log("Nothing to commit — working tree clean.");
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
if (options.yes) {
|
|
153
|
+
// Auto-stage all files when --yes
|
|
154
|
+
if (!options.dryRun) {
|
|
155
|
+
execSync("git add -A");
|
|
156
|
+
}
|
|
157
|
+
state.stagedFiles = allChanges.map((c) => c.file);
|
|
158
|
+
goNext();
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (allChanges.length === 1) {
|
|
162
|
+
const result = await confirmWithBack({
|
|
163
|
+
message: `Stage ${allChanges[0].file}?`,
|
|
164
|
+
default: true,
|
|
165
|
+
showBack: false, // First step
|
|
166
|
+
});
|
|
167
|
+
if (result !== true) {
|
|
168
|
+
console.log("No files staged. Aborting.");
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
if (!options.dryRun) {
|
|
172
|
+
execSync(`git add ${JSON.stringify(allChanges[0].file)}`);
|
|
173
|
+
}
|
|
174
|
+
state.stagedFiles = [allChanges[0].file];
|
|
175
|
+
goNext();
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Ask: stage all or select specific files?
|
|
179
|
+
const stageChoice = await selectWithBack({
|
|
180
|
+
message: "Stage files:",
|
|
181
|
+
choices: [
|
|
182
|
+
{ value: "all", name: `Stage all (${allChanges.length} files)` },
|
|
183
|
+
{ value: "select", name: "Select specific files" },
|
|
184
|
+
],
|
|
185
|
+
default: "all",
|
|
186
|
+
showBack: false, // First step
|
|
187
|
+
});
|
|
188
|
+
if (stageChoice === BACK_VALUE) {
|
|
189
|
+
// Can't go back from first step
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
if (stageChoice === "all") {
|
|
193
|
+
if (!options.dryRun) {
|
|
194
|
+
execSync("git add -A");
|
|
195
|
+
}
|
|
196
|
+
state.stagedFiles = allChanges.map((c) => c.file);
|
|
197
|
+
goNext();
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const filesToStage = await checkboxWithBack({
|
|
201
|
+
message: "Select files to stage:",
|
|
202
|
+
choices: allChanges.map((c) => ({ value: c.file, name: c.label })),
|
|
203
|
+
required: true,
|
|
204
|
+
showBack: true,
|
|
205
|
+
});
|
|
206
|
+
if (filesToStage === BACK_VALUE) {
|
|
207
|
+
// Stay on this step to re-show the all/select choice
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
if (filesToStage.length === 0) {
|
|
211
|
+
console.log("No files selected. Please select at least one file.");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (!options.dryRun) {
|
|
215
|
+
for (const file of filesToStage) {
|
|
216
|
+
execSync(`git add ${JSON.stringify(file)}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
state.stagedFiles = filesToStage;
|
|
220
|
+
goNext();
|
|
221
|
+
}
|
|
99
222
|
}
|
|
100
|
-
|
|
223
|
+
break;
|
|
101
224
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
225
|
+
case "type": {
|
|
226
|
+
if (options.type) {
|
|
227
|
+
goNext();
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
const result = await selectWithBack({
|
|
231
|
+
message: "Select commit type:",
|
|
232
|
+
choices: config.commitTypes.map((t) => ({ value: t.value, name: t.label })),
|
|
233
|
+
showBack: canGoBackToStageFiles,
|
|
106
234
|
});
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
235
|
+
if (result === BACK_VALUE) {
|
|
236
|
+
// Unstage files before going back
|
|
237
|
+
execSync("git reset HEAD", { stdio: "ignore" });
|
|
238
|
+
goBack();
|
|
110
239
|
}
|
|
111
|
-
|
|
112
|
-
|
|
240
|
+
else {
|
|
241
|
+
state.type = result;
|
|
242
|
+
goNext();
|
|
113
243
|
}
|
|
114
|
-
|
|
244
|
+
break;
|
|
115
245
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
246
|
+
case "scope": {
|
|
247
|
+
if (options.scope !== undefined) {
|
|
248
|
+
goNext();
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (config.scopes.length > 0) {
|
|
252
|
+
const inferredFromPaths = inferScopeFromPaths(state.stagedFiles, config.scopes);
|
|
253
|
+
const inferredFromLog = inferScope();
|
|
254
|
+
const inferred = inferredFromPaths || inferredFromLog;
|
|
255
|
+
const result = await searchWithBack({
|
|
256
|
+
message: inferred
|
|
257
|
+
? `Select scope (suggested: ${cyan(inferred)}):`
|
|
258
|
+
: "Select scope (type to filter):",
|
|
259
|
+
source: (term) => {
|
|
260
|
+
const filtered = config.scopes.filter((s) => !term ||
|
|
261
|
+
s.value.includes(term.toLowerCase()) ||
|
|
262
|
+
s.description.toLowerCase().includes(term.toLowerCase()));
|
|
263
|
+
if (inferred) {
|
|
264
|
+
filtered.sort((a, b) => a.value === inferred ? -1 : b.value === inferred ? 1 : 0);
|
|
265
|
+
}
|
|
266
|
+
return filtered.map((s) => ({
|
|
267
|
+
value: s.value,
|
|
268
|
+
name: `${s.value} — ${s.description}`,
|
|
269
|
+
}));
|
|
270
|
+
},
|
|
271
|
+
showBack: true,
|
|
272
|
+
});
|
|
273
|
+
if (result === BACK_VALUE) {
|
|
274
|
+
goBack();
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
state.scope = result;
|
|
278
|
+
goNext();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const inferredFromLog = inferScope();
|
|
283
|
+
const result = await inputWithBack({
|
|
284
|
+
message: inferredFromLog
|
|
285
|
+
? `Enter scope (default: ${inferredFromLog}):`
|
|
286
|
+
: "Enter scope (optional):",
|
|
287
|
+
default: inferredFromLog,
|
|
288
|
+
showBack: true,
|
|
289
|
+
});
|
|
290
|
+
if (result === BACK_VALUE) {
|
|
291
|
+
goBack();
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
state.scope = result;
|
|
295
|
+
goNext();
|
|
296
|
+
}
|
|
122
297
|
}
|
|
123
|
-
|
|
298
|
+
break;
|
|
124
299
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
300
|
+
case "message": {
|
|
301
|
+
if (options.message) {
|
|
302
|
+
goNext();
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
const result = await inputWithBack({
|
|
306
|
+
message: "Enter commit message:",
|
|
307
|
+
default: state.message || undefined,
|
|
308
|
+
validate: (val) => val.trim().length > 0 || "Commit message is required",
|
|
309
|
+
showBack: true,
|
|
133
310
|
});
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
311
|
+
if (result === BACK_VALUE) {
|
|
312
|
+
goBack();
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
state.message = result;
|
|
316
|
+
goNext();
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case "breaking": {
|
|
321
|
+
if (options.breaking !== undefined || options.yes) {
|
|
322
|
+
state.isBreaking = options.breaking ?? false;
|
|
323
|
+
goNext();
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
const result = await confirmWithBack({
|
|
327
|
+
message: "Is this a breaking change?",
|
|
328
|
+
default: state.isBreaking,
|
|
329
|
+
showBack: true,
|
|
330
|
+
});
|
|
331
|
+
if (result === BACK_VALUE) {
|
|
332
|
+
goBack();
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
state.isBreaking = result;
|
|
336
|
+
goNext();
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case "body": {
|
|
341
|
+
if (options.body || options.yes) {
|
|
342
|
+
goNext();
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
const result = await confirmWithBack({
|
|
346
|
+
message: "Add a longer description (body)?",
|
|
347
|
+
default: false,
|
|
348
|
+
showBack: true,
|
|
349
|
+
});
|
|
350
|
+
if (result === BACK_VALUE) {
|
|
351
|
+
goBack();
|
|
352
|
+
}
|
|
353
|
+
else if (result) {
|
|
354
|
+
const bodyResult = await inputWithBack({
|
|
355
|
+
message: "Body (longer explanation):",
|
|
356
|
+
default: state.body || undefined,
|
|
357
|
+
showBack: true,
|
|
358
|
+
});
|
|
359
|
+
if (bodyResult === BACK_VALUE) {
|
|
360
|
+
// Stay on this step
|
|
138
361
|
}
|
|
139
362
|
else {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
stagedFiles = filesToStage;
|
|
363
|
+
state.body = bodyResult;
|
|
364
|
+
goNext();
|
|
144
365
|
}
|
|
145
366
|
}
|
|
146
367
|
else {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
: filesToStage;
|
|
368
|
+
state.body = "";
|
|
369
|
+
goNext();
|
|
150
370
|
}
|
|
371
|
+
break;
|
|
151
372
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log("");
|
|
158
|
-
}
|
|
159
|
-
// Get commit type (from flag or prompt)
|
|
160
|
-
const type = options.type || await select({
|
|
161
|
-
message: "Select commit type:",
|
|
162
|
-
choices: config.commitTypes.map((t) => ({ value: t.value, name: t.label })),
|
|
163
|
-
});
|
|
164
|
-
// Get scope (from flag or prompt)
|
|
165
|
-
let finalScope;
|
|
166
|
-
if (options.scope !== undefined) {
|
|
167
|
-
finalScope = options.scope;
|
|
168
|
-
}
|
|
169
|
-
else if (config.scopes.length > 0) {
|
|
170
|
-
const inferredFromPaths = inferScopeFromPaths(stagedFiles, config.scopes);
|
|
171
|
-
const inferredFromLog = inferScope();
|
|
172
|
-
const inferred = inferredFromPaths || inferredFromLog;
|
|
173
|
-
finalScope = await search({
|
|
174
|
-
message: inferred
|
|
175
|
-
? `Select scope (suggested: ${cyan(inferred)}):`
|
|
176
|
-
: "Select scope (type to filter):",
|
|
177
|
-
source: (term) => {
|
|
178
|
-
const filtered = config.scopes.filter((s) => !term ||
|
|
179
|
-
s.value.includes(term.toLowerCase()) ||
|
|
180
|
-
s.description.toLowerCase().includes(term.toLowerCase()));
|
|
181
|
-
if (inferred) {
|
|
182
|
-
filtered.sort((a, b) => a.value === inferred ? -1 : b.value === inferred ? 1 : 0);
|
|
373
|
+
case "breakingDesc": {
|
|
374
|
+
if (!state.isBreaking) {
|
|
375
|
+
// Exit the loop - we're done collecting input
|
|
376
|
+
currentStep = undefined;
|
|
377
|
+
break;
|
|
183
378
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
else if (options.yes) {
|
|
217
|
-
isBreaking = false;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
isBreaking = await confirm({
|
|
221
|
-
message: "Is this a breaking change?",
|
|
222
|
-
default: false,
|
|
223
|
-
});
|
|
379
|
+
if (options.breakingDesc) {
|
|
380
|
+
currentStep = undefined;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
if (options.yes) {
|
|
384
|
+
console.log(yellow("Warning: Breaking change without description. Use --breaking-desc to provide one."));
|
|
385
|
+
currentStep = undefined;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
const result = await inputWithBack({
|
|
389
|
+
message: "Describe the breaking change:",
|
|
390
|
+
default: state.breakingDesc || undefined,
|
|
391
|
+
validate: (val) => val.trim().length > 0 || "Breaking change description is required",
|
|
392
|
+
showBack: true,
|
|
393
|
+
});
|
|
394
|
+
if (result === BACK_VALUE) {
|
|
395
|
+
goBack();
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
state.breakingDesc = result;
|
|
399
|
+
currentStep = undefined;
|
|
400
|
+
}
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
default:
|
|
404
|
+
currentStep = undefined;
|
|
405
|
+
}
|
|
406
|
+
// Check if we've passed the last step
|
|
407
|
+
if (currentStep && stepOrder.indexOf(currentStep) >= stepOrder.length) {
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
224
410
|
}
|
|
225
411
|
const ticket = inferTicket();
|
|
226
|
-
const breaking = isBreaking ? "!" : "";
|
|
227
|
-
const scope =
|
|
412
|
+
const breaking = state.isBreaking ? "!" : "";
|
|
413
|
+
const scope = state.scope || "";
|
|
228
414
|
const subject = formatCommitMessage(config.commitFormat, {
|
|
229
|
-
type,
|
|
415
|
+
type: state.type,
|
|
230
416
|
ticket,
|
|
231
417
|
breaking,
|
|
232
418
|
scope,
|
|
233
|
-
message: message.trim(),
|
|
419
|
+
message: state.message.trim(),
|
|
234
420
|
});
|
|
235
|
-
// Get body (from flag or prompt)
|
|
236
|
-
let body = options.body || "";
|
|
237
|
-
if (!options.body && !options.yes) {
|
|
238
|
-
const addBody = await confirm({
|
|
239
|
-
message: "Add a longer description (body)?",
|
|
240
|
-
default: false,
|
|
241
|
-
});
|
|
242
|
-
if (addBody) {
|
|
243
|
-
body = await input({
|
|
244
|
-
message: "Body (longer explanation):",
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// Get breaking change description (from flag or prompt)
|
|
249
|
-
let breakingFooter = "";
|
|
250
|
-
if (isBreaking) {
|
|
251
|
-
if (options.breakingDesc) {
|
|
252
|
-
breakingFooter = options.breakingDesc;
|
|
253
|
-
}
|
|
254
|
-
else if (options.yes) {
|
|
255
|
-
console.log(yellow("Warning: Breaking change without description. Use --breaking-desc to provide one."));
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
breakingFooter = await input({
|
|
259
|
-
message: "Describe the breaking change:",
|
|
260
|
-
validate: (val) => val.trim().length > 0 || "Breaking change description is required",
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
421
|
// Build full message
|
|
265
422
|
const parts = [subject];
|
|
266
|
-
if (body.trim())
|
|
267
|
-
parts.push(body.trim());
|
|
423
|
+
if (state.body.trim())
|
|
424
|
+
parts.push(state.body.trim());
|
|
268
425
|
const footers = [];
|
|
269
|
-
if (
|
|
270
|
-
footers.push(`BREAKING CHANGE: ${
|
|
426
|
+
if (state.breakingDesc.trim())
|
|
427
|
+
footers.push(`BREAKING CHANGE: ${state.breakingDesc.trim()}`);
|
|
271
428
|
if (ticket && ticket !== "UNTRACKED")
|
|
272
429
|
footers.push(`Refs: ${ticket}`);
|
|
273
430
|
if (footers.length > 0)
|
|
@@ -275,8 +432,8 @@ export async function commitCommand(options = {}) {
|
|
|
275
432
|
const fullMessage = parts.join("\n\n");
|
|
276
433
|
console.log(`\n${dim("───")} ${bold("Commit Preview")} ${dim("───")}`);
|
|
277
434
|
console.log(green(subject));
|
|
278
|
-
if (body.trim())
|
|
279
|
-
console.log(`\n${body.trim()}`);
|
|
435
|
+
if (state.body.trim())
|
|
436
|
+
console.log(`\n${state.body.trim()}`);
|
|
280
437
|
if (footers.length > 0)
|
|
281
438
|
console.log(`\n${dim(footers.join("\n"))}`);
|
|
282
439
|
console.log(`${dim("───────────────────")}\n`);
|
|
@@ -285,15 +442,26 @@ export async function commitCommand(options = {}) {
|
|
|
285
442
|
return;
|
|
286
443
|
}
|
|
287
444
|
// Confirm (skip if --yes)
|
|
445
|
+
let confirmed = true;
|
|
288
446
|
if (!options.yes) {
|
|
289
|
-
const
|
|
447
|
+
const confirmResult = await selectWithBack({
|
|
290
448
|
message: "Create this commit?",
|
|
291
|
-
|
|
449
|
+
choices: [
|
|
450
|
+
{ value: "yes", name: "Yes, create commit" },
|
|
451
|
+
{ value: "no", name: "No, abort" },
|
|
452
|
+
],
|
|
453
|
+
default: "yes",
|
|
454
|
+
showBack: true,
|
|
292
455
|
});
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
456
|
+
if (confirmResult === BACK_VALUE) {
|
|
457
|
+
// Go back to breaking desc or body step
|
|
458
|
+
return commitCommand(options); // Restart for simplicity (state is lost)
|
|
296
459
|
}
|
|
460
|
+
confirmed = confirmResult === "yes";
|
|
461
|
+
}
|
|
462
|
+
if (!confirmed) {
|
|
463
|
+
console.log("Commit aborted.");
|
|
464
|
+
process.exit(0);
|
|
297
465
|
}
|
|
298
466
|
execSync(`git commit -m ${JSON.stringify(fullMessage)}`, {
|
|
299
467
|
stdio: "inherit",
|