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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +101 -283
  3. 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 skips that repository without sending its diff; when Gitleaks is absent, the scan is skipped. The staged diff is sent to the configured endpoint, which may be remote.
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
- Preparation and commit failures are reported per repository while the others continue. A push failure stops all remaining sends. Any failure produces a nonzero exit code.
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
- In a terminal, commit, pull, and push output is streamed into Clack task logs; parallel pulls share one log grouped by repository. Silent commits and pushes create no output block. Git keeps terminal input, but stdout/stderr are pipes: hooks requiring a TTY on those streams are not supported. Git controls hook stdin; hooks can read from `/dev/tty`, although prompts written there appear outside the blocks. `--yes` skips commit review, not native prompts. Without a terminal, stdout/stderr are streamed directly. Each hook has a 60-second deadline, including time spent answering prompts; override it with `ZD_HOOK_TIMEOUT=120 zapdev commit`.
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), A2 = () => {
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
- A2(), T2 = setInterval(() => {
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.17.0",
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 styleText4 } from "node:util";
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
- import { styleText as styleText3 } from "node:util";
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 && !group ? spinner({ withGuide: false }) : void 0;
2590
- const startLoading = () => {
2591
- if (!loader) return;
2592
- const wasRaw = Boolean(process.stdin.isRaw);
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(onHook, onOutput);
2622
- loader?.clear();
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 true;
2480
+ return result;
2626
2481
  } catch (error) {
2627
2482
  loader?.clear();
2628
- const message = `${label}: ${operation} failed${error instanceof GitOutputError ? "" : `: ${errorMessage(error)}`}`;
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
- const repos = await findRepos(process.cwd());
2699
- if (repos.length === 0) {
2700
- log.warn("No git repository found here or in direct children.");
2701
- if (interactive) outro("Nothing to do.");
2702
- return;
2703
- }
2704
- const loader = interactive ? spinner() : void 0;
2705
- if (args.pull) {
2706
- loader?.start("Pulling repositories in parallel");
2707
- let pullLog;
2708
- const pulls = await Promise.allSettled(repos.map(async (repo) => {
2709
- const label = basename(repo);
2710
- const hasChanges = args.staged ? Boolean((await getStagedDiff(repo)).trim()) : (await getRepoStatus(repo)).hasChanges;
2711
- if (!hasChanges) return true;
2712
- const branch = await currentBranch(repo);
2713
- if (!await getUpstreamRemote(repo, branch)) {
2714
- return `${label}: no configured upstream remote. Skipping pull.`;
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
- if (interactive) {
2717
- loader?.clear();
2718
- pullLog ??= taskLog({ title: "Pulling repositories in parallel", limit: 10, retainLog: true });
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
- if (failed) {
2731
- process.exitCode = 1;
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
- let scan;
2736
- try {
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 = styleText4("bold", draft.label);
2632
+ draft.pendingLabel = styleText3("bold", draft.label);
2798
2633
  }
2799
2634
  }
2800
- if (!await runGitTask(draft.label, "commit", `${draft.label}: committed ${draft.message}`, (onHook, onOutput) => commit(draft.repo, draft.message, onHook, onOutput))) {
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
- if (toSend.length === 0) return;
2811
- if (!interactive && !args.push) return;
2812
- const destinations = [];
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
- let shouldPush = Boolean(args.push);
2826
- if (!shouldPush && interactive && destinations.length) {
2827
- const answer = await confirm({
2828
- message: `Push ${destinations.map(({ label }) => label).join(", ")}?`,
2829
- initialValue: false
2830
- });
2831
- if (isCancel(answer)) {
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(`(${styleText4("green", `+${stats.additions}`)} ${styleText4("red", `-${stats.deletions}`)}) ${pendingLabel}: ${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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapdev",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
5
5
  "type": "module",
6
6
  "bin": {