zapdev 1.9.0 → 1.11.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 +12 -12
- package/dist/cli.js +108 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ Stages all changes, scans them with Gitleaks when installed, generates Conventio
|
|
|
113
113
|
- **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.
|
|
114
114
|
- **Actions:** commit all, commit only a named repository, edit a named repository's message, or cancel. Editing returns to the review menu.
|
|
115
115
|
|
|
116
|
-
Repositories with no new changes can still send existing commits. Unselected or cancelled changes remain staged; failed or unselected commit drafts are not pushed.
|
|
116
|
+
Repositories with no new changes can still send existing commits. Unselected or cancelled changes remain staged; failed or unselected commit drafts are not pushed. The branch is chosen before committing, never when pushing. Push confirmation is asked once.
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
119
|
zapdev commit
|
|
@@ -124,8 +124,8 @@ zapdev commit
|
|
|
124
124
|
| `--url <url>` | Override the complete Chat Completions endpoint |
|
|
125
125
|
| `--model <model>` | Override the model |
|
|
126
126
|
| `--effort <effort>` | Override the reasoning effort |
|
|
127
|
-
| `-t, --type <type>` | Force
|
|
128
|
-
| `-p, --push` | Skip push confirmation
|
|
127
|
+
| `-t, --type <type>` | Force an exact lowercase Conventional Commit type (`feat`, `fix`, `chore`, etc.) |
|
|
128
|
+
| `-p, --push` | Skip push confirmation and push the current branch |
|
|
129
129
|
| `-s, --staged` | Commit only changes that are already staged |
|
|
130
130
|
| `-y, --yes` | Skip commit review; still confirm push unless `--push` is set |
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ zapdev commit
|
|
|
133
133
|
zapdev commit -t feat # force the type
|
|
134
134
|
zapdev commit --staged # leave unstaged changes untouched
|
|
135
135
|
zapdev commit -y # commit automatically, then ask before pushing
|
|
136
|
-
zapdev commit -yp #
|
|
136
|
+
zapdev commit -yp # skip review and push confirmation; choose a branch if protected
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
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,11 +142,11 @@ Preparation and commit failures are reported per repository while the others con
|
|
|
142
142
|
|
|
143
143
|
In a terminal, Git and its hooks display live logs and support native prompts, even with `--yes` (which skips commit review, not native Git prompts). Git controls hook stdin; interactive hooks should read from the terminal, for example `/dev/tty`. Each hook has a 60-second deadline, including time spent answering prompts; override it with `ZD_HOOK_TIMEOUT=120 zapdev commit`.
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
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.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
Pushes always target a remote branch with the current local branch's name, with no destination input. Classic pushes use the current branch's upstream remote, or the only configured remote if there is no upstream, and run `git push <remote> HEAD:refs/heads/<current-branch>`. Ambiguous remotes are skipped with a warning. Both modes check for unpublished commits against that remote branch, falling back to the remote's default branch (`HEAD`) if it is missing. If neither reference exists, publication is attempted. Classic mode compares HEAD; subtree mode compares its extracted history. Already published history is skipped. No rebase, merge, retry, or force-push.
|
|
148
148
|
|
|
149
|
-
Without a TTY, zapdev commits
|
|
149
|
+
Without a TTY, zapdev commits prepared repositories on nonprotected branches automatically; protected-branch commits are refused and changes remain staged. `--push` can publish without a TTY; without it, commits stay local. Repositories without new changes do not trigger an LLM call, branch prompt, or new commit.
|
|
150
150
|
|
|
151
151
|
#### Subtree publication
|
|
152
152
|
|
|
@@ -167,15 +167,15 @@ Place an optional `zapdev.json` in the directory where you launch zapdev:
|
|
|
167
167
|
|
|
168
168
|
- **Scope:** only the launch directory's file is read, not parents or child repositories. Its mapping applies to newly committed repositories and repositories without new changes.
|
|
169
169
|
- **Configuration:** only `zapdev.json` selects subtree mode; no subtree CLI flag. Missing or empty `subtrees` uses classic push. Invalid configuration stops the command before staging. Replace the removed `isSubtree` option with an explicit mapping.
|
|
170
|
-
- **Destinations:** only mapped folders are published, using their configured remotes; folder and remote names need not match.
|
|
171
|
-
- **Publication:** check
|
|
170
|
+
- **Destinations:** only mapped folders are published, using their configured remotes; folder and remote names need not match. All subtrees use the parent repository's current branch name. The parent itself is not pushed. Detached HEAD is refused.
|
|
171
|
+
- **Publication:** check each subtree using the shared unpublished-commit rule above, then run `git subtree push --prefix=<folder> <remote> <current-branch>`. Subtrees without unpublished commits are skipped without creating remote branches; uncommitted changes are excluded.
|
|
172
172
|
- **Failures:** stop all remaining publications, leaving earlier publications intact. No automatic recovery.
|
|
173
173
|
|
|
174
|
-
Interactive runs still ask for confirmation, including with `--yes`; `--push` skips
|
|
174
|
+
Interactive runs still ask for publication confirmation, including with `--yes`; `--push` skips that confirmation. The commit and branch-selection flow is identical to classic mode.
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
177
|
zapdev commit # with subtrees configured, ask to publish after committing
|
|
178
|
-
zapdev commit -yp #
|
|
178
|
+
zapdev commit -yp # publish changed subtrees on the parent's current branch
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
### `zapdev subtree-init`
|
|
@@ -202,7 +202,7 @@ zapdev subtree-init my-project \
|
|
|
202
202
|
|
|
203
203
|
### Zed IDE
|
|
204
204
|
|
|
205
|
-
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 commit review and push confirmation,
|
|
205
|
+
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 commit review and push confirmation, and asks for a branch before committing only if the current branch is protected.
|
|
206
206
|
|
|
207
207
|
Add the following tasks to `.zed/tasks.json`:
|
|
208
208
|
|
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.11.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: {
|
|
@@ -614,7 +614,7 @@ var package_default = {
|
|
|
614
614
|
|
|
615
615
|
// src/commands/commit.ts
|
|
616
616
|
import { basename } from "node:path";
|
|
617
|
-
import { styleText as
|
|
617
|
+
import { styleText as styleText4 } from "node:util";
|
|
618
618
|
|
|
619
619
|
// node_modules/@clack/core/dist/index.mjs
|
|
620
620
|
import { styleText } from "node:util";
|
|
@@ -1780,40 +1780,6 @@ ${r3}
|
|
|
1780
1780
|
}
|
|
1781
1781
|
}).prompt();
|
|
1782
1782
|
|
|
1783
|
-
// src/types/commit.ts
|
|
1784
|
-
var COMMIT_TYPES = [
|
|
1785
|
-
"feat",
|
|
1786
|
-
"fix",
|
|
1787
|
-
"chore",
|
|
1788
|
-
"docs",
|
|
1789
|
-
"style",
|
|
1790
|
-
"refactor",
|
|
1791
|
-
"perf",
|
|
1792
|
-
"test",
|
|
1793
|
-
"build",
|
|
1794
|
-
"ci",
|
|
1795
|
-
"revert"
|
|
1796
|
-
];
|
|
1797
|
-
|
|
1798
|
-
// src/lib/commit-message.ts
|
|
1799
|
-
function normalizeCommitType(input) {
|
|
1800
|
-
const normalized = input.trim().toLowerCase();
|
|
1801
|
-
return COMMIT_TYPES.includes(normalized) ? normalized : null;
|
|
1802
|
-
}
|
|
1803
|
-
function applyCommitType(systemPrompt, type) {
|
|
1804
|
-
return `${systemPrompt}
|
|
1805
|
-
|
|
1806
|
-
The type MUST be exactly "${type}".`;
|
|
1807
|
-
}
|
|
1808
|
-
var MAX_DIFF_CHARS = 12e3;
|
|
1809
|
-
function truncateDiff(diff, max = MAX_DIFF_CHARS) {
|
|
1810
|
-
return diff.length > max ? diff.slice(0, max) : diff;
|
|
1811
|
-
}
|
|
1812
|
-
function sanitizeCommitMessage(raw) {
|
|
1813
|
-
const firstLine = raw.split("\n", 1)[0] ?? "";
|
|
1814
|
-
return firstLine.replace(/[`"]/g, "").trim();
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
1783
|
// src/lib/config.ts
|
|
1818
1784
|
import { readFile } from "node:fs/promises";
|
|
1819
1785
|
import { isAbsolute, join } from "node:path";
|
|
@@ -2267,16 +2233,17 @@ function createHookReporter(onHook, onTimeout, timeout) {
|
|
|
2267
2233
|
}
|
|
2268
2234
|
|
|
2269
2235
|
// src/lib/git.ts
|
|
2270
|
-
async function git(args, cwd, onHook) {
|
|
2236
|
+
async function git(args, cwd, onHook, onNativeOutput) {
|
|
2271
2237
|
const timeout = resolveHookTimeout();
|
|
2272
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && ["commit", "push", "subtree", "fetch"].includes(args[0] ?? "") && !(args[0] === "subtree" && args[1] === "split"));
|
|
2238
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && ["commit", "push", "subtree", "fetch", "ls-remote"].includes(args[0] ?? "") && !(args[0] === "subtree" && args[1] === "split"));
|
|
2273
2239
|
const terminalState = interactive && process.platform !== "win32" ? execFileSync("stty", ["-g"], { stdio: ["inherit", "pipe", "ignore"], encoding: "utf8" }).trim() : void 0;
|
|
2240
|
+
if (interactive) onNativeOutput?.("start");
|
|
2274
2241
|
const child = R2("git", args, {
|
|
2275
2242
|
nodeOptions: {
|
|
2276
2243
|
cwd,
|
|
2277
2244
|
detached: !interactive && process.platform !== "win32",
|
|
2278
2245
|
env: { ...process.env, GIT_TRACE2_EVENT: "3" },
|
|
2279
|
-
stdio: interactive ? ["inherit", "inherit", "inherit", "pipe"] : ["pipe", "pipe", "pipe", "pipe"]
|
|
2246
|
+
stdio: interactive ? ["inherit", args[0] === "ls-remote" ? "pipe" : "inherit", "inherit", "pipe"] : ["pipe", "pipe", "pipe", "pipe"]
|
|
2280
2247
|
}
|
|
2281
2248
|
});
|
|
2282
2249
|
let timeoutError;
|
|
@@ -2313,7 +2280,11 @@ async function git(args, cwd, onHook) {
|
|
|
2313
2280
|
} finally {
|
|
2314
2281
|
reader?.close();
|
|
2315
2282
|
close();
|
|
2316
|
-
|
|
2283
|
+
try {
|
|
2284
|
+
if (terminalState) execFileSync("stty", [terminalState], { stdio: ["inherit", "ignore", "ignore"] });
|
|
2285
|
+
} finally {
|
|
2286
|
+
if (interactive) onNativeOutput?.("exit");
|
|
2287
|
+
}
|
|
2317
2288
|
}
|
|
2318
2289
|
}
|
|
2319
2290
|
function killProcessTree(pid) {
|
|
@@ -2346,12 +2317,18 @@ async function stageAll(repo) {
|
|
|
2346
2317
|
async function getStagedDiff(repo) {
|
|
2347
2318
|
return git(["diff", "--cached"], repo);
|
|
2348
2319
|
}
|
|
2349
|
-
async function commit(repo, message, onHook) {
|
|
2350
|
-
await git(["commit", "--quiet", "-m", message], repo, onHook);
|
|
2320
|
+
async function commit(repo, message, onHook, onNativeOutput) {
|
|
2321
|
+
await git(["commit", "--quiet", "-m", message], repo, onHook, onNativeOutput);
|
|
2351
2322
|
}
|
|
2352
2323
|
async function currentBranch(repo) {
|
|
2353
2324
|
return (await git(["symbolic-ref", "--short", "HEAD"], repo)).trim();
|
|
2354
2325
|
}
|
|
2326
|
+
async function switchBranch(repo, branch) {
|
|
2327
|
+
await git(["check-ref-format", "--branch", branch], repo);
|
|
2328
|
+
await git(["check-ref-format", `refs/heads/${branch}`], repo);
|
|
2329
|
+
const exists2 = await tryGit(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], repo) !== null;
|
|
2330
|
+
await git(exists2 ? ["switch", "--no-guess", "--", branch] : ["switch", "--no-track", "-c", branch], repo);
|
|
2331
|
+
}
|
|
2355
2332
|
async function getRepoStatus(repo) {
|
|
2356
2333
|
const lines = (await git(["status", "--porcelain=v2", "--branch", "--untracked-files=normal"], repo)).trim().split("\n");
|
|
2357
2334
|
const branch = lines.find((line) => line.startsWith("# branch.head "))?.slice(14) ?? "HEAD";
|
|
@@ -2366,23 +2343,21 @@ async function getPushRemote(repo, branch) {
|
|
|
2366
2343
|
if (upstream) return remotes.includes(upstream) ? upstream : null;
|
|
2367
2344
|
return remotes.length === 1 ? remotes[0] : null;
|
|
2368
2345
|
}
|
|
2369
|
-
async function push(repo, remote, branch, onHook) {
|
|
2346
|
+
async function push(repo, remote, branch, onHook, prefix, onNativeOutput) {
|
|
2370
2347
|
await git(["check-ref-format", "--branch", branch], repo);
|
|
2371
|
-
await git(["push", "--", remote, `HEAD:refs/heads/${branch}`], repo, onHook);
|
|
2348
|
+
await git(prefix ? ["subtree", "push", `--prefix=${prefix}`, remote, branch] : ["push", "--", remote, `HEAD:refs/heads/${branch}`], repo, onHook, onNativeOutput);
|
|
2372
2349
|
}
|
|
2373
|
-
async function hasUnpushedCommits(repo, remote, branch, onHook, prefix) {
|
|
2350
|
+
async function hasUnpushedCommits(repo, remote, branch, onHook, prefix, onNativeOutput) {
|
|
2374
2351
|
if (await tryGit(["rev-parse", "--verify", "HEAD"], repo) === null) return false;
|
|
2375
2352
|
await git(["check-ref-format", "--branch", branch], repo);
|
|
2376
2353
|
const source = prefix ? (await git(["subtree", "split", `--prefix=${prefix}`, "--quiet", "HEAD"], repo)).trim() : "HEAD";
|
|
2377
2354
|
const ref = `refs/heads/${branch}`;
|
|
2378
|
-
|
|
2379
|
-
|
|
2355
|
+
const refs = new Set((await git(["ls-remote", "--", remote, ref, "HEAD"], repo, onHook, onNativeOutput)).trim().split("\n").map((line) => line.split(" ")[1]));
|
|
2356
|
+
const target = refs.has(ref) ? ref : refs.has("HEAD") ? "HEAD" : null;
|
|
2357
|
+
if (!target) return true;
|
|
2358
|
+
await git(["fetch", "--quiet", "--", remote, target], repo, onHook, onNativeOutput);
|
|
2380
2359
|
return Boolean((await git(["rev-list", "--max-count=1", `FETCH_HEAD..${source}`], repo)).trim());
|
|
2381
2360
|
}
|
|
2382
|
-
async function publishSubtree(repo, prefix, remote, branch, onHook) {
|
|
2383
|
-
await git(["check-ref-format", "--branch", branch], repo);
|
|
2384
|
-
await git(["subtree", "push", `--prefix=${prefix}`, remote, branch], repo, onHook);
|
|
2385
|
-
}
|
|
2386
2361
|
async function findRepos(path) {
|
|
2387
2362
|
const root = await repoRoot(path);
|
|
2388
2363
|
if (root) return [root];
|
|
@@ -2424,6 +2399,21 @@ var commit_message_default = "You are zapdev, a CLI tool that creates commits qu
|
|
|
2424
2399
|
// src/prompts/index.ts
|
|
2425
2400
|
var COMMIT_SYSTEM_PROMPT = commit_message_default.trim();
|
|
2426
2401
|
|
|
2402
|
+
// src/lib/commit-message.ts
|
|
2403
|
+
function applyCommitType(systemPrompt, type) {
|
|
2404
|
+
return `${systemPrompt}
|
|
2405
|
+
|
|
2406
|
+
The type MUST be exactly "${type}".`;
|
|
2407
|
+
}
|
|
2408
|
+
var MAX_DIFF_CHARS = 12e3;
|
|
2409
|
+
function truncateDiff(diff, max = MAX_DIFF_CHARS) {
|
|
2410
|
+
return diff.length > max ? diff.slice(0, max) : diff;
|
|
2411
|
+
}
|
|
2412
|
+
function sanitizeCommitMessage(raw) {
|
|
2413
|
+
const firstLine = raw.split("\n", 1)[0] ?? "";
|
|
2414
|
+
return firstLine.replace(/[`"]/g, "").trim();
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2427
2417
|
// src/lib/llm.ts
|
|
2428
2418
|
var REQUEST_TIMEOUT_MS = 25e3;
|
|
2429
2419
|
async function generateCommitMessage(diff, config, type) {
|
|
@@ -2463,17 +2453,11 @@ async function generateCommitMessage(diff, config, type) {
|
|
|
2463
2453
|
return sanitizeCommitMessage(content);
|
|
2464
2454
|
}
|
|
2465
2455
|
function describeRequestError(error) {
|
|
2466
|
-
if (
|
|
2456
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
2467
2457
|
return `LLM did not answer within ${REQUEST_TIMEOUT_MS / 1e3}s.`;
|
|
2468
2458
|
}
|
|
2469
2459
|
return "Could not complete the LLM request. Check URL and endpoint availability.";
|
|
2470
2460
|
}
|
|
2471
|
-
function errorName(error) {
|
|
2472
|
-
if (error && typeof error === "object" && "name" in error && typeof error.name === "string") {
|
|
2473
|
-
return error.name;
|
|
2474
|
-
}
|
|
2475
|
-
return null;
|
|
2476
|
-
}
|
|
2477
2461
|
function serverError(body) {
|
|
2478
2462
|
if (!body || typeof body !== "object" || !("error" in body)) return null;
|
|
2479
2463
|
const error = body.error;
|
|
@@ -2491,6 +2475,28 @@ function messageContent(body) {
|
|
|
2491
2475
|
return typeof message.content === "string" ? message.content : null;
|
|
2492
2476
|
}
|
|
2493
2477
|
|
|
2478
|
+
// src/types/commit.ts
|
|
2479
|
+
var COMMIT_TYPES = [
|
|
2480
|
+
"feat",
|
|
2481
|
+
"fix",
|
|
2482
|
+
"chore",
|
|
2483
|
+
"docs",
|
|
2484
|
+
"style",
|
|
2485
|
+
"refactor",
|
|
2486
|
+
"perf",
|
|
2487
|
+
"test",
|
|
2488
|
+
"build",
|
|
2489
|
+
"ci",
|
|
2490
|
+
"revert"
|
|
2491
|
+
];
|
|
2492
|
+
|
|
2493
|
+
// src/commands/native-output.ts
|
|
2494
|
+
import { styleText as styleText3 } from "node:util";
|
|
2495
|
+
var reportNativeOutput = (phase) => {
|
|
2496
|
+
const symbol2 = styleText3("gray", phase === "start" ? S_BAR_END : S_BAR_START);
|
|
2497
|
+
log.message(phase === "start" ? symbol2 : ["", symbol2], { withGuide: false, spacing: 0 });
|
|
2498
|
+
};
|
|
2499
|
+
|
|
2494
2500
|
// src/commands/commit.ts
|
|
2495
2501
|
var commitCommand = defineCommand({
|
|
2496
2502
|
meta: {
|
|
@@ -2511,14 +2517,16 @@ var commitCommand = defineCommand({
|
|
|
2511
2517
|
description: "Override $ZD_EFFORT, sent as reasoning_effort."
|
|
2512
2518
|
},
|
|
2513
2519
|
type: {
|
|
2514
|
-
type: "
|
|
2520
|
+
type: "enum",
|
|
2521
|
+
options: [...COMMIT_TYPES],
|
|
2522
|
+
required: false,
|
|
2515
2523
|
alias: "t",
|
|
2516
2524
|
description: `Force the Conventional Commits type (${COMMIT_TYPES.join(", ")}).`
|
|
2517
2525
|
},
|
|
2518
2526
|
push: {
|
|
2519
2527
|
type: "boolean",
|
|
2520
2528
|
alias: "p",
|
|
2521
|
-
description: "Skip push confirmation
|
|
2529
|
+
description: "Skip push confirmation and push the current branch."
|
|
2522
2530
|
},
|
|
2523
2531
|
staged: {
|
|
2524
2532
|
type: "boolean",
|
|
@@ -2533,14 +2541,6 @@ var commitCommand = defineCommand({
|
|
|
2533
2541
|
},
|
|
2534
2542
|
async run({ args }) {
|
|
2535
2543
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
2536
|
-
const type = args.type ? normalizeCommitType(args.type) : void 0;
|
|
2537
|
-
if (type === null) {
|
|
2538
|
-
log.error(
|
|
2539
|
-
`Invalid type "${args.type}". Valid types: ${COMMIT_TYPES.join(", ")}.`
|
|
2540
|
-
);
|
|
2541
|
-
process.exitCode = 1;
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
2544
|
let config;
|
|
2545
2545
|
let subtrees;
|
|
2546
2546
|
try {
|
|
@@ -2580,12 +2580,12 @@ var commitCommand = defineCommand({
|
|
|
2580
2580
|
const { repo } = repository;
|
|
2581
2581
|
const { branch, hasChanges } = await getRepoStatus(repo);
|
|
2582
2582
|
repository.label = `${basename(repo)} (${branch})`;
|
|
2583
|
-
repository.pendingLabel = hasChanges ?
|
|
2583
|
+
repository.pendingLabel = hasChanges ? styleText4(["bold", "underline"], repository.label) : repository.label;
|
|
2584
2584
|
if (!args.staged) await stageAll(repo);
|
|
2585
2585
|
const diff = await getStagedDiff(repo);
|
|
2586
2586
|
if (!diff.trim()) return null;
|
|
2587
2587
|
if (scan) await scanStagedChanges(repo);
|
|
2588
|
-
const message = await generateCommitMessage(diff, config, type);
|
|
2588
|
+
const message = await generateCommitMessage(diff, config, args.type);
|
|
2589
2589
|
if (!message) throw new Error("The model returned an empty message.");
|
|
2590
2590
|
return { ...repository, message };
|
|
2591
2591
|
}));
|
|
@@ -2608,19 +2608,39 @@ var commitCommand = defineCommand({
|
|
|
2608
2608
|
cancel("Cancelled (changes left staged).");
|
|
2609
2609
|
return;
|
|
2610
2610
|
}
|
|
2611
|
-
const
|
|
2611
|
+
const toSend = [];
|
|
2612
|
+
let previousBranchInput = "";
|
|
2612
2613
|
for (const draft of selected) {
|
|
2613
|
-
log.info(`${draft.label}: committing`);
|
|
2614
2614
|
try {
|
|
2615
|
-
await
|
|
2616
|
-
|
|
2615
|
+
const current = await currentBranch(draft.repo);
|
|
2616
|
+
if (/^(main|master|principal|dev|development)$/.test(current)) {
|
|
2617
|
+
if (!interactive) throw new Error("Committing on a protected branch requires a terminal to choose a branch. Changes remain staged.");
|
|
2618
|
+
const answer = await text({
|
|
2619
|
+
message: `${basename(draft.repo)} (${current}): branch to commit to`,
|
|
2620
|
+
initialValue: previousBranchInput
|
|
2621
|
+
});
|
|
2622
|
+
if (isCancel(answer)) {
|
|
2623
|
+
cancel("Committing cancelled. Changes remain staged; earlier commits stay local.");
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
previousBranchInput = answer.trim();
|
|
2627
|
+
const branch = previousBranchInput || current;
|
|
2628
|
+
if (branch !== current) {
|
|
2629
|
+
await switchBranch(draft.repo, branch);
|
|
2630
|
+
draft.label = `${basename(draft.repo)} (${branch})`;
|
|
2631
|
+
draft.pendingLabel = styleText4(["bold", "underline"], draft.label);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
log.info(`${draft.label}: committing`);
|
|
2635
|
+
await commit(draft.repo, draft.message, reportHooks(draft.label), reportNativeOutput);
|
|
2636
|
+
toSend.push(draft);
|
|
2617
2637
|
log.success(`${draft.label}: committed ${draft.message}`);
|
|
2618
2638
|
} catch (error) {
|
|
2619
2639
|
reportGitFailure(`${draft.pendingLabel}: commit failed`, error);
|
|
2620
2640
|
process.exitCode = 1;
|
|
2621
2641
|
}
|
|
2622
2642
|
}
|
|
2623
|
-
|
|
2643
|
+
toSend.push(...unchanged);
|
|
2624
2644
|
if (toSend.length === 0) return;
|
|
2625
2645
|
let shouldPush = Boolean(args.push);
|
|
2626
2646
|
if (!shouldPush && interactive) {
|
|
@@ -2635,14 +2655,7 @@ var commitCommand = defineCommand({
|
|
|
2635
2655
|
shouldPush = answer;
|
|
2636
2656
|
}
|
|
2637
2657
|
if (shouldPush) {
|
|
2638
|
-
|
|
2639
|
-
log.error("Pushing requires a terminal to choose each destination branch. Commits remain local.");
|
|
2640
|
-
process.exitCode = 1;
|
|
2641
|
-
return;
|
|
2642
|
-
}
|
|
2643
|
-
let previousBranchInput = "";
|
|
2644
|
-
for (const repository of toSend) {
|
|
2645
|
-
const { repo, label } = repository;
|
|
2658
|
+
for (const { repo, label } of toSend) {
|
|
2646
2659
|
try {
|
|
2647
2660
|
const current = await currentBranch(repo);
|
|
2648
2661
|
const destinations = publishSubtreeMode ? Object.entries(subtrees) : [["", await getPushRemote(repo, current)]];
|
|
@@ -2651,24 +2664,14 @@ var commitCommand = defineCommand({
|
|
|
2651
2664
|
log.warn(`${label}: no remote or ambiguous remote choice. Skipping push; commit remains local.`);
|
|
2652
2665
|
continue;
|
|
2653
2666
|
}
|
|
2654
|
-
|
|
2667
|
+
log.info(`${label}: checking unpublished commits${prefix ? ` in ${prefix}` : ""} (${remote}/${current}).`);
|
|
2668
|
+
if (!await hasUnpushedCommits(repo, remote, current, reportHooks(label), prefix || void 0, reportNativeOutput)) {
|
|
2655
2669
|
log.info(`${label}: no unpushed commits${prefix ? ` in ${prefix}` : ""}. Skipping.`);
|
|
2656
2670
|
continue;
|
|
2657
2671
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
});
|
|
2662
|
-
if (isCancel(answer)) {
|
|
2663
|
-
outro("Sending cancelled. Remaining commits stay local.");
|
|
2664
|
-
return;
|
|
2665
|
-
}
|
|
2666
|
-
previousBranchInput = answer.trim();
|
|
2667
|
-
const branch = previousBranchInput || current;
|
|
2668
|
-
log.info(`${label}: pushing ${publishSubtreeMode ? `${prefix} \u2192 ` : ""}${remote}/${branch}`);
|
|
2669
|
-
if (publishSubtreeMode) await publishSubtree(repo, prefix, remote, branch, reportHooks(label));
|
|
2670
|
-
else await push(repo, remote, branch, reportHooks(label));
|
|
2671
|
-
log.success(`${label}: pushed to ${remote}/${branch}`);
|
|
2672
|
+
log.info(`${label}: pushing ${publishSubtreeMode ? `${prefix} \u2192 ` : ""}${remote}/${current}`);
|
|
2673
|
+
await push(repo, remote, current, reportHooks(label), prefix || void 0, reportNativeOutput);
|
|
2674
|
+
log.success(`${label}: pushed to ${remote}/${current}`);
|
|
2672
2675
|
}
|
|
2673
2676
|
} catch (error) {
|
|
2674
2677
|
reportGitFailure(`${label}: ${publishSubtreeMode ? "publication" : "push"} failed`, error);
|
|
@@ -2686,11 +2689,11 @@ function reportGitFailure(message, error) {
|
|
|
2686
2689
|
log.error(error instanceof GitOutputError ? message : `${message}: ${errorMessage(error)}`);
|
|
2687
2690
|
}
|
|
2688
2691
|
function reportHooks(label) {
|
|
2689
|
-
const options = { secondarySymbol: "" };
|
|
2692
|
+
const options = { secondarySymbol: "", withGuide: false };
|
|
2690
2693
|
return (event) => {
|
|
2691
2694
|
const hook = `${label}: ${event.name}`;
|
|
2692
2695
|
if (event.phase === "start") {
|
|
2693
|
-
log.step(
|
|
2696
|
+
log.step(styleText4("bold", hook), options);
|
|
2694
2697
|
} else {
|
|
2695
2698
|
if (event.exitCode === 0) log.success(`${hook}: completed`, options);
|
|
2696
2699
|
else log.error(`${hook}: failed (exit ${event.exitCode})`, options);
|
|
@@ -2784,7 +2787,7 @@ async function initializeSubtrees(directory, sources, options = {}) {
|
|
|
2784
2787
|
const imports = [];
|
|
2785
2788
|
for (const source of sources) {
|
|
2786
2789
|
options.onProgress?.(`Detecting default branch for ${source.name}`);
|
|
2787
|
-
const refs = await git(["ls-remote", "--symref", "--", source.url, "HEAD"], repo);
|
|
2790
|
+
const refs = await git(["ls-remote", "--symref", "--", source.url, "HEAD"], repo, void 0, options.onNativeOutput);
|
|
2788
2791
|
const branch = /^ref: refs\/heads\/(.+)\tHEAD$/m.exec(refs)?.[1];
|
|
2789
2792
|
if (!branch) options.onWarning?.(`Could not detect default branch for ${source.name}; using main.`);
|
|
2790
2793
|
imports.push({ ...source, branch: branch ?? "main" });
|
|
@@ -2795,12 +2798,12 @@ async function initializeSubtrees(directory, sources, options = {}) {
|
|
|
2795
2798
|
`);
|
|
2796
2799
|
await git(["add", "--", "zapdev.json"], repo);
|
|
2797
2800
|
if (initial || (await getStagedDiff(repo)).trim()) {
|
|
2798
|
-
await commit(repo, initial ? "chore: init" : "chore: configure subtrees");
|
|
2801
|
+
await commit(repo, initial ? "chore: init" : "chore: configure subtrees", void 0, options.onNativeOutput);
|
|
2799
2802
|
}
|
|
2800
2803
|
for (const [name, url] of missingRemotes) await git(["remote", "add", name, url], repo);
|
|
2801
2804
|
for (const { name, branch } of imports) {
|
|
2802
2805
|
options.onProgress?.(`Adding ${name} (${branch})`);
|
|
2803
|
-
await git(["subtree", "add", `--prefix=${name}`, name, branch, "--squash"], repo);
|
|
2806
|
+
await git(["subtree", "add", `--prefix=${name}`, name, branch, "--squash"], repo, void 0, options.onNativeOutput);
|
|
2804
2807
|
}
|
|
2805
2808
|
}
|
|
2806
2809
|
async function exists(path) {
|
|
@@ -2826,7 +2829,8 @@ var subtreeInitCommand = defineCommand({
|
|
|
2826
2829
|
await initializeSubtrees(args.directory, sources, {
|
|
2827
2830
|
origin: args.origin,
|
|
2828
2831
|
onProgress: log.info,
|
|
2829
|
-
onWarning: log.warn
|
|
2832
|
+
onWarning: log.warn,
|
|
2833
|
+
onNativeOutput: reportNativeOutput
|
|
2830
2834
|
});
|
|
2831
2835
|
if (interactive) outro("Subtrees ready.");
|
|
2832
2836
|
else log.info("Subtrees ready.");
|