zapdev 1.6.0 → 1.7.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 +29 -1
- package/dist/cli.js +79 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,9 @@ zapdev commit
|
|
|
125
125
|
| `--model <model>` | Override the model |
|
|
126
126
|
| `--effort <effort>` | Override the reasoning effort |
|
|
127
127
|
| `-t, --type <type>` | Force the Conventional Commit type (`feat`, `fix`, `chore`, etc.) |
|
|
128
|
-
| `-p, --push` | Push after committing without asking
|
|
128
|
+
| `-p, --push` | Push or publish subtrees after committing without asking |
|
|
129
|
+
| `--publish-subtree` | Publish subtrees instead of using the classic push |
|
|
130
|
+
| `--publish-subtree=false` | Force classic push, overriding `zapdev.json` |
|
|
129
131
|
| `-s, --staged` | Commit only changes that are already staged |
|
|
130
132
|
| `-r, --rebase` | Rebase on upstream if the push is rejected |
|
|
131
133
|
| `-m, --merge` | Merge upstream if the push is rejected |
|
|
@@ -146,6 +148,32 @@ Pushing is optimistic, with no preliminary fetch. If the branch is behind upstre
|
|
|
146
148
|
|
|
147
149
|
Without a TTY, zapdev commits all prepared repositories automatically and only pushes when `--push` is set.
|
|
148
150
|
|
|
151
|
+
#### Subtree publication
|
|
152
|
+
|
|
153
|
+
Place an optional `zapdev.json` in the directory where you launch zapdev:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{ "isSubtree": true }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| `zapdev.json` option | Default | Effect |
|
|
160
|
+
| ------------------- | ------- | ------ |
|
|
161
|
+
| `isSubtree` | `false` | Replace classic push with subtree publication when `true`; must be a boolean. |
|
|
162
|
+
|
|
163
|
+
- **Scope:** only the launch directory's file is read, not parents or child repositories. Its setting applies to all successfully committed repositories.
|
|
164
|
+
- **Priority:** explicit `--publish-subtree` / `--publish-subtree=false` overrides the file. Without a flag, invalid configuration stops the command before staging.
|
|
165
|
+
- **Destinations:** `projet-front` → remote `front`, `projet-back` → remote `back`, both using the current branch name. `main` and detached HEAD are refused.
|
|
166
|
+
- **Publication:** fetch each remote's `main`, split committed contents, skip contents identical to `main`, then push without force. Uncommitted changes are excluded.
|
|
167
|
+
- **Failures:** stop that repository's publication, leaving earlier publications intact; other repositories continue. No automatic rebase/merge recovery.
|
|
168
|
+
|
|
169
|
+
Interactive runs still ask for confirmation; `--push` publishes without asking. `--yes` alone does not publish.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
zapdev commit --publish-subtree # ask to publish after committing
|
|
173
|
+
zapdev commit --publish-subtree -yp # publish without zapdev prompts
|
|
174
|
+
zapdev commit --publish-subtree=false -yp # force classic push
|
|
175
|
+
```
|
|
176
|
+
|
|
149
177
|
### Zed IDE
|
|
150
178
|
|
|
151
179
|
For a faster review and commit workflow, review and stage changes from Zed's Git panel, then run `zapdev commit -syp` from a task. The command commits only staged changes, skips prompts, and pushes the commit.
|
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.7.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: {
|
|
@@ -1815,6 +1815,46 @@ function sanitizeCommitMessage(raw) {
|
|
|
1815
1815
|
}
|
|
1816
1816
|
|
|
1817
1817
|
// src/lib/config.ts
|
|
1818
|
+
import { readFile } from "node:fs/promises";
|
|
1819
|
+
import { join } from "node:path";
|
|
1820
|
+
|
|
1821
|
+
// src/lib/errors.ts
|
|
1822
|
+
var GitOutputError = class extends Error {
|
|
1823
|
+
constructor(message, hookFailureReported = false) {
|
|
1824
|
+
super(message);
|
|
1825
|
+
this.hookFailureReported = hookFailureReported;
|
|
1826
|
+
}
|
|
1827
|
+
hookFailureReported;
|
|
1828
|
+
};
|
|
1829
|
+
function errorMessage(error) {
|
|
1830
|
+
return error instanceof Error ? error.message : String(error);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// src/lib/config.ts
|
|
1834
|
+
async function resolvePublishSubtree(directory, override) {
|
|
1835
|
+
if (override !== void 0) return override;
|
|
1836
|
+
let content;
|
|
1837
|
+
try {
|
|
1838
|
+
content = await readFile(join(directory, "zapdev.json"), "utf8");
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
if (error.code === "ENOENT") return false;
|
|
1841
|
+
throw new Error(`Unable to read zapdev.json: ${errorMessage(error)}`, { cause: error });
|
|
1842
|
+
}
|
|
1843
|
+
let config;
|
|
1844
|
+
try {
|
|
1845
|
+
config = JSON.parse(content);
|
|
1846
|
+
} catch (error) {
|
|
1847
|
+
throw new Error("zapdev.json must contain valid JSON.", { cause: error });
|
|
1848
|
+
}
|
|
1849
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1850
|
+
throw new Error("zapdev.json must contain an object.");
|
|
1851
|
+
}
|
|
1852
|
+
const isSubtree = config.isSubtree;
|
|
1853
|
+
if (isSubtree !== void 0 && typeof isSubtree !== "boolean") {
|
|
1854
|
+
throw new Error("zapdev.json isSubtree must be a boolean.");
|
|
1855
|
+
}
|
|
1856
|
+
return isSubtree === true;
|
|
1857
|
+
}
|
|
1818
1858
|
function resolveHookTimeout(env = process.env) {
|
|
1819
1859
|
const timeout = Number(env.ZD_HOOK_TIMEOUT ?? 60) * 1e3;
|
|
1820
1860
|
if (!Number.isFinite(timeout) || timeout < 1 || timeout > 2147483647) {
|
|
@@ -1846,7 +1886,7 @@ function resolveConfig(env = process.env, overrides = {}) {
|
|
|
1846
1886
|
// src/lib/git.ts
|
|
1847
1887
|
import { execFileSync } from "node:child_process";
|
|
1848
1888
|
import { readdir } from "node:fs/promises";
|
|
1849
|
-
import { join } from "node:path";
|
|
1889
|
+
import { join as join2 } from "node:path";
|
|
1850
1890
|
import { createInterface as createInterface2 } from "node:readline";
|
|
1851
1891
|
import { Readable } from "node:stream";
|
|
1852
1892
|
|
|
@@ -2176,18 +2216,6 @@ var R2 = (e2, t3, n4) => {
|
|
|
2176
2216
|
};
|
|
2177
2217
|
var z = R2;
|
|
2178
2218
|
|
|
2179
|
-
// src/lib/errors.ts
|
|
2180
|
-
var GitOutputError = class extends Error {
|
|
2181
|
-
constructor(message, hookFailureReported = false) {
|
|
2182
|
-
super(message);
|
|
2183
|
-
this.hookFailureReported = hookFailureReported;
|
|
2184
|
-
}
|
|
2185
|
-
hookFailureReported;
|
|
2186
|
-
};
|
|
2187
|
-
function errorMessage(error) {
|
|
2188
|
-
return error instanceof Error ? error.message : String(error);
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
2219
|
// src/lib/git-hooks.ts
|
|
2192
2220
|
function createHookReporter(onHook, onTimeout, timeout) {
|
|
2193
2221
|
const hooks = /* @__PURE__ */ new Map();
|
|
@@ -2328,6 +2356,16 @@ async function push(repo, onHook) {
|
|
|
2328
2356
|
async function pushSetUpstream(repo, branch, onHook) {
|
|
2329
2357
|
await git(["push", "--quiet", "-u", "origin", branch], repo, onHook);
|
|
2330
2358
|
}
|
|
2359
|
+
async function publishSubtree(repo, remote, branch, onHook) {
|
|
2360
|
+
if (branch === "main") throw new Error("Refusing to publish directly to main.");
|
|
2361
|
+
await git(["check-ref-format", "--branch", branch], repo);
|
|
2362
|
+
await git(["fetch", "--quiet", remote, "main"], repo, onHook);
|
|
2363
|
+
const split = (await git(["subtree", "split", "--quiet", `--prefix=projet-${remote}`], repo)).trim();
|
|
2364
|
+
const changes = await git(["diff", "--name-only", "FETCH_HEAD", split, "--"], repo);
|
|
2365
|
+
if (!changes.trim()) return false;
|
|
2366
|
+
await git(["push", "--quiet", remote, `${split}:refs/heads/${branch}`], repo, onHook);
|
|
2367
|
+
return true;
|
|
2368
|
+
}
|
|
2331
2369
|
async function pullRebase(repo, onHook) {
|
|
2332
2370
|
await git(["pull", "--quiet", "--rebase"], repo, onHook);
|
|
2333
2371
|
}
|
|
@@ -2347,7 +2385,7 @@ async function findRepos(path) {
|
|
|
2347
2385
|
const entries = await readdir(path, { withFileTypes: true }).catch(() => null);
|
|
2348
2386
|
if (!entries) return [];
|
|
2349
2387
|
const dirs = entries.filter((entry) => entry.isDirectory() && entry.name !== "node_modules");
|
|
2350
|
-
const repos = await Promise.all(dirs.map((entry) => repoRoot(
|
|
2388
|
+
const repos = await Promise.all(dirs.map((entry) => repoRoot(join2(path, entry.name))));
|
|
2351
2389
|
return repos.filter((dir) => dir !== null).sort();
|
|
2352
2390
|
}
|
|
2353
2391
|
async function repoRoot(dir) {
|
|
@@ -2476,7 +2514,11 @@ var commitCommand = defineCommand({
|
|
|
2476
2514
|
push: {
|
|
2477
2515
|
type: "boolean",
|
|
2478
2516
|
alias: "p",
|
|
2479
|
-
description: "Push after committing without asking."
|
|
2517
|
+
description: "Push or publish subtrees after committing without asking."
|
|
2518
|
+
},
|
|
2519
|
+
"publish-subtree": {
|
|
2520
|
+
type: "boolean",
|
|
2521
|
+
description: "Publish projet-front and projet-back; use --publish-subtree=false to force classic push."
|
|
2480
2522
|
},
|
|
2481
2523
|
staged: {
|
|
2482
2524
|
type: "boolean",
|
|
@@ -2516,6 +2558,7 @@ var commitCommand = defineCommand({
|
|
|
2516
2558
|
return;
|
|
2517
2559
|
}
|
|
2518
2560
|
let config;
|
|
2561
|
+
let publishSubtreeMode;
|
|
2519
2562
|
try {
|
|
2520
2563
|
resolveHookTimeout();
|
|
2521
2564
|
config = resolveConfig(process.env, {
|
|
@@ -2523,6 +2566,7 @@ var commitCommand = defineCommand({
|
|
|
2523
2566
|
model: args.model,
|
|
2524
2567
|
effort: args.effort
|
|
2525
2568
|
});
|
|
2569
|
+
publishSubtreeMode = await resolvePublishSubtree(process.cwd(), args["publish-subtree"]);
|
|
2526
2570
|
} catch (error) {
|
|
2527
2571
|
log.error(errorMessage(error));
|
|
2528
2572
|
process.exitCode = 1;
|
|
@@ -2597,11 +2641,11 @@ var commitCommand = defineCommand({
|
|
|
2597
2641
|
let shouldPush = Boolean(args.push);
|
|
2598
2642
|
if (!shouldPush && interactive && !args.yes) {
|
|
2599
2643
|
const answer = await confirm({
|
|
2600
|
-
message:
|
|
2644
|
+
message: `${publishSubtreeMode ? "Publish subtrees in" : "Push"} ${committed.map(({ label }) => label).join(", ")}?`,
|
|
2601
2645
|
initialValue: false
|
|
2602
2646
|
});
|
|
2603
2647
|
if (isCancel(answer)) {
|
|
2604
|
-
outro("Committed. Not pushed.");
|
|
2648
|
+
outro(publishSubtreeMode ? "Committed. Not published." : "Committed. Not pushed.");
|
|
2605
2649
|
return;
|
|
2606
2650
|
}
|
|
2607
2651
|
shouldPush = answer;
|
|
@@ -2609,14 +2653,14 @@ var commitCommand = defineCommand({
|
|
|
2609
2653
|
if (shouldPush) {
|
|
2610
2654
|
for (const repository of committed) {
|
|
2611
2655
|
try {
|
|
2612
|
-
const pushed = await pushOptimistic(
|
|
2656
|
+
const pushed = publishSubtreeMode ? await publishSubtrees(repository) : await pushOptimistic(
|
|
2613
2657
|
repository,
|
|
2614
2658
|
interactive && !args.yes,
|
|
2615
2659
|
syncStrategy
|
|
2616
2660
|
);
|
|
2617
2661
|
if (!pushed) process.exitCode = 1;
|
|
2618
2662
|
} catch (error) {
|
|
2619
|
-
reportGitFailure(`${repository.label}: push failed`, error);
|
|
2663
|
+
reportGitFailure(`${repository.label}: ${publishSubtreeMode ? "publication" : "push"} failed`, error);
|
|
2620
2664
|
process.exitCode = 1;
|
|
2621
2665
|
}
|
|
2622
2666
|
}
|
|
@@ -2684,6 +2728,21 @@ async function syncWithUpstream({ repo, label }, strategy) {
|
|
|
2684
2728
|
return false;
|
|
2685
2729
|
}
|
|
2686
2730
|
}
|
|
2731
|
+
async function publishSubtrees({ repo, label }) {
|
|
2732
|
+
const branch = await currentBranch(repo);
|
|
2733
|
+
for (const remote of ["front", "back"]) {
|
|
2734
|
+
log.info(`${label}: publishing projet-${remote} \u2192 ${remote}/${branch}`);
|
|
2735
|
+
try {
|
|
2736
|
+
const published = await publishSubtree(repo, remote, branch, reportHooks(label));
|
|
2737
|
+
if (published) log.success(`${label}: published projet-${remote} \u2192 ${remote}/${branch}`);
|
|
2738
|
+
else log.info(`${label}: no changes in projet-${remote}. Skipping.`);
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
reportGitFailure(`${label}: publication to ${remote}/${branch} failed`, error);
|
|
2741
|
+
return false;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
return true;
|
|
2745
|
+
}
|
|
2687
2746
|
async function pushOptimistic(repository, canPrompt, strategy) {
|
|
2688
2747
|
const { repo, label } = repository;
|
|
2689
2748
|
const [upstream, branch] = await Promise.all([hasUpstream(repo), currentBranch(repo)]);
|