zapdev 1.2.0 → 1.4.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 +22 -7
- package/dist/cli.js +81 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/mvagnon/zapdev/main/.github/assets/preview.gif" alt="zapdev CLI demonstration">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
[](https://mvagnon.dev)
|
|
8
|
+
[](https://buymeacoffee.com/mvagnon)
|
|
9
|
+
|
|
1
10
|
# zapdev
|
|
2
11
|
|
|
3
12
|
## Project Introduction
|
|
@@ -28,10 +37,10 @@ flowchart LR
|
|
|
28
37
|
|
|
29
38
|
## Environment Variables
|
|
30
39
|
|
|
31
|
-
| Variable
|
|
32
|
-
|
|
|
33
|
-
| `ZD_URL`
|
|
34
|
-
| `ZD_MODEL`
|
|
40
|
+
| Variable | Required | Description |
|
|
41
|
+
| ----------- | ------------ | --------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `ZD_URL` | For `commit` | Complete HTTP(S) Chat Completions endpoint, including its path |
|
|
43
|
+
| `ZD_MODEL` | For `commit` | Model identifier supported by the endpoint |
|
|
35
44
|
| `ZD_EFFORT` | For `commit` | Sent as `reasoning_effort`; use a value supported by your model, such as `low`, `medium`, or `high` |
|
|
36
45
|
|
|
37
46
|
There are no defaults, automatic provider detection, or backup models. CLI flags override these variables. Legacy `OLLAMA_*` variables are no longer read.
|
|
@@ -55,6 +64,12 @@ Install zapdev globally for daily use:
|
|
|
55
64
|
npm install -g zapdev
|
|
56
65
|
```
|
|
57
66
|
|
|
67
|
+
or
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bun install -g zapdev
|
|
71
|
+
```
|
|
72
|
+
|
|
58
73
|
Configure your endpoint and model before running `commit` (replace these example values):
|
|
59
74
|
|
|
60
75
|
```bash
|
|
@@ -94,7 +109,7 @@ Stages all changes, scans them with Gitleaks when installed, generates Conventio
|
|
|
94
109
|
|
|
95
110
|
- **Inside a Git repository:** uses that repository only, including when launched from a subdirectory. Does not inspect child repositories.
|
|
96
111
|
- **Outside a Git repository:** processes only direct child repositories. No recursive search; `node_modules` is excluded.
|
|
97
|
-
- **Unified review:** generates messages in parallel, then presents every message with its repository name and branch. Repositories with pending changes are shown in bold when terminal styling is enabled.
|
|
112
|
+
- **Unified review:** generates messages in parallel, then presents every message with its repository name and branch. Repositories with pending changes are shown in bold and underlined when terminal styling is enabled.
|
|
98
113
|
- **Actions:** commit all, commit only a named repository, edit a named repository's message, or cancel. Editing returns to the review menu.
|
|
99
114
|
|
|
100
115
|
Repositories with nothing to commit are skipped. Unselected or cancelled changes remain staged. Push confirmation is asked once for the successfully committed repositories.
|
|
@@ -106,8 +121,8 @@ zapdev commit
|
|
|
106
121
|
| Flag | Description |
|
|
107
122
|
| ------------------- | ----------------------------------------------------------------- |
|
|
108
123
|
| `--url <url>` | Override the complete Chat Completions endpoint |
|
|
109
|
-
| `--model <model>` | Override the model
|
|
110
|
-
| `--effort <effort>` | Override the reasoning effort
|
|
124
|
+
| `--model <model>` | Override the model |
|
|
125
|
+
| `--effort <effort>` | Override the reasoning effort |
|
|
111
126
|
| `-t, --type <type>` | Force the Conventional Commit type (`feat`, `fix`, `chore`, etc.) |
|
|
112
127
|
| `-p, --push` | Push after committing without asking |
|
|
113
128
|
| `-s, --staged` | Commit only changes that are already staged |
|
package/dist/cli.js
CHANGED
|
@@ -554,7 +554,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
554
554
|
// package.json
|
|
555
555
|
var package_default = {
|
|
556
556
|
name: "zapdev",
|
|
557
|
-
version: "1.
|
|
557
|
+
version: "1.4.0",
|
|
558
558
|
description: "Fast, performant and lightweight git chores and dev tools using your terminal and an OpenAI-compatible LLM endpoint.",
|
|
559
559
|
type: "module",
|
|
560
560
|
bin: {
|
|
@@ -1839,6 +1839,8 @@ function resolveConfig(env = process.env, overrides = {}) {
|
|
|
1839
1839
|
// src/lib/git.ts
|
|
1840
1840
|
import { readdir } from "node:fs/promises";
|
|
1841
1841
|
import { join } from "node:path";
|
|
1842
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
1843
|
+
import { Readable } from "node:stream";
|
|
1842
1844
|
|
|
1843
1845
|
// node_modules/tinyexec/dist/main.mjs
|
|
1844
1846
|
import { spawn as e, spawnSync as t2 } from "node:child_process";
|
|
@@ -2166,10 +2168,43 @@ var R2 = (e2, t3, n4) => {
|
|
|
2166
2168
|
};
|
|
2167
2169
|
var z = R2;
|
|
2168
2170
|
|
|
2171
|
+
// src/lib/git-hooks.ts
|
|
2172
|
+
function createHookReporter(onHook) {
|
|
2173
|
+
const hooks = /* @__PURE__ */ new Map();
|
|
2174
|
+
return (line) => {
|
|
2175
|
+
let event;
|
|
2176
|
+
try {
|
|
2177
|
+
const value = JSON.parse(line);
|
|
2178
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2179
|
+
event = value;
|
|
2180
|
+
} catch {
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (typeof event.sid !== "string" || typeof event.child_id !== "number") return;
|
|
2184
|
+
const key = `${event.sid}:${event.child_id}`;
|
|
2185
|
+
if (event.event === "child_start" && event.child_class === "hook" && typeof event.hook_name === "string") {
|
|
2186
|
+
hooks.set(key, event.hook_name);
|
|
2187
|
+
onHook({ name: event.hook_name, phase: "start" });
|
|
2188
|
+
} else if (event.event === "child_exit" && typeof event.code === "number") {
|
|
2189
|
+
const name = hooks.get(key);
|
|
2190
|
+
if (name === void 0) return;
|
|
2191
|
+
hooks.delete(key);
|
|
2192
|
+
onHook({ name, phase: "exit", exitCode: event.code });
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2169
2197
|
// src/lib/git.ts
|
|
2170
2198
|
var UPSTREAM_REF = "@{upstream}";
|
|
2171
|
-
async function git(args, cwd) {
|
|
2172
|
-
const
|
|
2199
|
+
async function git(args, cwd, onHook) {
|
|
2200
|
+
const child = R2("git", args, {
|
|
2201
|
+
nodeOptions: onHook ? { cwd, env: { ...process.env, GIT_TRACE2_EVENT: "3" }, stdio: ["pipe", "pipe", "pipe", "pipe"] } : { cwd }
|
|
2202
|
+
});
|
|
2203
|
+
const trace = child.process?.stdio[3];
|
|
2204
|
+
if (onHook && trace instanceof Readable) {
|
|
2205
|
+
createInterface2({ input: trace }).on("line", createHookReporter(onHook));
|
|
2206
|
+
}
|
|
2207
|
+
const result = await child;
|
|
2173
2208
|
if (result.exitCode !== 0) {
|
|
2174
2209
|
throw new Error(result.stderr.trim() || `git ${args.join(" ")} failed`);
|
|
2175
2210
|
}
|
|
@@ -2185,8 +2220,8 @@ async function stageAll(repo) {
|
|
|
2185
2220
|
async function getStagedDiff(repo) {
|
|
2186
2221
|
return git(["diff", "--cached"], repo);
|
|
2187
2222
|
}
|
|
2188
|
-
async function commit(repo, message) {
|
|
2189
|
-
await git(["commit", "-m", message], repo);
|
|
2223
|
+
async function commit(repo, message, onHook) {
|
|
2224
|
+
await git(["commit", "-m", message], repo, onHook);
|
|
2190
2225
|
}
|
|
2191
2226
|
async function currentBranch(repo) {
|
|
2192
2227
|
return (await git(["symbolic-ref", "--short", "HEAD"], repo)).trim();
|
|
@@ -2203,20 +2238,20 @@ async function hasUpstream(repo) {
|
|
|
2203
2238
|
const result = await tryGit(["rev-parse", "--abbrev-ref", "--symbolic-full-name", UPSTREAM_REF], repo);
|
|
2204
2239
|
return result !== null;
|
|
2205
2240
|
}
|
|
2206
|
-
async function push(repo) {
|
|
2207
|
-
await git(["push"], repo);
|
|
2241
|
+
async function push(repo, onHook) {
|
|
2242
|
+
await git(["push"], repo, onHook);
|
|
2208
2243
|
}
|
|
2209
|
-
async function pushSetUpstream(repo, branch) {
|
|
2210
|
-
await git(["push", "-u", "origin", branch], repo);
|
|
2244
|
+
async function pushSetUpstream(repo, branch, onHook) {
|
|
2245
|
+
await git(["push", "-u", "origin", branch], repo, onHook);
|
|
2211
2246
|
}
|
|
2212
|
-
async function pullRebase(repo) {
|
|
2213
|
-
await git(["pull", "--rebase"], repo);
|
|
2247
|
+
async function pullRebase(repo, onHook) {
|
|
2248
|
+
await git(["pull", "--rebase"], repo, onHook);
|
|
2214
2249
|
}
|
|
2215
|
-
async function pullMerge(repo) {
|
|
2216
|
-
await git(["pull", "--no-rebase", "--no-edit"], repo);
|
|
2250
|
+
async function pullMerge(repo, onHook) {
|
|
2251
|
+
await git(["pull", "--no-rebase", "--no-edit"], repo, onHook);
|
|
2217
2252
|
}
|
|
2218
|
-
async function fetchRemote(repo) {
|
|
2219
|
-
await git(["fetch"], repo);
|
|
2253
|
+
async function fetchRemote(repo, onHook) {
|
|
2254
|
+
await git(["fetch"], repo, onHook);
|
|
2220
2255
|
}
|
|
2221
2256
|
async function behindCount(repo) {
|
|
2222
2257
|
const out = await tryGit(["rev-list", "--count", `HEAD..${UPSTREAM_REF}`], repo);
|
|
@@ -2436,7 +2471,7 @@ var commitCommand = defineCommand({
|
|
|
2436
2471
|
const { repo } = repository;
|
|
2437
2472
|
const { branch, hasChanges } = await getRepoStatus(repo);
|
|
2438
2473
|
repository.label = `${basename(repo)} (${branch})`;
|
|
2439
|
-
repository.pendingLabel = hasChanges ? styleText3("bold", repository.label) : repository.label;
|
|
2474
|
+
repository.pendingLabel = hasChanges ? styleText3(["bold", "underline"], repository.label) : repository.label;
|
|
2440
2475
|
if (!args.staged) await stageAll(repo);
|
|
2441
2476
|
const diff = await getStagedDiff(repo);
|
|
2442
2477
|
if (!diff.trim()) return null;
|
|
@@ -2468,11 +2503,16 @@ var commitCommand = defineCommand({
|
|
|
2468
2503
|
}
|
|
2469
2504
|
const committed = [];
|
|
2470
2505
|
for (const draft of selected) {
|
|
2506
|
+
const loader2 = interactive ? spinner() : void 0;
|
|
2507
|
+
const message = `${draft.label}: committing`;
|
|
2508
|
+
loader2?.start(message);
|
|
2471
2509
|
try {
|
|
2472
|
-
await commit(draft.repo, draft.message);
|
|
2510
|
+
await commit(draft.repo, draft.message, reportHooks(draft.label, loader2, message));
|
|
2511
|
+
loader2?.clear();
|
|
2473
2512
|
committed.push(draft);
|
|
2474
2513
|
log.success(`${draft.label}: committed ${draft.message}`);
|
|
2475
2514
|
} catch (error) {
|
|
2515
|
+
loader2?.clear();
|
|
2476
2516
|
log.error(`${draft.pendingLabel}: commit failed: ${errorMessage(error)}`);
|
|
2477
2517
|
process.exitCode = 1;
|
|
2478
2518
|
}
|
|
@@ -2509,6 +2549,20 @@ var commitCommand = defineCommand({
|
|
|
2509
2549
|
if (interactive) outro(process.exitCode ? "Finished with errors." : "Done.");
|
|
2510
2550
|
}
|
|
2511
2551
|
});
|
|
2552
|
+
function reportHooks(label, loader, message) {
|
|
2553
|
+
return (event) => {
|
|
2554
|
+
const hook = `${label}: ${event.name}`;
|
|
2555
|
+
if (event.phase === "start") {
|
|
2556
|
+
if (loader) loader.message(`${hook} running\u2026`);
|
|
2557
|
+
else log.info(`${hook} running\u2026`);
|
|
2558
|
+
} else {
|
|
2559
|
+
loader?.clear();
|
|
2560
|
+
if (event.exitCode === 0) log.success(`${hook} \u2713`);
|
|
2561
|
+
else log.error(`${hook} \u2717 (exit ${event.exitCode})`);
|
|
2562
|
+
loader?.start(message);
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2512
2566
|
async function reviewMessages(drafts, canPrompt) {
|
|
2513
2567
|
while (true) {
|
|
2514
2568
|
for (const draft of drafts) log.message(`${draft.pendingLabel}: ${draft.message}`);
|
|
@@ -2544,9 +2598,10 @@ async function reviewMessages(drafts, canPrompt) {
|
|
|
2544
2598
|
async function syncWithUpstream({ repo, label }, strategy, interactive) {
|
|
2545
2599
|
const loader = interactive ? spinner() : void 0;
|
|
2546
2600
|
const actionLabel = strategy === "rebase" ? "Rebase" : "Merge";
|
|
2547
|
-
|
|
2601
|
+
const message = `${label}: pulling --${strategy === "rebase" ? "rebase" : "no-rebase"}`;
|
|
2602
|
+
loader?.start(message);
|
|
2548
2603
|
try {
|
|
2549
|
-
await (strategy === "rebase" ? pullRebase
|
|
2604
|
+
await (strategy === "rebase" ? pullRebase : pullMerge)(repo, reportHooks(label, loader, message));
|
|
2550
2605
|
loader?.stop(strategy === "rebase" ? "\u2713 Rebased on upstream" : "\u2713 Merged upstream");
|
|
2551
2606
|
return true;
|
|
2552
2607
|
} catch (error) {
|
|
@@ -2558,7 +2613,7 @@ async function syncWithUpstream({ repo, label }, strategy, interactive) {
|
|
|
2558
2613
|
async function pushOptimistic(repository, interactive, canPrompt, strategy) {
|
|
2559
2614
|
const { repo, label } = repository;
|
|
2560
2615
|
const [upstream, branch] = await Promise.all([hasUpstream(repo), currentBranch(repo)]);
|
|
2561
|
-
const doPush = () => upstream ? push(repo) : pushSetUpstream(repo, branch);
|
|
2616
|
+
const doPush = (onHook) => upstream ? push(repo, onHook) : pushSetUpstream(repo, branch, onHook);
|
|
2562
2617
|
const first = await tryPush(repository, interactive, doPush);
|
|
2563
2618
|
if (first.ok) return true;
|
|
2564
2619
|
if (upstream && await isBehind(repository, interactive)) {
|
|
@@ -2593,9 +2648,10 @@ async function chooseSyncStrategy({ label }, interactive) {
|
|
|
2593
2648
|
}
|
|
2594
2649
|
async function tryPush({ label }, interactive, doPush) {
|
|
2595
2650
|
const loader = interactive ? spinner() : void 0;
|
|
2596
|
-
|
|
2651
|
+
const message = `${label}: pushing`;
|
|
2652
|
+
loader?.start(message);
|
|
2597
2653
|
try {
|
|
2598
|
-
await doPush();
|
|
2654
|
+
await doPush(reportHooks(label, loader, message));
|
|
2599
2655
|
loader?.stop("\u2713 Pushed");
|
|
2600
2656
|
return { ok: true };
|
|
2601
2657
|
} catch (error) {
|
|
@@ -2605,9 +2661,10 @@ async function tryPush({ label }, interactive, doPush) {
|
|
|
2605
2661
|
}
|
|
2606
2662
|
async function isBehind({ repo, label }, interactive) {
|
|
2607
2663
|
const loader = interactive ? spinner() : void 0;
|
|
2608
|
-
|
|
2664
|
+
const message = `${label}: checking upstream`;
|
|
2665
|
+
loader?.start(message);
|
|
2609
2666
|
try {
|
|
2610
|
-
await fetchRemote(repo);
|
|
2667
|
+
await fetchRemote(repo, reportHooks(label, loader, message));
|
|
2611
2668
|
const behind = await behindCount(repo);
|
|
2612
2669
|
loader?.stop(
|
|
2613
2670
|
behind > 0 ? `Behind upstream by ${behind} commit${behind > 1 ? "s" : ""}` : "Up to date with upstream"
|