zapdev 0.7.0 → 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.
- package/README.md +6 -3
- package/dist/cli.js +99 -19
- 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,15 +44,17 @@ 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`
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
Operates on a git repo, or — when the path is a plain directory — on its **direct child** repos (level 1, non-recursive). For each one: `git fetch --prune`, switch to a branch (chosen interactively, or forced with `--principal` / `--target`), then permanently delete every other local branch and every linked worktree. The principal branch (from `origin/HEAD`) and the branch you switched to are always kept. Ends with an optional `pull`.
|
|
51
54
|
|
|
52
55
|
```bash
|
|
53
|
-
zapdev reset # child repos of the current directory
|
|
54
|
-
zapdev reset ~/dev # child repos of a
|
|
56
|
+
zapdev reset # the current repo, or the child repos of the current directory
|
|
57
|
+
zapdev reset ~/dev # a repo, or the child repos of a directory
|
|
55
58
|
zapdev reset -p # switch each repo to its principal branch, no prompt
|
|
56
59
|
zapdev reset -t dev # switch each repo to `dev` (or principal if absent)
|
|
57
60
|
```
|
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.
|
|
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,13 +2222,24 @@ async function push() {
|
|
|
2222
2222
|
async function pushSetUpstream(branch) {
|
|
2223
2223
|
await git(["push", "-u", "origin", branch]);
|
|
2224
2224
|
}
|
|
2225
|
-
async function
|
|
2226
|
-
|
|
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
|
+
}
|
|
2235
|
+
async function findRepos(path) {
|
|
2236
|
+
if (await isGitRepo(path)) return [path];
|
|
2237
|
+
const entries = await readdir(path, { withFileTypes: true }).catch(() => null);
|
|
2227
2238
|
if (!entries) return [];
|
|
2228
2239
|
const dirs = entries.filter((entry) => entry.isDirectory() && entry.name !== "node_modules");
|
|
2229
2240
|
const repos = await Promise.all(
|
|
2230
2241
|
dirs.map(async (entry) => {
|
|
2231
|
-
const dir = join(
|
|
2242
|
+
const dir = join(path, entry.name);
|
|
2232
2243
|
return await isGitRepo(dir) ? dir : null;
|
|
2233
2244
|
})
|
|
2234
2245
|
);
|
|
@@ -2371,6 +2382,10 @@ var commitCommand = defineCommand({
|
|
|
2371
2382
|
alias: "t",
|
|
2372
2383
|
description: `Force the Conventional Commits type (${COMMIT_TYPES.join(", ")}).`
|
|
2373
2384
|
},
|
|
2385
|
+
pull: {
|
|
2386
|
+
type: "boolean",
|
|
2387
|
+
description: "Rebase onto upstream (git pull --rebase) after committing, before pushing."
|
|
2388
|
+
},
|
|
2374
2389
|
push: {
|
|
2375
2390
|
type: "boolean",
|
|
2376
2391
|
alias: "p",
|
|
@@ -2456,6 +2471,10 @@ var commitCommand = defineCommand({
|
|
|
2456
2471
|
}
|
|
2457
2472
|
await commit(finalMessage);
|
|
2458
2473
|
if (interactive) log.success(`Committed: ${finalMessage}`);
|
|
2474
|
+
if (args.pull && !await rebaseOnUpstream(interactive)) {
|
|
2475
|
+
process.exitCode = 1;
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2459
2478
|
let shouldPush = Boolean(args.push);
|
|
2460
2479
|
if (!shouldPush && interactive && !args.yes) {
|
|
2461
2480
|
const answer = await confirm({ message: "Push?", initialValue: false });
|
|
@@ -2466,16 +2485,8 @@ var commitCommand = defineCommand({
|
|
|
2466
2485
|
shouldPush = answer;
|
|
2467
2486
|
}
|
|
2468
2487
|
if (shouldPush) {
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
try {
|
|
2472
|
-
const [upstream, branch] = await Promise.all([hasUpstream(), currentBranch()]);
|
|
2473
|
-
if (upstream) await push();
|
|
2474
|
-
else await pushSetUpstream(branch);
|
|
2475
|
-
pushLoader?.stop("\u2713 Pushed");
|
|
2476
|
-
} catch (error) {
|
|
2477
|
-
pushLoader?.error("Push failed");
|
|
2478
|
-
log.error(`Push failed: ${errorMessage(error)}`);
|
|
2488
|
+
const pushed = await pushOptimistic(interactive, Boolean(args.yes));
|
|
2489
|
+
if (!pushed) {
|
|
2479
2490
|
process.exitCode = 1;
|
|
2480
2491
|
return;
|
|
2481
2492
|
}
|
|
@@ -2483,20 +2494,89 @@ var commitCommand = defineCommand({
|
|
|
2483
2494
|
if (interactive) outro("Done.");
|
|
2484
2495
|
}
|
|
2485
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
|
+
}
|
|
2486
2566
|
|
|
2487
2567
|
// src/commands/reset.ts
|
|
2488
2568
|
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2489
2569
|
var resetCommand = defineCommand({
|
|
2490
2570
|
meta: {
|
|
2491
2571
|
name: "reset",
|
|
2492
|
-
description: "Switch
|
|
2572
|
+
description: "Switch each repo to a branch, then delete all other local branches and worktrees."
|
|
2493
2573
|
},
|
|
2494
2574
|
args: {
|
|
2495
2575
|
path: {
|
|
2496
2576
|
type: "positional",
|
|
2497
2577
|
required: false,
|
|
2498
2578
|
default: ".",
|
|
2499
|
-
description: "
|
|
2579
|
+
description: "A git repo to reset, or a directory whose direct child repos are reset."
|
|
2500
2580
|
},
|
|
2501
2581
|
principal: {
|
|
2502
2582
|
type: "boolean",
|
|
@@ -2528,9 +2608,9 @@ var resetCommand = defineCommand({
|
|
|
2528
2608
|
};
|
|
2529
2609
|
const path = args.path || ".";
|
|
2530
2610
|
if (interactive) intro("zapdev reset");
|
|
2531
|
-
const repos = await
|
|
2611
|
+
const repos = await findRepos(path);
|
|
2532
2612
|
if (repos.length === 0) {
|
|
2533
|
-
log.warn(`No
|
|
2613
|
+
log.warn(`No git repository found at or under ${path}.`);
|
|
2534
2614
|
if (interactive) outro("Nothing to do.");
|
|
2535
2615
|
return;
|
|
2536
2616
|
}
|
|
@@ -2685,7 +2765,7 @@ var commands = {
|
|
|
2685
2765
|
};
|
|
2686
2766
|
var MENU = {
|
|
2687
2767
|
commit: "Stage all changes and commit with an AI-generated message",
|
|
2688
|
-
reset: "Switch
|
|
2768
|
+
reset: "Switch repos to a branch and delete the other branches and worktrees"
|
|
2689
2769
|
};
|
|
2690
2770
|
var menuOptions = Object.keys(MENU).map((value) => ({
|
|
2691
2771
|
value,
|