@alejandrochaves/devflow-cli 1.3.2 → 1.5.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 +267 -103
- 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 +456 -110
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts.map +1 -1
- package/dist/commands/issue.js +353 -85
- 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/dist/providers/tickets.d.ts +7 -0
- package/dist/providers/tickets.d.ts.map +1 -1
- package/dist/providers/tickets.js +29 -2
- package/dist/providers/tickets.js.map +1 -1
- package/package.json +3 -2
package/dist/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import { confirm, input, select } from "@inquirer/prompts";
|
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { PRESETS } from "../config.js";
|
|
6
6
|
import { writeVersionInfo, getCliVersion } from "../devflow-version.js";
|
|
7
|
+
import { selectWithBack, inputWithBack, confirmWithBack, BACK_VALUE, } from "../prompts.js";
|
|
7
8
|
const DEFAULT_CHECKLIST = [
|
|
8
9
|
"Code follows project conventions",
|
|
9
10
|
"Self-reviewed the changes",
|
|
@@ -42,6 +43,95 @@ function getGitHubIssuesUrl() {
|
|
|
42
43
|
}
|
|
43
44
|
return undefined;
|
|
44
45
|
}
|
|
46
|
+
function getGitHubRepo() {
|
|
47
|
+
try {
|
|
48
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
51
|
+
}).trim();
|
|
52
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
53
|
+
const httpsMatch = remoteUrl.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
54
|
+
const match = sshMatch || httpsMatch;
|
|
55
|
+
if (match) {
|
|
56
|
+
return { owner: match[1], name: match[2] };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Not a git repo or no remote
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function hasProjectScopes() {
|
|
65
|
+
try {
|
|
66
|
+
// Try to list projects - if it fails with scope error, we don't have permissions
|
|
67
|
+
execSync("gh project list --owner @me --limit 1", {
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const errorMessage = error.message || "";
|
|
75
|
+
return !errorMessage.includes("missing required scopes") && !errorMessage.includes("read:project");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getRepoLinkedProjects(repo) {
|
|
79
|
+
try {
|
|
80
|
+
const query = `{
|
|
81
|
+
repository(owner: "${repo.owner}", name: "${repo.name}") {
|
|
82
|
+
projectsV2(first: 20) {
|
|
83
|
+
nodes { number title }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}`;
|
|
87
|
+
const result = execSync(`gh api graphql -f query='${query}'`, {
|
|
88
|
+
encoding: "utf-8",
|
|
89
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
90
|
+
}).trim();
|
|
91
|
+
if (!result)
|
|
92
|
+
return [];
|
|
93
|
+
const data = JSON.parse(result);
|
|
94
|
+
const nodes = data?.data?.repository?.projectsV2?.nodes || [];
|
|
95
|
+
return nodes.map((p) => ({
|
|
96
|
+
number: p.number,
|
|
97
|
+
title: p.title,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function refreshProjectScopes() {
|
|
105
|
+
try {
|
|
106
|
+
execSync("gh auth refresh -s project", {
|
|
107
|
+
stdio: "inherit",
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function createAndLinkProject(repo, title) {
|
|
116
|
+
try {
|
|
117
|
+
// Create the project
|
|
118
|
+
const createResult = execSync(`gh project create --owner ${repo.owner} --title "${title}" --format json`, {
|
|
119
|
+
encoding: "utf-8",
|
|
120
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
121
|
+
}).trim();
|
|
122
|
+
const project = JSON.parse(createResult);
|
|
123
|
+
const projectNumber = project.number;
|
|
124
|
+
// Link it to the repo
|
|
125
|
+
execSync(`gh project link ${projectNumber} --owner ${repo.owner} --repo ${repo.name}`, {
|
|
126
|
+
encoding: "utf-8",
|
|
127
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
128
|
+
});
|
|
129
|
+
return { number: projectNumber, title };
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
45
135
|
function writePackageJson(cwd, pkg) {
|
|
46
136
|
const pkgPath = resolve(cwd, "package.json");
|
|
47
137
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
@@ -52,133 +142,376 @@ export async function initCommand() {
|
|
|
52
142
|
const devflowDir = resolve(cwd, ".devflow");
|
|
53
143
|
const configPath = resolve(devflowDir, "config.json");
|
|
54
144
|
if (existsSync(configPath)) {
|
|
55
|
-
const overwrite = await
|
|
145
|
+
const overwrite = await confirmWithBack({
|
|
56
146
|
message: "Devflow config already exists. Overwrite?",
|
|
57
147
|
default: false,
|
|
148
|
+
showBack: false,
|
|
58
149
|
});
|
|
59
|
-
if (
|
|
150
|
+
if (overwrite !== true) {
|
|
60
151
|
console.log("Aborted.");
|
|
61
152
|
process.exit(0);
|
|
62
153
|
}
|
|
63
154
|
}
|
|
64
155
|
console.log("\n devflow setup\n");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
else if (scopeChoice === "custom") {
|
|
106
|
-
console.log("\nAdd scopes one at a time. Press Enter with empty name to finish.\n");
|
|
107
|
-
let addingScopes = true;
|
|
108
|
-
while (addingScopes) {
|
|
109
|
-
const value = await input({
|
|
110
|
-
message: `Scope name${scopes.length > 0 ? " (blank to finish)" : ""}:`,
|
|
111
|
-
});
|
|
112
|
-
if (!value.trim()) {
|
|
113
|
-
addingScopes = false;
|
|
156
|
+
const state = {
|
|
157
|
+
preset: "scrum",
|
|
158
|
+
ticketBaseUrl: "",
|
|
159
|
+
useGitHubIssues: false,
|
|
160
|
+
projectEnabled: false,
|
|
161
|
+
projectNumber: null,
|
|
162
|
+
scopeChoice: "",
|
|
163
|
+
scopes: [],
|
|
164
|
+
checklistChoice: "",
|
|
165
|
+
checklist: [...DEFAULT_CHECKLIST],
|
|
166
|
+
customizeFormats: false,
|
|
167
|
+
branchFormat: "",
|
|
168
|
+
commitFormat: "{type}[{ticket}]{breaking}({scope}): {message}",
|
|
169
|
+
};
|
|
170
|
+
let currentStep = "preset";
|
|
171
|
+
while (currentStep !== "done") {
|
|
172
|
+
switch (currentStep) {
|
|
173
|
+
case "preset": {
|
|
174
|
+
const result = await selectWithBack({
|
|
175
|
+
message: "Select a workflow preset:",
|
|
176
|
+
choices: [
|
|
177
|
+
{ value: "scrum", name: "Scrum - User stories, sprints, acceptance criteria" },
|
|
178
|
+
{ value: "kanban", name: "Kanban - Simple flow-based workflow" },
|
|
179
|
+
{ value: "simple", name: "Simple - Minimal configuration, no ticket required" },
|
|
180
|
+
{ value: "custom", name: "Custom - Configure everything manually" },
|
|
181
|
+
],
|
|
182
|
+
default: state.preset,
|
|
183
|
+
showBack: false, // First step
|
|
184
|
+
});
|
|
185
|
+
if (result === BACK_VALUE) {
|
|
186
|
+
// Can't go back from first step
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
state.preset = result;
|
|
190
|
+
state.branchFormat = PRESETS[state.preset].branchFormat;
|
|
191
|
+
console.log("");
|
|
192
|
+
// Skip ticket URL for simple preset
|
|
193
|
+
currentStep = state.preset === "simple" ? "scopes" : "ticketUrl";
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
114
196
|
}
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
197
|
+
case "ticketUrl": {
|
|
198
|
+
const detectedIssuesUrl = getGitHubIssuesUrl();
|
|
199
|
+
const result = await inputWithBack({
|
|
200
|
+
message: "Ticket base URL (e.g., https://github.com/org/repo/issues):",
|
|
201
|
+
default: state.ticketBaseUrl || detectedIssuesUrl,
|
|
202
|
+
showBack: true,
|
|
118
203
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
204
|
+
if (result === BACK_VALUE) {
|
|
205
|
+
currentStep = "preset";
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
state.ticketBaseUrl = result;
|
|
209
|
+
currentStep = "githubIssues";
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "githubIssues": {
|
|
214
|
+
const result = await confirmWithBack({
|
|
215
|
+
message: "Use GitHub Issues for ticket tracking? (enables issue picker in branch command)",
|
|
216
|
+
default: state.useGitHubIssues || state.ticketBaseUrl.includes("github.com"),
|
|
217
|
+
showBack: true,
|
|
122
218
|
});
|
|
219
|
+
if (result === BACK_VALUE) {
|
|
220
|
+
currentStep = "ticketUrl";
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
state.useGitHubIssues = result === true;
|
|
224
|
+
// If using GitHub issues, offer project integration
|
|
225
|
+
currentStep = result === true ? "projectSetup" : "scopes";
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
123
228
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
229
|
+
case "projectSetup": {
|
|
230
|
+
// Check if we have project scopes
|
|
231
|
+
const hasScopes = hasProjectScopes();
|
|
232
|
+
if (!hasScopes) {
|
|
233
|
+
console.log("\n GitHub Projects Integration\n");
|
|
234
|
+
console.log(" Connecting to a GitHub Project enables:");
|
|
235
|
+
console.log(" • Auto-move issues to 'In Progress' when you start a branch");
|
|
236
|
+
console.log(" • Auto-move issues to 'In Review' when you open a PR");
|
|
237
|
+
console.log(" • View project issues with `devflow issues`\n");
|
|
238
|
+
console.log(" This requires the 'project' OAuth scope for your GitHub CLI.");
|
|
239
|
+
console.log(" If you skip this, you can still use GitHub Issues without board automation.\n");
|
|
240
|
+
const addScopes = await confirmWithBack({
|
|
241
|
+
message: "Authorize project access?",
|
|
242
|
+
default: true,
|
|
243
|
+
showBack: true,
|
|
244
|
+
});
|
|
245
|
+
if (addScopes === BACK_VALUE) {
|
|
246
|
+
currentStep = "githubIssues";
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
if (addScopes === true) {
|
|
250
|
+
console.log("\nOpening GitHub to authorize project permissions...\n");
|
|
251
|
+
const success = refreshProjectScopes();
|
|
252
|
+
if (!success) {
|
|
253
|
+
console.log("⚠ Failed to add project permissions. Skipping project integration.");
|
|
254
|
+
currentStep = "scopes";
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
console.log("✓ Project permissions added\n");
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Skip project integration
|
|
261
|
+
state.projectEnabled = false;
|
|
262
|
+
currentStep = "scopes";
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Now we have scopes, list available projects
|
|
267
|
+
const repo = getGitHubRepo();
|
|
268
|
+
if (!repo) {
|
|
269
|
+
console.log("⚠ Could not determine repository. Skipping project integration.");
|
|
270
|
+
currentStep = "scopes";
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
// First, try to get projects linked to this repo
|
|
274
|
+
const linkedProjects = getRepoLinkedProjects(repo);
|
|
275
|
+
if (linkedProjects.length > 0) {
|
|
276
|
+
// Show only linked projects
|
|
277
|
+
const projectResult = await selectWithBack({
|
|
278
|
+
message: "Select a GitHub Project for issue tracking:",
|
|
279
|
+
choices: [
|
|
280
|
+
...linkedProjects.map((p) => ({
|
|
281
|
+
value: p.number,
|
|
282
|
+
name: `#${p.number} ${p.title}`,
|
|
283
|
+
})),
|
|
284
|
+
{ value: -1, name: "Skip - don't use project integration" },
|
|
285
|
+
],
|
|
286
|
+
showBack: true,
|
|
287
|
+
});
|
|
288
|
+
if (projectResult === BACK_VALUE) {
|
|
289
|
+
currentStep = "githubIssues";
|
|
290
|
+
}
|
|
291
|
+
else if (projectResult === -1) {
|
|
292
|
+
state.projectEnabled = false;
|
|
293
|
+
currentStep = "scopes";
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
state.projectEnabled = true;
|
|
297
|
+
state.projectNumber = projectResult;
|
|
298
|
+
console.log(`✓ Will use project #${projectResult}\n`);
|
|
299
|
+
currentStep = "scopes";
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
151
302
|
}
|
|
152
|
-
|
|
303
|
+
// No linked projects - offer to create one
|
|
304
|
+
console.log(`\n No GitHub Projects are linked to ${repo.owner}/${repo.name}.`);
|
|
305
|
+
const createChoice = await selectWithBack({
|
|
306
|
+
message: "What would you like to do?",
|
|
307
|
+
choices: [
|
|
308
|
+
{ value: "create", name: "Create a new project" },
|
|
309
|
+
{ value: "skip", name: "Skip project integration for now" },
|
|
310
|
+
],
|
|
311
|
+
showBack: true,
|
|
312
|
+
});
|
|
313
|
+
if (createChoice === BACK_VALUE) {
|
|
314
|
+
currentStep = "githubIssues";
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
if (createChoice === "skip") {
|
|
318
|
+
state.projectEnabled = false;
|
|
319
|
+
currentStep = "scopes";
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
// Ask for project name
|
|
323
|
+
const defaultProjectName = `${repo.name} Board`;
|
|
324
|
+
const projectName = await inputWithBack({
|
|
325
|
+
message: "Project name:",
|
|
326
|
+
default: defaultProjectName,
|
|
327
|
+
showBack: true,
|
|
328
|
+
});
|
|
329
|
+
if (projectName === BACK_VALUE) {
|
|
330
|
+
// Go back to the create/skip choice - stay on this step
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
// Create and link a new project
|
|
334
|
+
console.log(`\nCreating project "${projectName}"...`);
|
|
335
|
+
const newProject = createAndLinkProject(repo, projectName);
|
|
336
|
+
if (newProject) {
|
|
337
|
+
console.log(`✓ Created and linked project #${newProject.number}\n`);
|
|
338
|
+
state.projectEnabled = true;
|
|
339
|
+
state.projectNumber = newProject.number;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.log("⚠ Failed to create project. Skipping project integration.");
|
|
343
|
+
state.projectEnabled = false;
|
|
344
|
+
}
|
|
345
|
+
currentStep = "scopes";
|
|
346
|
+
break;
|
|
153
347
|
}
|
|
154
|
-
|
|
155
|
-
|
|
348
|
+
case "scopes": {
|
|
349
|
+
const result = await selectWithBack({
|
|
350
|
+
message: "How would you like to configure commit scopes?",
|
|
351
|
+
choices: [
|
|
352
|
+
{ value: "defaults", name: "Use defaults (core, ui, api, config, deps, ci)" },
|
|
353
|
+
{ value: "custom", name: "Define custom scopes" },
|
|
354
|
+
{ value: "skip", name: "Skip - configure later" },
|
|
355
|
+
],
|
|
356
|
+
default: state.scopeChoice || undefined,
|
|
357
|
+
showBack: true,
|
|
358
|
+
});
|
|
359
|
+
if (result === BACK_VALUE) {
|
|
360
|
+
// Go back to appropriate step based on config
|
|
361
|
+
if (state.preset === "simple") {
|
|
362
|
+
currentStep = "preset";
|
|
363
|
+
}
|
|
364
|
+
else if (state.useGitHubIssues) {
|
|
365
|
+
currentStep = "projectSetup";
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
currentStep = "githubIssues";
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
state.scopeChoice = result;
|
|
373
|
+
state.scopes = [];
|
|
374
|
+
if (result === "defaults") {
|
|
375
|
+
state.scopes = [
|
|
376
|
+
{ value: "core", description: "Core functionality" },
|
|
377
|
+
{ value: "ui", description: "UI components" },
|
|
378
|
+
{ value: "api", description: "API layer" },
|
|
379
|
+
{ value: "config", description: "Configuration" },
|
|
380
|
+
{ value: "deps", description: "Dependencies" },
|
|
381
|
+
{ value: "ci", description: "CI/CD" },
|
|
382
|
+
];
|
|
383
|
+
currentStep = "checklist";
|
|
384
|
+
}
|
|
385
|
+
else if (result === "custom") {
|
|
386
|
+
console.log("\nAdd scopes one at a time. Press Enter with empty name to finish.\n");
|
|
387
|
+
let addingScopes = true;
|
|
388
|
+
while (addingScopes) {
|
|
389
|
+
const scopeValue = await input({
|
|
390
|
+
message: `Scope name${state.scopes.length > 0 ? " (blank to finish)" : ""}:`,
|
|
391
|
+
});
|
|
392
|
+
if (!scopeValue.trim()) {
|
|
393
|
+
addingScopes = false;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const description = await input({
|
|
397
|
+
message: `Description for "${scopeValue.trim()}":`,
|
|
398
|
+
});
|
|
399
|
+
state.scopes.push({
|
|
400
|
+
value: scopeValue.trim().toLowerCase(),
|
|
401
|
+
description: description.trim() || scopeValue.trim(),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
currentStep = "checklist";
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
currentStep = "checklist";
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "checklist": {
|
|
414
|
+
const result = await selectWithBack({
|
|
415
|
+
message: "PR checklist items (shown in pull request descriptions):",
|
|
416
|
+
choices: [
|
|
417
|
+
{
|
|
418
|
+
value: "defaults",
|
|
419
|
+
name: `Use defaults (${DEFAULT_CHECKLIST.length} items: conventions, self-review, no warnings)`,
|
|
420
|
+
},
|
|
421
|
+
{ value: "custom", name: "Define custom checklist" },
|
|
422
|
+
{ value: "skip", name: "Skip - no checklist" },
|
|
423
|
+
],
|
|
424
|
+
default: state.checklistChoice || undefined,
|
|
425
|
+
showBack: true,
|
|
426
|
+
});
|
|
427
|
+
if (result === BACK_VALUE) {
|
|
428
|
+
currentStep = "scopes";
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
state.checklistChoice = result;
|
|
432
|
+
if (result === "custom") {
|
|
433
|
+
console.log("\nAdd checklist items one at a time. Press Enter with empty text to finish.\n");
|
|
434
|
+
state.checklist = [];
|
|
435
|
+
let addingChecklist = true;
|
|
436
|
+
while (addingChecklist) {
|
|
437
|
+
const item = await input({
|
|
438
|
+
message: `Checklist item${state.checklist.length > 0 ? " (blank to finish)" : ""}:`,
|
|
439
|
+
});
|
|
440
|
+
if (!item.trim()) {
|
|
441
|
+
if (state.checklist.length === 0) {
|
|
442
|
+
state.checklist = [...DEFAULT_CHECKLIST];
|
|
443
|
+
console.log("Using default checklist.");
|
|
444
|
+
}
|
|
445
|
+
addingChecklist = false;
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
state.checklist.push(item.trim());
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
else if (result === "skip") {
|
|
453
|
+
state.checklist = [];
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
state.checklist = [...DEFAULT_CHECKLIST];
|
|
457
|
+
}
|
|
458
|
+
currentStep = "formats";
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
156
461
|
}
|
|
462
|
+
case "formats": {
|
|
463
|
+
const result = await confirmWithBack({
|
|
464
|
+
message: "Customize branch/commit formats? (advanced)",
|
|
465
|
+
default: state.customizeFormats,
|
|
466
|
+
showBack: true,
|
|
467
|
+
});
|
|
468
|
+
if (result === BACK_VALUE) {
|
|
469
|
+
currentStep = "checklist";
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
state.customizeFormats = result === true;
|
|
473
|
+
if (state.customizeFormats) {
|
|
474
|
+
console.log("\nAvailable placeholders:");
|
|
475
|
+
console.log(" Branch: {type}, {ticket}, {description}");
|
|
476
|
+
console.log(" Commit: {type}, {ticket}, {scope}, {message}, {breaking}\n");
|
|
477
|
+
const branchResult = await inputWithBack({
|
|
478
|
+
message: "Branch format:",
|
|
479
|
+
default: state.branchFormat,
|
|
480
|
+
showBack: true,
|
|
481
|
+
});
|
|
482
|
+
if (branchResult === BACK_VALUE) {
|
|
483
|
+
// Stay on formats step
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
state.branchFormat = branchResult;
|
|
487
|
+
const commitResult = await inputWithBack({
|
|
488
|
+
message: "Commit format:",
|
|
489
|
+
default: state.commitFormat,
|
|
490
|
+
showBack: true,
|
|
491
|
+
});
|
|
492
|
+
if (commitResult === BACK_VALUE) {
|
|
493
|
+
// Stay on formats step
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
state.commitFormat = commitResult;
|
|
497
|
+
}
|
|
498
|
+
currentStep = "done";
|
|
499
|
+
}
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
default:
|
|
503
|
+
currentStep = "done";
|
|
157
504
|
}
|
|
158
505
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
if (customizeFormats) {
|
|
170
|
-
console.log("\nAvailable placeholders:");
|
|
171
|
-
console.log(" Branch: {type}, {ticket}, {description}");
|
|
172
|
-
console.log(" Commit: {type}, {ticket}, {scope}, {message}, {breaking}\n");
|
|
173
|
-
branchFormat = await input({
|
|
174
|
-
message: "Branch format:",
|
|
175
|
-
default: branchFormat,
|
|
176
|
-
});
|
|
177
|
-
commitFormat = await input({
|
|
178
|
-
message: "Commit format:",
|
|
179
|
-
default: commitFormat,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
506
|
+
// Extract configuration from state
|
|
507
|
+
const preset = state.preset;
|
|
508
|
+
const presetConfig = PRESETS[preset];
|
|
509
|
+
const ticketBaseUrl = state.ticketBaseUrl;
|
|
510
|
+
const useGitHubIssues = state.useGitHubIssues;
|
|
511
|
+
const scopes = state.scopes;
|
|
512
|
+
const checklist = state.checklist;
|
|
513
|
+
const branchFormat = state.branchFormat || presetConfig.branchFormat;
|
|
514
|
+
const commitFormat = state.commitFormat;
|
|
182
515
|
// 5. Commitlint setup (validates commit messages on git commit)
|
|
183
516
|
const commitlintChoice = await select({
|
|
184
517
|
message: "Set up commitlint? (rejects commits that don't match the format)",
|
|
@@ -416,6 +749,19 @@ devflow amend # If you need to add more changes
|
|
|
416
749
|
if (useGitHubIssues) {
|
|
417
750
|
config.ticketProvider = { type: "github" };
|
|
418
751
|
}
|
|
752
|
+
if (state.projectEnabled && state.projectNumber) {
|
|
753
|
+
config.project = {
|
|
754
|
+
enabled: true,
|
|
755
|
+
number: state.projectNumber,
|
|
756
|
+
statusField: "Status",
|
|
757
|
+
statuses: {
|
|
758
|
+
todo: "Todo",
|
|
759
|
+
inProgress: "In Progress",
|
|
760
|
+
inReview: "In Review",
|
|
761
|
+
done: "Done",
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
}
|
|
419
765
|
if (preset === "custom") {
|
|
420
766
|
config.issueTypes = presetConfig.issueTypes;
|
|
421
767
|
config.prTemplate = presetConfig.prTemplate;
|