zapdev 1.1.1 → 1.3.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 +1 -1
- package/dist/cli.js +51 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Stages all changes, scans them with Gitleaks when installed, generates Conventio
|
|
|
94
94
|
|
|
95
95
|
- **Inside a Git repository:** uses that repository only, including when launched from a subdirectory. Does not inspect child repositories.
|
|
96
96
|
- **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.
|
|
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 and underlined when terminal styling is enabled.
|
|
98
98
|
- **Actions:** commit all, commit only a named repository, edit a named repository's message, or cancel. Editing returns to the review menu.
|
|
99
99
|
|
|
100
100
|
Repositories with nothing to commit are skipped. Unselected or cancelled changes remain staged. Push confirmation is asked once for the successfully committed repositories.
|
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.3.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,6 +614,7 @@ var package_default = {
|
|
|
614
614
|
|
|
615
615
|
// src/commands/commit.ts
|
|
616
616
|
import { basename } from "node:path";
|
|
617
|
+
import { styleText as styleText3 } from "node:util";
|
|
617
618
|
|
|
618
619
|
// node_modules/@clack/core/dist/index.mjs
|
|
619
620
|
import { styleText } from "node:util";
|
|
@@ -2190,6 +2191,14 @@ async function commit(repo, message) {
|
|
|
2190
2191
|
async function currentBranch(repo) {
|
|
2191
2192
|
return (await git(["symbolic-ref", "--short", "HEAD"], repo)).trim();
|
|
2192
2193
|
}
|
|
2194
|
+
async function getRepoStatus(repo) {
|
|
2195
|
+
const lines = (await git(["status", "--porcelain=v2", "--branch", "--untracked-files=normal"], repo)).trim().split("\n");
|
|
2196
|
+
const branch = lines.find((line) => line.startsWith("# branch.head "))?.slice(14) ?? "HEAD";
|
|
2197
|
+
return {
|
|
2198
|
+
branch: branch === "(detached)" ? "detached HEAD" : branch,
|
|
2199
|
+
hasChanges: lines.some((line) => !line.startsWith("#"))
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2193
2202
|
async function hasUpstream(repo) {
|
|
2194
2203
|
const result = await tryGit(["rev-parse", "--abbrev-ref", "--symbolic-full-name", UPSTREAM_REF], repo);
|
|
2195
2204
|
return result !== null;
|
|
@@ -2422,25 +2431,30 @@ var commitCommand = defineCommand({
|
|
|
2422
2431
|
if (!scan) log.info("Gitleaks not found, skipping secret scan.");
|
|
2423
2432
|
const loader = interactive ? spinner() : void 0;
|
|
2424
2433
|
loader?.start("Preparing repositories and generating commit messages in parallel");
|
|
2425
|
-
const
|
|
2434
|
+
const repositories = repos.map((repo) => ({ repo, label: basename(repo), pendingLabel: basename(repo) }));
|
|
2435
|
+
const results = await Promise.allSettled(repositories.map(async (repository) => {
|
|
2436
|
+
const { repo } = repository;
|
|
2437
|
+
const { branch, hasChanges } = await getRepoStatus(repo);
|
|
2438
|
+
repository.label = `${basename(repo)} (${branch})`;
|
|
2439
|
+
repository.pendingLabel = hasChanges ? styleText3(["bold", "underline"], repository.label) : repository.label;
|
|
2426
2440
|
if (!args.staged) await stageAll(repo);
|
|
2427
2441
|
const diff = await getStagedDiff(repo);
|
|
2428
2442
|
if (!diff.trim()) return null;
|
|
2429
2443
|
if (scan) await scanStagedChanges(repo);
|
|
2430
2444
|
const message = await generateCommitMessage(diff, config, type);
|
|
2431
2445
|
if (!message) throw new Error("The model returned an empty message.");
|
|
2432
|
-
return {
|
|
2446
|
+
return { ...repository, message };
|
|
2433
2447
|
}));
|
|
2434
2448
|
loader?.stop("Repositories prepared");
|
|
2435
2449
|
const drafts = [];
|
|
2436
2450
|
for (const [index, result] of results.entries()) {
|
|
2437
2451
|
if (result.status === "rejected") {
|
|
2438
|
-
log.error(`${
|
|
2452
|
+
log.error(`${repositories[index].pendingLabel}: ${errorMessage(result.reason)}`);
|
|
2439
2453
|
process.exitCode = 1;
|
|
2440
2454
|
} else if (result.value) {
|
|
2441
2455
|
drafts.push(result.value);
|
|
2442
2456
|
} else {
|
|
2443
|
-
log.info(`${
|
|
2457
|
+
log.info(`${repositories[index].pendingLabel}: nothing to commit.`);
|
|
2444
2458
|
}
|
|
2445
2459
|
}
|
|
2446
2460
|
if (drafts.length === 0) {
|
|
@@ -2457,9 +2471,9 @@ var commitCommand = defineCommand({
|
|
|
2457
2471
|
try {
|
|
2458
2472
|
await commit(draft.repo, draft.message);
|
|
2459
2473
|
committed.push(draft);
|
|
2460
|
-
log.success(`${
|
|
2474
|
+
log.success(`${draft.label}: committed ${draft.message}`);
|
|
2461
2475
|
} catch (error) {
|
|
2462
|
-
log.error(`${
|
|
2476
|
+
log.error(`${draft.pendingLabel}: commit failed: ${errorMessage(error)}`);
|
|
2463
2477
|
process.exitCode = 1;
|
|
2464
2478
|
}
|
|
2465
2479
|
}
|
|
@@ -2467,7 +2481,7 @@ var commitCommand = defineCommand({
|
|
|
2467
2481
|
let shouldPush = Boolean(args.push);
|
|
2468
2482
|
if (!shouldPush && interactive && !args.yes) {
|
|
2469
2483
|
const answer = await confirm({
|
|
2470
|
-
message: `Push ${committed.map(({
|
|
2484
|
+
message: `Push ${committed.map(({ label }) => label).join(", ")}?`,
|
|
2471
2485
|
initialValue: false
|
|
2472
2486
|
});
|
|
2473
2487
|
if (isCancel(answer)) {
|
|
@@ -2477,17 +2491,17 @@ var commitCommand = defineCommand({
|
|
|
2477
2491
|
shouldPush = answer;
|
|
2478
2492
|
}
|
|
2479
2493
|
if (shouldPush) {
|
|
2480
|
-
for (const
|
|
2494
|
+
for (const repository of committed) {
|
|
2481
2495
|
try {
|
|
2482
2496
|
const pushed = await pushOptimistic(
|
|
2483
|
-
|
|
2497
|
+
repository,
|
|
2484
2498
|
interactive,
|
|
2485
2499
|
interactive && !args.yes,
|
|
2486
2500
|
syncStrategy
|
|
2487
2501
|
);
|
|
2488
2502
|
if (!pushed) process.exitCode = 1;
|
|
2489
2503
|
} catch (error) {
|
|
2490
|
-
log.error(`${
|
|
2504
|
+
log.error(`${repository.label}: push failed: ${errorMessage(error)}`);
|
|
2491
2505
|
process.exitCode = 1;
|
|
2492
2506
|
}
|
|
2493
2507
|
}
|
|
@@ -2497,7 +2511,7 @@ var commitCommand = defineCommand({
|
|
|
2497
2511
|
});
|
|
2498
2512
|
async function reviewMessages(drafts, canPrompt) {
|
|
2499
2513
|
while (true) {
|
|
2500
|
-
for (const draft of drafts) log.message(`${
|
|
2514
|
+
for (const draft of drafts) log.message(`${draft.pendingLabel}: ${draft.message}`);
|
|
2501
2515
|
if (!canPrompt) return drafts;
|
|
2502
2516
|
const action = await select({
|
|
2503
2517
|
message: "Action",
|
|
@@ -2506,11 +2520,11 @@ async function reviewMessages(drafts, canPrompt) {
|
|
|
2506
2520
|
{ value: "all", label: drafts.length > 1 ? "Commit all" : "Commit" },
|
|
2507
2521
|
...drafts.length > 1 ? drafts.map((draft) => ({
|
|
2508
2522
|
value: { action: "commit", draft },
|
|
2509
|
-
label: `Commit only "${
|
|
2523
|
+
label: `Commit only "${draft.pendingLabel}"`
|
|
2510
2524
|
})) : [],
|
|
2511
2525
|
...drafts.map((draft) => ({
|
|
2512
2526
|
value: { action: "edit", draft },
|
|
2513
|
-
label: `Edit message for "${
|
|
2527
|
+
label: `Edit message for "${draft.pendingLabel}"`
|
|
2514
2528
|
})),
|
|
2515
2529
|
{ value: "cancel", label: "Cancel" }
|
|
2516
2530
|
]
|
|
@@ -2519,7 +2533,7 @@ async function reviewMessages(drafts, canPrompt) {
|
|
|
2519
2533
|
if (action === "all") return drafts;
|
|
2520
2534
|
if (action.action === "commit") return [action.draft];
|
|
2521
2535
|
const edited = await text({
|
|
2522
|
-
message: `Edit message for "${
|
|
2536
|
+
message: `Edit message for "${action.draft.pendingLabel}"`,
|
|
2523
2537
|
initialValue: action.draft.message,
|
|
2524
2538
|
validate: (value) => value?.trim() ? void 0 : "Message cannot be empty."
|
|
2525
2539
|
});
|
|
@@ -2527,43 +2541,44 @@ async function reviewMessages(drafts, canPrompt) {
|
|
|
2527
2541
|
action.draft.message = edited.trim();
|
|
2528
2542
|
}
|
|
2529
2543
|
}
|
|
2530
|
-
async function syncWithUpstream(repo, strategy, interactive) {
|
|
2544
|
+
async function syncWithUpstream({ repo, label }, strategy, interactive) {
|
|
2531
2545
|
const loader = interactive ? spinner() : void 0;
|
|
2532
|
-
const
|
|
2533
|
-
loader?.start(`${
|
|
2546
|
+
const actionLabel = strategy === "rebase" ? "Rebase" : "Merge";
|
|
2547
|
+
loader?.start(`${label}: pulling --${strategy === "rebase" ? "rebase" : "no-rebase"}`);
|
|
2534
2548
|
try {
|
|
2535
2549
|
await (strategy === "rebase" ? pullRebase(repo) : pullMerge(repo));
|
|
2536
2550
|
loader?.stop(strategy === "rebase" ? "\u2713 Rebased on upstream" : "\u2713 Merged upstream");
|
|
2537
2551
|
return true;
|
|
2538
2552
|
} catch (error) {
|
|
2539
|
-
loader?.error(`${
|
|
2540
|
-
log.error(`${
|
|
2553
|
+
loader?.error(`${actionLabel} failed`);
|
|
2554
|
+
log.error(`${label}: ${actionLabel} failed (resolve conflicts, then push): ${errorMessage(error)}`);
|
|
2541
2555
|
return false;
|
|
2542
2556
|
}
|
|
2543
2557
|
}
|
|
2544
|
-
async function pushOptimistic(
|
|
2558
|
+
async function pushOptimistic(repository, interactive, canPrompt, strategy) {
|
|
2559
|
+
const { repo, label } = repository;
|
|
2545
2560
|
const [upstream, branch] = await Promise.all([hasUpstream(repo), currentBranch(repo)]);
|
|
2546
2561
|
const doPush = () => upstream ? push(repo) : pushSetUpstream(repo, branch);
|
|
2547
|
-
const first = await tryPush(
|
|
2562
|
+
const first = await tryPush(repository, interactive, doPush);
|
|
2548
2563
|
if (first.ok) return true;
|
|
2549
|
-
if (upstream && await isBehind(
|
|
2550
|
-
const syncStrategy = strategy ?? await chooseSyncStrategy(
|
|
2551
|
-
if (!syncStrategy || !await syncWithUpstream(
|
|
2552
|
-
const retry = await tryPush(
|
|
2564
|
+
if (upstream && await isBehind(repository, interactive)) {
|
|
2565
|
+
const syncStrategy = strategy ?? await chooseSyncStrategy(repository, canPrompt);
|
|
2566
|
+
if (!syncStrategy || !await syncWithUpstream(repository, syncStrategy, interactive)) return false;
|
|
2567
|
+
const retry = await tryPush(repository, interactive, doPush);
|
|
2553
2568
|
if (retry.ok) return true;
|
|
2554
|
-
log.error(`${
|
|
2569
|
+
log.error(`${label}: push failed: ${errorMessage(retry.error)}`);
|
|
2555
2570
|
return false;
|
|
2556
2571
|
}
|
|
2557
|
-
log.error(`${
|
|
2572
|
+
log.error(`${label}: push failed: ${errorMessage(first.error)}`);
|
|
2558
2573
|
return false;
|
|
2559
2574
|
}
|
|
2560
|
-
async function chooseSyncStrategy(
|
|
2575
|
+
async function chooseSyncStrategy({ label }, interactive) {
|
|
2561
2576
|
if (!interactive) {
|
|
2562
|
-
log.error(`${
|
|
2577
|
+
log.error(`${label}: branch is behind upstream. Re-run with --rebase or --merge.`);
|
|
2563
2578
|
return null;
|
|
2564
2579
|
}
|
|
2565
2580
|
const action = await select({
|
|
2566
|
-
message: `${
|
|
2581
|
+
message: `${label}: branch is behind upstream. How should zapdev sync it?`,
|
|
2567
2582
|
options: [
|
|
2568
2583
|
{ value: "rebase", label: "Rebase" },
|
|
2569
2584
|
{ value: "merge", label: "Merge" },
|
|
@@ -2576,9 +2591,9 @@ async function chooseSyncStrategy(repo, interactive) {
|
|
|
2576
2591
|
}
|
|
2577
2592
|
return action;
|
|
2578
2593
|
}
|
|
2579
|
-
async function tryPush(
|
|
2594
|
+
async function tryPush({ label }, interactive, doPush) {
|
|
2580
2595
|
const loader = interactive ? spinner() : void 0;
|
|
2581
|
-
loader?.start(`${
|
|
2596
|
+
loader?.start(`${label}: pushing`);
|
|
2582
2597
|
try {
|
|
2583
2598
|
await doPush();
|
|
2584
2599
|
loader?.stop("\u2713 Pushed");
|
|
@@ -2588,9 +2603,9 @@ async function tryPush(repo, interactive, doPush) {
|
|
|
2588
2603
|
return { ok: false, error };
|
|
2589
2604
|
}
|
|
2590
2605
|
}
|
|
2591
|
-
async function isBehind(repo, interactive) {
|
|
2606
|
+
async function isBehind({ repo, label }, interactive) {
|
|
2592
2607
|
const loader = interactive ? spinner() : void 0;
|
|
2593
|
-
loader?.start(`${
|
|
2608
|
+
loader?.start(`${label}: checking upstream`);
|
|
2594
2609
|
try {
|
|
2595
2610
|
await fetchRemote(repo);
|
|
2596
2611
|
const behind = await behindCount(repo);
|
|
@@ -2600,7 +2615,7 @@ async function isBehind(repo, interactive) {
|
|
|
2600
2615
|
return behind > 0;
|
|
2601
2616
|
} catch (error) {
|
|
2602
2617
|
loader?.error("Could not check upstream");
|
|
2603
|
-
log.warn(`${
|
|
2618
|
+
log.warn(`${label}: could not check upstream: ${errorMessage(error)}`);
|
|
2604
2619
|
return false;
|
|
2605
2620
|
}
|
|
2606
2621
|
}
|