zapdev 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.js +101 -283
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,13 +139,13 @@ zapdev commit -yp # skip review and push confirmation; choose a branch
|
|
|
139
139
|
zapdev commit --pull # pull first, then follow the normal commit flow
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
Before contacting the LLM endpoint, zapdev runs `gitleaks git --staged --verbose` in each changed repository when Gitleaks is installed. A failed scan
|
|
142
|
+
Before contacting the LLM endpoint, zapdev runs `gitleaks git --staged --verbose` in each changed repository when Gitleaks is installed. A failed scan never sends that repository's diff to the LLM and stops the flow before any commit; when Gitleaks is absent, the scan is skipped. The staged diff is sent to the configured endpoint, which may be remote.
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
Any error stops the flow with a nonzero exit code. Parallel work already in flight finishes before exiting; later steps never start. Earlier successful pulls or commits remain applied.
|
|
145
145
|
|
|
146
146
|
With `--pull`, only repositories with local changes are pulled, in parallel. With `--staged`, only repositories with a staged diff are pulled. All pulls must finish successfully before preparation and message generation start in parallel. Git uses each current branch's configured upstream. Repositories without a configured upstream remote are skipped with a warning, even if they have a single remote. Divergent history, a missing upstream branch, or conflicting local changes stop the flow before staging, after all in-flight pulls finish; no rebase, merge commit, or automatic stash is performed. Successful pulls remain applied.
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Each step uses one spinner: start, wait, stop. Normal Git and hook output is captured without live task logs; failure diagnostics are displayed once. Without a terminal, steps print plain progress messages instead. Git keeps terminal input, but interactive hooks must read and write through `/dev/tty`; prompts on captured stdout/stderr are not displayed. Each hook retains a 60-second deadline; override it with `ZD_HOOK_TIMEOUT=120 zapdev commit`. `--yes` skips commit review, not native prompts.
|
|
149
149
|
|
|
150
150
|
Immediately before each selected commit, zapdev asks for a local branch only when the current name exactly matches `main`, `master`, `principal`, `dev`, or `development`, including with `--yes`. The first prompt starts empty; subsequent prompts reuse the previous input across repositories. Press Enter to accept the prefilled name; an empty or cleared input keeps the current branch and leaves the next prompt empty. A different name switches to the existing local branch or creates it from HEAD, without forcing or discarding pending changes. Other branches and repositories with nothing to commit never trigger this prompt. Cancelling stops the remaining commits and all pushes; earlier commits remain local.
|
|
151
151
|
|
package/dist/cli.js
CHANGED
|
@@ -830,7 +830,6 @@ function isUnicodeSupported() {
|
|
|
830
830
|
}
|
|
831
831
|
var unicode = isUnicodeSupported();
|
|
832
832
|
var isCI = () => process.env.CI === "true";
|
|
833
|
-
var isTTY = (o2) => o2.isTTY === true;
|
|
834
833
|
var unicodeOr = (o2, e2) => unicode ? o2 : e2;
|
|
835
834
|
var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
|
|
836
835
|
var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
|
|
@@ -1076,7 +1075,7 @@ var spinner = ({
|
|
|
1076
1075
|
const x2 = getColumns(n4), k2 = I3?.styleFrame ?? W, g2 = (e2) => {
|
|
1077
1076
|
const r3 = e2 > 1 ? O2 ?? settings.messages.error : G ?? settings.messages.cancel;
|
|
1078
1077
|
S2 = e2 === 1, d2 && (a3(r3, e2), S2 && typeof h3 == "function" && h3());
|
|
1079
|
-
}, f2 = () => g2(2), i3 = () => g2(1),
|
|
1078
|
+
}, f2 = () => g2(2), i3 = () => g2(1), A = () => {
|
|
1080
1079
|
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i3), process.on("SIGTERM", i3), process.on("exit", g2), m2 && m2.addEventListener("abort", i3);
|
|
1081
1080
|
}, H = () => {
|
|
1082
1081
|
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i3), process.removeListener("SIGTERM", i3), process.removeListener("exit", g2), m2 && m2.removeEventListener("abort", i3);
|
|
@@ -1097,7 +1096,7 @@ var spinner = ({
|
|
|
1097
1096
|
d2 = true, M = block({ output: n4 }), s2 = C2(e2), w2 = performance.now(), N2 && n4.write(`${styleText2("gray", S_BAR)}
|
|
1098
1097
|
`);
|
|
1099
1098
|
let r3 = 0, t3 = 0;
|
|
1100
|
-
|
|
1099
|
+
A(), T2 = setInterval(() => {
|
|
1101
1100
|
if (u4 && s2 === p2)
|
|
1102
1101
|
return;
|
|
1103
1102
|
y2(), p2 = s2;
|
|
@@ -1225,123 +1224,6 @@ ${b2}
|
|
|
1225
1224
|
}).prompt();
|
|
1226
1225
|
};
|
|
1227
1226
|
var i = `${styleText2("gray", S_BAR)} `;
|
|
1228
|
-
var A = (l3) => l3.replace(/\x1b\[(?:\d+;)*\d*[ABCDEFGHfJKSTsu]|\x1b\[(s|u)/g, "");
|
|
1229
|
-
var taskLog = (l3) => {
|
|
1230
|
-
const r3 = l3.output ?? process.stdout, O2 = getColumns(r3), i3 = styleText2("gray", S_BAR), p2 = l3.spacing ?? 1, k2 = 3, m2 = l3.retainLog === true, d2 = !isCI() && isTTY(r3);
|
|
1231
|
-
r3.write(`${i3}
|
|
1232
|
-
`), r3.write(`${styleText2("green", S_STEP_SUBMIT)} ${l3.title}
|
|
1233
|
-
`);
|
|
1234
|
-
for (let e2 = 0; e2 < p2; e2++)
|
|
1235
|
-
r3.write(`${i3}
|
|
1236
|
-
`);
|
|
1237
|
-
const n4 = [
|
|
1238
|
-
{
|
|
1239
|
-
value: "",
|
|
1240
|
-
full: ""
|
|
1241
|
-
}
|
|
1242
|
-
];
|
|
1243
|
-
let v2 = false;
|
|
1244
|
-
const f2 = (e2) => {
|
|
1245
|
-
if (n4.length === 0)
|
|
1246
|
-
return;
|
|
1247
|
-
let s2 = 0;
|
|
1248
|
-
e2 && (s2 += p2 + 2);
|
|
1249
|
-
for (const t3 of n4) {
|
|
1250
|
-
const { value: o2, result: a3 } = t3;
|
|
1251
|
-
let g2 = a3?.message ?? o2;
|
|
1252
|
-
if (g2.length === 0)
|
|
1253
|
-
continue;
|
|
1254
|
-
a3 === void 0 && t3.header !== void 0 && t3.header !== "" && (g2 += `
|
|
1255
|
-
${t3.header}`);
|
|
1256
|
-
const E2 = g2.split(`
|
|
1257
|
-
`).reduce((b2, w2) => w2 === "" ? b2 + 1 : b2 + Math.ceil((w2.length + k2) / O2), 0);
|
|
1258
|
-
s2 += E2;
|
|
1259
|
-
}
|
|
1260
|
-
s2 > 0 && (s2 += 1, r3.write(import_sisteransi2.erase.lines(s2)));
|
|
1261
|
-
}, h3 = (e2, s2, t3) => {
|
|
1262
|
-
const o2 = t3 ? `${e2.full}
|
|
1263
|
-
${e2.value}` : e2.value;
|
|
1264
|
-
e2.header !== void 0 && e2.header !== "" && log.message(
|
|
1265
|
-
e2.header.split(`
|
|
1266
|
-
`).map((a3) => styleText2("bold", a3)),
|
|
1267
|
-
{
|
|
1268
|
-
output: r3,
|
|
1269
|
-
secondarySymbol: i3,
|
|
1270
|
-
symbol: i3,
|
|
1271
|
-
spacing: 0
|
|
1272
|
-
}
|
|
1273
|
-
), log.message(
|
|
1274
|
-
o2.split(`
|
|
1275
|
-
`).map((a3) => styleText2("dim", a3)),
|
|
1276
|
-
{
|
|
1277
|
-
output: r3,
|
|
1278
|
-
secondarySymbol: i3,
|
|
1279
|
-
symbol: i3,
|
|
1280
|
-
spacing: s2 ?? p2
|
|
1281
|
-
}
|
|
1282
|
-
);
|
|
1283
|
-
}, T2 = () => {
|
|
1284
|
-
for (const e2 of n4) {
|
|
1285
|
-
const { header: s2, value: t3, full: o2 } = e2;
|
|
1286
|
-
(s2 === void 0 || s2.length === 0) && t3.length === 0 || h3(e2, void 0, m2 === true && o2.length > 0);
|
|
1287
|
-
}
|
|
1288
|
-
}, L = (e2, s2, t3) => {
|
|
1289
|
-
if (f2(false), (t3?.raw !== true || !v2) && e2.value !== "" && (e2.value += `
|
|
1290
|
-
`), e2.value += A(s2), v2 = t3?.raw === true, l3.limit !== void 0) {
|
|
1291
|
-
const o2 = e2.value.split(`
|
|
1292
|
-
`), a3 = o2.length - l3.limit;
|
|
1293
|
-
if (a3 > 0) {
|
|
1294
|
-
const g2 = o2.splice(0, a3);
|
|
1295
|
-
m2 && (e2.full += (e2.full === "" ? "" : `
|
|
1296
|
-
`) + g2.join(`
|
|
1297
|
-
`));
|
|
1298
|
-
}
|
|
1299
|
-
e2.value = o2.join(`
|
|
1300
|
-
`);
|
|
1301
|
-
}
|
|
1302
|
-
d2 && y2();
|
|
1303
|
-
}, y2 = () => {
|
|
1304
|
-
for (const e2 of n4)
|
|
1305
|
-
e2.result ? e2.result.status === "error" ? log.error(e2.result.message, { output: r3, secondarySymbol: i3, spacing: 0 }) : log.success(e2.result.message, { output: r3, secondarySymbol: i3, spacing: 0 }) : e2.value !== "" && h3(e2, 0);
|
|
1306
|
-
}, B = (e2, s2) => {
|
|
1307
|
-
f2(false), e2.result = s2, d2 && y2();
|
|
1308
|
-
};
|
|
1309
|
-
return {
|
|
1310
|
-
message(e2, s2) {
|
|
1311
|
-
L(n4[0], e2, s2);
|
|
1312
|
-
},
|
|
1313
|
-
group(e2) {
|
|
1314
|
-
const s2 = {
|
|
1315
|
-
header: e2,
|
|
1316
|
-
value: "",
|
|
1317
|
-
full: ""
|
|
1318
|
-
};
|
|
1319
|
-
return n4.push(s2), {
|
|
1320
|
-
message(t3, o2) {
|
|
1321
|
-
L(s2, t3, o2);
|
|
1322
|
-
},
|
|
1323
|
-
error(t3) {
|
|
1324
|
-
B(s2, {
|
|
1325
|
-
status: "error",
|
|
1326
|
-
message: t3
|
|
1327
|
-
});
|
|
1328
|
-
},
|
|
1329
|
-
success(t3) {
|
|
1330
|
-
B(s2, {
|
|
1331
|
-
status: "success",
|
|
1332
|
-
message: t3
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
};
|
|
1336
|
-
},
|
|
1337
|
-
error(e2, s2) {
|
|
1338
|
-
f2(true), log.error(e2, { output: r3, secondarySymbol: i3, spacing: 1 }), s2?.showLog !== false && T2(), n4.splice(1, n4.length - 1), n4[0].value = "", n4[0].full = "";
|
|
1339
|
-
},
|
|
1340
|
-
success(e2, s2) {
|
|
1341
|
-
f2(true), log.success(e2, { output: r3, secondarySymbol: i3, spacing: 1 }), s2?.showLog === true && T2(), n4.splice(1, n4.length - 1), n4[0].value = "", n4[0].full = "";
|
|
1342
|
-
}
|
|
1343
|
-
};
|
|
1344
|
-
};
|
|
1345
1227
|
var text = (e2) => new n2({
|
|
1346
1228
|
validate: e2.validate,
|
|
1347
1229
|
placeholder: e2.placeholder,
|
|
@@ -1854,7 +1736,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
1854
1736
|
// package.json
|
|
1855
1737
|
var package_default = {
|
|
1856
1738
|
name: "zapdev",
|
|
1857
|
-
version: "1.
|
|
1739
|
+
version: "1.18.0",
|
|
1858
1740
|
description: "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
|
|
1859
1741
|
type: "module",
|
|
1860
1742
|
bin: {
|
|
@@ -1914,7 +1796,7 @@ var package_default = {
|
|
|
1914
1796
|
|
|
1915
1797
|
// src/commands/commit.ts
|
|
1916
1798
|
import { basename } from "node:path";
|
|
1917
|
-
import { styleText as
|
|
1799
|
+
import { styleText as styleText3 } from "node:util";
|
|
1918
1800
|
|
|
1919
1801
|
// src/lib/config.ts
|
|
1920
1802
|
function resolveHookTimeout(env = process.env) {
|
|
@@ -2582,54 +2464,23 @@ var COMMIT_TYPES = [
|
|
|
2582
2464
|
];
|
|
2583
2465
|
|
|
2584
2466
|
// src/commands/git-task.ts
|
|
2585
|
-
|
|
2586
|
-
async function runGitTask(label, operation, successMessage, run, group) {
|
|
2587
|
-
const title = `${label}: ${operation}`;
|
|
2467
|
+
async function runTask(title, successMessage, run) {
|
|
2588
2468
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
2589
|
-
const loader = interactive
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
loader.start(title);
|
|
2469
|
+
const loader = interactive ? spinner({ withGuide: false }) : void 0;
|
|
2470
|
+
const wasRaw = Boolean(process.stdin.isRaw);
|
|
2471
|
+
loader?.start(title);
|
|
2472
|
+
if (loader) {
|
|
2594
2473
|
process.stdin.setRawMode?.(wasRaw);
|
|
2595
2474
|
process.stdin.pause();
|
|
2596
|
-
};
|
|
2597
|
-
let task = group;
|
|
2598
|
-
const onOutput = (chunk, stream) => {
|
|
2599
|
-
if (!interactive) {
|
|
2600
|
-
process[stream].write(chunk);
|
|
2601
|
-
return;
|
|
2602
|
-
}
|
|
2603
|
-
if (!task) loader?.clear();
|
|
2604
|
-
task ??= taskLog({ title: `${title}: running\u2026`, limit: 10, retainLog: true });
|
|
2605
|
-
task.message(chunk, { raw: true });
|
|
2606
|
-
};
|
|
2607
|
-
const onHook = (event) => {
|
|
2608
|
-
loader?.clear();
|
|
2609
|
-
if (event.phase === "start" || event.exitCode !== 0) {
|
|
2610
|
-
const hook = `${label}: ${event.name}`;
|
|
2611
|
-
const message = event.phase === "start" ? `${hook}: running\u2026` : `${hook}: failed (exit ${event.exitCode})`;
|
|
2612
|
-
if (task) task.message(message);
|
|
2613
|
-
else if (event.phase === "start") log.step(styleText3("bold", message));
|
|
2614
|
-
else log.error(message);
|
|
2615
|
-
}
|
|
2616
|
-
if (event.phase === "exit" && !task) startLoading();
|
|
2617
|
-
};
|
|
2618
|
-
if (!interactive) log.info(title);
|
|
2619
|
-
startLoading();
|
|
2475
|
+
} else log.info(title);
|
|
2620
2476
|
try {
|
|
2621
|
-
await run(
|
|
2622
|
-
loader
|
|
2623
|
-
if (task) task.success(successMessage, { showLog: true });
|
|
2477
|
+
const result = await run(() => void 0);
|
|
2478
|
+
if (loader) loader.stop(successMessage);
|
|
2624
2479
|
else log.success(successMessage);
|
|
2625
|
-
return
|
|
2480
|
+
return result;
|
|
2626
2481
|
} catch (error) {
|
|
2627
2482
|
loader?.clear();
|
|
2628
|
-
|
|
2629
|
-
group?.message(message);
|
|
2630
|
-
if (task) task.error(message);
|
|
2631
|
-
else if (!(error instanceof GitOutputError && error.hookFailureReported)) log.error(message);
|
|
2632
|
-
return false;
|
|
2483
|
+
throw new Error(`${title} failed: ${errorMessage(error)}`, { cause: error });
|
|
2633
2484
|
}
|
|
2634
2485
|
}
|
|
2635
2486
|
|
|
@@ -2695,89 +2546,73 @@ var commitCommand = defineCommand({
|
|
|
2695
2546
|
return;
|
|
2696
2547
|
}
|
|
2697
2548
|
if (interactive) intro("zapdev commit");
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2549
|
+
try {
|
|
2550
|
+
const repos = await findRepos(process.cwd());
|
|
2551
|
+
if (repos.length === 0) {
|
|
2552
|
+
log.warn("No git repository found here or in direct children.");
|
|
2553
|
+
if (interactive) outro("Nothing to do.");
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (args.pull) {
|
|
2557
|
+
const pulls = await runTask("Pulling repositories in parallel", "Repositories pulled", async (onOutput) => {
|
|
2558
|
+
const results2 = await Promise.allSettled(repos.map(async (repo) => {
|
|
2559
|
+
const label = basename(repo);
|
|
2560
|
+
const hasChanges = args.staged ? Boolean((await getStagedDiff(repo)).trim()) : (await getRepoStatus(repo)).hasChanges;
|
|
2561
|
+
if (!hasChanges) return null;
|
|
2562
|
+
const branch = await currentBranch(repo);
|
|
2563
|
+
if (!await getUpstreamRemote(repo, branch)) {
|
|
2564
|
+
return `${label}: no configured upstream remote. Skipping pull.`;
|
|
2565
|
+
}
|
|
2566
|
+
await git(["pull", "--ff-only", "--no-rebase", "--no-autostash"], repo, void 0, onOutput);
|
|
2567
|
+
return null;
|
|
2568
|
+
}));
|
|
2569
|
+
for (const [index, result] of results2.entries()) {
|
|
2570
|
+
if (result.status === "rejected") throw new Error(`${basename(repos[index])}: ${errorMessage(result.reason)}`);
|
|
2571
|
+
}
|
|
2572
|
+
return results2;
|
|
2573
|
+
});
|
|
2574
|
+
for (const result of pulls) {
|
|
2575
|
+
if (result.status === "fulfilled" && result.value) log.warn(result.value);
|
|
2715
2576
|
}
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2577
|
+
}
|
|
2578
|
+
const repositories = repos.map((repo) => ({ repo, label: basename(repo), pendingLabel: basename(repo) }));
|
|
2579
|
+
const { results, scan } = await runTask("Preparing repositories and generating commit messages in parallel", "Repositories prepared", async () => {
|
|
2580
|
+
const scan2 = await hasGitleaks();
|
|
2581
|
+
const results2 = await Promise.allSettled(repositories.map(async (repository) => {
|
|
2582
|
+
const { repo } = repository;
|
|
2583
|
+
const { branch, hasChanges } = await getRepoStatus(repo);
|
|
2584
|
+
repository.label = `${basename(repo)} (${branch})`;
|
|
2585
|
+
repository.pendingLabel = hasChanges ? styleText3("bold", repository.label) : repository.label;
|
|
2586
|
+
if (!args.staged) await stageAll(repo);
|
|
2587
|
+
const diff = await getStagedDiff(repo);
|
|
2588
|
+
if (!diff.trim()) return null;
|
|
2589
|
+
const stats = await getStagedDiffStats(repo);
|
|
2590
|
+
if (scan2) await scanStagedChanges(repo);
|
|
2591
|
+
const message = await generateCommitMessage(diff, config, args.type);
|
|
2592
|
+
if (!message) throw new Error("The model returned an empty message.");
|
|
2593
|
+
return { ...repository, message, stats };
|
|
2594
|
+
}));
|
|
2595
|
+
for (const [index, result] of results2.entries()) {
|
|
2596
|
+
if (result.status === "rejected") throw new Error(`${repositories[index].pendingLabel}: ${errorMessage(result.reason)}`);
|
|
2597
|
+
}
|
|
2598
|
+
return { results: results2, scan: scan2 };
|
|
2599
|
+
});
|
|
2600
|
+
if (!scan) log.info("Gitleaks not found, skipping secret scan.");
|
|
2601
|
+
const drafts = [];
|
|
2602
|
+
for (const [index, result] of results.entries()) {
|
|
2603
|
+
if (result.status === "fulfilled") {
|
|
2604
|
+
if (result.value) drafts.push(result.value);
|
|
2605
|
+
else log.info(`${repositories[index].pendingLabel}: nothing to commit.`);
|
|
2719
2606
|
}
|
|
2720
|
-
return runGitTask(label, "pull", `${label}: pulled`, (onHook, onOutput) => git(["pull", "--ff-only", "--no-rebase", "--no-autostash"], repo, onHook, onOutput), pullLog?.group(label));
|
|
2721
|
-
}));
|
|
2722
|
-
loader?.clear();
|
|
2723
|
-
const failed = pulls.some((result) => result.status === "rejected" || result.value === false);
|
|
2724
|
-
if (failed) pullLog?.error("Pulling failed");
|
|
2725
|
-
else pullLog?.success("Repositories pulled", { showLog: true });
|
|
2726
|
-
for (const [index, result] of pulls.entries()) {
|
|
2727
|
-
if (result.status === "rejected") reportGitFailure(`${basename(repos[index])}: pull failed`, result.reason);
|
|
2728
|
-
else if (typeof result.value === "string") log.warn(result.value);
|
|
2729
2607
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2608
|
+
const selected = drafts.length ? await reviewMessages(drafts, interactive && !args.yes) : [];
|
|
2609
|
+
if (!selected) {
|
|
2610
|
+
cancel("Cancelled (changes left staged).");
|
|
2732
2611
|
return;
|
|
2733
2612
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
scan = await hasGitleaks();
|
|
2738
|
-
} catch (error) {
|
|
2739
|
-
log.error(`Gitleaks check failed: ${errorMessage(error)}`);
|
|
2740
|
-
process.exitCode = 1;
|
|
2741
|
-
return;
|
|
2742
|
-
}
|
|
2743
|
-
if (!scan) log.info("Gitleaks not found, skipping secret scan.");
|
|
2744
|
-
loader?.start("Preparing repositories and generating commit messages in parallel");
|
|
2745
|
-
const repositories = repos.map((repo) => ({ repo, label: basename(repo), pendingLabel: basename(repo) }));
|
|
2746
|
-
const results = await Promise.allSettled(repositories.map(async (repository) => {
|
|
2747
|
-
const { repo } = repository;
|
|
2748
|
-
const { branch, hasChanges } = await getRepoStatus(repo);
|
|
2749
|
-
repository.label = `${basename(repo)} (${branch})`;
|
|
2750
|
-
repository.pendingLabel = hasChanges ? styleText4("bold", repository.label) : repository.label;
|
|
2751
|
-
if (!args.staged) await stageAll(repo);
|
|
2752
|
-
const diff = await getStagedDiff(repo);
|
|
2753
|
-
if (!diff.trim()) return null;
|
|
2754
|
-
const stats = await getStagedDiffStats(repo);
|
|
2755
|
-
if (scan) await scanStagedChanges(repo);
|
|
2756
|
-
const message = await generateCommitMessage(diff, config, args.type);
|
|
2757
|
-
if (!message) throw new Error("The model returned an empty message.");
|
|
2758
|
-
return { ...repository, message, stats };
|
|
2759
|
-
}));
|
|
2760
|
-
loader?.stop("Repositories prepared");
|
|
2761
|
-
const drafts = [];
|
|
2762
|
-
for (const [index, result] of results.entries()) {
|
|
2763
|
-
if (result.status === "rejected") {
|
|
2764
|
-
log.error(`${repositories[index].pendingLabel}: ${errorMessage(result.reason)}`);
|
|
2765
|
-
process.exitCode = 1;
|
|
2766
|
-
} else if (result.value) {
|
|
2767
|
-
drafts.push(result.value);
|
|
2768
|
-
} else {
|
|
2769
|
-
log.info(`${repositories[index].pendingLabel}: nothing to commit.`);
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
const selected = drafts.length ? await reviewMessages(drafts, interactive && !args.yes) : [];
|
|
2773
|
-
if (!selected) {
|
|
2774
|
-
cancel("Cancelled (changes left staged).");
|
|
2775
|
-
return;
|
|
2776
|
-
}
|
|
2777
|
-
const toSend = [];
|
|
2778
|
-
let previousBranchInput = "";
|
|
2779
|
-
for (const draft of selected) {
|
|
2780
|
-
try {
|
|
2613
|
+
const toSend = [];
|
|
2614
|
+
let previousBranchInput = "";
|
|
2615
|
+
for (const draft of selected) {
|
|
2781
2616
|
const current = await currentBranch(draft.repo);
|
|
2782
2617
|
if (/^(main|master|principal|dev|development)$/.test(current)) {
|
|
2783
2618
|
if (!interactive) throw new Error("Committing on a protected branch requires a terminal to choose a branch. Changes remain staged.");
|
|
@@ -2794,66 +2629,49 @@ var commitCommand = defineCommand({
|
|
|
2794
2629
|
if (branch !== current) {
|
|
2795
2630
|
await switchBranch(draft.repo, branch);
|
|
2796
2631
|
draft.label = `${basename(draft.repo)} (${branch})`;
|
|
2797
|
-
draft.pendingLabel =
|
|
2632
|
+
draft.pendingLabel = styleText3("bold", draft.label);
|
|
2798
2633
|
}
|
|
2799
2634
|
}
|
|
2800
|
-
|
|
2801
|
-
process.exitCode = 1;
|
|
2802
|
-
continue;
|
|
2803
|
-
}
|
|
2635
|
+
await runTask(`${draft.label}: commit`, `${draft.label}: committed ${draft.message}`, (onOutput) => commit(draft.repo, draft.message, void 0, onOutput));
|
|
2804
2636
|
toSend.push(draft);
|
|
2805
|
-
} catch (error) {
|
|
2806
|
-
reportGitFailure(`${draft.pendingLabel}: commit failed`, error);
|
|
2807
|
-
process.exitCode = 1;
|
|
2808
2637
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
for (const { repo, label } of toSend) {
|
|
2814
|
-
try {
|
|
2638
|
+
if (toSend.length === 0) return;
|
|
2639
|
+
if (!interactive && !args.push) return;
|
|
2640
|
+
const destinations = [];
|
|
2641
|
+
for (const { repo, label } of toSend) {
|
|
2815
2642
|
const branch = await currentBranch(repo);
|
|
2816
2643
|
const remote = await getUpstreamRemote(repo, branch);
|
|
2817
2644
|
if (remote) destinations.push({ repo, label, branch, remote });
|
|
2818
2645
|
else log.warn(`${label}: no configured upstream remote. Skipping push; commit remains local.`);
|
|
2819
|
-
} catch (error) {
|
|
2820
|
-
reportGitFailure(`${label}: push failed`, error);
|
|
2821
|
-
process.exitCode = 1;
|
|
2822
|
-
return;
|
|
2823
2646
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
outro("Committed. Not pushed.");
|
|
2833
|
-
return;
|
|
2834
|
-
}
|
|
2835
|
-
shouldPush = answer;
|
|
2836
|
-
}
|
|
2837
|
-
if (shouldPush) {
|
|
2838
|
-
for (const { repo, label, branch, remote } of destinations) {
|
|
2839
|
-
if (!await runGitTask(label, "push", `${label}: pushed to ${remote}/${branch}`, (onHook, onOutput) => push(repo, remote, branch, onHook, onOutput))) {
|
|
2840
|
-
process.exitCode = 1;
|
|
2841
|
-
outro("Sending stopped after a Git failure.");
|
|
2647
|
+
let shouldPush = Boolean(args.push);
|
|
2648
|
+
if (!shouldPush && interactive && destinations.length) {
|
|
2649
|
+
const answer = await confirm({
|
|
2650
|
+
message: `Push ${destinations.map(({ label }) => label).join(", ")}?`,
|
|
2651
|
+
initialValue: false
|
|
2652
|
+
});
|
|
2653
|
+
if (isCancel(answer)) {
|
|
2654
|
+
outro("Committed. Not pushed.");
|
|
2842
2655
|
return;
|
|
2843
2656
|
}
|
|
2657
|
+
shouldPush = answer;
|
|
2658
|
+
}
|
|
2659
|
+
if (shouldPush) {
|
|
2660
|
+
for (const { repo, label, branch, remote } of destinations) {
|
|
2661
|
+
await runTask(`${label}: push`, `${label}: pushed to ${remote}/${branch}`, (onOutput) => push(repo, remote, branch, void 0, onOutput));
|
|
2662
|
+
}
|
|
2844
2663
|
}
|
|
2664
|
+
if (interactive) outro("Done.");
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
log.error(errorMessage(error));
|
|
2667
|
+
process.exitCode = 1;
|
|
2845
2668
|
}
|
|
2846
|
-
if (interactive) outro(process.exitCode ? "Finished with errors." : "Done.");
|
|
2847
2669
|
}
|
|
2848
2670
|
});
|
|
2849
|
-
function reportGitFailure(message, error) {
|
|
2850
|
-
if (error instanceof GitOutputError && error.hookFailureReported) return;
|
|
2851
|
-
log.error(error instanceof GitOutputError ? message : `${message}: ${errorMessage(error)}`);
|
|
2852
|
-
}
|
|
2853
2671
|
async function reviewMessages(drafts, canPrompt) {
|
|
2854
2672
|
while (true) {
|
|
2855
2673
|
for (const { stats, pendingLabel, message } of drafts) {
|
|
2856
|
-
log.message(`(${
|
|
2674
|
+
log.message(`(${styleText3("green", `+${stats.additions}`)} ${styleText3("red", `-${stats.deletions}`)}) ${pendingLabel}: ${message}`);
|
|
2857
2675
|
}
|
|
2858
2676
|
if (!canPrompt) return drafts;
|
|
2859
2677
|
const action = await select({
|