contribute-now 0.2.0-dev.2621ffa → 0.2.0-dev.69b11fd
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/index.js +630 -1067
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,11 +3,27 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
import { defineCommand as
|
|
6
|
+
import { defineCommand as defineCommand12, runMain } from "citty";
|
|
7
7
|
|
|
8
|
-
// src/commands/
|
|
8
|
+
// src/commands/clean.ts
|
|
9
9
|
import { defineCommand } from "citty";
|
|
10
|
-
import
|
|
10
|
+
import pc4 from "picocolors";
|
|
11
|
+
|
|
12
|
+
// src/utils/branch.ts
|
|
13
|
+
var DEFAULT_PREFIXES = ["feature", "fix", "docs", "chore", "test", "refactor"];
|
|
14
|
+
function hasPrefix(branchName, prefixes = DEFAULT_PREFIXES) {
|
|
15
|
+
return prefixes.some((p) => branchName.startsWith(`${p}/`));
|
|
16
|
+
}
|
|
17
|
+
function formatBranchName(prefix, name) {
|
|
18
|
+
const sanitized = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
19
|
+
return `${prefix}/${sanitized}`;
|
|
20
|
+
}
|
|
21
|
+
function isValidBranchName(name) {
|
|
22
|
+
return /^[a-zA-Z0-9._/-]+$/.test(name) && !name.startsWith("/") && !name.endsWith("/");
|
|
23
|
+
}
|
|
24
|
+
function looksLikeNaturalLanguage(input) {
|
|
25
|
+
return input.includes(" ") && !input.includes("/");
|
|
26
|
+
}
|
|
11
27
|
|
|
12
28
|
// src/utils/config.ts
|
|
13
29
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -64,623 +80,9 @@ function getDefaultConfig() {
|
|
|
64
80
|
};
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
// src/utils/git.ts
|
|
68
|
-
import { execFile as execFileCb } from "node:child_process";
|
|
69
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
70
|
-
import { join as join2 } from "node:path";
|
|
71
|
-
function run(args) {
|
|
72
|
-
return new Promise((resolve) => {
|
|
73
|
-
execFileCb("git", args, (error, stdout, stderr) => {
|
|
74
|
-
resolve({
|
|
75
|
-
exitCode: error ? error.code === "ENOENT" ? 127 : error.status ?? 1 : 0,
|
|
76
|
-
stdout: stdout ?? "",
|
|
77
|
-
stderr: stderr ?? ""
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
async function isGitRepo() {
|
|
83
|
-
const { exitCode } = await run(["rev-parse", "--is-inside-work-tree"]);
|
|
84
|
-
return exitCode === 0;
|
|
85
|
-
}
|
|
86
|
-
async function getCurrentBranch() {
|
|
87
|
-
const { exitCode, stdout } = await run(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
88
|
-
if (exitCode !== 0)
|
|
89
|
-
return null;
|
|
90
|
-
return stdout.trim() || null;
|
|
91
|
-
}
|
|
92
|
-
async function getRemotes() {
|
|
93
|
-
const { exitCode, stdout } = await run(["remote"]);
|
|
94
|
-
if (exitCode !== 0)
|
|
95
|
-
return [];
|
|
96
|
-
return stdout.trim().split(`
|
|
97
|
-
`).map((r) => r.trim()).filter(Boolean);
|
|
98
|
-
}
|
|
99
|
-
async function getRemoteUrl(remote) {
|
|
100
|
-
const { exitCode, stdout } = await run(["remote", "get-url", remote]);
|
|
101
|
-
if (exitCode !== 0)
|
|
102
|
-
return null;
|
|
103
|
-
return stdout.trim() || null;
|
|
104
|
-
}
|
|
105
|
-
async function hasUncommittedChanges() {
|
|
106
|
-
const { exitCode, stdout } = await run(["status", "--porcelain"]);
|
|
107
|
-
if (exitCode !== 0)
|
|
108
|
-
return false;
|
|
109
|
-
return stdout.trim().length > 0;
|
|
110
|
-
}
|
|
111
|
-
async function fetchRemote(remote) {
|
|
112
|
-
return run(["fetch", remote]);
|
|
113
|
-
}
|
|
114
|
-
async function fetchAll() {
|
|
115
|
-
return run(["fetch", "--all", "--quiet"]);
|
|
116
|
-
}
|
|
117
|
-
async function checkoutBranch2(branch) {
|
|
118
|
-
return run(["checkout", branch]);
|
|
119
|
-
}
|
|
120
|
-
async function createBranch(branch, from) {
|
|
121
|
-
const args = from ? ["checkout", "-b", branch, from] : ["checkout", "-b", branch];
|
|
122
|
-
return run(args);
|
|
123
|
-
}
|
|
124
|
-
async function resetHard(ref) {
|
|
125
|
-
return run(["reset", "--hard", ref]);
|
|
126
|
-
}
|
|
127
|
-
async function updateLocalBranch(branch, target) {
|
|
128
|
-
const current = await getCurrentBranch();
|
|
129
|
-
if (current === branch) {
|
|
130
|
-
return resetHard(target);
|
|
131
|
-
}
|
|
132
|
-
return run(["branch", "-f", branch, target]);
|
|
133
|
-
}
|
|
134
|
-
async function pushSetUpstream(remote, branch) {
|
|
135
|
-
return run(["push", "-u", remote, branch]);
|
|
136
|
-
}
|
|
137
|
-
async function rebase(branch) {
|
|
138
|
-
return run(["rebase", branch]);
|
|
139
|
-
}
|
|
140
|
-
async function getUpstreamRef() {
|
|
141
|
-
const { exitCode, stdout } = await run([
|
|
142
|
-
"rev-parse",
|
|
143
|
-
"--abbrev-ref",
|
|
144
|
-
"--symbolic-full-name",
|
|
145
|
-
"@{u}"
|
|
146
|
-
]);
|
|
147
|
-
if (exitCode !== 0)
|
|
148
|
-
return null;
|
|
149
|
-
return stdout.trim() || null;
|
|
150
|
-
}
|
|
151
|
-
async function unsetUpstream() {
|
|
152
|
-
return run(["branch", "--unset-upstream"]);
|
|
153
|
-
}
|
|
154
|
-
async function rebaseOnto(newBase, oldBase) {
|
|
155
|
-
return run(["rebase", "--onto", newBase, oldBase]);
|
|
156
|
-
}
|
|
157
|
-
async function getMergeBase(ref1, ref2) {
|
|
158
|
-
const { exitCode, stdout } = await run(["merge-base", ref1, ref2]);
|
|
159
|
-
if (exitCode !== 0)
|
|
160
|
-
return null;
|
|
161
|
-
return stdout.trim() || null;
|
|
162
|
-
}
|
|
163
|
-
async function getCommitHash(ref) {
|
|
164
|
-
const { exitCode, stdout } = await run(["rev-parse", ref]);
|
|
165
|
-
if (exitCode !== 0)
|
|
166
|
-
return null;
|
|
167
|
-
return stdout.trim() || null;
|
|
168
|
-
}
|
|
169
|
-
async function determineRebaseStrategy(currentBranch, syncRef) {
|
|
170
|
-
const upstreamRef = await getUpstreamRef();
|
|
171
|
-
if (!upstreamRef) {
|
|
172
|
-
return { strategy: "plain" };
|
|
173
|
-
}
|
|
174
|
-
const upstreamHash = await getCommitHash(upstreamRef);
|
|
175
|
-
if (!upstreamHash) {
|
|
176
|
-
return { strategy: "plain" };
|
|
177
|
-
}
|
|
178
|
-
const slashIdx = upstreamRef.indexOf("/");
|
|
179
|
-
const upstreamBranchName = slashIdx !== -1 ? upstreamRef.slice(slashIdx + 1) : upstreamRef;
|
|
180
|
-
if (upstreamBranchName === currentBranch) {
|
|
181
|
-
return { strategy: "plain" };
|
|
182
|
-
}
|
|
183
|
-
const [forkFromUpstream, forkFromSync] = await Promise.all([
|
|
184
|
-
getMergeBase("HEAD", upstreamRef),
|
|
185
|
-
getMergeBase("HEAD", syncRef)
|
|
186
|
-
]);
|
|
187
|
-
if (forkFromUpstream && forkFromSync && forkFromUpstream === forkFromSync) {
|
|
188
|
-
return { strategy: "plain" };
|
|
189
|
-
}
|
|
190
|
-
if (forkFromUpstream) {
|
|
191
|
-
return { strategy: "onto", ontoOldBase: forkFromUpstream };
|
|
192
|
-
}
|
|
193
|
-
return { strategy: "plain" };
|
|
194
|
-
}
|
|
195
|
-
async function getStagedDiff() {
|
|
196
|
-
const { stdout } = await run(["diff", "--cached"]);
|
|
197
|
-
return stdout;
|
|
198
|
-
}
|
|
199
|
-
async function getStagedFiles() {
|
|
200
|
-
const { exitCode, stdout } = await run(["diff", "--cached", "--name-only"]);
|
|
201
|
-
if (exitCode !== 0)
|
|
202
|
-
return [];
|
|
203
|
-
return stdout.trim().split(`
|
|
204
|
-
`).filter(Boolean);
|
|
205
|
-
}
|
|
206
|
-
async function getChangedFiles() {
|
|
207
|
-
const { exitCode, stdout } = await run(["status", "--porcelain"]);
|
|
208
|
-
if (exitCode !== 0)
|
|
209
|
-
return [];
|
|
210
|
-
return stdout.trimEnd().split(`
|
|
211
|
-
`).filter(Boolean).map((l) => {
|
|
212
|
-
const line = l.replace(/\r$/, "");
|
|
213
|
-
const match = line.match(/^..\s+(.*)/);
|
|
214
|
-
if (!match)
|
|
215
|
-
return "";
|
|
216
|
-
const file = match[1];
|
|
217
|
-
const renameIdx = file.indexOf(" -> ");
|
|
218
|
-
return renameIdx !== -1 ? file.slice(renameIdx + 4) : file;
|
|
219
|
-
}).filter(Boolean);
|
|
220
|
-
}
|
|
221
|
-
async function getDivergence(branch, base) {
|
|
222
|
-
const { exitCode, stdout } = await run([
|
|
223
|
-
"rev-list",
|
|
224
|
-
"--left-right",
|
|
225
|
-
"--count",
|
|
226
|
-
`${base}...${branch}`
|
|
227
|
-
]);
|
|
228
|
-
if (exitCode !== 0)
|
|
229
|
-
return { ahead: 0, behind: 0 };
|
|
230
|
-
const parts = stdout.trim().split(/\s+/);
|
|
231
|
-
return {
|
|
232
|
-
behind: Number.parseInt(parts[0] ?? "0", 10),
|
|
233
|
-
ahead: Number.parseInt(parts[1] ?? "0", 10)
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
async function getMergedBranches(base) {
|
|
237
|
-
const { exitCode, stdout } = await run(["branch", "--merged", base]);
|
|
238
|
-
if (exitCode !== 0)
|
|
239
|
-
return [];
|
|
240
|
-
return stdout.trim().split(`
|
|
241
|
-
`).map((b) => b.replace(/^\*?\s+/, "").trim()).filter(Boolean);
|
|
242
|
-
}
|
|
243
|
-
async function getGoneBranches() {
|
|
244
|
-
const { exitCode, stdout } = await run(["branch", "-vv"]);
|
|
245
|
-
if (exitCode !== 0)
|
|
246
|
-
return [];
|
|
247
|
-
return stdout.trimEnd().split(`
|
|
248
|
-
`).filter((line) => line.includes(": gone]")).map((line) => line.replace(/^\*?\s+/, "").split(/\s+/)[0]).filter(Boolean);
|
|
249
|
-
}
|
|
250
|
-
async function deleteBranch(branch) {
|
|
251
|
-
return run(["branch", "-d", branch]);
|
|
252
|
-
}
|
|
253
|
-
async function forceDeleteBranch(branch) {
|
|
254
|
-
return run(["branch", "-D", branch]);
|
|
255
|
-
}
|
|
256
|
-
async function renameBranch(oldName, newName) {
|
|
257
|
-
return run(["branch", "-m", oldName, newName]);
|
|
258
|
-
}
|
|
259
|
-
async function hasLocalWork(remote, branch) {
|
|
260
|
-
const uncommitted = await hasUncommittedChanges();
|
|
261
|
-
const trackingRef = `${remote}/${branch}`;
|
|
262
|
-
const { exitCode, stdout } = await run(["rev-list", "--count", `${trackingRef}..${branch}`]);
|
|
263
|
-
const unpushedCommits = exitCode === 0 ? Number.parseInt(stdout.trim(), 10) || 0 : 0;
|
|
264
|
-
return { uncommitted, unpushedCommits };
|
|
265
|
-
}
|
|
266
|
-
async function deleteRemoteBranch(remote, branch) {
|
|
267
|
-
return run(["push", remote, "--delete", branch]);
|
|
268
|
-
}
|
|
269
|
-
async function mergeSquash(branch) {
|
|
270
|
-
return run(["merge", "--squash", branch]);
|
|
271
|
-
}
|
|
272
|
-
async function pushBranch(remote, branch) {
|
|
273
|
-
return run(["push", remote, branch]);
|
|
274
|
-
}
|
|
275
|
-
async function pruneRemote(remote) {
|
|
276
|
-
return run(["remote", "prune", remote]);
|
|
277
|
-
}
|
|
278
|
-
async function commitWithMessage(message) {
|
|
279
|
-
return run(["commit", "-m", message]);
|
|
280
|
-
}
|
|
281
|
-
async function getLogDiff(base, head) {
|
|
282
|
-
const { stdout } = await run(["diff", `${base}...${head}`]);
|
|
283
|
-
return stdout;
|
|
284
|
-
}
|
|
285
|
-
async function getLog(base, head) {
|
|
286
|
-
const { exitCode, stdout } = await run(["log", `${base}..${head}`, "--oneline"]);
|
|
287
|
-
if (exitCode !== 0)
|
|
288
|
-
return [];
|
|
289
|
-
return stdout.trim().split(`
|
|
290
|
-
`).filter(Boolean);
|
|
291
|
-
}
|
|
292
|
-
async function pullBranch(remote, branch) {
|
|
293
|
-
return run(["pull", remote, branch]);
|
|
294
|
-
}
|
|
295
|
-
async function stageFiles(files) {
|
|
296
|
-
return run(["add", "--", ...files]);
|
|
297
|
-
}
|
|
298
|
-
async function unstageFiles(files) {
|
|
299
|
-
return run(["reset", "HEAD", "--", ...files]);
|
|
300
|
-
}
|
|
301
|
-
async function stageAll() {
|
|
302
|
-
return run(["add", "-A"]);
|
|
303
|
-
}
|
|
304
|
-
async function getFullDiffForFiles(files) {
|
|
305
|
-
const [unstaged, staged, untracked] = await Promise.all([
|
|
306
|
-
run(["diff", "--", ...files]),
|
|
307
|
-
run(["diff", "--cached", "--", ...files]),
|
|
308
|
-
getUntrackedFiles()
|
|
309
|
-
]);
|
|
310
|
-
const parts = [staged.stdout, unstaged.stdout].filter(Boolean);
|
|
311
|
-
const untrackedSet = new Set(untracked);
|
|
312
|
-
const MAX_FILE_CONTENT = 2000;
|
|
313
|
-
for (const file of files) {
|
|
314
|
-
if (untrackedSet.has(file)) {
|
|
315
|
-
try {
|
|
316
|
-
const content = readFileSync2(join2(process.cwd(), file), "utf-8");
|
|
317
|
-
const truncated = content.length > MAX_FILE_CONTENT ? `${content.slice(0, MAX_FILE_CONTENT)}
|
|
318
|
-
... (truncated)` : content;
|
|
319
|
-
const lines = truncated.split(`
|
|
320
|
-
`).map((l) => `+${l}`);
|
|
321
|
-
parts.push(`diff --git a/${file} b/${file}
|
|
322
|
-
new file
|
|
323
|
-
--- /dev/null
|
|
324
|
-
+++ b/${file}
|
|
325
|
-
${lines.join(`
|
|
326
|
-
`)}`);
|
|
327
|
-
} catch {}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return parts.join(`
|
|
331
|
-
`);
|
|
332
|
-
}
|
|
333
|
-
async function getUntrackedFiles() {
|
|
334
|
-
const { exitCode, stdout } = await run(["ls-files", "--others", "--exclude-standard"]);
|
|
335
|
-
if (exitCode !== 0)
|
|
336
|
-
return [];
|
|
337
|
-
return stdout.trim().split(`
|
|
338
|
-
`).filter(Boolean);
|
|
339
|
-
}
|
|
340
|
-
async function getFileStatus() {
|
|
341
|
-
const { exitCode, stdout } = await run(["status", "--porcelain"]);
|
|
342
|
-
if (exitCode !== 0)
|
|
343
|
-
return { staged: [], modified: [], untracked: [] };
|
|
344
|
-
const result = { staged: [], modified: [], untracked: [] };
|
|
345
|
-
const STATUS_LABELS = {
|
|
346
|
-
A: "new file",
|
|
347
|
-
M: "modified",
|
|
348
|
-
D: "deleted",
|
|
349
|
-
R: "renamed",
|
|
350
|
-
C: "copied",
|
|
351
|
-
T: "type changed"
|
|
352
|
-
};
|
|
353
|
-
for (const raw of stdout.trimEnd().split(`
|
|
354
|
-
`).filter(Boolean)) {
|
|
355
|
-
const line = raw.replace(/\r$/, "");
|
|
356
|
-
const indexStatus = line[0];
|
|
357
|
-
const workTreeStatus = line[1];
|
|
358
|
-
const pathPart = line.slice(3);
|
|
359
|
-
const renameIdx = pathPart.indexOf(" -> ");
|
|
360
|
-
const file = renameIdx !== -1 ? pathPart.slice(renameIdx + 4) : pathPart;
|
|
361
|
-
if (indexStatus === "?" && workTreeStatus === "?") {
|
|
362
|
-
result.untracked.push(file);
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
|
|
366
|
-
result.staged.push({ file, status: STATUS_LABELS[indexStatus] ?? indexStatus });
|
|
367
|
-
}
|
|
368
|
-
if (workTreeStatus && workTreeStatus !== " " && workTreeStatus !== "?") {
|
|
369
|
-
result.modified.push({ file, status: STATUS_LABELS[workTreeStatus] ?? workTreeStatus });
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return result;
|
|
373
|
-
}
|
|
374
|
-
async function getLogGraph(options) {
|
|
375
|
-
const count = options?.count ?? 20;
|
|
376
|
-
const args = [
|
|
377
|
-
"log",
|
|
378
|
-
"--oneline",
|
|
379
|
-
"--graph",
|
|
380
|
-
"--decorate",
|
|
381
|
-
`--max-count=${count}`,
|
|
382
|
-
"--color=never"
|
|
383
|
-
];
|
|
384
|
-
if (options?.all) {
|
|
385
|
-
args.push("--all");
|
|
386
|
-
}
|
|
387
|
-
if (options?.branch) {
|
|
388
|
-
args.push(options.branch);
|
|
389
|
-
}
|
|
390
|
-
const { exitCode, stdout } = await run(args);
|
|
391
|
-
if (exitCode !== 0)
|
|
392
|
-
return [];
|
|
393
|
-
return stdout.trimEnd().split(`
|
|
394
|
-
`);
|
|
395
|
-
}
|
|
396
|
-
async function getLogEntries(options) {
|
|
397
|
-
const count = options?.count ?? 20;
|
|
398
|
-
const args = [
|
|
399
|
-
"log",
|
|
400
|
-
`--format=%h||%s||%D`,
|
|
401
|
-
`--max-count=${count}`
|
|
402
|
-
];
|
|
403
|
-
if (options?.all) {
|
|
404
|
-
args.push("--all");
|
|
405
|
-
}
|
|
406
|
-
if (options?.branch) {
|
|
407
|
-
args.push(options.branch);
|
|
408
|
-
}
|
|
409
|
-
const { exitCode, stdout } = await run(args);
|
|
410
|
-
if (exitCode !== 0)
|
|
411
|
-
return [];
|
|
412
|
-
return stdout.trimEnd().split(`
|
|
413
|
-
`).filter(Boolean).map((line) => {
|
|
414
|
-
const [hash = "", subject = "", refs = ""] = line.split("||");
|
|
415
|
-
return { hash: hash.trim(), subject: subject.trim(), refs: refs.trim() };
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
async function getLocalBranches() {
|
|
419
|
-
const { exitCode, stdout } = await run(["branch", "-vv", "--no-color"]);
|
|
420
|
-
if (exitCode !== 0)
|
|
421
|
-
return [];
|
|
422
|
-
return stdout.trimEnd().split(`
|
|
423
|
-
`).filter(Boolean).map((line) => {
|
|
424
|
-
const isCurrent = line.startsWith("*");
|
|
425
|
-
const trimmed = line.slice(2);
|
|
426
|
-
const nameMatch = trimmed.match(/^(\S+)/);
|
|
427
|
-
const name = nameMatch?.[1] ?? "";
|
|
428
|
-
const upstreamMatch = trimmed.match(/\[([^\]]+)\]/);
|
|
429
|
-
let upstream = null;
|
|
430
|
-
let gone = false;
|
|
431
|
-
if (upstreamMatch) {
|
|
432
|
-
const bracketContent = upstreamMatch[1];
|
|
433
|
-
gone = bracketContent.includes(": gone");
|
|
434
|
-
upstream = bracketContent.split(":")[0].trim();
|
|
435
|
-
}
|
|
436
|
-
return { name, isCurrent, upstream, gone };
|
|
437
|
-
}).filter((b) => b.name.length > 0);
|
|
438
|
-
}
|
|
439
|
-
async function getRemoteBranches() {
|
|
440
|
-
const { exitCode, stdout } = await run(["branch", "-r", "--no-color"]);
|
|
441
|
-
if (exitCode !== 0)
|
|
442
|
-
return [];
|
|
443
|
-
return stdout.trimEnd().split(`
|
|
444
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0 && !line.includes(" -> "));
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// src/utils/logger.ts
|
|
448
|
-
import { LogEngine, LogMode } from "@wgtechlabs/log-engine";
|
|
449
|
-
import pc from "picocolors";
|
|
450
|
-
LogEngine.configure({
|
|
451
|
-
mode: LogMode.INFO,
|
|
452
|
-
format: {
|
|
453
|
-
includeIsoTimestamp: false,
|
|
454
|
-
includeLocalTime: true,
|
|
455
|
-
includeEmoji: true
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
function success(msg) {
|
|
459
|
-
LogEngine.log(msg);
|
|
460
|
-
}
|
|
461
|
-
function error(msg) {
|
|
462
|
-
LogEngine.error(msg);
|
|
463
|
-
}
|
|
464
|
-
function warn(msg) {
|
|
465
|
-
LogEngine.warn(msg);
|
|
466
|
-
}
|
|
467
|
-
function info(msg) {
|
|
468
|
-
LogEngine.info(msg);
|
|
469
|
-
}
|
|
470
|
-
function heading(msg) {
|
|
471
|
-
console.log(`
|
|
472
|
-
${pc.bold(msg)}`);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// src/utils/workflow.ts
|
|
476
|
-
var WORKFLOW_DESCRIPTIONS = {
|
|
477
|
-
"clean-flow": "Clean Flow — main + dev, squash features into dev, merge dev into main",
|
|
478
|
-
"github-flow": "GitHub Flow — main + feature branches, squash/merge into main",
|
|
479
|
-
"git-flow": "Git Flow — main + develop + release + hotfix branches"
|
|
480
|
-
};
|
|
481
|
-
function getBaseBranch(config) {
|
|
482
|
-
switch (config.workflow) {
|
|
483
|
-
case "clean-flow":
|
|
484
|
-
case "git-flow":
|
|
485
|
-
return config.devBranch ?? "dev";
|
|
486
|
-
case "github-flow":
|
|
487
|
-
return config.mainBranch;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function hasDevBranch(workflow) {
|
|
491
|
-
return workflow === "clean-flow" || workflow === "git-flow";
|
|
492
|
-
}
|
|
493
|
-
function getSyncSource(config) {
|
|
494
|
-
const { workflow, role, mainBranch, origin, upstream } = config;
|
|
495
|
-
const devBranch = config.devBranch ?? "dev";
|
|
496
|
-
switch (workflow) {
|
|
497
|
-
case "clean-flow":
|
|
498
|
-
if (role === "contributor") {
|
|
499
|
-
return { remote: upstream, ref: `${upstream}/${devBranch}`, strategy: "pull" };
|
|
500
|
-
}
|
|
501
|
-
return { remote: origin, ref: `${origin}/${devBranch}`, strategy: "pull" };
|
|
502
|
-
case "github-flow":
|
|
503
|
-
if (role === "contributor") {
|
|
504
|
-
return { remote: upstream, ref: `${upstream}/${mainBranch}`, strategy: "pull" };
|
|
505
|
-
}
|
|
506
|
-
return { remote: origin, ref: `${origin}/${mainBranch}`, strategy: "pull" };
|
|
507
|
-
case "git-flow":
|
|
508
|
-
if (role === "contributor") {
|
|
509
|
-
return { remote: upstream, ref: `${upstream}/${devBranch}`, strategy: "pull" };
|
|
510
|
-
}
|
|
511
|
-
return { remote: origin, ref: `${origin}/${devBranch}`, strategy: "pull" };
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function getProtectedBranches(config) {
|
|
515
|
-
const branches = [config.mainBranch];
|
|
516
|
-
if (hasDevBranch(config.workflow) && config.devBranch) {
|
|
517
|
-
branches.push(config.devBranch);
|
|
518
|
-
}
|
|
519
|
-
return branches;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// src/commands/branch.ts
|
|
523
|
-
var branch_default = defineCommand({
|
|
524
|
-
meta: {
|
|
525
|
-
name: "branch",
|
|
526
|
-
description: "List branches with workflow-aware labels and status"
|
|
527
|
-
},
|
|
528
|
-
args: {
|
|
529
|
-
all: {
|
|
530
|
-
type: "boolean",
|
|
531
|
-
alias: "a",
|
|
532
|
-
description: "Show both local and remote branches",
|
|
533
|
-
default: false
|
|
534
|
-
},
|
|
535
|
-
remote: {
|
|
536
|
-
type: "boolean",
|
|
537
|
-
alias: "r",
|
|
538
|
-
description: "Show only remote branches",
|
|
539
|
-
default: false
|
|
540
|
-
}
|
|
541
|
-
},
|
|
542
|
-
async run({ args }) {
|
|
543
|
-
if (!await isGitRepo()) {
|
|
544
|
-
error("Not inside a git repository.");
|
|
545
|
-
process.exit(1);
|
|
546
|
-
}
|
|
547
|
-
const config = readConfig();
|
|
548
|
-
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
549
|
-
const currentBranch = await getCurrentBranch();
|
|
550
|
-
const showRemoteOnly = args.remote;
|
|
551
|
-
const showAll = args.all;
|
|
552
|
-
heading("\uD83C\uDF3F branches");
|
|
553
|
-
console.log();
|
|
554
|
-
if (!showRemoteOnly) {
|
|
555
|
-
const localBranches = await getLocalBranches();
|
|
556
|
-
if (localBranches.length === 0) {
|
|
557
|
-
console.log(pc2.dim(" No local branches found."));
|
|
558
|
-
} else {
|
|
559
|
-
console.log(` ${pc2.bold("Local")}`);
|
|
560
|
-
console.log();
|
|
561
|
-
for (const branch of localBranches) {
|
|
562
|
-
const parts = [];
|
|
563
|
-
if (branch.isCurrent) {
|
|
564
|
-
parts.push(pc2.green("* "));
|
|
565
|
-
} else {
|
|
566
|
-
parts.push(" ");
|
|
567
|
-
}
|
|
568
|
-
const nameStr = colorBranchName(branch.name, protectedBranches, currentBranch);
|
|
569
|
-
parts.push(nameStr.padEnd(30));
|
|
570
|
-
if (branch.gone) {
|
|
571
|
-
parts.push(pc2.red(" ✗ remote gone"));
|
|
572
|
-
} else if (branch.upstream) {
|
|
573
|
-
parts.push(pc2.dim(` → ${branch.upstream}`));
|
|
574
|
-
} else {
|
|
575
|
-
parts.push(pc2.dim(" (no remote)"));
|
|
576
|
-
}
|
|
577
|
-
const labels = getBranchLabels(branch.name, protectedBranches, config);
|
|
578
|
-
if (labels.length > 0) {
|
|
579
|
-
parts.push(` ${labels.join(" ")}`);
|
|
580
|
-
}
|
|
581
|
-
console.log(` ${parts.join("")}`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (showRemoteOnly || showAll) {
|
|
586
|
-
const remoteBranches = await getRemoteBranches();
|
|
587
|
-
if (!showRemoteOnly) {
|
|
588
|
-
console.log();
|
|
589
|
-
}
|
|
590
|
-
if (remoteBranches.length === 0) {
|
|
591
|
-
console.log(pc2.dim(" No remote branches found."));
|
|
592
|
-
} else {
|
|
593
|
-
const grouped = groupByRemote(remoteBranches);
|
|
594
|
-
for (const [remote, branches] of Object.entries(grouped)) {
|
|
595
|
-
console.log(` ${pc2.bold(`Remote: ${remote}`)}`);
|
|
596
|
-
console.log();
|
|
597
|
-
for (const fullRef of branches) {
|
|
598
|
-
const branchName = fullRef.slice(remote.length + 1);
|
|
599
|
-
const nameStr = colorBranchName(branchName, protectedBranches, currentBranch);
|
|
600
|
-
const remotePrefix = pc2.dim(`${remote}/`);
|
|
601
|
-
console.log(` ${remotePrefix}${nameStr}`);
|
|
602
|
-
}
|
|
603
|
-
console.log();
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
const tips = [];
|
|
608
|
-
if (!showAll && !showRemoteOnly) {
|
|
609
|
-
tips.push(`Use ${pc2.bold("contrib branch -a")} to include remote branches`);
|
|
610
|
-
}
|
|
611
|
-
if (!showRemoteOnly) {
|
|
612
|
-
tips.push(`Use ${pc2.bold("contrib start")} to create a new feature branch`);
|
|
613
|
-
tips.push(`Use ${pc2.bold("contrib clean")} to remove merged/stale branches`);
|
|
614
|
-
}
|
|
615
|
-
if (tips.length > 0) {
|
|
616
|
-
console.log(` ${pc2.dim("\uD83D\uDCA1 Tip:")}`);
|
|
617
|
-
for (const tip of tips) {
|
|
618
|
-
console.log(` ${pc2.dim(tip)}`);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
console.log();
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
function colorBranchName(name, protectedBranches, currentBranch) {
|
|
625
|
-
if (name === currentBranch) {
|
|
626
|
-
return pc2.bold(pc2.green(name));
|
|
627
|
-
}
|
|
628
|
-
if (protectedBranches.includes(name)) {
|
|
629
|
-
return pc2.bold(pc2.red(name));
|
|
630
|
-
}
|
|
631
|
-
return name;
|
|
632
|
-
}
|
|
633
|
-
function getBranchLabels(name, protectedBranches, config) {
|
|
634
|
-
const labels = [];
|
|
635
|
-
if (protectedBranches.includes(name)) {
|
|
636
|
-
labels.push(pc2.dim(pc2.red("[protected]")));
|
|
637
|
-
}
|
|
638
|
-
if (config) {
|
|
639
|
-
if (name === config.mainBranch) {
|
|
640
|
-
labels.push(pc2.dim(pc2.cyan("[main]")));
|
|
641
|
-
}
|
|
642
|
-
if (config.devBranch && name === config.devBranch) {
|
|
643
|
-
labels.push(pc2.dim(pc2.cyan("[dev]")));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return labels;
|
|
647
|
-
}
|
|
648
|
-
function groupByRemote(branches) {
|
|
649
|
-
const grouped = {};
|
|
650
|
-
for (const ref of branches) {
|
|
651
|
-
const slashIdx = ref.indexOf("/");
|
|
652
|
-
const remote = slashIdx !== -1 ? ref.slice(0, slashIdx) : "unknown";
|
|
653
|
-
if (!grouped[remote]) {
|
|
654
|
-
grouped[remote] = [];
|
|
655
|
-
}
|
|
656
|
-
grouped[remote].push(ref);
|
|
657
|
-
}
|
|
658
|
-
return grouped;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// src/commands/clean.ts
|
|
662
|
-
import { defineCommand as defineCommand2 } from "citty";
|
|
663
|
-
import pc5 from "picocolors";
|
|
664
|
-
|
|
665
|
-
// src/utils/branch.ts
|
|
666
|
-
var DEFAULT_PREFIXES = ["feature", "fix", "docs", "chore", "test", "refactor"];
|
|
667
|
-
function hasPrefix(branchName, prefixes = DEFAULT_PREFIXES) {
|
|
668
|
-
return prefixes.some((p) => branchName.startsWith(`${p}/`));
|
|
669
|
-
}
|
|
670
|
-
function formatBranchName(prefix, name) {
|
|
671
|
-
const sanitized = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
672
|
-
return `${prefix}/${sanitized}`;
|
|
673
|
-
}
|
|
674
|
-
function isValidBranchName(name) {
|
|
675
|
-
return /^[a-zA-Z0-9._/-]+$/.test(name) && !name.startsWith("/") && !name.endsWith("/");
|
|
676
|
-
}
|
|
677
|
-
function looksLikeNaturalLanguage(input) {
|
|
678
|
-
return input.includes(" ") && !input.includes("/");
|
|
679
|
-
}
|
|
680
|
-
|
|
681
83
|
// src/utils/confirm.ts
|
|
682
84
|
import * as clack from "@clack/prompts";
|
|
683
|
-
import
|
|
85
|
+
import pc from "picocolors";
|
|
684
86
|
function handleCancel(value) {
|
|
685
87
|
if (clack.isCancel(value)) {
|
|
686
88
|
clack.cancel("Cancelled.");
|
|
@@ -711,7 +113,7 @@ async function inputPrompt(message, defaultValue) {
|
|
|
711
113
|
}
|
|
712
114
|
async function multiSelectPrompt(message, choices) {
|
|
713
115
|
const result = await clack.multiselect({
|
|
714
|
-
message: `${message} ${
|
|
116
|
+
message: `${message} ${pc.dim("(space to toggle, enter to confirm)")}`,
|
|
715
117
|
options: choices.map((choice) => ({ value: choice, label: choice })),
|
|
716
118
|
required: false
|
|
717
119
|
});
|
|
@@ -1030,12 +432,12 @@ ${diffs.slice(0, 4000)}`;
|
|
|
1030
432
|
}
|
|
1031
433
|
|
|
1032
434
|
// src/utils/gh.ts
|
|
1033
|
-
import { execFile as
|
|
1034
|
-
function
|
|
435
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
436
|
+
function run(args) {
|
|
1035
437
|
return new Promise((resolve) => {
|
|
1036
|
-
|
|
438
|
+
execFileCb("gh", args, (error, stdout, stderr) => {
|
|
1037
439
|
resolve({
|
|
1038
|
-
exitCode:
|
|
440
|
+
exitCode: error ? error.code === "ENOENT" ? 127 : error.status ?? 1 : 0,
|
|
1039
441
|
stdout: stdout ?? "",
|
|
1040
442
|
stderr: stderr ?? ""
|
|
1041
443
|
});
|
|
@@ -1044,7 +446,7 @@ function run2(args) {
|
|
|
1044
446
|
}
|
|
1045
447
|
async function checkGhInstalled() {
|
|
1046
448
|
try {
|
|
1047
|
-
const { exitCode } = await
|
|
449
|
+
const { exitCode } = await run(["--version"]);
|
|
1048
450
|
return exitCode === 0;
|
|
1049
451
|
} catch {
|
|
1050
452
|
return false;
|
|
@@ -1052,7 +454,7 @@ async function checkGhInstalled() {
|
|
|
1052
454
|
}
|
|
1053
455
|
async function checkGhAuth() {
|
|
1054
456
|
try {
|
|
1055
|
-
const { exitCode } = await
|
|
457
|
+
const { exitCode } = await run(["auth", "status"]);
|
|
1056
458
|
return exitCode === 0;
|
|
1057
459
|
} catch {
|
|
1058
460
|
return false;
|
|
@@ -1062,7 +464,7 @@ var SAFE_SLUG = /^[\w.-]+$/;
|
|
|
1062
464
|
async function checkRepoPermissions(owner, repo) {
|
|
1063
465
|
if (!SAFE_SLUG.test(owner) || !SAFE_SLUG.test(repo))
|
|
1064
466
|
return null;
|
|
1065
|
-
const { exitCode, stdout } = await
|
|
467
|
+
const { exitCode, stdout } = await run(["api", `repos/${owner}/${repo}`, "--jq", ".permissions"]);
|
|
1066
468
|
if (exitCode !== 0)
|
|
1067
469
|
return null;
|
|
1068
470
|
try {
|
|
@@ -1072,7 +474,7 @@ async function checkRepoPermissions(owner, repo) {
|
|
|
1072
474
|
}
|
|
1073
475
|
}
|
|
1074
476
|
async function isRepoFork() {
|
|
1075
|
-
const { exitCode, stdout } = await
|
|
477
|
+
const { exitCode, stdout } = await run(["repo", "view", "--json", "isFork", "-q", ".isFork"]);
|
|
1076
478
|
if (exitCode !== 0)
|
|
1077
479
|
return null;
|
|
1078
480
|
const val = stdout.trim();
|
|
@@ -1083,7 +485,7 @@ async function isRepoFork() {
|
|
|
1083
485
|
return null;
|
|
1084
486
|
}
|
|
1085
487
|
async function getCurrentRepoInfo() {
|
|
1086
|
-
const { exitCode, stdout } = await
|
|
488
|
+
const { exitCode, stdout } = await run([
|
|
1087
489
|
"repo",
|
|
1088
490
|
"view",
|
|
1089
491
|
"--json",
|
|
@@ -1114,16 +516,16 @@ async function createPR(options) {
|
|
|
1114
516
|
];
|
|
1115
517
|
if (options.draft)
|
|
1116
518
|
args.push("--draft");
|
|
1117
|
-
return
|
|
519
|
+
return run(args);
|
|
1118
520
|
}
|
|
1119
521
|
async function createPRFill(base, draft) {
|
|
1120
522
|
const args = ["pr", "create", "--base", base, "--fill"];
|
|
1121
523
|
if (draft)
|
|
1122
524
|
args.push("--draft");
|
|
1123
|
-
return
|
|
525
|
+
return run(args);
|
|
1124
526
|
}
|
|
1125
527
|
async function getPRForBranch(headBranch) {
|
|
1126
|
-
const { exitCode, stdout } = await
|
|
528
|
+
const { exitCode, stdout } = await run([
|
|
1127
529
|
"pr",
|
|
1128
530
|
"list",
|
|
1129
531
|
"--head",
|
|
@@ -1144,31 +546,367 @@ async function getPRForBranch(headBranch) {
|
|
|
1144
546
|
return null;
|
|
1145
547
|
}
|
|
1146
548
|
}
|
|
1147
|
-
async function getMergedPRForBranch(headBranch) {
|
|
1148
|
-
const { exitCode, stdout } = await
|
|
1149
|
-
"pr",
|
|
1150
|
-
"list",
|
|
1151
|
-
"--head",
|
|
1152
|
-
headBranch,
|
|
1153
|
-
"--state",
|
|
1154
|
-
"merged",
|
|
1155
|
-
"--json",
|
|
1156
|
-
"number,url,title,state",
|
|
1157
|
-
"--limit",
|
|
1158
|
-
"1"
|
|
1159
|
-
]);
|
|
549
|
+
async function getMergedPRForBranch(headBranch) {
|
|
550
|
+
const { exitCode, stdout } = await run([
|
|
551
|
+
"pr",
|
|
552
|
+
"list",
|
|
553
|
+
"--head",
|
|
554
|
+
headBranch,
|
|
555
|
+
"--state",
|
|
556
|
+
"merged",
|
|
557
|
+
"--json",
|
|
558
|
+
"number,url,title,state",
|
|
559
|
+
"--limit",
|
|
560
|
+
"1"
|
|
561
|
+
]);
|
|
562
|
+
if (exitCode !== 0)
|
|
563
|
+
return null;
|
|
564
|
+
try {
|
|
565
|
+
const prs = JSON.parse(stdout.trim());
|
|
566
|
+
return prs.length > 0 ? prs[0] : null;
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/utils/git.ts
|
|
573
|
+
import { execFile as execFileCb2 } from "node:child_process";
|
|
574
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
575
|
+
import { join as join2 } from "node:path";
|
|
576
|
+
function run2(args) {
|
|
577
|
+
return new Promise((resolve) => {
|
|
578
|
+
execFileCb2("git", args, (error, stdout, stderr) => {
|
|
579
|
+
resolve({
|
|
580
|
+
exitCode: error ? error.code === "ENOENT" ? 127 : error.status ?? 1 : 0,
|
|
581
|
+
stdout: stdout ?? "",
|
|
582
|
+
stderr: stderr ?? ""
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async function isGitRepo() {
|
|
588
|
+
const { exitCode } = await run2(["rev-parse", "--is-inside-work-tree"]);
|
|
589
|
+
return exitCode === 0;
|
|
590
|
+
}
|
|
591
|
+
async function getCurrentBranch() {
|
|
592
|
+
const { exitCode, stdout } = await run2(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
593
|
+
if (exitCode !== 0)
|
|
594
|
+
return null;
|
|
595
|
+
return stdout.trim() || null;
|
|
596
|
+
}
|
|
597
|
+
async function getRemotes() {
|
|
598
|
+
const { exitCode, stdout } = await run2(["remote"]);
|
|
599
|
+
if (exitCode !== 0)
|
|
600
|
+
return [];
|
|
601
|
+
return stdout.trim().split(`
|
|
602
|
+
`).map((r) => r.trim()).filter(Boolean);
|
|
603
|
+
}
|
|
604
|
+
async function getRemoteUrl(remote) {
|
|
605
|
+
const { exitCode, stdout } = await run2(["remote", "get-url", remote]);
|
|
606
|
+
if (exitCode !== 0)
|
|
607
|
+
return null;
|
|
608
|
+
return stdout.trim() || null;
|
|
609
|
+
}
|
|
610
|
+
async function hasUncommittedChanges() {
|
|
611
|
+
const { exitCode, stdout } = await run2(["status", "--porcelain"]);
|
|
612
|
+
if (exitCode !== 0)
|
|
613
|
+
return false;
|
|
614
|
+
return stdout.trim().length > 0;
|
|
615
|
+
}
|
|
616
|
+
async function fetchRemote(remote) {
|
|
617
|
+
return run2(["fetch", remote]);
|
|
618
|
+
}
|
|
619
|
+
async function fetchAll() {
|
|
620
|
+
return run2(["fetch", "--all", "--quiet"]);
|
|
621
|
+
}
|
|
622
|
+
async function checkoutBranch2(branch) {
|
|
623
|
+
return run2(["checkout", branch]);
|
|
624
|
+
}
|
|
625
|
+
async function createBranch(branch, from) {
|
|
626
|
+
const args = from ? ["checkout", "-b", branch, from] : ["checkout", "-b", branch];
|
|
627
|
+
return run2(args);
|
|
628
|
+
}
|
|
629
|
+
async function resetHard(ref) {
|
|
630
|
+
return run2(["reset", "--hard", ref]);
|
|
631
|
+
}
|
|
632
|
+
async function updateLocalBranch(branch, target) {
|
|
633
|
+
const current = await getCurrentBranch();
|
|
634
|
+
if (current === branch) {
|
|
635
|
+
return resetHard(target);
|
|
636
|
+
}
|
|
637
|
+
return run2(["branch", "-f", branch, target]);
|
|
638
|
+
}
|
|
639
|
+
async function pushSetUpstream(remote, branch) {
|
|
640
|
+
return run2(["push", "-u", remote, branch]);
|
|
641
|
+
}
|
|
642
|
+
async function rebase(branch) {
|
|
643
|
+
return run2(["rebase", branch]);
|
|
644
|
+
}
|
|
645
|
+
async function getUpstreamRef() {
|
|
646
|
+
const { exitCode, stdout } = await run2([
|
|
647
|
+
"rev-parse",
|
|
648
|
+
"--abbrev-ref",
|
|
649
|
+
"--symbolic-full-name",
|
|
650
|
+
"@{u}"
|
|
651
|
+
]);
|
|
652
|
+
if (exitCode !== 0)
|
|
653
|
+
return null;
|
|
654
|
+
return stdout.trim() || null;
|
|
655
|
+
}
|
|
656
|
+
async function unsetUpstream() {
|
|
657
|
+
return run2(["branch", "--unset-upstream"]);
|
|
658
|
+
}
|
|
659
|
+
async function rebaseOnto(newBase, oldBase) {
|
|
660
|
+
return run2(["rebase", "--onto", newBase, oldBase]);
|
|
661
|
+
}
|
|
662
|
+
async function getMergeBase(ref1, ref2) {
|
|
663
|
+
const { exitCode, stdout } = await run2(["merge-base", ref1, ref2]);
|
|
664
|
+
if (exitCode !== 0)
|
|
665
|
+
return null;
|
|
666
|
+
return stdout.trim() || null;
|
|
667
|
+
}
|
|
668
|
+
async function getCommitHash(ref) {
|
|
669
|
+
const { exitCode, stdout } = await run2(["rev-parse", ref]);
|
|
670
|
+
if (exitCode !== 0)
|
|
671
|
+
return null;
|
|
672
|
+
return stdout.trim() || null;
|
|
673
|
+
}
|
|
674
|
+
async function determineRebaseStrategy(currentBranch, syncRef) {
|
|
675
|
+
const upstreamRef = await getUpstreamRef();
|
|
676
|
+
if (!upstreamRef) {
|
|
677
|
+
return { strategy: "plain" };
|
|
678
|
+
}
|
|
679
|
+
const upstreamHash = await getCommitHash(upstreamRef);
|
|
680
|
+
if (!upstreamHash) {
|
|
681
|
+
return { strategy: "plain" };
|
|
682
|
+
}
|
|
683
|
+
const slashIdx = upstreamRef.indexOf("/");
|
|
684
|
+
const upstreamBranchName = slashIdx !== -1 ? upstreamRef.slice(slashIdx + 1) : upstreamRef;
|
|
685
|
+
if (upstreamBranchName === currentBranch) {
|
|
686
|
+
return { strategy: "plain" };
|
|
687
|
+
}
|
|
688
|
+
const [forkFromUpstream, forkFromSync] = await Promise.all([
|
|
689
|
+
getMergeBase("HEAD", upstreamRef),
|
|
690
|
+
getMergeBase("HEAD", syncRef)
|
|
691
|
+
]);
|
|
692
|
+
if (forkFromUpstream && forkFromSync && forkFromUpstream === forkFromSync) {
|
|
693
|
+
return { strategy: "plain" };
|
|
694
|
+
}
|
|
695
|
+
if (forkFromUpstream) {
|
|
696
|
+
return { strategy: "onto", ontoOldBase: forkFromUpstream };
|
|
697
|
+
}
|
|
698
|
+
return { strategy: "plain" };
|
|
699
|
+
}
|
|
700
|
+
async function getStagedDiff() {
|
|
701
|
+
const { stdout } = await run2(["diff", "--cached"]);
|
|
702
|
+
return stdout;
|
|
703
|
+
}
|
|
704
|
+
async function getStagedFiles() {
|
|
705
|
+
const { exitCode, stdout } = await run2(["diff", "--cached", "--name-only"]);
|
|
706
|
+
if (exitCode !== 0)
|
|
707
|
+
return [];
|
|
708
|
+
return stdout.trim().split(`
|
|
709
|
+
`).filter(Boolean);
|
|
710
|
+
}
|
|
711
|
+
async function getChangedFiles() {
|
|
712
|
+
const { exitCode, stdout } = await run2(["status", "--porcelain"]);
|
|
713
|
+
if (exitCode !== 0)
|
|
714
|
+
return [];
|
|
715
|
+
return stdout.trimEnd().split(`
|
|
716
|
+
`).filter(Boolean).map((l) => {
|
|
717
|
+
const line = l.replace(/\r$/, "");
|
|
718
|
+
const match = line.match(/^..\s+(.*)/);
|
|
719
|
+
if (!match)
|
|
720
|
+
return "";
|
|
721
|
+
const file = match[1];
|
|
722
|
+
const renameIdx = file.indexOf(" -> ");
|
|
723
|
+
return renameIdx !== -1 ? file.slice(renameIdx + 4) : file;
|
|
724
|
+
}).filter(Boolean);
|
|
725
|
+
}
|
|
726
|
+
async function getDivergence(branch, base) {
|
|
727
|
+
const { exitCode, stdout } = await run2([
|
|
728
|
+
"rev-list",
|
|
729
|
+
"--left-right",
|
|
730
|
+
"--count",
|
|
731
|
+
`${base}...${branch}`
|
|
732
|
+
]);
|
|
733
|
+
if (exitCode !== 0)
|
|
734
|
+
return { ahead: 0, behind: 0 };
|
|
735
|
+
const parts = stdout.trim().split(/\s+/);
|
|
736
|
+
return {
|
|
737
|
+
behind: Number.parseInt(parts[0] ?? "0", 10),
|
|
738
|
+
ahead: Number.parseInt(parts[1] ?? "0", 10)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
async function getMergedBranches(base) {
|
|
742
|
+
const { exitCode, stdout } = await run2(["branch", "--merged", base]);
|
|
743
|
+
if (exitCode !== 0)
|
|
744
|
+
return [];
|
|
745
|
+
return stdout.trim().split(`
|
|
746
|
+
`).map((b) => b.replace(/^\*?\s+/, "").trim()).filter(Boolean);
|
|
747
|
+
}
|
|
748
|
+
async function getGoneBranches() {
|
|
749
|
+
const { exitCode, stdout } = await run2(["branch", "-vv"]);
|
|
750
|
+
if (exitCode !== 0)
|
|
751
|
+
return [];
|
|
752
|
+
return stdout.trimEnd().split(`
|
|
753
|
+
`).filter((line) => line.includes(": gone]")).map((line) => line.replace(/^\*?\s+/, "").split(/\s+/)[0]).filter(Boolean);
|
|
754
|
+
}
|
|
755
|
+
async function deleteBranch(branch) {
|
|
756
|
+
return run2(["branch", "-d", branch]);
|
|
757
|
+
}
|
|
758
|
+
async function forceDeleteBranch(branch) {
|
|
759
|
+
return run2(["branch", "-D", branch]);
|
|
760
|
+
}
|
|
761
|
+
async function renameBranch(oldName, newName) {
|
|
762
|
+
return run2(["branch", "-m", oldName, newName]);
|
|
763
|
+
}
|
|
764
|
+
async function hasLocalWork(remote, branch) {
|
|
765
|
+
const uncommitted = await hasUncommittedChanges();
|
|
766
|
+
const trackingRef = `${remote}/${branch}`;
|
|
767
|
+
const { exitCode, stdout } = await run2(["rev-list", "--count", `${trackingRef}..${branch}`]);
|
|
768
|
+
const unpushedCommits = exitCode === 0 ? Number.parseInt(stdout.trim(), 10) || 0 : 0;
|
|
769
|
+
return { uncommitted, unpushedCommits };
|
|
770
|
+
}
|
|
771
|
+
async function deleteRemoteBranch(remote, branch) {
|
|
772
|
+
return run2(["push", remote, "--delete", branch]);
|
|
773
|
+
}
|
|
774
|
+
async function mergeSquash(branch) {
|
|
775
|
+
return run2(["merge", "--squash", branch]);
|
|
776
|
+
}
|
|
777
|
+
async function pushBranch(remote, branch) {
|
|
778
|
+
return run2(["push", remote, branch]);
|
|
779
|
+
}
|
|
780
|
+
async function pruneRemote(remote) {
|
|
781
|
+
return run2(["remote", "prune", remote]);
|
|
782
|
+
}
|
|
783
|
+
async function commitWithMessage(message) {
|
|
784
|
+
return run2(["commit", "-m", message]);
|
|
785
|
+
}
|
|
786
|
+
async function getLogDiff(base, head) {
|
|
787
|
+
const { stdout } = await run2(["diff", `${base}...${head}`]);
|
|
788
|
+
return stdout;
|
|
789
|
+
}
|
|
790
|
+
async function getLog(base, head) {
|
|
791
|
+
const { exitCode, stdout } = await run2(["log", `${base}..${head}`, "--oneline"]);
|
|
792
|
+
if (exitCode !== 0)
|
|
793
|
+
return [];
|
|
794
|
+
return stdout.trim().split(`
|
|
795
|
+
`).filter(Boolean);
|
|
796
|
+
}
|
|
797
|
+
async function pullBranch(remote, branch) {
|
|
798
|
+
return run2(["pull", remote, branch]);
|
|
799
|
+
}
|
|
800
|
+
async function stageFiles(files) {
|
|
801
|
+
return run2(["add", "--", ...files]);
|
|
802
|
+
}
|
|
803
|
+
async function unstageFiles(files) {
|
|
804
|
+
return run2(["reset", "HEAD", "--", ...files]);
|
|
805
|
+
}
|
|
806
|
+
async function stageAll() {
|
|
807
|
+
return run2(["add", "-A"]);
|
|
808
|
+
}
|
|
809
|
+
async function getFullDiffForFiles(files) {
|
|
810
|
+
const [unstaged, staged, untracked] = await Promise.all([
|
|
811
|
+
run2(["diff", "--", ...files]),
|
|
812
|
+
run2(["diff", "--cached", "--", ...files]),
|
|
813
|
+
getUntrackedFiles()
|
|
814
|
+
]);
|
|
815
|
+
const parts = [staged.stdout, unstaged.stdout].filter(Boolean);
|
|
816
|
+
const untrackedSet = new Set(untracked);
|
|
817
|
+
const MAX_FILE_CONTENT = 2000;
|
|
818
|
+
for (const file of files) {
|
|
819
|
+
if (untrackedSet.has(file)) {
|
|
820
|
+
try {
|
|
821
|
+
const content = readFileSync2(join2(process.cwd(), file), "utf-8");
|
|
822
|
+
const truncated = content.length > MAX_FILE_CONTENT ? `${content.slice(0, MAX_FILE_CONTENT)}
|
|
823
|
+
... (truncated)` : content;
|
|
824
|
+
const lines = truncated.split(`
|
|
825
|
+
`).map((l) => `+${l}`);
|
|
826
|
+
parts.push(`diff --git a/${file} b/${file}
|
|
827
|
+
new file
|
|
828
|
+
--- /dev/null
|
|
829
|
+
+++ b/${file}
|
|
830
|
+
${lines.join(`
|
|
831
|
+
`)}`);
|
|
832
|
+
} catch {}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return parts.join(`
|
|
836
|
+
`);
|
|
837
|
+
}
|
|
838
|
+
async function getUntrackedFiles() {
|
|
839
|
+
const { exitCode, stdout } = await run2(["ls-files", "--others", "--exclude-standard"]);
|
|
1160
840
|
if (exitCode !== 0)
|
|
1161
|
-
return
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
841
|
+
return [];
|
|
842
|
+
return stdout.trim().split(`
|
|
843
|
+
`).filter(Boolean);
|
|
844
|
+
}
|
|
845
|
+
async function getFileStatus() {
|
|
846
|
+
const { exitCode, stdout } = await run2(["status", "--porcelain"]);
|
|
847
|
+
if (exitCode !== 0)
|
|
848
|
+
return { staged: [], modified: [], untracked: [] };
|
|
849
|
+
const result = { staged: [], modified: [], untracked: [] };
|
|
850
|
+
const STATUS_LABELS = {
|
|
851
|
+
A: "new file",
|
|
852
|
+
M: "modified",
|
|
853
|
+
D: "deleted",
|
|
854
|
+
R: "renamed",
|
|
855
|
+
C: "copied",
|
|
856
|
+
T: "type changed"
|
|
857
|
+
};
|
|
858
|
+
for (const raw of stdout.trimEnd().split(`
|
|
859
|
+
`).filter(Boolean)) {
|
|
860
|
+
const line = raw.replace(/\r$/, "");
|
|
861
|
+
const indexStatus = line[0];
|
|
862
|
+
const workTreeStatus = line[1];
|
|
863
|
+
const pathPart = line.slice(3);
|
|
864
|
+
const renameIdx = pathPart.indexOf(" -> ");
|
|
865
|
+
const file = renameIdx !== -1 ? pathPart.slice(renameIdx + 4) : pathPart;
|
|
866
|
+
if (indexStatus === "?" && workTreeStatus === "?") {
|
|
867
|
+
result.untracked.push(file);
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
|
|
871
|
+
result.staged.push({ file, status: STATUS_LABELS[indexStatus] ?? indexStatus });
|
|
872
|
+
}
|
|
873
|
+
if (workTreeStatus && workTreeStatus !== " " && workTreeStatus !== "?") {
|
|
874
|
+
result.modified.push({ file, status: STATUS_LABELS[workTreeStatus] ?? workTreeStatus });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/utils/logger.ts
|
|
881
|
+
import { LogEngine, LogMode } from "@wgtechlabs/log-engine";
|
|
882
|
+
import pc2 from "picocolors";
|
|
883
|
+
LogEngine.configure({
|
|
884
|
+
mode: LogMode.INFO,
|
|
885
|
+
format: {
|
|
886
|
+
includeIsoTimestamp: false,
|
|
887
|
+
includeLocalTime: true,
|
|
888
|
+
includeEmoji: true
|
|
1167
889
|
}
|
|
890
|
+
});
|
|
891
|
+
function success(msg) {
|
|
892
|
+
LogEngine.log(msg);
|
|
893
|
+
}
|
|
894
|
+
function error(msg) {
|
|
895
|
+
LogEngine.error(msg);
|
|
896
|
+
}
|
|
897
|
+
function warn(msg) {
|
|
898
|
+
LogEngine.warn(msg);
|
|
899
|
+
}
|
|
900
|
+
function info(msg) {
|
|
901
|
+
LogEngine.info(msg);
|
|
902
|
+
}
|
|
903
|
+
function heading(msg) {
|
|
904
|
+
console.log(`
|
|
905
|
+
${pc2.bold(msg)}`);
|
|
1168
906
|
}
|
|
1169
907
|
|
|
1170
908
|
// src/utils/spinner.ts
|
|
1171
|
-
import
|
|
909
|
+
import pc3 from "picocolors";
|
|
1172
910
|
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
1173
911
|
function createSpinner(text2) {
|
|
1174
912
|
let frameIdx = 0;
|
|
@@ -1180,7 +918,7 @@ function createSpinner(text2) {
|
|
|
1180
918
|
const render = () => {
|
|
1181
919
|
if (stopped)
|
|
1182
920
|
return;
|
|
1183
|
-
const frame =
|
|
921
|
+
const frame = pc3.cyan(FRAMES[frameIdx % FRAMES.length]);
|
|
1184
922
|
clearLine();
|
|
1185
923
|
process.stderr.write(`${frame} ${currentText}`);
|
|
1186
924
|
frameIdx++;
|
|
@@ -1200,12 +938,12 @@ function createSpinner(text2) {
|
|
|
1200
938
|
},
|
|
1201
939
|
success(msg) {
|
|
1202
940
|
stop();
|
|
1203
|
-
process.stderr.write(`${
|
|
941
|
+
process.stderr.write(`${pc3.green("✔")} ${msg}
|
|
1204
942
|
`);
|
|
1205
943
|
},
|
|
1206
944
|
fail(msg) {
|
|
1207
945
|
stop();
|
|
1208
|
-
process.stderr.write(`${
|
|
946
|
+
process.stderr.write(`${pc3.red("✖")} ${msg}
|
|
1209
947
|
`);
|
|
1210
948
|
},
|
|
1211
949
|
stop() {
|
|
@@ -1214,6 +952,53 @@ function createSpinner(text2) {
|
|
|
1214
952
|
};
|
|
1215
953
|
}
|
|
1216
954
|
|
|
955
|
+
// src/utils/workflow.ts
|
|
956
|
+
var WORKFLOW_DESCRIPTIONS = {
|
|
957
|
+
"clean-flow": "Clean Flow — main + dev, squash features into dev, merge dev into main",
|
|
958
|
+
"github-flow": "GitHub Flow — main + feature branches, squash/merge into main",
|
|
959
|
+
"git-flow": "Git Flow — main + develop + release + hotfix branches"
|
|
960
|
+
};
|
|
961
|
+
function getBaseBranch(config) {
|
|
962
|
+
switch (config.workflow) {
|
|
963
|
+
case "clean-flow":
|
|
964
|
+
case "git-flow":
|
|
965
|
+
return config.devBranch ?? "dev";
|
|
966
|
+
case "github-flow":
|
|
967
|
+
return config.mainBranch;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function hasDevBranch(workflow) {
|
|
971
|
+
return workflow === "clean-flow" || workflow === "git-flow";
|
|
972
|
+
}
|
|
973
|
+
function getSyncSource(config) {
|
|
974
|
+
const { workflow, role, mainBranch, origin, upstream } = config;
|
|
975
|
+
const devBranch = config.devBranch ?? "dev";
|
|
976
|
+
switch (workflow) {
|
|
977
|
+
case "clean-flow":
|
|
978
|
+
if (role === "contributor") {
|
|
979
|
+
return { remote: upstream, ref: `${upstream}/${devBranch}`, strategy: "pull" };
|
|
980
|
+
}
|
|
981
|
+
return { remote: origin, ref: `${origin}/${devBranch}`, strategy: "pull" };
|
|
982
|
+
case "github-flow":
|
|
983
|
+
if (role === "contributor") {
|
|
984
|
+
return { remote: upstream, ref: `${upstream}/${mainBranch}`, strategy: "pull" };
|
|
985
|
+
}
|
|
986
|
+
return { remote: origin, ref: `${origin}/${mainBranch}`, strategy: "pull" };
|
|
987
|
+
case "git-flow":
|
|
988
|
+
if (role === "contributor") {
|
|
989
|
+
return { remote: upstream, ref: `${upstream}/${devBranch}`, strategy: "pull" };
|
|
990
|
+
}
|
|
991
|
+
return { remote: origin, ref: `${origin}/${devBranch}`, strategy: "pull" };
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function getProtectedBranches(config) {
|
|
995
|
+
const branches = [config.mainBranch];
|
|
996
|
+
if (hasDevBranch(config.workflow) && config.devBranch) {
|
|
997
|
+
branches.push(config.devBranch);
|
|
998
|
+
}
|
|
999
|
+
return branches;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1217
1002
|
// src/commands/clean.ts
|
|
1218
1003
|
async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
1219
1004
|
if (!config)
|
|
@@ -1226,18 +1011,18 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1226
1011
|
warn("You have uncommitted changes in your working tree.");
|
|
1227
1012
|
}
|
|
1228
1013
|
if (localWork.unpushedCommits > 0) {
|
|
1229
|
-
warn(`You have ${
|
|
1014
|
+
warn(`You have ${pc4.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not pushed.`);
|
|
1230
1015
|
}
|
|
1231
1016
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
1232
1017
|
const DISCARD = "Discard all changes and clean up";
|
|
1233
1018
|
const CANCEL = "Skip this branch";
|
|
1234
|
-
const action = await selectPrompt(`${
|
|
1019
|
+
const action = await selectPrompt(`${pc4.bold(currentBranch)} has local changes. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
1235
1020
|
if (action === CANCEL)
|
|
1236
1021
|
return "skipped";
|
|
1237
1022
|
if (action === SAVE_NEW_BRANCH) {
|
|
1238
1023
|
if (!config)
|
|
1239
1024
|
return "skipped";
|
|
1240
|
-
info(
|
|
1025
|
+
info(pc4.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
1241
1026
|
const description = await inputPrompt("What are you working on?");
|
|
1242
1027
|
let newBranchName = description;
|
|
1243
1028
|
if (looksLikeNaturalLanguage(description)) {
|
|
@@ -1246,8 +1031,8 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1246
1031
|
if (suggested) {
|
|
1247
1032
|
spinner.success("Branch name suggestion ready.");
|
|
1248
1033
|
console.log(`
|
|
1249
|
-
${
|
|
1250
|
-
const accepted = await confirmPrompt(`Use ${
|
|
1034
|
+
${pc4.dim("AI suggestion:")} ${pc4.bold(pc4.cyan(suggested))}`);
|
|
1035
|
+
const accepted = await confirmPrompt(`Use ${pc4.bold(suggested)} as your branch name?`);
|
|
1251
1036
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
1252
1037
|
} else {
|
|
1253
1038
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -1255,7 +1040,7 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1255
1040
|
}
|
|
1256
1041
|
}
|
|
1257
1042
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
1258
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
1043
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc4.bold(newBranchName)}:`, config.branchPrefixes);
|
|
1259
1044
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
1260
1045
|
}
|
|
1261
1046
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -1267,16 +1052,16 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1267
1052
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
1268
1053
|
return "skipped";
|
|
1269
1054
|
}
|
|
1270
|
-
success(`Renamed ${
|
|
1055
|
+
success(`Renamed ${pc4.bold(currentBranch)} → ${pc4.bold(newBranchName)}`);
|
|
1271
1056
|
const syncSource2 = getSyncSource(config);
|
|
1272
1057
|
await fetchRemote(syncSource2.remote);
|
|
1273
1058
|
const savedUpstreamRef = await getUpstreamRef();
|
|
1274
1059
|
const rebaseResult = savedUpstreamRef && savedUpstreamRef !== syncSource2.ref ? await rebaseOnto(syncSource2.ref, savedUpstreamRef) : await rebase(syncSource2.ref);
|
|
1275
1060
|
if (rebaseResult.exitCode !== 0) {
|
|
1276
1061
|
warn("Rebase encountered conflicts. Resolve them after cleanup:");
|
|
1277
|
-
info(` ${
|
|
1062
|
+
info(` ${pc4.bold(`git checkout ${newBranchName} && git rebase --continue`)}`);
|
|
1278
1063
|
} else {
|
|
1279
|
-
success(`Rebased ${
|
|
1064
|
+
success(`Rebased ${pc4.bold(newBranchName)} onto ${pc4.bold(syncSource2.ref)}.`);
|
|
1280
1065
|
}
|
|
1281
1066
|
const coResult2 = await checkoutBranch(baseBranch);
|
|
1282
1067
|
if (coResult2.exitCode !== 0) {
|
|
@@ -1284,12 +1069,12 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1284
1069
|
return "saved";
|
|
1285
1070
|
}
|
|
1286
1071
|
await updateLocalBranch(baseBranch, syncSource2.ref);
|
|
1287
|
-
success(`Synced ${
|
|
1072
|
+
success(`Synced ${pc4.bold(baseBranch)} with ${pc4.bold(syncSource2.ref)}.`);
|
|
1288
1073
|
return "saved";
|
|
1289
1074
|
}
|
|
1290
1075
|
}
|
|
1291
1076
|
const syncSource = getSyncSource(config);
|
|
1292
|
-
info(`Switching to ${
|
|
1077
|
+
info(`Switching to ${pc4.bold(baseBranch)} and syncing...`);
|
|
1293
1078
|
await fetchRemote(syncSource.remote);
|
|
1294
1079
|
const coResult = await checkoutBranch(baseBranch);
|
|
1295
1080
|
if (coResult.exitCode !== 0) {
|
|
@@ -1297,10 +1082,10 @@ async function handleCurrentBranchDeletion(currentBranch, baseBranch, config) {
|
|
|
1297
1082
|
return "skipped";
|
|
1298
1083
|
}
|
|
1299
1084
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
1300
|
-
success(`Synced ${
|
|
1085
|
+
success(`Synced ${pc4.bold(baseBranch)} with ${pc4.bold(syncSource.ref)}.`);
|
|
1301
1086
|
return "switched";
|
|
1302
1087
|
}
|
|
1303
|
-
var clean_default =
|
|
1088
|
+
var clean_default = defineCommand({
|
|
1304
1089
|
meta: {
|
|
1305
1090
|
name: "clean",
|
|
1306
1091
|
description: "Delete merged branches and prune remote refs"
|
|
@@ -1345,21 +1130,21 @@ var clean_default = defineCommand2({
|
|
|
1345
1130
|
if (ghInstalled && ghAuthed) {
|
|
1346
1131
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
1347
1132
|
if (mergedPR) {
|
|
1348
|
-
warn(`PR #${mergedPR.number} (${
|
|
1349
|
-
info(`Link: ${
|
|
1133
|
+
warn(`PR #${mergedPR.number} (${pc4.bold(mergedPR.title)}) has already been merged.`);
|
|
1134
|
+
info(`Link: ${pc4.underline(mergedPR.url)}`);
|
|
1350
1135
|
goneCandidates.push(currentBranch);
|
|
1351
1136
|
}
|
|
1352
1137
|
}
|
|
1353
1138
|
}
|
|
1354
1139
|
if (mergedCandidates.length > 0) {
|
|
1355
1140
|
console.log(`
|
|
1356
|
-
${
|
|
1141
|
+
${pc4.bold("Merged branches to delete:")}`);
|
|
1357
1142
|
for (const b of mergedCandidates) {
|
|
1358
|
-
const marker = b === currentBranch ?
|
|
1359
|
-
console.log(` ${
|
|
1143
|
+
const marker = b === currentBranch ? pc4.yellow(" (current)") : "";
|
|
1144
|
+
console.log(` ${pc4.dim("•")} ${b}${marker}`);
|
|
1360
1145
|
}
|
|
1361
1146
|
console.log();
|
|
1362
|
-
const ok = args.yes || await confirmPrompt(`Delete ${
|
|
1147
|
+
const ok = args.yes || await confirmPrompt(`Delete ${pc4.bold(String(mergedCandidates.length))} merged branch${mergedCandidates.length !== 1 ? "es" : ""}?`);
|
|
1363
1148
|
if (ok) {
|
|
1364
1149
|
for (const branch of mergedCandidates) {
|
|
1365
1150
|
if (branch === currentBranch) {
|
|
@@ -1376,7 +1161,7 @@ ${pc5.bold("Merged branches to delete:")}`);
|
|
|
1376
1161
|
}
|
|
1377
1162
|
const result = await deleteBranch(branch);
|
|
1378
1163
|
if (result.exitCode === 0) {
|
|
1379
|
-
success(` Deleted ${
|
|
1164
|
+
success(` Deleted ${pc4.bold(branch)}`);
|
|
1380
1165
|
} else {
|
|
1381
1166
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
1382
1167
|
}
|
|
@@ -1387,13 +1172,13 @@ ${pc5.bold("Merged branches to delete:")}`);
|
|
|
1387
1172
|
}
|
|
1388
1173
|
if (goneCandidates.length > 0) {
|
|
1389
1174
|
console.log(`
|
|
1390
|
-
${
|
|
1175
|
+
${pc4.bold("Stale branches (remote deleted, likely squash-merged):")}`);
|
|
1391
1176
|
for (const b of goneCandidates) {
|
|
1392
|
-
const marker = b === currentBranch ?
|
|
1393
|
-
console.log(` ${
|
|
1177
|
+
const marker = b === currentBranch ? pc4.yellow(" (current)") : "";
|
|
1178
|
+
console.log(` ${pc4.dim("•")} ${b}${marker}`);
|
|
1394
1179
|
}
|
|
1395
1180
|
console.log();
|
|
1396
|
-
const ok = args.yes || await confirmPrompt(`Delete ${
|
|
1181
|
+
const ok = args.yes || await confirmPrompt(`Delete ${pc4.bold(String(goneCandidates.length))} stale branch${goneCandidates.length !== 1 ? "es" : ""}?`);
|
|
1397
1182
|
if (ok) {
|
|
1398
1183
|
for (const branch of goneCandidates) {
|
|
1399
1184
|
if (branch === currentBranch) {
|
|
@@ -1410,7 +1195,7 @@ ${pc5.bold("Stale branches (remote deleted, likely squash-merged):")}`);
|
|
|
1410
1195
|
}
|
|
1411
1196
|
const result = await forceDeleteBranch(branch);
|
|
1412
1197
|
if (result.exitCode === 0) {
|
|
1413
|
-
success(` Deleted ${
|
|
1198
|
+
success(` Deleted ${pc4.bold(branch)}`);
|
|
1414
1199
|
} else {
|
|
1415
1200
|
warn(` Failed to delete ${branch}: ${result.stderr.trim()}`);
|
|
1416
1201
|
}
|
|
@@ -1425,14 +1210,14 @@ ${pc5.bold("Stale branches (remote deleted, likely squash-merged):")}`);
|
|
|
1425
1210
|
const finalBranch = await getCurrentBranch();
|
|
1426
1211
|
if (finalBranch && protectedBranches.has(finalBranch)) {
|
|
1427
1212
|
console.log();
|
|
1428
|
-
info(`You're on ${
|
|
1213
|
+
info(`You're on ${pc4.bold(finalBranch)}. Run ${pc4.bold("contrib start")} to begin a new feature.`);
|
|
1429
1214
|
}
|
|
1430
1215
|
}
|
|
1431
1216
|
});
|
|
1432
1217
|
|
|
1433
1218
|
// src/commands/commit.ts
|
|
1434
|
-
import { defineCommand as
|
|
1435
|
-
import
|
|
1219
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
1220
|
+
import pc5 from "picocolors";
|
|
1436
1221
|
|
|
1437
1222
|
// src/utils/convention.ts
|
|
1438
1223
|
var CLEAN_COMMIT_PATTERN = /^(📦|🔧|🗑\uFE0F?|🔒|⚙\uFE0F?|☕|🧪|📖|🚀) (new|update|remove|security|setup|chore|test|docs|release)(!?)( \([a-zA-Z0-9][a-zA-Z0-9-]*\))?: .{1,72}$/u;
|
|
@@ -1478,7 +1263,7 @@ function getValidationError(convention) {
|
|
|
1478
1263
|
}
|
|
1479
1264
|
|
|
1480
1265
|
// src/commands/commit.ts
|
|
1481
|
-
var commit_default =
|
|
1266
|
+
var commit_default = defineCommand2({
|
|
1482
1267
|
meta: {
|
|
1483
1268
|
name: "commit",
|
|
1484
1269
|
description: "Stage changes and create a commit message (AI-powered)"
|
|
@@ -1522,9 +1307,9 @@ var commit_default = defineCommand3({
|
|
|
1522
1307
|
process.exit(1);
|
|
1523
1308
|
}
|
|
1524
1309
|
console.log(`
|
|
1525
|
-
${
|
|
1310
|
+
${pc5.bold("Changed files:")}`);
|
|
1526
1311
|
for (const f of changedFiles) {
|
|
1527
|
-
console.log(` ${
|
|
1312
|
+
console.log(` ${pc5.dim("•")} ${f}`);
|
|
1528
1313
|
}
|
|
1529
1314
|
const stageAction = await selectPrompt("No staged changes. How would you like to stage?", [
|
|
1530
1315
|
"Stage all changes",
|
|
@@ -1574,7 +1359,7 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1574
1359
|
if (commitMessage) {
|
|
1575
1360
|
spinner.success("AI commit message generated.");
|
|
1576
1361
|
console.log(`
|
|
1577
|
-
${
|
|
1362
|
+
${pc5.dim("AI suggestion:")} ${pc5.bold(pc5.cyan(commitMessage))}`);
|
|
1578
1363
|
} else {
|
|
1579
1364
|
spinner.fail("AI did not return a commit message.");
|
|
1580
1365
|
warn("Falling back to manual entry.");
|
|
@@ -1600,7 +1385,7 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1600
1385
|
if (regen) {
|
|
1601
1386
|
spinner.success("Commit message regenerated.");
|
|
1602
1387
|
console.log(`
|
|
1603
|
-
${
|
|
1388
|
+
${pc5.dim("AI suggestion:")} ${pc5.bold(pc5.cyan(regen))}`);
|
|
1604
1389
|
const ok = await confirmPrompt("Use this message?");
|
|
1605
1390
|
finalMessage = ok ? regen : await inputPrompt("Enter commit message manually");
|
|
1606
1391
|
} else {
|
|
@@ -1615,7 +1400,7 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1615
1400
|
if (convention2 !== "none") {
|
|
1616
1401
|
console.log();
|
|
1617
1402
|
for (const hint of CONVENTION_FORMAT_HINTS[convention2]) {
|
|
1618
|
-
console.log(
|
|
1403
|
+
console.log(pc5.dim(hint));
|
|
1619
1404
|
}
|
|
1620
1405
|
console.log();
|
|
1621
1406
|
}
|
|
@@ -1639,7 +1424,7 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1639
1424
|
error(`Failed to commit: ${result.stderr}`);
|
|
1640
1425
|
process.exit(1);
|
|
1641
1426
|
}
|
|
1642
|
-
success(`✅ Committed: ${
|
|
1427
|
+
success(`✅ Committed: ${pc5.bold(finalMessage)}`);
|
|
1643
1428
|
}
|
|
1644
1429
|
});
|
|
1645
1430
|
async function runGroupCommit(model, config) {
|
|
@@ -1656,9 +1441,9 @@ async function runGroupCommit(model, config) {
|
|
|
1656
1441
|
process.exit(1);
|
|
1657
1442
|
}
|
|
1658
1443
|
console.log(`
|
|
1659
|
-
${
|
|
1444
|
+
${pc5.bold("Changed files:")}`);
|
|
1660
1445
|
for (const f of changedFiles) {
|
|
1661
|
-
console.log(` ${
|
|
1446
|
+
console.log(` ${pc5.dim("•")} ${f}`);
|
|
1662
1447
|
}
|
|
1663
1448
|
const spinner = createSpinner(`Asking AI to group ${changedFiles.length} file(s) into logical commits...`);
|
|
1664
1449
|
const diffs = await getFullDiffForFiles(changedFiles);
|
|
@@ -1696,13 +1481,13 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1696
1481
|
let commitAll = false;
|
|
1697
1482
|
while (!proceedToCommit) {
|
|
1698
1483
|
console.log(`
|
|
1699
|
-
${
|
|
1484
|
+
${pc5.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
1700
1485
|
`);
|
|
1701
1486
|
for (let i = 0;i < validGroups.length; i++) {
|
|
1702
1487
|
const g = validGroups[i];
|
|
1703
|
-
console.log(` ${
|
|
1488
|
+
console.log(` ${pc5.cyan(`Group ${i + 1}:`)} ${pc5.bold(g.message)}`);
|
|
1704
1489
|
for (const f of g.files) {
|
|
1705
|
-
console.log(` ${
|
|
1490
|
+
console.log(` ${pc5.dim("•")} ${f}`);
|
|
1706
1491
|
}
|
|
1707
1492
|
console.log();
|
|
1708
1493
|
}
|
|
@@ -1746,16 +1531,16 @@ ${pc6.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
|
1746
1531
|
continue;
|
|
1747
1532
|
}
|
|
1748
1533
|
committed++;
|
|
1749
|
-
success(`✅ Committed group ${i + 1}: ${
|
|
1534
|
+
success(`✅ Committed group ${i + 1}: ${pc5.bold(group.message)}`);
|
|
1750
1535
|
}
|
|
1751
1536
|
} else {
|
|
1752
1537
|
for (let i = 0;i < validGroups.length; i++) {
|
|
1753
1538
|
const group = validGroups[i];
|
|
1754
|
-
console.log(
|
|
1539
|
+
console.log(pc5.bold(`
|
|
1755
1540
|
── Group ${i + 1}/${validGroups.length} ──`));
|
|
1756
|
-
console.log(` ${
|
|
1541
|
+
console.log(` ${pc5.cyan(group.message)}`);
|
|
1757
1542
|
for (const f of group.files) {
|
|
1758
|
-
console.log(` ${
|
|
1543
|
+
console.log(` ${pc5.dim("•")} ${f}`);
|
|
1759
1544
|
}
|
|
1760
1545
|
let message = group.message;
|
|
1761
1546
|
let actionDone = false;
|
|
@@ -1777,7 +1562,7 @@ ${pc6.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
|
1777
1562
|
if (newMsg) {
|
|
1778
1563
|
message = newMsg;
|
|
1779
1564
|
group.message = newMsg;
|
|
1780
|
-
regenSpinner.success(`New message: ${
|
|
1565
|
+
regenSpinner.success(`New message: ${pc5.bold(message)}`);
|
|
1781
1566
|
} else {
|
|
1782
1567
|
regenSpinner.fail("AI could not generate a new message. Keeping current one.");
|
|
1783
1568
|
}
|
|
@@ -1817,7 +1602,7 @@ ${pc6.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
|
1817
1602
|
continue;
|
|
1818
1603
|
}
|
|
1819
1604
|
committed++;
|
|
1820
|
-
success(`✅ Committed group ${i + 1}: ${
|
|
1605
|
+
success(`✅ Committed group ${i + 1}: ${pc5.bold(message)}`);
|
|
1821
1606
|
actionDone = true;
|
|
1822
1607
|
}
|
|
1823
1608
|
}
|
|
@@ -1833,12 +1618,12 @@ ${pc6.bold(`AI suggested ${validGroups.length} commit group(s):`)}
|
|
|
1833
1618
|
|
|
1834
1619
|
// src/commands/doctor.ts
|
|
1835
1620
|
import { execFile as execFileCb3 } from "node:child_process";
|
|
1836
|
-
import { defineCommand as
|
|
1837
|
-
import
|
|
1621
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1622
|
+
import pc6 from "picocolors";
|
|
1838
1623
|
// package.json
|
|
1839
1624
|
var package_default = {
|
|
1840
1625
|
name: "contribute-now",
|
|
1841
|
-
version: "0.2.0-dev.
|
|
1626
|
+
version: "0.2.0-dev.69b11fd",
|
|
1842
1627
|
description: "Git workflow CLI for squash-merge two-branch models. Keeps dev in sync with main after squash merges.",
|
|
1843
1628
|
type: "module",
|
|
1844
1629
|
bin: {
|
|
@@ -1927,16 +1712,16 @@ async function getRepoInfoFromRemote(remote = "origin") {
|
|
|
1927
1712
|
}
|
|
1928
1713
|
|
|
1929
1714
|
// src/commands/doctor.ts
|
|
1930
|
-
var PASS = ` ${
|
|
1931
|
-
var FAIL = ` ${
|
|
1932
|
-
var WARN = ` ${
|
|
1715
|
+
var PASS = ` ${pc6.green("✔")} `;
|
|
1716
|
+
var FAIL = ` ${pc6.red("✗")} `;
|
|
1717
|
+
var WARN = ` ${pc6.yellow("⚠")} `;
|
|
1933
1718
|
function printReport(report) {
|
|
1934
1719
|
for (const section of report.sections) {
|
|
1935
1720
|
console.log(`
|
|
1936
|
-
${
|
|
1721
|
+
${pc6.bold(pc6.underline(section.title))}`);
|
|
1937
1722
|
for (const check of section.checks) {
|
|
1938
1723
|
const prefix = check.ok ? check.warning ? WARN : PASS : FAIL;
|
|
1939
|
-
const text2 = check.detail ? `${check.label} ${
|
|
1724
|
+
const text2 = check.detail ? `${check.label} ${pc6.dim(`— ${check.detail}`)}` : check.label;
|
|
1940
1725
|
console.log(`${prefix}${text2}`);
|
|
1941
1726
|
}
|
|
1942
1727
|
}
|
|
@@ -2144,7 +1929,7 @@ function envSection() {
|
|
|
2144
1929
|
}
|
|
2145
1930
|
return { title: "Environment", checks };
|
|
2146
1931
|
}
|
|
2147
|
-
var doctor_default =
|
|
1932
|
+
var doctor_default = defineCommand3({
|
|
2148
1933
|
meta: {
|
|
2149
1934
|
name: "doctor",
|
|
2150
1935
|
description: "Diagnose the contribute-now CLI environment and configuration"
|
|
@@ -2180,14 +1965,14 @@ var doctor_default = defineCommand4({
|
|
|
2180
1965
|
const failures = total.filter((c) => !c.ok);
|
|
2181
1966
|
const warnings = total.filter((c) => c.ok && c.warning);
|
|
2182
1967
|
if (failures.length === 0 && warnings.length === 0) {
|
|
2183
|
-
console.log(` ${
|
|
1968
|
+
console.log(` ${pc6.green("All checks passed!")} No issues detected.
|
|
2184
1969
|
`);
|
|
2185
1970
|
} else {
|
|
2186
1971
|
if (failures.length > 0) {
|
|
2187
|
-
console.log(` ${
|
|
1972
|
+
console.log(` ${pc6.red(`${failures.length} issue${failures.length !== 1 ? "s" : ""} found.`)}`);
|
|
2188
1973
|
}
|
|
2189
1974
|
if (warnings.length > 0) {
|
|
2190
|
-
console.log(` ${
|
|
1975
|
+
console.log(` ${pc6.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}.`)}`);
|
|
2191
1976
|
}
|
|
2192
1977
|
console.log();
|
|
2193
1978
|
}
|
|
@@ -2197,8 +1982,8 @@ var doctor_default = defineCommand4({
|
|
|
2197
1982
|
// src/commands/hook.ts
|
|
2198
1983
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2199
1984
|
import { join as join3 } from "node:path";
|
|
2200
|
-
import { defineCommand as
|
|
2201
|
-
import
|
|
1985
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1986
|
+
import pc7 from "picocolors";
|
|
2202
1987
|
var HOOK_MARKER = "# managed by contribute-now";
|
|
2203
1988
|
function getHooksDir(cwd = process.cwd()) {
|
|
2204
1989
|
return join3(cwd, ".git", "hooks");
|
|
@@ -2236,7 +2021,7 @@ else
|
|
|
2236
2021
|
fi
|
|
2237
2022
|
`;
|
|
2238
2023
|
}
|
|
2239
|
-
var hook_default =
|
|
2024
|
+
var hook_default = defineCommand4({
|
|
2240
2025
|
meta: {
|
|
2241
2026
|
name: "hook",
|
|
2242
2027
|
description: "Install or uninstall the commit-msg git hook"
|
|
@@ -2294,8 +2079,8 @@ async function installHook() {
|
|
|
2294
2079
|
}
|
|
2295
2080
|
writeFileSync2(hookPath, generateHookScript(), { mode: 493 });
|
|
2296
2081
|
success(`commit-msg hook installed.`);
|
|
2297
|
-
info(`Convention: ${
|
|
2298
|
-
info(`Path: ${
|
|
2082
|
+
info(`Convention: ${pc7.bold(CONVENTION_LABELS[config.commitConvention])}`);
|
|
2083
|
+
info(`Path: ${pc7.dim(hookPath)}`);
|
|
2299
2084
|
}
|
|
2300
2085
|
async function uninstallHook() {
|
|
2301
2086
|
heading("\uD83E\uDE9D hook uninstall");
|
|
@@ -2313,165 +2098,10 @@ async function uninstallHook() {
|
|
|
2313
2098
|
success("commit-msg hook removed.");
|
|
2314
2099
|
}
|
|
2315
2100
|
|
|
2316
|
-
// src/commands/log.ts
|
|
2317
|
-
import { defineCommand as defineCommand6 } from "citty";
|
|
2318
|
-
import pc9 from "picocolors";
|
|
2319
|
-
var log_default = defineCommand6({
|
|
2320
|
-
meta: {
|
|
2321
|
-
name: "log",
|
|
2322
|
-
description: "Show a colorized, workflow-aware commit log with graph"
|
|
2323
|
-
},
|
|
2324
|
-
args: {
|
|
2325
|
-
count: {
|
|
2326
|
-
type: "string",
|
|
2327
|
-
alias: "n",
|
|
2328
|
-
description: "Number of commits to show (default: 20)"
|
|
2329
|
-
},
|
|
2330
|
-
all: {
|
|
2331
|
-
type: "boolean",
|
|
2332
|
-
alias: "a",
|
|
2333
|
-
description: "Show all branches, not just current",
|
|
2334
|
-
default: false
|
|
2335
|
-
},
|
|
2336
|
-
graph: {
|
|
2337
|
-
type: "boolean",
|
|
2338
|
-
alias: "g",
|
|
2339
|
-
description: "Show graph view with branch lines",
|
|
2340
|
-
default: true
|
|
2341
|
-
},
|
|
2342
|
-
branch: {
|
|
2343
|
-
type: "string",
|
|
2344
|
-
alias: "b",
|
|
2345
|
-
description: "Show log for a specific branch"
|
|
2346
|
-
}
|
|
2347
|
-
},
|
|
2348
|
-
async run({ args }) {
|
|
2349
|
-
if (!await isGitRepo()) {
|
|
2350
|
-
error("Not inside a git repository.");
|
|
2351
|
-
process.exit(1);
|
|
2352
|
-
}
|
|
2353
|
-
const config = readConfig();
|
|
2354
|
-
const count = args.count ? Number.parseInt(args.count, 10) : 20;
|
|
2355
|
-
const showAll = args.all;
|
|
2356
|
-
const showGraph = args.graph;
|
|
2357
|
-
const targetBranch = args.branch;
|
|
2358
|
-
const protectedBranches = config ? getProtectedBranches(config) : ["main", "master"];
|
|
2359
|
-
const currentBranch = await getCurrentBranch();
|
|
2360
|
-
heading("\uD83D\uDCDC commit log");
|
|
2361
|
-
if (showGraph) {
|
|
2362
|
-
const lines = await getLogGraph({ count, all: showAll, branch: targetBranch });
|
|
2363
|
-
if (lines.length === 0) {
|
|
2364
|
-
console.log(pc9.dim(" No commits found."));
|
|
2365
|
-
console.log();
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
console.log();
|
|
2369
|
-
for (const line of lines) {
|
|
2370
|
-
console.log(` ${colorizeGraphLine(line, protectedBranches, currentBranch)}`);
|
|
2371
|
-
}
|
|
2372
|
-
} else {
|
|
2373
|
-
const entries = await getLogEntries({ count, all: showAll, branch: targetBranch });
|
|
2374
|
-
if (entries.length === 0) {
|
|
2375
|
-
console.log(pc9.dim(" No commits found."));
|
|
2376
|
-
console.log();
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
console.log();
|
|
2380
|
-
for (const entry of entries) {
|
|
2381
|
-
const hashStr = pc9.yellow(entry.hash);
|
|
2382
|
-
const refsStr = entry.refs ? ` ${colorizeRefs(entry.refs, protectedBranches, currentBranch)}` : "";
|
|
2383
|
-
const subjectStr = colorizeSubject(entry.subject);
|
|
2384
|
-
console.log(` ${hashStr}${refsStr} ${subjectStr}`);
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
console.log();
|
|
2388
|
-
console.log(pc9.dim(` Showing ${count} most recent commits${showAll ? " (all branches)" : targetBranch ? ` (${targetBranch})` : ""}`));
|
|
2389
|
-
console.log(pc9.dim(` Use ${pc9.bold("contrib log -n 50")} for more, or ${pc9.bold("contrib log --all")} for all branches`));
|
|
2390
|
-
console.log();
|
|
2391
|
-
}
|
|
2392
|
-
});
|
|
2393
|
-
function colorizeGraphLine(line, protectedBranches, currentBranch) {
|
|
2394
|
-
const match = line.match(/^([|/\\*\s_.-]*)([a-f0-9]{7,12})(\s+\(([^)]+)\))?\s*(.*)/);
|
|
2395
|
-
if (!match) {
|
|
2396
|
-
return pc9.cyan(line);
|
|
2397
|
-
}
|
|
2398
|
-
const [, graphPart = "", hash, , refs, subject = ""] = match;
|
|
2399
|
-
const parts = [];
|
|
2400
|
-
if (graphPart) {
|
|
2401
|
-
parts.push(colorizeGraphChars(graphPart));
|
|
2402
|
-
}
|
|
2403
|
-
parts.push(pc9.yellow(hash));
|
|
2404
|
-
if (refs) {
|
|
2405
|
-
parts.push(` (${colorizeRefs(refs, protectedBranches, currentBranch)})`);
|
|
2406
|
-
}
|
|
2407
|
-
parts.push(` ${colorizeSubject(subject)}`);
|
|
2408
|
-
return parts.join("");
|
|
2409
|
-
}
|
|
2410
|
-
function colorizeGraphChars(graphPart) {
|
|
2411
|
-
return graphPart.split("").map((ch) => {
|
|
2412
|
-
switch (ch) {
|
|
2413
|
-
case "*":
|
|
2414
|
-
return pc9.green(ch);
|
|
2415
|
-
case "|":
|
|
2416
|
-
return pc9.cyan(ch);
|
|
2417
|
-
case "/":
|
|
2418
|
-
case "\\":
|
|
2419
|
-
return pc9.cyan(ch);
|
|
2420
|
-
case "-":
|
|
2421
|
-
case "_":
|
|
2422
|
-
return pc9.cyan(ch);
|
|
2423
|
-
default:
|
|
2424
|
-
return ch;
|
|
2425
|
-
}
|
|
2426
|
-
}).join("");
|
|
2427
|
-
}
|
|
2428
|
-
function colorizeRefs(refs, protectedBranches, currentBranch) {
|
|
2429
|
-
return refs.split(",").map((ref) => {
|
|
2430
|
-
const trimmed = ref.trim();
|
|
2431
|
-
if (trimmed.startsWith("HEAD ->") || trimmed === "HEAD") {
|
|
2432
|
-
const branchName = trimmed.replace("HEAD -> ", "");
|
|
2433
|
-
if (trimmed === "HEAD") {
|
|
2434
|
-
return pc9.bold(pc9.cyan("HEAD"));
|
|
2435
|
-
}
|
|
2436
|
-
return `${pc9.bold(pc9.cyan("HEAD"))} ${pc9.dim("->")} ${colorizeRefName(branchName, protectedBranches, currentBranch)}`;
|
|
2437
|
-
}
|
|
2438
|
-
if (trimmed.startsWith("tag:")) {
|
|
2439
|
-
return pc9.bold(pc9.magenta(trimmed));
|
|
2440
|
-
}
|
|
2441
|
-
return colorizeRefName(trimmed, protectedBranches, currentBranch);
|
|
2442
|
-
}).join(pc9.dim(", "));
|
|
2443
|
-
}
|
|
2444
|
-
function colorizeRefName(name, protectedBranches, currentBranch) {
|
|
2445
|
-
const isRemote = name.includes("/");
|
|
2446
|
-
const localName = isRemote ? name.split("/").slice(1).join("/") : name;
|
|
2447
|
-
if (protectedBranches.includes(localName)) {
|
|
2448
|
-
return isRemote ? pc9.bold(pc9.red(name)) : pc9.bold(pc9.red(name));
|
|
2449
|
-
}
|
|
2450
|
-
if (localName === currentBranch) {
|
|
2451
|
-
return pc9.bold(pc9.green(name));
|
|
2452
|
-
}
|
|
2453
|
-
if (isRemote) {
|
|
2454
|
-
return pc9.blue(name);
|
|
2455
|
-
}
|
|
2456
|
-
return pc9.green(name);
|
|
2457
|
-
}
|
|
2458
|
-
function colorizeSubject(subject) {
|
|
2459
|
-
const emojiMatch = subject.match(/^([\p{Emoji_Presentation}\p{Emoji}\uFE0F]+\s*)/u);
|
|
2460
|
-
if (emojiMatch) {
|
|
2461
|
-
const emoji = emojiMatch[1];
|
|
2462
|
-
const rest = subject.slice(emoji.length);
|
|
2463
|
-
return `${emoji}${pc9.white(rest)}`;
|
|
2464
|
-
}
|
|
2465
|
-
if (subject.startsWith("Merge ")) {
|
|
2466
|
-
return pc9.dim(subject);
|
|
2467
|
-
}
|
|
2468
|
-
return pc9.white(subject);
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
2101
|
// src/commands/setup.ts
|
|
2472
|
-
import { defineCommand as
|
|
2473
|
-
import
|
|
2474
|
-
var setup_default =
|
|
2102
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
2103
|
+
import pc8 from "picocolors";
|
|
2104
|
+
var setup_default = defineCommand5({
|
|
2475
2105
|
meta: {
|
|
2476
2106
|
name: "setup",
|
|
2477
2107
|
description: "Initialize contribute-now config for this repo (.contributerc.json)"
|
|
@@ -2492,7 +2122,7 @@ var setup_default = defineCommand7({
|
|
|
2492
2122
|
workflow = "github-flow";
|
|
2493
2123
|
else if (workflowChoice.startsWith("Git Flow"))
|
|
2494
2124
|
workflow = "git-flow";
|
|
2495
|
-
info(`Workflow: ${
|
|
2125
|
+
info(`Workflow: ${pc8.bold(WORKFLOW_DESCRIPTIONS[workflow])}`);
|
|
2496
2126
|
const conventionChoice = await selectPrompt("Which commit convention should this project use?", [
|
|
2497
2127
|
`${CONVENTION_DESCRIPTIONS["clean-commit"]} (recommended)`,
|
|
2498
2128
|
CONVENTION_DESCRIPTIONS.conventional,
|
|
@@ -2545,8 +2175,8 @@ var setup_default = defineCommand7({
|
|
|
2545
2175
|
detectedRole = roleChoice;
|
|
2546
2176
|
detectionSource = "user selection";
|
|
2547
2177
|
} else {
|
|
2548
|
-
info(`Detected role: ${
|
|
2549
|
-
const confirmed = await confirmPrompt(`Role detected as ${
|
|
2178
|
+
info(`Detected role: ${pc8.bold(detectedRole)} (via ${detectionSource})`);
|
|
2179
|
+
const confirmed = await confirmPrompt(`Role detected as ${pc8.bold(detectedRole)}. Is this correct?`);
|
|
2550
2180
|
if (!confirmed) {
|
|
2551
2181
|
const roleChoice = await selectPrompt("Select your role:", ["maintainer", "contributor"]);
|
|
2552
2182
|
detectedRole = roleChoice;
|
|
@@ -2591,22 +2221,22 @@ var setup_default = defineCommand7({
|
|
|
2591
2221
|
warn(' echo ".contributerc.json" >> .gitignore');
|
|
2592
2222
|
}
|
|
2593
2223
|
console.log();
|
|
2594
|
-
info(`Workflow: ${
|
|
2595
|
-
info(`Convention: ${
|
|
2596
|
-
info(`Role: ${
|
|
2224
|
+
info(`Workflow: ${pc8.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
2225
|
+
info(`Convention: ${pc8.bold(CONVENTION_DESCRIPTIONS[config.commitConvention])}`);
|
|
2226
|
+
info(`Role: ${pc8.bold(config.role)}`);
|
|
2597
2227
|
if (config.devBranch) {
|
|
2598
|
-
info(`Main: ${
|
|
2228
|
+
info(`Main: ${pc8.bold(config.mainBranch)} | Dev: ${pc8.bold(config.devBranch)}`);
|
|
2599
2229
|
} else {
|
|
2600
|
-
info(`Main: ${
|
|
2230
|
+
info(`Main: ${pc8.bold(config.mainBranch)}`);
|
|
2601
2231
|
}
|
|
2602
|
-
info(`Origin: ${
|
|
2232
|
+
info(`Origin: ${pc8.bold(config.origin)}${config.role === "contributor" ? ` | Upstream: ${pc8.bold(config.upstream)}` : ""}`);
|
|
2603
2233
|
}
|
|
2604
2234
|
});
|
|
2605
2235
|
|
|
2606
2236
|
// src/commands/start.ts
|
|
2607
|
-
import { defineCommand as
|
|
2608
|
-
import
|
|
2609
|
-
var start_default =
|
|
2237
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
2238
|
+
import pc9 from "picocolors";
|
|
2239
|
+
var start_default = defineCommand6({
|
|
2610
2240
|
meta: {
|
|
2611
2241
|
name: "start",
|
|
2612
2242
|
description: "Create a new feature branch from the latest base branch"
|
|
@@ -2653,8 +2283,8 @@ var start_default = defineCommand8({
|
|
|
2653
2283
|
if (suggested) {
|
|
2654
2284
|
spinner.success("Branch name suggestion ready.");
|
|
2655
2285
|
console.log(`
|
|
2656
|
-
${
|
|
2657
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2286
|
+
${pc9.dim("AI suggestion:")} ${pc9.bold(pc9.cyan(suggested))}`);
|
|
2287
|
+
const accepted = await confirmPrompt(`Use ${pc9.bold(suggested)} as your branch name?`);
|
|
2658
2288
|
if (accepted) {
|
|
2659
2289
|
branchName = suggested;
|
|
2660
2290
|
} else {
|
|
@@ -2665,14 +2295,14 @@ var start_default = defineCommand8({
|
|
|
2665
2295
|
}
|
|
2666
2296
|
}
|
|
2667
2297
|
if (!hasPrefix(branchName, branchPrefixes)) {
|
|
2668
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2298
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc9.bold(branchName)}:`, branchPrefixes);
|
|
2669
2299
|
branchName = formatBranchName(prefix, branchName);
|
|
2670
2300
|
}
|
|
2671
2301
|
if (!isValidBranchName(branchName)) {
|
|
2672
2302
|
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
2673
2303
|
process.exit(1);
|
|
2674
2304
|
}
|
|
2675
|
-
info(`Creating branch: ${
|
|
2305
|
+
info(`Creating branch: ${pc9.bold(branchName)}`);
|
|
2676
2306
|
await fetchRemote(syncSource.remote);
|
|
2677
2307
|
const updateResult = await updateLocalBranch(baseBranch, syncSource.ref);
|
|
2678
2308
|
if (updateResult.exitCode !== 0) {}
|
|
@@ -2681,14 +2311,14 @@ var start_default = defineCommand8({
|
|
|
2681
2311
|
error(`Failed to create branch: ${result.stderr}`);
|
|
2682
2312
|
process.exit(1);
|
|
2683
2313
|
}
|
|
2684
|
-
success(`✅ Created ${
|
|
2314
|
+
success(`✅ Created ${pc9.bold(branchName)} from latest ${pc9.bold(baseBranch)}`);
|
|
2685
2315
|
}
|
|
2686
2316
|
});
|
|
2687
2317
|
|
|
2688
2318
|
// src/commands/status.ts
|
|
2689
|
-
import { defineCommand as
|
|
2690
|
-
import
|
|
2691
|
-
var status_default =
|
|
2319
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
2320
|
+
import pc10 from "picocolors";
|
|
2321
|
+
var status_default = defineCommand7({
|
|
2692
2322
|
meta: {
|
|
2693
2323
|
name: "status",
|
|
2694
2324
|
description: "Show sync status of branches"
|
|
@@ -2704,8 +2334,8 @@ var status_default = defineCommand9({
|
|
|
2704
2334
|
process.exit(1);
|
|
2705
2335
|
}
|
|
2706
2336
|
heading("\uD83D\uDCCA contribute-now status");
|
|
2707
|
-
console.log(` ${
|
|
2708
|
-
console.log(` ${
|
|
2337
|
+
console.log(` ${pc10.dim("Workflow:")} ${pc10.bold(WORKFLOW_DESCRIPTIONS[config.workflow])}`);
|
|
2338
|
+
console.log(` ${pc10.dim("Role:")} ${pc10.bold(config.role)}`);
|
|
2709
2339
|
console.log();
|
|
2710
2340
|
await fetchAll();
|
|
2711
2341
|
const currentBranch = await getCurrentBranch();
|
|
@@ -2714,7 +2344,7 @@ var status_default = defineCommand9({
|
|
|
2714
2344
|
const isContributor = config.role === "contributor";
|
|
2715
2345
|
const [dirty, fileStatus] = await Promise.all([hasUncommittedChanges(), getFileStatus()]);
|
|
2716
2346
|
if (dirty) {
|
|
2717
|
-
console.log(` ${
|
|
2347
|
+
console.log(` ${pc10.yellow("⚠")} ${pc10.yellow("Uncommitted changes in working tree")}`);
|
|
2718
2348
|
console.log();
|
|
2719
2349
|
}
|
|
2720
2350
|
const mainRemote = `${origin}/${mainBranch}`;
|
|
@@ -2730,82 +2360,82 @@ var status_default = defineCommand9({
|
|
|
2730
2360
|
if (currentBranch && currentBranch !== mainBranch && currentBranch !== config.devBranch) {
|
|
2731
2361
|
const branchDiv = await getDivergence(currentBranch, baseBranch);
|
|
2732
2362
|
const branchLine = formatStatus(currentBranch, baseBranch, branchDiv.ahead, branchDiv.behind);
|
|
2733
|
-
console.log(branchLine +
|
|
2363
|
+
console.log(branchLine + pc10.dim(` (current ${pc10.green("*")})`));
|
|
2734
2364
|
} else if (currentBranch) {
|
|
2735
|
-
console.log(
|
|
2365
|
+
console.log(pc10.dim(` (on ${pc10.bold(currentBranch)} branch)`));
|
|
2736
2366
|
}
|
|
2737
2367
|
const hasFiles = fileStatus.staged.length > 0 || fileStatus.modified.length > 0 || fileStatus.untracked.length > 0;
|
|
2738
2368
|
if (hasFiles) {
|
|
2739
2369
|
console.log();
|
|
2740
2370
|
if (fileStatus.staged.length > 0) {
|
|
2741
|
-
console.log(` ${
|
|
2371
|
+
console.log(` ${pc10.green("Staged for commit:")}`);
|
|
2742
2372
|
for (const { file, status } of fileStatus.staged) {
|
|
2743
|
-
console.log(` ${
|
|
2373
|
+
console.log(` ${pc10.green("+")} ${pc10.dim(`${status}:`)} ${file}`);
|
|
2744
2374
|
}
|
|
2745
2375
|
}
|
|
2746
2376
|
if (fileStatus.modified.length > 0) {
|
|
2747
|
-
console.log(` ${
|
|
2377
|
+
console.log(` ${pc10.yellow("Unstaged changes:")}`);
|
|
2748
2378
|
for (const { file, status } of fileStatus.modified) {
|
|
2749
|
-
console.log(` ${
|
|
2379
|
+
console.log(` ${pc10.yellow("~")} ${pc10.dim(`${status}:`)} ${file}`);
|
|
2750
2380
|
}
|
|
2751
2381
|
}
|
|
2752
2382
|
if (fileStatus.untracked.length > 0) {
|
|
2753
|
-
console.log(` ${
|
|
2383
|
+
console.log(` ${pc10.red("Untracked files:")}`);
|
|
2754
2384
|
for (const file of fileStatus.untracked) {
|
|
2755
|
-
console.log(` ${
|
|
2385
|
+
console.log(` ${pc10.red("?")} ${file}`);
|
|
2756
2386
|
}
|
|
2757
2387
|
}
|
|
2758
2388
|
} else if (!dirty) {
|
|
2759
|
-
console.log(` ${
|
|
2389
|
+
console.log(` ${pc10.green("✓")} ${pc10.dim("Working tree clean")}`);
|
|
2760
2390
|
}
|
|
2761
2391
|
const tips = [];
|
|
2762
2392
|
if (fileStatus.staged.length > 0) {
|
|
2763
|
-
tips.push(`Run ${
|
|
2393
|
+
tips.push(`Run ${pc10.bold("contrib commit")} to commit staged changes`);
|
|
2764
2394
|
}
|
|
2765
2395
|
if (fileStatus.modified.length > 0 || fileStatus.untracked.length > 0) {
|
|
2766
|
-
tips.push(`Run ${
|
|
2396
|
+
tips.push(`Run ${pc10.bold("contrib commit")} to stage and commit changes`);
|
|
2767
2397
|
}
|
|
2768
2398
|
if (fileStatus.staged.length === 0 && fileStatus.modified.length === 0 && fileStatus.untracked.length === 0 && currentBranch && currentBranch !== mainBranch && currentBranch !== config.devBranch) {
|
|
2769
2399
|
const branchDiv = await getDivergence(currentBranch, `${origin}/${currentBranch}`);
|
|
2770
2400
|
if (branchDiv.ahead > 0) {
|
|
2771
|
-
tips.push(`Run ${
|
|
2401
|
+
tips.push(`Run ${pc10.bold("contrib submit")} to push and create/update your PR`);
|
|
2772
2402
|
}
|
|
2773
2403
|
}
|
|
2774
2404
|
if (tips.length > 0) {
|
|
2775
2405
|
console.log();
|
|
2776
|
-
console.log(` ${
|
|
2406
|
+
console.log(` ${pc10.dim("\uD83D\uDCA1 Tip:")}`);
|
|
2777
2407
|
for (const tip of tips) {
|
|
2778
|
-
console.log(` ${
|
|
2408
|
+
console.log(` ${pc10.dim(tip)}`);
|
|
2779
2409
|
}
|
|
2780
2410
|
}
|
|
2781
2411
|
console.log();
|
|
2782
2412
|
}
|
|
2783
2413
|
});
|
|
2784
2414
|
function formatStatus(branch, base, ahead, behind) {
|
|
2785
|
-
const label =
|
|
2415
|
+
const label = pc10.bold(branch.padEnd(20));
|
|
2786
2416
|
if (ahead === 0 && behind === 0) {
|
|
2787
|
-
return ` ${
|
|
2417
|
+
return ` ${pc10.green("✓")} ${label} ${pc10.dim(`in sync with ${base}`)}`;
|
|
2788
2418
|
}
|
|
2789
2419
|
if (ahead > 0 && behind === 0) {
|
|
2790
|
-
return ` ${
|
|
2420
|
+
return ` ${pc10.yellow("↑")} ${label} ${pc10.yellow(`${ahead} commit${ahead !== 1 ? "s" : ""} ahead of ${base}`)}`;
|
|
2791
2421
|
}
|
|
2792
2422
|
if (behind > 0 && ahead === 0) {
|
|
2793
|
-
return ` ${
|
|
2423
|
+
return ` ${pc10.red("↓")} ${label} ${pc10.red(`${behind} commit${behind !== 1 ? "s" : ""} behind ${base}`)}`;
|
|
2794
2424
|
}
|
|
2795
|
-
return ` ${
|
|
2425
|
+
return ` ${pc10.red("⚡")} ${label} ${pc10.yellow(`${ahead} ahead`)}${pc10.dim(", ")}${pc10.red(`${behind} behind`)} ${pc10.dim(base)}`;
|
|
2796
2426
|
}
|
|
2797
2427
|
|
|
2798
2428
|
// src/commands/submit.ts
|
|
2799
|
-
import { defineCommand as
|
|
2800
|
-
import
|
|
2429
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2430
|
+
import pc11 from "picocolors";
|
|
2801
2431
|
async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
2802
|
-
info(`Checking out ${
|
|
2432
|
+
info(`Checking out ${pc11.bold(baseBranch)}...`);
|
|
2803
2433
|
const coResult = await checkoutBranch2(baseBranch);
|
|
2804
2434
|
if (coResult.exitCode !== 0) {
|
|
2805
2435
|
error(`Failed to checkout ${baseBranch}: ${coResult.stderr}`);
|
|
2806
2436
|
process.exit(1);
|
|
2807
2437
|
}
|
|
2808
|
-
info(`Squash merging ${
|
|
2438
|
+
info(`Squash merging ${pc11.bold(featureBranch)} into ${pc11.bold(baseBranch)}...`);
|
|
2809
2439
|
const mergeResult = await mergeSquash(featureBranch);
|
|
2810
2440
|
if (mergeResult.exitCode !== 0) {
|
|
2811
2441
|
error(`Squash merge failed: ${mergeResult.stderr}`);
|
|
@@ -2835,26 +2465,26 @@ async function performSquashMerge(origin, baseBranch, featureBranch, options) {
|
|
|
2835
2465
|
error(`Commit failed: ${commitResult.stderr}`);
|
|
2836
2466
|
process.exit(1);
|
|
2837
2467
|
}
|
|
2838
|
-
info(`Pushing ${
|
|
2468
|
+
info(`Pushing ${pc11.bold(baseBranch)} to ${origin}...`);
|
|
2839
2469
|
const pushResult = await pushBranch(origin, baseBranch);
|
|
2840
2470
|
if (pushResult.exitCode !== 0) {
|
|
2841
2471
|
error(`Failed to push ${baseBranch}: ${pushResult.stderr}`);
|
|
2842
2472
|
process.exit(1);
|
|
2843
2473
|
}
|
|
2844
|
-
info(`Deleting local branch ${
|
|
2474
|
+
info(`Deleting local branch ${pc11.bold(featureBranch)}...`);
|
|
2845
2475
|
const delLocal = await forceDeleteBranch(featureBranch);
|
|
2846
2476
|
if (delLocal.exitCode !== 0) {
|
|
2847
2477
|
warn(`Could not delete local branch: ${delLocal.stderr.trim()}`);
|
|
2848
2478
|
}
|
|
2849
|
-
info(`Deleting remote branch ${
|
|
2479
|
+
info(`Deleting remote branch ${pc11.bold(featureBranch)}...`);
|
|
2850
2480
|
const delRemote = await deleteRemoteBranch(origin, featureBranch);
|
|
2851
2481
|
if (delRemote.exitCode !== 0) {
|
|
2852
2482
|
warn(`Could not delete remote branch: ${delRemote.stderr.trim()}`);
|
|
2853
2483
|
}
|
|
2854
|
-
success(`✅ Squash merged ${
|
|
2855
|
-
info(`Run ${
|
|
2484
|
+
success(`✅ Squash merged ${pc11.bold(featureBranch)} into ${pc11.bold(baseBranch)} and pushed.`);
|
|
2485
|
+
info(`Run ${pc11.bold("contrib start")} to begin a new feature.`);
|
|
2856
2486
|
}
|
|
2857
|
-
var submit_default =
|
|
2487
|
+
var submit_default = defineCommand8({
|
|
2858
2488
|
meta: {
|
|
2859
2489
|
name: "submit",
|
|
2860
2490
|
description: "Push current branch and create a pull request"
|
|
@@ -2894,73 +2524,8 @@ var submit_default = defineCommand10({
|
|
|
2894
2524
|
process.exit(1);
|
|
2895
2525
|
}
|
|
2896
2526
|
if (protectedBranches.includes(currentBranch)) {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
await fetchAll();
|
|
2900
|
-
const remoteRef = `${origin}/${currentBranch}`;
|
|
2901
|
-
const localWork = await hasLocalWork(origin, currentBranch);
|
|
2902
|
-
const dirty = await hasUncommittedChanges();
|
|
2903
|
-
const hasCommits = localWork.unpushedCommits > 0;
|
|
2904
|
-
const hasAnything = hasCommits || dirty;
|
|
2905
|
-
if (!hasAnything) {
|
|
2906
|
-
error("No local changes or commits to move. Switch to a feature branch first.");
|
|
2907
|
-
info(` Run ${pc13.bold("contrib start")} to create a new feature branch.`);
|
|
2908
|
-
process.exit(1);
|
|
2909
|
-
}
|
|
2910
|
-
if (hasCommits) {
|
|
2911
|
-
info(`Found ${pc13.bold(String(localWork.unpushedCommits))} unpushed commit${localWork.unpushedCommits !== 1 ? "s" : ""} on ${pc13.bold(currentBranch)}.`);
|
|
2912
|
-
}
|
|
2913
|
-
if (dirty) {
|
|
2914
|
-
info("You also have uncommitted changes in the working tree.");
|
|
2915
|
-
}
|
|
2916
|
-
console.log();
|
|
2917
|
-
const MOVE_BRANCH = "Move my changes to a new feature branch";
|
|
2918
|
-
const CANCEL2 = "Cancel (stay on this branch)";
|
|
2919
|
-
const action = await selectPrompt("Let's get you back on track. What would you like to do?", [MOVE_BRANCH, CANCEL2]);
|
|
2920
|
-
if (action === CANCEL2) {
|
|
2921
|
-
info("No changes made. You are still on your current branch.");
|
|
2922
|
-
return;
|
|
2923
|
-
}
|
|
2924
|
-
info(pc13.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
2925
|
-
const description = await inputPrompt("What are you working on?");
|
|
2926
|
-
let newBranchName = description;
|
|
2927
|
-
if (looksLikeNaturalLanguage(description)) {
|
|
2928
|
-
const copilotError = await checkCopilotAvailable();
|
|
2929
|
-
if (!copilotError) {
|
|
2930
|
-
const spinner = createSpinner("Generating branch name suggestion...");
|
|
2931
|
-
const suggested = await suggestBranchName(description, args.model);
|
|
2932
|
-
if (suggested) {
|
|
2933
|
-
spinner.success("Branch name suggestion ready.");
|
|
2934
|
-
console.log(`
|
|
2935
|
-
${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(suggested))}`);
|
|
2936
|
-
const accepted = await confirmPrompt(`Use ${pc13.bold(suggested)} as your branch name?`);
|
|
2937
|
-
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
2938
|
-
} else {
|
|
2939
|
-
spinner.fail("AI did not return a suggestion.");
|
|
2940
|
-
newBranchName = await inputPrompt("Enter branch name", description);
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
2945
|
-
const prefix = await selectPrompt(`Choose a branch type for ${pc13.bold(newBranchName)}:`, config.branchPrefixes);
|
|
2946
|
-
newBranchName = formatBranchName(prefix, newBranchName);
|
|
2947
|
-
}
|
|
2948
|
-
if (!isValidBranchName(newBranchName)) {
|
|
2949
|
-
error("Invalid branch name. Use only alphanumeric characters, dots, hyphens, underscores, and slashes.");
|
|
2950
|
-
process.exit(1);
|
|
2951
|
-
}
|
|
2952
|
-
const branchResult = await createBranch(newBranchName);
|
|
2953
|
-
if (branchResult.exitCode !== 0) {
|
|
2954
|
-
error(`Failed to create branch: ${branchResult.stderr}`);
|
|
2955
|
-
process.exit(1);
|
|
2956
|
-
}
|
|
2957
|
-
success(`Created ${pc13.bold(newBranchName)} with your changes.`);
|
|
2958
|
-
await updateLocalBranch(currentBranch, remoteRef);
|
|
2959
|
-
info(`Reset ${pc13.bold(currentBranch)} back to ${pc13.bold(remoteRef)} — no damage done.`);
|
|
2960
|
-
console.log();
|
|
2961
|
-
success(`You're now on ${pc13.bold(newBranchName)} with all your work intact.`);
|
|
2962
|
-
info(`Run ${pc13.bold("contrib submit")} again to push and create your PR.`);
|
|
2963
|
-
return;
|
|
2527
|
+
error(`Cannot submit ${protectedBranches.map((b) => pc11.bold(b)).join(" or ")} as a PR. Switch to your feature branch.`);
|
|
2528
|
+
process.exit(1);
|
|
2964
2529
|
}
|
|
2965
2530
|
heading("\uD83D\uDE80 contrib submit");
|
|
2966
2531
|
const ghInstalled = await checkGhInstalled();
|
|
@@ -2968,7 +2533,7 @@ var submit_default = defineCommand10({
|
|
|
2968
2533
|
if (ghInstalled && ghAuthed) {
|
|
2969
2534
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
2970
2535
|
if (mergedPR) {
|
|
2971
|
-
warn(`PR #${mergedPR.number} (${
|
|
2536
|
+
warn(`PR #${mergedPR.number} (${pc11.bold(mergedPR.title)}) was already merged.`);
|
|
2972
2537
|
const localWork = await hasLocalWork(origin, currentBranch);
|
|
2973
2538
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
2974
2539
|
if (hasWork) {
|
|
@@ -2976,7 +2541,7 @@ var submit_default = defineCommand10({
|
|
|
2976
2541
|
warn("You have uncommitted changes in your working tree.");
|
|
2977
2542
|
}
|
|
2978
2543
|
if (localWork.unpushedCommits > 0) {
|
|
2979
|
-
warn(`You have ${
|
|
2544
|
+
warn(`You have ${pc11.bold(String(localWork.unpushedCommits))} local commit${localWork.unpushedCommits !== 1 ? "s" : ""} not in the merged PR.`);
|
|
2980
2545
|
}
|
|
2981
2546
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
2982
2547
|
const DISCARD = "Discard all changes and clean up";
|
|
@@ -2987,7 +2552,7 @@ var submit_default = defineCommand10({
|
|
|
2987
2552
|
return;
|
|
2988
2553
|
}
|
|
2989
2554
|
if (action === SAVE_NEW_BRANCH) {
|
|
2990
|
-
info(
|
|
2555
|
+
info(pc11.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
2991
2556
|
const description = await inputPrompt("What are you working on?");
|
|
2992
2557
|
let newBranchName = description;
|
|
2993
2558
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -2996,8 +2561,8 @@ var submit_default = defineCommand10({
|
|
|
2996
2561
|
if (suggested) {
|
|
2997
2562
|
spinner.success("Branch name suggestion ready.");
|
|
2998
2563
|
console.log(`
|
|
2999
|
-
${
|
|
3000
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2564
|
+
${pc11.dim("AI suggestion:")} ${pc11.bold(pc11.cyan(suggested))}`);
|
|
2565
|
+
const accepted = await confirmPrompt(`Use ${pc11.bold(suggested)} as your branch name?`);
|
|
3001
2566
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
3002
2567
|
} else {
|
|
3003
2568
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -3005,7 +2570,7 @@ var submit_default = defineCommand10({
|
|
|
3005
2570
|
}
|
|
3006
2571
|
}
|
|
3007
2572
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
3008
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2573
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc11.bold(newBranchName)}:`, config.branchPrefixes);
|
|
3009
2574
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
3010
2575
|
}
|
|
3011
2576
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -3019,10 +2584,10 @@ var submit_default = defineCommand10({
|
|
|
3019
2584
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
3020
2585
|
process.exit(1);
|
|
3021
2586
|
}
|
|
3022
|
-
success(`Renamed ${
|
|
2587
|
+
success(`Renamed ${pc11.bold(currentBranch)} → ${pc11.bold(newBranchName)}`);
|
|
3023
2588
|
await unsetUpstream();
|
|
3024
2589
|
const syncSource2 = getSyncSource(config);
|
|
3025
|
-
info(`Syncing ${
|
|
2590
|
+
info(`Syncing ${pc11.bold(newBranchName)} with latest ${pc11.bold(baseBranch)}...`);
|
|
3026
2591
|
await fetchRemote(syncSource2.remote);
|
|
3027
2592
|
let rebaseResult;
|
|
3028
2593
|
if (staleUpstreamHash) {
|
|
@@ -3033,17 +2598,17 @@ var submit_default = defineCommand10({
|
|
|
3033
2598
|
}
|
|
3034
2599
|
if (rebaseResult.exitCode !== 0) {
|
|
3035
2600
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
3036
|
-
info(` ${
|
|
2601
|
+
info(` ${pc11.bold("git rebase --continue")}`);
|
|
3037
2602
|
} else {
|
|
3038
|
-
success(`Rebased ${
|
|
2603
|
+
success(`Rebased ${pc11.bold(newBranchName)} onto ${pc11.bold(syncSource2.ref)}.`);
|
|
3039
2604
|
}
|
|
3040
|
-
info(`All your changes are preserved. Run ${
|
|
2605
|
+
info(`All your changes are preserved. Run ${pc11.bold("contrib submit")} when ready to create a new PR.`);
|
|
3041
2606
|
return;
|
|
3042
2607
|
}
|
|
3043
2608
|
warn("Discarding local changes...");
|
|
3044
2609
|
}
|
|
3045
2610
|
const syncSource = getSyncSource(config);
|
|
3046
|
-
info(`Switching to ${
|
|
2611
|
+
info(`Switching to ${pc11.bold(baseBranch)} and syncing...`);
|
|
3047
2612
|
await fetchRemote(syncSource.remote);
|
|
3048
2613
|
const coResult = await checkoutBranch2(baseBranch);
|
|
3049
2614
|
if (coResult.exitCode !== 0) {
|
|
@@ -3051,16 +2616,16 @@ var submit_default = defineCommand10({
|
|
|
3051
2616
|
process.exit(1);
|
|
3052
2617
|
}
|
|
3053
2618
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
3054
|
-
success(`Synced ${
|
|
3055
|
-
info(`Deleting stale branch ${
|
|
2619
|
+
success(`Synced ${pc11.bold(baseBranch)} with ${pc11.bold(syncSource.ref)}.`);
|
|
2620
|
+
info(`Deleting stale branch ${pc11.bold(currentBranch)}...`);
|
|
3056
2621
|
const delResult = await forceDeleteBranch(currentBranch);
|
|
3057
2622
|
if (delResult.exitCode === 0) {
|
|
3058
|
-
success(`Deleted ${
|
|
2623
|
+
success(`Deleted ${pc11.bold(currentBranch)}.`);
|
|
3059
2624
|
} else {
|
|
3060
2625
|
warn(`Could not delete branch: ${delResult.stderr.trim()}`);
|
|
3061
2626
|
}
|
|
3062
2627
|
console.log();
|
|
3063
|
-
info(`You're now on ${
|
|
2628
|
+
info(`You're now on ${pc11.bold(baseBranch)}. Run ${pc11.bold("contrib start")} to begin a new feature.`);
|
|
3064
2629
|
return;
|
|
3065
2630
|
}
|
|
3066
2631
|
}
|
|
@@ -3080,10 +2645,10 @@ var submit_default = defineCommand10({
|
|
|
3080
2645
|
prBody = result.body;
|
|
3081
2646
|
spinner.success("PR description generated.");
|
|
3082
2647
|
console.log(`
|
|
3083
|
-
${
|
|
2648
|
+
${pc11.dim("AI title:")} ${pc11.bold(pc11.cyan(prTitle))}`);
|
|
3084
2649
|
console.log(`
|
|
3085
|
-
${
|
|
3086
|
-
console.log(
|
|
2650
|
+
${pc11.dim("AI body preview:")}`);
|
|
2651
|
+
console.log(pc11.dim(prBody.slice(0, 300) + (prBody.length > 300 ? "..." : "")));
|
|
3087
2652
|
} else {
|
|
3088
2653
|
spinner.fail("AI did not return a PR description.");
|
|
3089
2654
|
}
|
|
@@ -3172,7 +2737,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
3172
2737
|
});
|
|
3173
2738
|
return;
|
|
3174
2739
|
}
|
|
3175
|
-
info(`Pushing ${
|
|
2740
|
+
info(`Pushing ${pc11.bold(currentBranch)} to ${origin}...`);
|
|
3176
2741
|
const pushResult = await pushSetUpstream(origin, currentBranch);
|
|
3177
2742
|
if (pushResult.exitCode !== 0) {
|
|
3178
2743
|
error(`Failed to push: ${pushResult.stderr}`);
|
|
@@ -3184,7 +2749,7 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
3184
2749
|
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/compare/${baseBranch}...${currentBranch}?expand=1`;
|
|
3185
2750
|
console.log();
|
|
3186
2751
|
info("Create your PR manually:");
|
|
3187
|
-
console.log(` ${
|
|
2752
|
+
console.log(` ${pc11.cyan(prUrl)}`);
|
|
3188
2753
|
} else {
|
|
3189
2754
|
info("gh CLI not available. Create your PR manually on GitHub.");
|
|
3190
2755
|
}
|
|
@@ -3192,8 +2757,8 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
3192
2757
|
}
|
|
3193
2758
|
const existingPR = await getPRForBranch(currentBranch);
|
|
3194
2759
|
if (existingPR) {
|
|
3195
|
-
success(`Pushed changes to existing PR #${existingPR.number}: ${
|
|
3196
|
-
console.log(` ${
|
|
2760
|
+
success(`Pushed changes to existing PR #${existingPR.number}: ${pc11.bold(existingPR.title)}`);
|
|
2761
|
+
console.log(` ${pc11.cyan(existingPR.url)}`);
|
|
3197
2762
|
return;
|
|
3198
2763
|
}
|
|
3199
2764
|
if (submitAction === "fill") {
|
|
@@ -3224,9 +2789,9 @@ ${pc13.dim("AI body preview:")}`);
|
|
|
3224
2789
|
});
|
|
3225
2790
|
|
|
3226
2791
|
// src/commands/sync.ts
|
|
3227
|
-
import { defineCommand as
|
|
3228
|
-
import
|
|
3229
|
-
var sync_default =
|
|
2792
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
2793
|
+
import pc12 from "picocolors";
|
|
2794
|
+
var sync_default = defineCommand9({
|
|
3230
2795
|
meta: {
|
|
3231
2796
|
name: "sync",
|
|
3232
2797
|
description: "Sync your local branches with the remote"
|
|
@@ -3268,12 +2833,12 @@ var sync_default = defineCommand11({
|
|
|
3268
2833
|
}
|
|
3269
2834
|
const div = await getDivergence(baseBranch, syncSource.ref);
|
|
3270
2835
|
if (div.ahead > 0 || div.behind > 0) {
|
|
3271
|
-
info(`${
|
|
2836
|
+
info(`${pc12.bold(baseBranch)} is ${pc12.yellow(`${div.ahead} ahead`)} and ${pc12.red(`${div.behind} behind`)} ${syncSource.ref}`);
|
|
3272
2837
|
} else {
|
|
3273
|
-
info(`${
|
|
2838
|
+
info(`${pc12.bold(baseBranch)} is already in sync with ${syncSource.ref}`);
|
|
3274
2839
|
}
|
|
3275
2840
|
if (!args.yes) {
|
|
3276
|
-
const ok = await confirmPrompt(`This will pull ${
|
|
2841
|
+
const ok = await confirmPrompt(`This will pull ${pc12.bold(syncSource.ref)} into local ${pc12.bold(baseBranch)}.`);
|
|
3277
2842
|
if (!ok)
|
|
3278
2843
|
process.exit(0);
|
|
3279
2844
|
}
|
|
@@ -3291,7 +2856,7 @@ var sync_default = defineCommand11({
|
|
|
3291
2856
|
if (hasDevBranch(workflow) && role === "maintainer") {
|
|
3292
2857
|
const mainDiv = await getDivergence(config.mainBranch, `${origin}/${config.mainBranch}`);
|
|
3293
2858
|
if (mainDiv.behind > 0) {
|
|
3294
|
-
info(`Also syncing ${
|
|
2859
|
+
info(`Also syncing ${pc12.bold(config.mainBranch)}...`);
|
|
3295
2860
|
const mainCoResult = await checkoutBranch2(config.mainBranch);
|
|
3296
2861
|
if (mainCoResult.exitCode === 0) {
|
|
3297
2862
|
const mainPullResult = await pullBranch(origin, config.mainBranch);
|
|
@@ -3307,9 +2872,9 @@ var sync_default = defineCommand11({
|
|
|
3307
2872
|
|
|
3308
2873
|
// src/commands/update.ts
|
|
3309
2874
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
3310
|
-
import { defineCommand as
|
|
3311
|
-
import
|
|
3312
|
-
var update_default =
|
|
2875
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
2876
|
+
import pc13 from "picocolors";
|
|
2877
|
+
var update_default = defineCommand10({
|
|
3313
2878
|
meta: {
|
|
3314
2879
|
name: "update",
|
|
3315
2880
|
description: "Rebase current branch onto the latest base branch"
|
|
@@ -3344,7 +2909,7 @@ var update_default = defineCommand12({
|
|
|
3344
2909
|
process.exit(1);
|
|
3345
2910
|
}
|
|
3346
2911
|
if (protectedBranches.includes(currentBranch)) {
|
|
3347
|
-
error(`Use \`contrib sync\` to update ${protectedBranches.map((b) =>
|
|
2912
|
+
error(`Use \`contrib sync\` to update ${protectedBranches.map((b) => pc13.bold(b)).join(" or ")} branches.`);
|
|
3348
2913
|
process.exit(1);
|
|
3349
2914
|
}
|
|
3350
2915
|
if (await hasUncommittedChanges()) {
|
|
@@ -3354,8 +2919,8 @@ var update_default = defineCommand12({
|
|
|
3354
2919
|
heading("\uD83D\uDD03 contrib update");
|
|
3355
2920
|
const mergedPR = await getMergedPRForBranch(currentBranch);
|
|
3356
2921
|
if (mergedPR) {
|
|
3357
|
-
warn(`PR #${mergedPR.number} (${
|
|
3358
|
-
info(`Link: ${
|
|
2922
|
+
warn(`PR #${mergedPR.number} (${pc13.bold(mergedPR.title)}) has already been merged.`);
|
|
2923
|
+
info(`Link: ${pc13.underline(mergedPR.url)}`);
|
|
3359
2924
|
const localWork = await hasLocalWork(syncSource.remote, currentBranch);
|
|
3360
2925
|
const hasWork = localWork.uncommitted || localWork.unpushedCommits > 0;
|
|
3361
2926
|
if (hasWork) {
|
|
@@ -3368,13 +2933,13 @@ var update_default = defineCommand12({
|
|
|
3368
2933
|
const SAVE_NEW_BRANCH = "Save changes to a new branch";
|
|
3369
2934
|
const DISCARD = "Discard all changes and clean up";
|
|
3370
2935
|
const CANCEL = "Cancel";
|
|
3371
|
-
const action = await selectPrompt(`${
|
|
2936
|
+
const action = await selectPrompt(`${pc13.bold(currentBranch)} is stale but has local work. What would you like to do?`, [SAVE_NEW_BRANCH, DISCARD, CANCEL]);
|
|
3372
2937
|
if (action === CANCEL) {
|
|
3373
2938
|
info("No changes made. You are still on your current branch.");
|
|
3374
2939
|
return;
|
|
3375
2940
|
}
|
|
3376
2941
|
if (action === SAVE_NEW_BRANCH) {
|
|
3377
|
-
info(
|
|
2942
|
+
info(pc13.dim("Tip: Describe what you're working on in plain English and we'll generate a branch name."));
|
|
3378
2943
|
const description = await inputPrompt("What are you working on?");
|
|
3379
2944
|
let newBranchName = description;
|
|
3380
2945
|
if (!args["no-ai"] && looksLikeNaturalLanguage(description)) {
|
|
@@ -3383,8 +2948,8 @@ var update_default = defineCommand12({
|
|
|
3383
2948
|
if (suggested) {
|
|
3384
2949
|
spinner.success("Branch name suggestion ready.");
|
|
3385
2950
|
console.log(`
|
|
3386
|
-
${
|
|
3387
|
-
const accepted = await confirmPrompt(`Use ${
|
|
2951
|
+
${pc13.dim("AI suggestion:")} ${pc13.bold(pc13.cyan(suggested))}`);
|
|
2952
|
+
const accepted = await confirmPrompt(`Use ${pc13.bold(suggested)} as your branch name?`);
|
|
3388
2953
|
newBranchName = accepted ? suggested : await inputPrompt("Enter branch name", description);
|
|
3389
2954
|
} else {
|
|
3390
2955
|
spinner.fail("AI did not return a suggestion.");
|
|
@@ -3392,7 +2957,7 @@ var update_default = defineCommand12({
|
|
|
3392
2957
|
}
|
|
3393
2958
|
}
|
|
3394
2959
|
if (!hasPrefix(newBranchName, config.branchPrefixes)) {
|
|
3395
|
-
const prefix = await selectPrompt(`Choose a branch type for ${
|
|
2960
|
+
const prefix = await selectPrompt(`Choose a branch type for ${pc13.bold(newBranchName)}:`, config.branchPrefixes);
|
|
3396
2961
|
newBranchName = formatBranchName(prefix, newBranchName);
|
|
3397
2962
|
}
|
|
3398
2963
|
if (!isValidBranchName(newBranchName)) {
|
|
@@ -3406,7 +2971,7 @@ var update_default = defineCommand12({
|
|
|
3406
2971
|
error(`Failed to rename branch: ${renameResult.stderr}`);
|
|
3407
2972
|
process.exit(1);
|
|
3408
2973
|
}
|
|
3409
|
-
success(`Renamed ${
|
|
2974
|
+
success(`Renamed ${pc13.bold(currentBranch)} → ${pc13.bold(newBranchName)}`);
|
|
3410
2975
|
await unsetUpstream();
|
|
3411
2976
|
await fetchRemote(syncSource.remote);
|
|
3412
2977
|
let rebaseResult2;
|
|
@@ -3418,11 +2983,11 @@ var update_default = defineCommand12({
|
|
|
3418
2983
|
}
|
|
3419
2984
|
if (rebaseResult2.exitCode !== 0) {
|
|
3420
2985
|
warn("Rebase encountered conflicts. Resolve them manually, then run:");
|
|
3421
|
-
info(` ${
|
|
2986
|
+
info(` ${pc13.bold("git rebase --continue")}`);
|
|
3422
2987
|
} else {
|
|
3423
|
-
success(`Rebased ${
|
|
2988
|
+
success(`Rebased ${pc13.bold(newBranchName)} onto ${pc13.bold(syncSource.ref)}.`);
|
|
3424
2989
|
}
|
|
3425
|
-
info(`All your changes are preserved. Run ${
|
|
2990
|
+
info(`All your changes are preserved. Run ${pc13.bold("contrib submit")} when ready to create a new PR.`);
|
|
3426
2991
|
return;
|
|
3427
2992
|
}
|
|
3428
2993
|
warn("Discarding local changes...");
|
|
@@ -3434,19 +2999,19 @@ var update_default = defineCommand12({
|
|
|
3434
2999
|
process.exit(1);
|
|
3435
3000
|
}
|
|
3436
3001
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
3437
|
-
success(`Synced ${
|
|
3438
|
-
info(`Deleting stale branch ${
|
|
3002
|
+
success(`Synced ${pc13.bold(baseBranch)} with ${pc13.bold(syncSource.ref)}.`);
|
|
3003
|
+
info(`Deleting stale branch ${pc13.bold(currentBranch)}...`);
|
|
3439
3004
|
await forceDeleteBranch(currentBranch);
|
|
3440
|
-
success(`Deleted ${
|
|
3441
|
-
info(`Run ${
|
|
3005
|
+
success(`Deleted ${pc13.bold(currentBranch)}.`);
|
|
3006
|
+
info(`Run ${pc13.bold("contrib start")} to begin a new feature branch.`);
|
|
3442
3007
|
return;
|
|
3443
3008
|
}
|
|
3444
|
-
info(`Updating ${
|
|
3009
|
+
info(`Updating ${pc13.bold(currentBranch)} with latest ${pc13.bold(baseBranch)}...`);
|
|
3445
3010
|
await fetchRemote(syncSource.remote);
|
|
3446
3011
|
await updateLocalBranch(baseBranch, syncSource.ref);
|
|
3447
3012
|
const rebaseStrategy = await determineRebaseStrategy(currentBranch, syncSource.ref);
|
|
3448
3013
|
if (rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase) {
|
|
3449
|
-
info(
|
|
3014
|
+
info(pc13.dim(`Using --onto rebase (branch was based on a different ref)`));
|
|
3450
3015
|
}
|
|
3451
3016
|
const rebaseResult = rebaseStrategy.strategy === "onto" && rebaseStrategy.ontoOldBase ? await rebaseOnto(syncSource.ref, rebaseStrategy.ontoOldBase) : await rebase(syncSource.ref);
|
|
3452
3017
|
if (rebaseResult.exitCode !== 0) {
|
|
@@ -3475,10 +3040,10 @@ ${content.slice(0, 2000)}
|
|
|
3475
3040
|
if (suggestion) {
|
|
3476
3041
|
spinner.success("AI conflict guidance ready.");
|
|
3477
3042
|
console.log(`
|
|
3478
|
-
${
|
|
3479
|
-
console.log(
|
|
3043
|
+
${pc13.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
3044
|
+
console.log(pc13.dim("─".repeat(60)));
|
|
3480
3045
|
console.log(suggestion);
|
|
3481
|
-
console.log(
|
|
3046
|
+
console.log(pc13.dim("─".repeat(60)));
|
|
3482
3047
|
console.log();
|
|
3483
3048
|
} else {
|
|
3484
3049
|
spinner.fail("AI could not analyze the conflicts.");
|
|
@@ -3486,22 +3051,22 @@ ${pc15.bold("\uD83D\uDCA1 AI Conflict Resolution Guidance:")}`);
|
|
|
3486
3051
|
}
|
|
3487
3052
|
}
|
|
3488
3053
|
}
|
|
3489
|
-
console.log(
|
|
3054
|
+
console.log(pc13.bold("To resolve:"));
|
|
3490
3055
|
console.log(` 1. Fix conflicts in the affected files`);
|
|
3491
|
-
console.log(` 2. ${
|
|
3492
|
-
console.log(` 3. ${
|
|
3056
|
+
console.log(` 2. ${pc13.cyan("git add <resolved-files>")}`);
|
|
3057
|
+
console.log(` 3. ${pc13.cyan("git rebase --continue")}`);
|
|
3493
3058
|
console.log();
|
|
3494
|
-
console.log(` Or abort: ${
|
|
3059
|
+
console.log(` Or abort: ${pc13.cyan("git rebase --abort")}`);
|
|
3495
3060
|
process.exit(1);
|
|
3496
3061
|
}
|
|
3497
|
-
success(`✅ ${
|
|
3062
|
+
success(`✅ ${pc13.bold(currentBranch)} has been rebased onto latest ${pc13.bold(baseBranch)}`);
|
|
3498
3063
|
}
|
|
3499
3064
|
});
|
|
3500
3065
|
|
|
3501
3066
|
// src/commands/validate.ts
|
|
3502
|
-
import { defineCommand as
|
|
3503
|
-
import
|
|
3504
|
-
var validate_default =
|
|
3067
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
3068
|
+
import pc14 from "picocolors";
|
|
3069
|
+
var validate_default = defineCommand11({
|
|
3505
3070
|
meta: {
|
|
3506
3071
|
name: "validate",
|
|
3507
3072
|
description: "Validate a commit message against the configured convention"
|
|
@@ -3531,7 +3096,7 @@ var validate_default = defineCommand13({
|
|
|
3531
3096
|
}
|
|
3532
3097
|
const errors = getValidationError(convention);
|
|
3533
3098
|
for (const line of errors) {
|
|
3534
|
-
console.error(
|
|
3099
|
+
console.error(pc14.red(` ✗ ${line}`));
|
|
3535
3100
|
}
|
|
3536
3101
|
process.exit(1);
|
|
3537
3102
|
}
|
|
@@ -3539,7 +3104,7 @@ var validate_default = defineCommand13({
|
|
|
3539
3104
|
|
|
3540
3105
|
// src/ui/banner.ts
|
|
3541
3106
|
import figlet from "figlet";
|
|
3542
|
-
import
|
|
3107
|
+
import pc15 from "picocolors";
|
|
3543
3108
|
var LOGO_BIG;
|
|
3544
3109
|
try {
|
|
3545
3110
|
LOGO_BIG = figlet.textSync(`Contribute
|
|
@@ -3561,14 +3126,14 @@ function getAuthor() {
|
|
|
3561
3126
|
}
|
|
3562
3127
|
function showBanner(variant = "small") {
|
|
3563
3128
|
const logo = variant === "big" ? LOGO_BIG : LOGO_SMALL;
|
|
3564
|
-
console.log(
|
|
3129
|
+
console.log(pc15.cyan(`
|
|
3565
3130
|
${logo}`));
|
|
3566
|
-
console.log(` ${
|
|
3131
|
+
console.log(` ${pc15.dim(`v${getVersion()}`)} ${pc15.dim("—")} ${pc15.dim(`Built by ${getAuthor()}`)}`);
|
|
3567
3132
|
if (variant === "big") {
|
|
3568
3133
|
console.log();
|
|
3569
|
-
console.log(` ${
|
|
3570
|
-
console.log(` ${
|
|
3571
|
-
console.log(` ${
|
|
3134
|
+
console.log(` ${pc15.yellow("Star")} ${pc15.cyan("https://github.com/warengonzaga/contribute-now")}`);
|
|
3135
|
+
console.log(` ${pc15.green("Contribute")} ${pc15.cyan("https://github.com/warengonzaga/contribute-now/blob/main/CONTRIBUTING.md")}`);
|
|
3136
|
+
console.log(` ${pc15.magenta("Sponsor")} ${pc15.cyan("https://warengonzaga.com/sponsor")}`);
|
|
3572
3137
|
}
|
|
3573
3138
|
console.log();
|
|
3574
3139
|
}
|
|
@@ -3576,13 +3141,13 @@ ${logo}`));
|
|
|
3576
3141
|
// src/index.ts
|
|
3577
3142
|
var isVersion = process.argv.includes("--version") || process.argv.includes("-v");
|
|
3578
3143
|
if (!isVersion) {
|
|
3579
|
-
const subCommands = ["setup", "sync", "start", "commit", "update", "submit", "clean", "status", "
|
|
3144
|
+
const subCommands = ["setup", "sync", "start", "commit", "update", "submit", "clean", "status", "hook", "validate", "doctor"];
|
|
3580
3145
|
const isHelp = process.argv.includes("--help") || process.argv.includes("-h");
|
|
3581
3146
|
const hasSubCommand = subCommands.some((cmd) => process.argv.includes(cmd));
|
|
3582
3147
|
const useBigBanner = isHelp || !hasSubCommand;
|
|
3583
3148
|
showBanner(useBigBanner ? "big" : "small");
|
|
3584
3149
|
}
|
|
3585
|
-
var main =
|
|
3150
|
+
var main = defineCommand12({
|
|
3586
3151
|
meta: {
|
|
3587
3152
|
name: "contrib",
|
|
3588
3153
|
version: getVersion(),
|
|
@@ -3602,10 +3167,8 @@ var main = defineCommand14({
|
|
|
3602
3167
|
commit: commit_default,
|
|
3603
3168
|
update: update_default,
|
|
3604
3169
|
submit: submit_default,
|
|
3605
|
-
branch: branch_default,
|
|
3606
3170
|
clean: clean_default,
|
|
3607
3171
|
status: status_default,
|
|
3608
|
-
log: log_default,
|
|
3609
3172
|
hook: hook_default,
|
|
3610
3173
|
validate: validate_default,
|
|
3611
3174
|
doctor: doctor_default
|