zapdev 0.7.1 → 0.8.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 -0
  2. package/dist/cli.js +90 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,6 +36,7 @@ zapdev commit
36
36
  | --- | --- |
37
37
  | `-m, --model <model>` | Override the Ollama model |
38
38
  | `-t, --type <type>` | Force the Conventional Commits type (`feat`, `fix`, `chore`, …) |
39
+ | `--pull` | Rebase onto upstream (`git pull --rebase`) after committing, before pushing |
39
40
  | `-p, --push` | Push after committing without asking |
40
41
  | `-y, --yes` | Skip prompts and commit directly |
41
42
 
@@ -43,6 +44,8 @@ zapdev commit
43
44
  zapdev commit -t feat # force the type; the model still writes scope + description
44
45
  ```
45
46
 
47
+ Pushing is optimistic — no preliminary fetch, so the common case stays a single round-trip. If the push is rejected because the branch is behind its upstream, in a TTY it offers to `git pull --rebase` and retry; non-interactively (`--yes` / piped) it stops and asks you to re-run with `--pull`. Pass `--pull` to rebase onto upstream right after the commit (a rebase conflict stops before pushing). `--yes` only silences prompts; it never rebases on its own.
48
+
46
49
  Without a TTY (piped / CI), it commits automatically and only pushes when `--push` is set.
47
50
 
48
51
  ### `zapdev reset`
package/dist/cli.js CHANGED
@@ -1718,7 +1718,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
1718
1718
  // package.json
1719
1719
  var package_default = {
1720
1720
  name: "zapdev",
1721
- version: "0.7.1",
1721
+ version: "0.8.0",
1722
1722
  description: "Fast, performant and lightweight git chores and dev tools using your terminal and your own local LLM.",
1723
1723
  type: "module",
1724
1724
  bin: {
@@ -2222,6 +2222,16 @@ async function push() {
2222
2222
  async function pushSetUpstream(branch) {
2223
2223
  await git(["push", "-u", "origin", branch]);
2224
2224
  }
2225
+ async function pullRebase() {
2226
+ await git(["pull", "--rebase"]);
2227
+ }
2228
+ async function fetchRemote() {
2229
+ await git(["fetch"]);
2230
+ }
2231
+ async function behindCount() {
2232
+ const out = await tryGit(["rev-list", "--count", `HEAD..${UPSTREAM_REF}`]);
2233
+ return out === null ? 0 : Number(out.trim()) || 0;
2234
+ }
2225
2235
  async function findRepos(path) {
2226
2236
  if (await isGitRepo(path)) return [path];
2227
2237
  const entries = await readdir(path, { withFileTypes: true }).catch(() => null);
@@ -2372,6 +2382,10 @@ var commitCommand = defineCommand({
2372
2382
  alias: "t",
2373
2383
  description: `Force the Conventional Commits type (${COMMIT_TYPES.join(", ")}).`
2374
2384
  },
2385
+ pull: {
2386
+ type: "boolean",
2387
+ description: "Rebase onto upstream (git pull --rebase) after committing, before pushing."
2388
+ },
2375
2389
  push: {
2376
2390
  type: "boolean",
2377
2391
  alias: "p",
@@ -2457,6 +2471,10 @@ var commitCommand = defineCommand({
2457
2471
  }
2458
2472
  await commit(finalMessage);
2459
2473
  if (interactive) log.success(`Committed: ${finalMessage}`);
2474
+ if (args.pull && !await rebaseOnUpstream(interactive)) {
2475
+ process.exitCode = 1;
2476
+ return;
2477
+ }
2460
2478
  let shouldPush = Boolean(args.push);
2461
2479
  if (!shouldPush && interactive && !args.yes) {
2462
2480
  const answer = await confirm({ message: "Push?", initialValue: false });
@@ -2467,16 +2485,8 @@ var commitCommand = defineCommand({
2467
2485
  shouldPush = answer;
2468
2486
  }
2469
2487
  if (shouldPush) {
2470
- const pushLoader = interactive ? spinner() : void 0;
2471
- pushLoader?.start("Pushing");
2472
- try {
2473
- const [upstream, branch] = await Promise.all([hasUpstream(), currentBranch()]);
2474
- if (upstream) await push();
2475
- else await pushSetUpstream(branch);
2476
- pushLoader?.stop("\u2713 Pushed");
2477
- } catch (error) {
2478
- pushLoader?.error("Push failed");
2479
- log.error(`Push failed: ${errorMessage(error)}`);
2488
+ const pushed = await pushOptimistic(interactive, Boolean(args.yes));
2489
+ if (!pushed) {
2480
2490
  process.exitCode = 1;
2481
2491
  return;
2482
2492
  }
@@ -2484,6 +2494,75 @@ var commitCommand = defineCommand({
2484
2494
  if (interactive) outro("Done.");
2485
2495
  }
2486
2496
  });
2497
+ async function rebaseOnUpstream(interactive) {
2498
+ if (!await hasUpstream()) {
2499
+ log.info("No upstream to pull from; skipping --pull.");
2500
+ return true;
2501
+ }
2502
+ const loader = interactive ? spinner() : void 0;
2503
+ loader?.start("Pulling --rebase");
2504
+ try {
2505
+ await pullRebase();
2506
+ loader?.stop("\u2713 Rebased on upstream");
2507
+ return true;
2508
+ } catch (error) {
2509
+ loader?.error("Rebase failed");
2510
+ log.error(`Rebase failed (resolve conflicts, then push): ${errorMessage(error)}`);
2511
+ return false;
2512
+ }
2513
+ }
2514
+ async function pushOptimistic(interactive, assumeYes) {
2515
+ const [upstream, branch] = await Promise.all([hasUpstream(), currentBranch()]);
2516
+ const doPush = () => upstream ? push() : pushSetUpstream(branch);
2517
+ const first = await tryPush(interactive, doPush);
2518
+ if (first.ok) return true;
2519
+ if (upstream && await isBehind(interactive)) {
2520
+ if (assumeYes || !interactive) {
2521
+ log.error("Behind upstream \u2014 re-run with --pull to rebase and push.");
2522
+ return false;
2523
+ }
2524
+ const answer = await confirm({ message: "Pull --rebase and retry push?", initialValue: true });
2525
+ if (isCancel(answer) || !answer) {
2526
+ log.info("Not pushed (still behind).");
2527
+ return true;
2528
+ }
2529
+ if (!await rebaseOnUpstream(interactive)) return false;
2530
+ const retry = await tryPush(interactive, doPush);
2531
+ if (retry.ok) return true;
2532
+ log.error(`Push failed: ${errorMessage(retry.error)}`);
2533
+ return false;
2534
+ }
2535
+ log.error(`Push failed: ${errorMessage(first.error)}`);
2536
+ return false;
2537
+ }
2538
+ async function tryPush(interactive, doPush) {
2539
+ const loader = interactive ? spinner() : void 0;
2540
+ loader?.start("Pushing");
2541
+ try {
2542
+ await doPush();
2543
+ loader?.stop("\u2713 Pushed");
2544
+ return { ok: true };
2545
+ } catch (error) {
2546
+ loader?.error("Push failed");
2547
+ return { ok: false, error };
2548
+ }
2549
+ }
2550
+ async function isBehind(interactive) {
2551
+ const loader = interactive ? spinner() : void 0;
2552
+ loader?.start("Checking upstream");
2553
+ try {
2554
+ await fetchRemote();
2555
+ const behind = await behindCount();
2556
+ loader?.stop(
2557
+ behind > 0 ? `Behind upstream by ${behind} commit${behind > 1 ? "s" : ""}` : "Up to date with upstream"
2558
+ );
2559
+ return behind > 0;
2560
+ } catch (error) {
2561
+ loader?.error("Could not check upstream");
2562
+ log.warn(`Could not check upstream: ${errorMessage(error)}`);
2563
+ return false;
2564
+ }
2565
+ }
2487
2566
 
2488
2567
  // src/commands/reset.ts
2489
2568
  var CANCEL = /* @__PURE__ */ Symbol("cancel");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapdev",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Fast, performant and lightweight git chores and dev tools using your terminal and your own local LLM.",
5
5
  "type": "module",
6
6
  "bin": {