zapdev 0.6.0 → 0.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 +11 -8
- package/dist/cli.js +321 -389
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,22 +45,25 @@ zapdev commit -t feat # force the type; the model still writes scope + desc
|
|
|
45
45
|
|
|
46
46
|
Without a TTY (piped / CI), it commits automatically and only pushes when `--push` is set.
|
|
47
47
|
|
|
48
|
-
### `zapdev
|
|
48
|
+
### `zapdev reset`
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Scans a directory for its **direct child** git 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
51
|
|
|
52
52
|
```bash
|
|
53
|
-
zapdev
|
|
54
|
-
zapdev
|
|
53
|
+
zapdev reset # child repos of the current directory
|
|
54
|
+
zapdev reset ~/dev # child repos of a specific directory
|
|
55
|
+
zapdev reset -p # switch each repo to its principal branch, no prompt
|
|
56
|
+
zapdev reset -t dev # switch each repo to `dev` (or principal if absent)
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
| Flag | Description |
|
|
58
60
|
| --- | --- |
|
|
59
|
-
| `-
|
|
60
|
-
| `-
|
|
61
|
-
|
|
|
61
|
+
| `-p, --principal` | Switch every repo to its resolved principal branch (`origin/HEAD`) |
|
|
62
|
+
| `-t, --target <branch>` | Switch every repo to `<branch>`, falling back to the principal one |
|
|
63
|
+
| `--pull` | Pull the checked-out branch after reset without asking |
|
|
64
|
+
| `-y, --yes` | Non-interactive: switch and delete without confirmation |
|
|
62
65
|
|
|
63
|
-
Deletion
|
|
66
|
+
Deletion is permanent: branches via `git branch -D` (force), worktrees via `git worktree remove --force`. Worktrees are removed first so the branches they held become deletable. Without a TTY, pass `--yes` — otherwise the destructive step is refused. `node_modules` is never scanned.
|
|
64
67
|
|
|
65
68
|
## Configuration
|
|
66
69
|
|
package/dist/cli.js
CHANGED
|
@@ -529,8 +529,8 @@ function block({
|
|
|
529
529
|
return;
|
|
530
530
|
}
|
|
531
531
|
if (!o2) return;
|
|
532
|
-
const i3 = a3 === "return" ? 0 : -1,
|
|
533
|
-
l.moveCursor(r3, i3,
|
|
532
|
+
const i3 = a3 === "return" ? 0 : -1, m2 = a3 === "return" ? -1 : 0;
|
|
533
|
+
l.moveCursor(r3, i3, m2, () => {
|
|
534
534
|
l.clearLine(r3, 1, () => {
|
|
535
535
|
e2.once("keypress", n4);
|
|
536
536
|
});
|
|
@@ -548,9 +548,9 @@ function wrapTextWithPrefix(e2, r3, o2, t3 = o2, s2 = o2, n4) {
|
|
|
548
548
|
hard: true,
|
|
549
549
|
trim: false
|
|
550
550
|
}).split(`
|
|
551
|
-
`).map((c3, i3,
|
|
551
|
+
`).map((c3, i3, m2) => {
|
|
552
552
|
const d2 = n4 ? n4(c3, i3) : c3;
|
|
553
|
-
return i3 === 0 ? `${t3}${d2}` : i3 ===
|
|
553
|
+
return i3 === 0 ? `${t3}${d2}` : i3 === m2.length - 1 ? `${s2}${d2}` : `${o2}${d2}`;
|
|
554
554
|
}).join(`
|
|
555
555
|
`);
|
|
556
556
|
}
|
|
@@ -747,56 +747,6 @@ var r = class extends V {
|
|
|
747
747
|
});
|
|
748
748
|
}
|
|
749
749
|
};
|
|
750
|
-
var a = class extends V {
|
|
751
|
-
options;
|
|
752
|
-
cursor = 0;
|
|
753
|
-
get _value() {
|
|
754
|
-
return this.options[this.cursor]?.value;
|
|
755
|
-
}
|
|
756
|
-
get _enabledOptions() {
|
|
757
|
-
return this.options.filter((e2) => e2.disabled !== true);
|
|
758
|
-
}
|
|
759
|
-
toggleAll() {
|
|
760
|
-
const e2 = this._enabledOptions, i3 = this.value !== void 0 && this.value.length === e2.length;
|
|
761
|
-
this.value = i3 ? [] : e2.map((t3) => t3.value);
|
|
762
|
-
}
|
|
763
|
-
toggleInvert() {
|
|
764
|
-
const e2 = this.value;
|
|
765
|
-
if (!e2)
|
|
766
|
-
return;
|
|
767
|
-
const i3 = this._enabledOptions.filter((t3) => !e2.includes(t3.value));
|
|
768
|
-
this.value = i3.map((t3) => t3.value);
|
|
769
|
-
}
|
|
770
|
-
toggleValue() {
|
|
771
|
-
this.value === void 0 && (this.value = []);
|
|
772
|
-
const e2 = this.value.includes(this._value);
|
|
773
|
-
this.value = e2 ? this.value.filter((i3) => i3 !== this._value) : [...this.value, this._value];
|
|
774
|
-
}
|
|
775
|
-
constructor(e2) {
|
|
776
|
-
super(e2, false), this.options = e2.options, this.value = [...e2.initialValues ?? []];
|
|
777
|
-
const i3 = Math.max(
|
|
778
|
-
this.options.findIndex(({ value: t3 }) => t3 === e2.cursorAt),
|
|
779
|
-
0
|
|
780
|
-
);
|
|
781
|
-
this.cursor = this.options[i3]?.disabled ? findCursor(i3, 1, this.options) : i3, this.on("key", (t3, l3) => {
|
|
782
|
-
l3.name === "a" && this.toggleAll(), l3.name === "i" && this.toggleInvert();
|
|
783
|
-
}), this.on("cursor", (t3) => {
|
|
784
|
-
switch (t3) {
|
|
785
|
-
case "left":
|
|
786
|
-
case "up":
|
|
787
|
-
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
788
|
-
break;
|
|
789
|
-
case "down":
|
|
790
|
-
case "right":
|
|
791
|
-
this.cursor = findCursor(this.cursor, 1, this.options);
|
|
792
|
-
break;
|
|
793
|
-
case "space":
|
|
794
|
-
this.toggleValue();
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
};
|
|
800
750
|
var n$1 = class n extends V {
|
|
801
751
|
options;
|
|
802
752
|
cursor = 0;
|
|
@@ -922,13 +872,13 @@ var I = (l3, e2, w2, p2, b2, C2 = false) => {
|
|
|
922
872
|
let r3 = e2, O2 = 0;
|
|
923
873
|
if (C2)
|
|
924
874
|
for (let i3 = p2 - 1; i3 >= w2; i3--) {
|
|
925
|
-
const
|
|
926
|
-
if (
|
|
875
|
+
const m2 = l3[i3];
|
|
876
|
+
if (m2 && (r3 -= m2.length), O2++, r3 <= b2) break;
|
|
927
877
|
}
|
|
928
878
|
else
|
|
929
879
|
for (let i3 = w2; i3 < p2; i3++) {
|
|
930
|
-
const
|
|
931
|
-
if (
|
|
880
|
+
const m2 = l3[i3];
|
|
881
|
+
if (m2 && (r3 -= m2.length), O2++, r3 <= b2) break;
|
|
932
882
|
}
|
|
933
883
|
return { lineCount: r3, removals: O2 };
|
|
934
884
|
};
|
|
@@ -941,7 +891,7 @@ var limitOptions = ({
|
|
|
941
891
|
columnPadding: C2 = 0,
|
|
942
892
|
rowPadding: r3 = 4
|
|
943
893
|
}) => {
|
|
944
|
-
const i3 = getColumns(p2) - C2,
|
|
894
|
+
const i3 = getColumns(p2) - C2, m2 = getRows(p2), M = styleText2("dim", "..."), v2 = Math.max(m2 - r3, 0), a3 = Math.max(Math.min(b2, v2), 5);
|
|
945
895
|
let f2 = 0;
|
|
946
896
|
l3 >= a3 - 3 && (f2 = Math.max(
|
|
947
897
|
Math.min(l3 - a3 + 3, e2.length - a3),
|
|
@@ -1029,118 +979,11 @@ var MULTISELECT_INSTRUCTIONS = [
|
|
|
1029
979
|
`${styleText2("dim", "Space:")} select`,
|
|
1030
980
|
`${styleText2("dim", "Enter:")} confirm`
|
|
1031
981
|
];
|
|
1032
|
-
var m = (i3, u4) => i3.split(`
|
|
1033
|
-
`).map((d2) => u4(d2)).join(`
|
|
1034
|
-
`);
|
|
1035
|
-
var multiselect = (i3) => {
|
|
1036
|
-
const u4 = (t3, a3) => {
|
|
1037
|
-
const r3 = t3.label ?? String(t3.value);
|
|
1038
|
-
return a3 === "disabled" ? `${styleText2("gray", S_CHECKBOX_INACTIVE)} ${m(r3, (o2) => styleText2(["strikethrough", "gray"], o2))}${t3.hint ? ` ${styleText2("dim", `(${t3.hint ?? "disabled"})`)}` : ""}` : a3 === "active" ? `${styleText2("cyan", S_CHECKBOX_ACTIVE)} ${r3}${t3.hint ? ` ${styleText2("dim", `(${t3.hint})`)}` : ""}` : a3 === "selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${m(r3, (o2) => styleText2("dim", o2))}${t3.hint ? ` ${styleText2("dim", `(${t3.hint})`)}` : ""}` : a3 === "cancelled" ? `${m(r3, (o2) => styleText2(["strikethrough", "dim"], o2))}` : a3 === "active-selected" ? `${styleText2("green", S_CHECKBOX_SELECTED)} ${r3}${t3.hint ? ` ${styleText2("dim", `(${t3.hint})`)}` : ""}` : a3 === "submitted" ? `${m(r3, (o2) => styleText2("dim", o2))}` : `${styleText2("dim", S_CHECKBOX_INACTIVE)} ${m(r3, (o2) => styleText2("dim", o2))}`;
|
|
1039
|
-
}, d2 = i3.required ?? true, v2 = i3.showInstructions ?? true;
|
|
1040
|
-
return new a({
|
|
1041
|
-
options: i3.options,
|
|
1042
|
-
signal: i3.signal,
|
|
1043
|
-
input: i3.input,
|
|
1044
|
-
output: i3.output,
|
|
1045
|
-
initialValues: i3.initialValues,
|
|
1046
|
-
required: d2,
|
|
1047
|
-
cursorAt: i3.cursorAt,
|
|
1048
|
-
validate(t3) {
|
|
1049
|
-
if (d2 && (t3 === void 0 || t3.length === 0))
|
|
1050
|
-
return `Please select at least one option.
|
|
1051
|
-
${styleText2(
|
|
1052
|
-
"reset",
|
|
1053
|
-
styleText2(
|
|
1054
|
-
"dim",
|
|
1055
|
-
`Press ${styleText2(["gray", "bgWhite", "inverse"], " space ")} to select, ${styleText2(
|
|
1056
|
-
"gray",
|
|
1057
|
-
styleText2("bgWhite", styleText2("inverse", " enter "))
|
|
1058
|
-
)} to submit`
|
|
1059
|
-
)
|
|
1060
|
-
)}`;
|
|
1061
|
-
},
|
|
1062
|
-
render() {
|
|
1063
|
-
const t3 = i3.withGuide ?? settings.withGuide, a3 = wrapTextWithPrefix(
|
|
1064
|
-
i3.output,
|
|
1065
|
-
i3.message,
|
|
1066
|
-
t3 ? `${symbolBar(this.state)} ` : "",
|
|
1067
|
-
`${symbol(this.state)} `
|
|
1068
|
-
), r3 = `${t3 ? `${styleText2("gray", S_BAR)}
|
|
1069
|
-
` : ""}${a3}
|
|
1070
|
-
`, o2 = this.value ?? [], p2 = (n4, l3) => {
|
|
1071
|
-
if (n4.disabled)
|
|
1072
|
-
return u4(n4, "disabled");
|
|
1073
|
-
const s2 = o2.includes(n4.value);
|
|
1074
|
-
return l3 && s2 ? u4(n4, "active-selected") : s2 ? u4(n4, "selected") : u4(n4, l3 ? "active" : "inactive");
|
|
1075
|
-
};
|
|
1076
|
-
switch (this.state) {
|
|
1077
|
-
case "submit": {
|
|
1078
|
-
const n4 = this.options.filter(({ value: s2 }) => o2.includes(s2)).map((s2) => u4(s2, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), l3 = wrapTextWithPrefix(
|
|
1079
|
-
i3.output,
|
|
1080
|
-
n4,
|
|
1081
|
-
t3 ? `${styleText2("gray", S_BAR)} ` : ""
|
|
1082
|
-
);
|
|
1083
|
-
return `${r3}${l3}`;
|
|
1084
|
-
}
|
|
1085
|
-
case "cancel": {
|
|
1086
|
-
const n4 = this.options.filter(({ value: s2 }) => o2.includes(s2)).map((s2) => u4(s2, "cancelled")).join(styleText2("dim", ", "));
|
|
1087
|
-
if (n4.trim() === "")
|
|
1088
|
-
return `${r3}${styleText2("gray", S_BAR)}`;
|
|
1089
|
-
const l3 = wrapTextWithPrefix(
|
|
1090
|
-
i3.output,
|
|
1091
|
-
n4,
|
|
1092
|
-
t3 ? `${styleText2("gray", S_BAR)} ` : ""
|
|
1093
|
-
);
|
|
1094
|
-
return `${r3}${l3}${t3 ? `
|
|
1095
|
-
${styleText2("gray", S_BAR)}` : ""}`;
|
|
1096
|
-
}
|
|
1097
|
-
case "error": {
|
|
1098
|
-
const n4 = t3 ? `${styleText2("yellow", S_BAR)} ` : "", l3 = this.error.split(`
|
|
1099
|
-
`).map(
|
|
1100
|
-
($, C2) => C2 === 0 ? `${t3 ? `${styleText2("yellow", S_BAR_END)} ` : ""}${styleText2("yellow", $)}` : ` ${$}`
|
|
1101
|
-
).join(`
|
|
1102
|
-
`), s2 = r3.split(`
|
|
1103
|
-
`).length, h3 = l3.split(`
|
|
1104
|
-
`).length + 1;
|
|
1105
|
-
return `${r3}${n4}${limitOptions({
|
|
1106
|
-
output: i3.output,
|
|
1107
|
-
options: this.options,
|
|
1108
|
-
cursor: this.cursor,
|
|
1109
|
-
maxItems: i3.maxItems,
|
|
1110
|
-
columnPadding: n4.length,
|
|
1111
|
-
rowPadding: s2 + h3,
|
|
1112
|
-
style: p2
|
|
1113
|
-
}).join(`
|
|
1114
|
-
${n4}`)}
|
|
1115
|
-
${l3}
|
|
1116
|
-
`;
|
|
1117
|
-
}
|
|
1118
|
-
default: {
|
|
1119
|
-
const n4 = t3 ? `${styleText2("cyan", S_BAR)} ` : "", l3 = r3.split(`
|
|
1120
|
-
`).length, s2 = v2 ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, t3) : t3 ? [styleText2("cyan", S_BAR_END)] : [], h3 = s2.join(`
|
|
1121
|
-
`), $ = s2.length + 1;
|
|
1122
|
-
return `${r3}${n4}${limitOptions({
|
|
1123
|
-
output: i3.output,
|
|
1124
|
-
options: this.options,
|
|
1125
|
-
cursor: this.cursor,
|
|
1126
|
-
maxItems: i3.maxItems,
|
|
1127
|
-
columnPadding: n4.length,
|
|
1128
|
-
rowPadding: l3 + $,
|
|
1129
|
-
style: p2
|
|
1130
|
-
}).join(`
|
|
1131
|
-
${n4}`)}
|
|
1132
|
-
${h3}
|
|
1133
|
-
`;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}).prompt();
|
|
1138
|
-
};
|
|
1139
982
|
var log = {
|
|
1140
983
|
message: (s2 = [], {
|
|
1141
984
|
symbol: e2 = styleText2("gray", S_BAR),
|
|
1142
985
|
secondarySymbol: r3 = styleText2("gray", S_BAR),
|
|
1143
|
-
output:
|
|
986
|
+
output: m2 = process.stdout,
|
|
1144
987
|
spacing: l3 = 1,
|
|
1145
988
|
withGuide: c3
|
|
1146
989
|
} = {}) => {
|
|
@@ -1155,7 +998,7 @@ var log = {
|
|
|
1155
998
|
for (const p2 of y2)
|
|
1156
999
|
p2.length > 0 ? t3.push(`${u4}${p2}`) : t3.push(o2 ? r3 : "");
|
|
1157
1000
|
}
|
|
1158
|
-
|
|
1001
|
+
m2.write(`${t3.join(`
|
|
1159
1002
|
`)}
|
|
1160
1003
|
`);
|
|
1161
1004
|
},
|
|
@@ -1206,7 +1049,7 @@ var spinner = ({
|
|
|
1206
1049
|
errorMessage: O2,
|
|
1207
1050
|
frames: E2 = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
|
|
1208
1051
|
delay: F2 = unicode ? 80 : 120,
|
|
1209
|
-
signal:
|
|
1052
|
+
signal: m2,
|
|
1210
1053
|
...I3
|
|
1211
1054
|
} = {}) => {
|
|
1212
1055
|
const u4 = isCI();
|
|
@@ -1215,9 +1058,9 @@ var spinner = ({
|
|
|
1215
1058
|
const r3 = e2 > 1 ? O2 ?? settings.messages.error : G ?? settings.messages.cancel;
|
|
1216
1059
|
S2 = e2 === 1, d2 && (a3(r3, e2), S2 && typeof h3 == "function" && h3());
|
|
1217
1060
|
}, f2 = () => g2(2), i3 = () => g2(1), A = () => {
|
|
1218
|
-
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i3), process.on("SIGTERM", i3), process.on("exit", g2),
|
|
1061
|
+
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i3), process.on("SIGTERM", i3), process.on("exit", g2), m2 && m2.addEventListener("abort", i3);
|
|
1219
1062
|
}, H = () => {
|
|
1220
|
-
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i3), process.removeListener("SIGTERM", i3), process.removeListener("exit", g2),
|
|
1063
|
+
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i3), process.removeListener("SIGTERM", i3), process.removeListener("exit", g2), m2 && m2.removeEventListener("abort", i3);
|
|
1221
1064
|
}, y2 = () => {
|
|
1222
1065
|
if (p2 === void 0) return;
|
|
1223
1066
|
u4 && n4.write(`
|
|
@@ -1291,11 +1134,11 @@ var c = (t3, o2) => t3.includes(`
|
|
|
1291
1134
|
`).map((d2) => o2(d2)).join(`
|
|
1292
1135
|
`) : o2(t3);
|
|
1293
1136
|
var select = (t3) => {
|
|
1294
|
-
const o2 = (n4,
|
|
1137
|
+
const o2 = (n4, m2) => {
|
|
1295
1138
|
if (n4 === void 0)
|
|
1296
1139
|
return "";
|
|
1297
1140
|
const s2 = n4.label ?? String(n4.value);
|
|
1298
|
-
switch (
|
|
1141
|
+
switch (m2) {
|
|
1299
1142
|
case "disabled":
|
|
1300
1143
|
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c(s2, (i3) => styleText2("gray", i3))}${n4.hint ? ` ${styleText2("dim", `(${n4.hint ?? "disabled"})`)}` : ""}`;
|
|
1301
1144
|
case "selected":
|
|
@@ -1315,11 +1158,11 @@ var select = (t3) => {
|
|
|
1315
1158
|
output: t3.output,
|
|
1316
1159
|
initialValue: t3.initialValue,
|
|
1317
1160
|
render() {
|
|
1318
|
-
const n4 = t3.withGuide ?? settings.withGuide,
|
|
1161
|
+
const n4 = t3.withGuide ?? settings.withGuide, m2 = `${symbol(this.state)} `, s2 = `${symbolBar(this.state)} `, i3 = wrapTextWithPrefix(
|
|
1319
1162
|
t3.output,
|
|
1320
1163
|
t3.message,
|
|
1321
1164
|
s2,
|
|
1322
|
-
|
|
1165
|
+
m2
|
|
1323
1166
|
), u4 = `${n4 ? `${styleText2("gray", S_BAR)}
|
|
1324
1167
|
` : ""}${i3}
|
|
1325
1168
|
`;
|
|
@@ -1875,7 +1718,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
1875
1718
|
// package.json
|
|
1876
1719
|
var package_default = {
|
|
1877
1720
|
name: "zapdev",
|
|
1878
|
-
version: "0.
|
|
1721
|
+
version: "0.7.0",
|
|
1879
1722
|
description: "Fast, performant and lightweight git chores and dev tools using your terminal and your own local LLM.",
|
|
1880
1723
|
type: "module",
|
|
1881
1724
|
bin: {
|
|
@@ -1934,23 +1777,52 @@ var package_default = {
|
|
|
1934
1777
|
}
|
|
1935
1778
|
};
|
|
1936
1779
|
|
|
1937
|
-
// src/
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1780
|
+
// src/types/commit.ts
|
|
1781
|
+
var COMMIT_TYPES = [
|
|
1782
|
+
"feat",
|
|
1783
|
+
"fix",
|
|
1784
|
+
"chore",
|
|
1785
|
+
"docs",
|
|
1786
|
+
"style",
|
|
1787
|
+
"refactor",
|
|
1788
|
+
"perf",
|
|
1789
|
+
"test",
|
|
1790
|
+
"build",
|
|
1791
|
+
"ci",
|
|
1792
|
+
"revert"
|
|
1793
|
+
];
|
|
1794
|
+
|
|
1795
|
+
// src/lib/commit-message.ts
|
|
1796
|
+
function normalizeCommitType(input) {
|
|
1797
|
+
const normalized = input.trim().toLowerCase();
|
|
1798
|
+
return COMMIT_TYPES.includes(normalized) ? normalized : null;
|
|
1942
1799
|
}
|
|
1943
|
-
function
|
|
1944
|
-
return
|
|
1800
|
+
function applyCommitType(systemPrompt, type) {
|
|
1801
|
+
return `${systemPrompt}
|
|
1802
|
+
|
|
1803
|
+
The type MUST be exactly "${type}".`;
|
|
1945
1804
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
return
|
|
1805
|
+
var MAX_DIFF_CHARS = 12e3;
|
|
1806
|
+
function truncateDiff(diff, max = MAX_DIFF_CHARS) {
|
|
1807
|
+
return diff.length > max ? diff.slice(0, max) : diff;
|
|
1808
|
+
}
|
|
1809
|
+
function sanitizeCommitMessage(raw) {
|
|
1810
|
+
const firstLine = raw.split("\n", 1)[0] ?? "";
|
|
1811
|
+
return firstLine.replace(/[`"]/g, "").trim();
|
|
1949
1812
|
}
|
|
1950
1813
|
|
|
1951
|
-
// src/lib/
|
|
1952
|
-
|
|
1953
|
-
|
|
1814
|
+
// src/lib/config.ts
|
|
1815
|
+
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
1816
|
+
var DEFAULT_MODEL = "deepseek-v4-flash:cloud";
|
|
1817
|
+
function resolveConfig(env = process.env, overrides = {}) {
|
|
1818
|
+
return {
|
|
1819
|
+
ollamaUrl: normalizeBaseUrl(overrides.ollamaUrl ?? env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL),
|
|
1820
|
+
model: overrides.model ?? env.OLLAMA_MODEL ?? DEFAULT_MODEL
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function normalizeBaseUrl(raw) {
|
|
1824
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
1825
|
+
return /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
1954
1826
|
}
|
|
1955
1827
|
|
|
1956
1828
|
// src/lib/git.ts
|
|
@@ -1964,7 +1836,7 @@ import { basename as r2, delimiter as i2, dirname as a2, normalize as o, resolve
|
|
|
1964
1836
|
import { pipeline as c2 } from "node:stream/promises";
|
|
1965
1837
|
import { PassThrough as l2 } from "node:stream";
|
|
1966
1838
|
import u3 from "node:readline";
|
|
1967
|
-
import { closeSync as d, openSync as f, readSync as p, statSync as
|
|
1839
|
+
import { closeSync as d, openSync as f, readSync as p, statSync as m } from "node:fs";
|
|
1968
1840
|
var h2 = /^path$/i;
|
|
1969
1841
|
var g = {
|
|
1970
1842
|
key: "PATH",
|
|
@@ -2105,7 +1977,7 @@ function O(e2, t3) {
|
|
|
2105
1977
|
for (const e3 of l3) {
|
|
2106
1978
|
const t5 = n4 + e3;
|
|
2107
1979
|
try {
|
|
2108
|
-
if (
|
|
1980
|
+
if (m(t5).isFile()) return t5;
|
|
2109
1981
|
} catch {
|
|
2110
1982
|
}
|
|
2111
1983
|
}
|
|
@@ -2283,6 +2155,37 @@ var R2 = (e2, t3, n4) => {
|
|
|
2283
2155
|
};
|
|
2284
2156
|
var z = R2;
|
|
2285
2157
|
|
|
2158
|
+
// src/lib/branches.ts
|
|
2159
|
+
function parseDefaultBranch(originHead) {
|
|
2160
|
+
const trimmed = originHead.trim();
|
|
2161
|
+
if (!trimmed) return null;
|
|
2162
|
+
return trimmed.replace(/^origin\//, "");
|
|
2163
|
+
}
|
|
2164
|
+
function parseBranches(raw) {
|
|
2165
|
+
return raw.split("\n").map((branch) => branch.trim()).filter(Boolean);
|
|
2166
|
+
}
|
|
2167
|
+
function deletableBranches(all, protectedBranches) {
|
|
2168
|
+
const kept = new Set(protectedBranches.filter(Boolean));
|
|
2169
|
+
return all.filter((branch) => !kept.has(branch));
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// src/lib/worktrees.ts
|
|
2173
|
+
var WORKTREE_PREFIX = "worktree ";
|
|
2174
|
+
var BRANCH_PREFIX = "branch ";
|
|
2175
|
+
function parseWorktrees(porcelain) {
|
|
2176
|
+
const worktrees = [];
|
|
2177
|
+
let current = null;
|
|
2178
|
+
for (const line of porcelain.split("\n")) {
|
|
2179
|
+
if (line.startsWith(WORKTREE_PREFIX)) {
|
|
2180
|
+
current = { path: line.slice(WORKTREE_PREFIX.length).trim(), branch: null };
|
|
2181
|
+
worktrees.push(current);
|
|
2182
|
+
} else if (current && line.startsWith(BRANCH_PREFIX)) {
|
|
2183
|
+
current.branch = line.slice(BRANCH_PREFIX.length).trim().replace(/^refs\/heads\//, "");
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return worktrees;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2286
2189
|
// src/lib/git.ts
|
|
2287
2190
|
var UPSTREAM_REF = "@{upstream}";
|
|
2288
2191
|
var BRANCH_FORMAT = "%(refname:short)";
|
|
@@ -2319,22 +2222,21 @@ async function push() {
|
|
|
2319
2222
|
async function pushSetUpstream(branch) {
|
|
2320
2223
|
await git(["push", "-u", "origin", branch]);
|
|
2321
2224
|
}
|
|
2322
|
-
async function
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2225
|
+
async function findChildRepos(parent) {
|
|
2226
|
+
const entries = await readdir(parent, { withFileTypes: true }).catch(() => null);
|
|
2227
|
+
if (!entries) return [];
|
|
2228
|
+
const dirs = entries.filter((entry) => entry.isDirectory() && entry.name !== "node_modules");
|
|
2229
|
+
const repos = await Promise.all(
|
|
2230
|
+
dirs.map(async (entry) => {
|
|
2231
|
+
const dir = join(parent, entry.name);
|
|
2232
|
+
return await isGitRepo(dir) ? dir : null;
|
|
2233
|
+
})
|
|
2234
|
+
);
|
|
2235
|
+
return repos.filter((dir) => dir !== null).sort();
|
|
2326
2236
|
}
|
|
2327
|
-
async function
|
|
2237
|
+
async function isGitRepo(dir) {
|
|
2328
2238
|
const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
|
|
2329
|
-
|
|
2330
|
-
if (entries.some((entry) => entry.name === ".git")) {
|
|
2331
|
-
repos.push(dir);
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
const subdirs = entries.filter(
|
|
2335
|
-
(entry) => entry.isDirectory() && entry.name !== "node_modules"
|
|
2336
|
-
);
|
|
2337
|
-
await Promise.all(subdirs.map((entry) => collectRepos(join(dir, entry.name), repos)));
|
|
2239
|
+
return entries?.some((entry) => entry.name === ".git" && entry.isDirectory()) ?? false;
|
|
2338
2240
|
}
|
|
2339
2241
|
async function fetchPrune(repo) {
|
|
2340
2242
|
await git(["fetch", "--prune"], repo);
|
|
@@ -2353,6 +2255,21 @@ async function currentBranchAt(repo) {
|
|
|
2353
2255
|
async function localBranches(repo) {
|
|
2354
2256
|
return parseBranches(await git(["branch", `--format=${BRANCH_FORMAT}`], repo));
|
|
2355
2257
|
}
|
|
2258
|
+
async function branchExists(repo, branch) {
|
|
2259
|
+
const local = await tryGit(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], repo);
|
|
2260
|
+
if (local !== null) return true;
|
|
2261
|
+
const remote = await tryGit(
|
|
2262
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`],
|
|
2263
|
+
repo
|
|
2264
|
+
);
|
|
2265
|
+
return remote !== null;
|
|
2266
|
+
}
|
|
2267
|
+
async function listWorktrees(repo) {
|
|
2268
|
+
return parseWorktrees(await git(["worktree", "list", "--porcelain"], repo));
|
|
2269
|
+
}
|
|
2270
|
+
async function removeWorktree(repo, path) {
|
|
2271
|
+
await git(["worktree", "remove", "--force", path], repo);
|
|
2272
|
+
}
|
|
2356
2273
|
async function switchBranch(repo, branch) {
|
|
2357
2274
|
await git(["switch", branch], repo);
|
|
2358
2275
|
}
|
|
@@ -2363,188 +2280,9 @@ async function pull(repo) {
|
|
|
2363
2280
|
await git(["pull"], repo);
|
|
2364
2281
|
}
|
|
2365
2282
|
|
|
2366
|
-
// src/
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
name: "cleanup",
|
|
2370
|
-
description: "Prune every git repo under a path, then delete stale local branches."
|
|
2371
|
-
},
|
|
2372
|
-
args: {
|
|
2373
|
-
path: {
|
|
2374
|
-
type: "positional",
|
|
2375
|
-
required: false,
|
|
2376
|
-
default: ".",
|
|
2377
|
-
description: "Directory to scan for git repos (recursively)."
|
|
2378
|
-
},
|
|
2379
|
-
stay: {
|
|
2380
|
-
type: "boolean",
|
|
2381
|
-
alias: "s",
|
|
2382
|
-
description: "Stay on the current branch instead of switching to the default."
|
|
2383
|
-
},
|
|
2384
|
-
pull: {
|
|
2385
|
-
type: "boolean",
|
|
2386
|
-
alias: "p",
|
|
2387
|
-
description: "Pull the checked-out branch after cleanup without asking."
|
|
2388
|
-
},
|
|
2389
|
-
yes: {
|
|
2390
|
-
type: "boolean",
|
|
2391
|
-
alias: "y",
|
|
2392
|
-
description: "Non-interactive: switch to default and delete every other local branch."
|
|
2393
|
-
}
|
|
2394
|
-
},
|
|
2395
|
-
async run({ args }) {
|
|
2396
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !args.yes;
|
|
2397
|
-
const options = { stay: Boolean(args.stay), pull: Boolean(args.pull) };
|
|
2398
|
-
const path = args.path || ".";
|
|
2399
|
-
if (interactive) intro("zapdev cleanup");
|
|
2400
|
-
const repos = await findGitRepos(path);
|
|
2401
|
-
if (repos.length === 0) {
|
|
2402
|
-
log.warn(`No git repository found under ${path}.`);
|
|
2403
|
-
if (interactive) outro("Nothing to do.");
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
|
-
for (const repo of repos) {
|
|
2407
|
-
const cancelled = await cleanupRepo(repo, options, interactive);
|
|
2408
|
-
if (cancelled) {
|
|
2409
|
-
cancel("Cancelled.");
|
|
2410
|
-
return;
|
|
2411
|
-
}
|
|
2412
|
-
}
|
|
2413
|
-
if (interactive) outro(`Done (${repos.length} repo${repos.length > 1 ? "s" : ""}).`);
|
|
2414
|
-
}
|
|
2415
|
-
});
|
|
2416
|
-
async function cleanupRepo(repo, options, interactive) {
|
|
2417
|
-
log.step(`Cleaning ${repo}`);
|
|
2418
|
-
const fetchLoader = interactive ? spinner() : void 0;
|
|
2419
|
-
fetchLoader?.start("Fetching + pruning\u2026");
|
|
2420
|
-
try {
|
|
2421
|
-
await fetchPrune(repo);
|
|
2422
|
-
fetchLoader?.stop("Fetched + pruned");
|
|
2423
|
-
} catch (error) {
|
|
2424
|
-
fetchLoader?.error("Fetch failed");
|
|
2425
|
-
log.warn(` Fetch failed, continuing: ${errorMessage(error)}`);
|
|
2426
|
-
}
|
|
2427
|
-
const [base, current] = await Promise.all([defaultBranch(repo), currentBranchAt(repo)]);
|
|
2428
|
-
if (!base) {
|
|
2429
|
-
log.warn(" Skipped: no origin/HEAD.");
|
|
2430
|
-
return false;
|
|
2431
|
-
}
|
|
2432
|
-
let endBranch = base;
|
|
2433
|
-
if (current && current !== base) {
|
|
2434
|
-
if (options.stay) {
|
|
2435
|
-
endBranch = current;
|
|
2436
|
-
} else if (interactive) {
|
|
2437
|
-
const choice = await select({
|
|
2438
|
-
message: "After cleanup, be on:",
|
|
2439
|
-
initialValue: base,
|
|
2440
|
-
options: [
|
|
2441
|
-
{ value: base, label: `Switch to ${base}` },
|
|
2442
|
-
{ value: current, label: `Stay on ${current} (keep it)` }
|
|
2443
|
-
]
|
|
2444
|
-
});
|
|
2445
|
-
if (isCancel(choice)) return true;
|
|
2446
|
-
endBranch = choice;
|
|
2447
|
-
}
|
|
2448
|
-
}
|
|
2449
|
-
if (current !== endBranch) {
|
|
2450
|
-
try {
|
|
2451
|
-
await switchBranch(repo, endBranch);
|
|
2452
|
-
} catch (error) {
|
|
2453
|
-
log.error(` Could not switch to ${endBranch}: ${errorMessage(error)}`);
|
|
2454
|
-
return false;
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
const candidates = deletableBranches(await localBranches(repo), [base, endBranch]);
|
|
2458
|
-
if (candidates.length === 0) {
|
|
2459
|
-
log.info(" No branch to delete.");
|
|
2460
|
-
} else {
|
|
2461
|
-
let toDelete = candidates;
|
|
2462
|
-
if (interactive) {
|
|
2463
|
-
const selected = await multiselect({
|
|
2464
|
-
message: "Branches to delete",
|
|
2465
|
-
options: candidates.map((branch) => ({ value: branch, label: branch })),
|
|
2466
|
-
required: false
|
|
2467
|
-
});
|
|
2468
|
-
if (isCancel(selected)) return true;
|
|
2469
|
-
toDelete = selected;
|
|
2470
|
-
}
|
|
2471
|
-
let deleted = 0;
|
|
2472
|
-
for (const branch of toDelete) {
|
|
2473
|
-
try {
|
|
2474
|
-
await deleteBranch(repo, branch);
|
|
2475
|
-
deleted += 1;
|
|
2476
|
-
} catch (error) {
|
|
2477
|
-
log.error(` Could not delete ${branch}: ${errorMessage(error)}`);
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
if (deleted > 0) log.success(` Deleted ${deleted} branch${deleted > 1 ? "es" : ""}.`);
|
|
2481
|
-
}
|
|
2482
|
-
let shouldPull = options.pull;
|
|
2483
|
-
if (!shouldPull && interactive) {
|
|
2484
|
-
const answer = await confirm({ message: `Pull ${endBranch}?`, initialValue: false });
|
|
2485
|
-
if (isCancel(answer)) return true;
|
|
2486
|
-
shouldPull = answer;
|
|
2487
|
-
}
|
|
2488
|
-
if (shouldPull) {
|
|
2489
|
-
const pullLoader = interactive ? spinner() : void 0;
|
|
2490
|
-
pullLoader?.start(`Pulling ${endBranch}\u2026`);
|
|
2491
|
-
try {
|
|
2492
|
-
await pull(repo);
|
|
2493
|
-
pullLoader?.stop(`\u2713 Pulled ${endBranch}`);
|
|
2494
|
-
} catch (error) {
|
|
2495
|
-
pullLoader?.error("Pull failed");
|
|
2496
|
-
log.error(` Pull failed: ${errorMessage(error)}`);
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
return false;
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
// src/types/commit.ts
|
|
2503
|
-
var COMMIT_TYPES = [
|
|
2504
|
-
"feat",
|
|
2505
|
-
"fix",
|
|
2506
|
-
"chore",
|
|
2507
|
-
"docs",
|
|
2508
|
-
"style",
|
|
2509
|
-
"refactor",
|
|
2510
|
-
"perf",
|
|
2511
|
-
"test",
|
|
2512
|
-
"build",
|
|
2513
|
-
"ci",
|
|
2514
|
-
"revert"
|
|
2515
|
-
];
|
|
2516
|
-
|
|
2517
|
-
// src/lib/commit-message.ts
|
|
2518
|
-
function normalizeCommitType(input) {
|
|
2519
|
-
const normalized = input.trim().toLowerCase();
|
|
2520
|
-
return COMMIT_TYPES.includes(normalized) ? normalized : null;
|
|
2521
|
-
}
|
|
2522
|
-
function applyCommitType(systemPrompt, type) {
|
|
2523
|
-
return `${systemPrompt}
|
|
2524
|
-
|
|
2525
|
-
The type MUST be exactly "${type}".`;
|
|
2526
|
-
}
|
|
2527
|
-
var MAX_DIFF_CHARS = 12e3;
|
|
2528
|
-
function truncateDiff(diff, max = MAX_DIFF_CHARS) {
|
|
2529
|
-
return diff.length > max ? diff.slice(0, max) : diff;
|
|
2530
|
-
}
|
|
2531
|
-
function sanitizeCommitMessage(raw) {
|
|
2532
|
-
const firstLine = raw.split("\n", 1)[0] ?? "";
|
|
2533
|
-
return firstLine.replace(/[`"]/g, "").trim();
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
// src/lib/config.ts
|
|
2537
|
-
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
2538
|
-
var DEFAULT_MODEL = "deepseek-v4-flash:cloud";
|
|
2539
|
-
function resolveConfig(env = process.env, overrides = {}) {
|
|
2540
|
-
return {
|
|
2541
|
-
ollamaUrl: normalizeBaseUrl(overrides.ollamaUrl ?? env.OLLAMA_URL ?? DEFAULT_OLLAMA_URL),
|
|
2542
|
-
model: overrides.model ?? env.OLLAMA_MODEL ?? DEFAULT_MODEL
|
|
2543
|
-
};
|
|
2544
|
-
}
|
|
2545
|
-
function normalizeBaseUrl(raw) {
|
|
2546
|
-
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
2547
|
-
return /^https?:\/\//.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
2283
|
+
// src/lib/errors.ts
|
|
2284
|
+
function errorMessage(error) {
|
|
2285
|
+
return error instanceof Error ? error.message : String(error);
|
|
2548
2286
|
}
|
|
2549
2287
|
|
|
2550
2288
|
// src/prompts/commit-message.md
|
|
@@ -2746,14 +2484,208 @@ var commitCommand = defineCommand({
|
|
|
2746
2484
|
}
|
|
2747
2485
|
});
|
|
2748
2486
|
|
|
2487
|
+
// src/commands/reset.ts
|
|
2488
|
+
var CANCEL = /* @__PURE__ */ Symbol("cancel");
|
|
2489
|
+
var resetCommand = defineCommand({
|
|
2490
|
+
meta: {
|
|
2491
|
+
name: "reset",
|
|
2492
|
+
description: "Switch every child repo to a branch, then delete all other local branches and worktrees."
|
|
2493
|
+
},
|
|
2494
|
+
args: {
|
|
2495
|
+
path: {
|
|
2496
|
+
type: "positional",
|
|
2497
|
+
required: false,
|
|
2498
|
+
default: ".",
|
|
2499
|
+
description: "Parent directory whose direct child repos are reset."
|
|
2500
|
+
},
|
|
2501
|
+
principal: {
|
|
2502
|
+
type: "boolean",
|
|
2503
|
+
alias: "p",
|
|
2504
|
+
description: "Switch every repo to its resolved principal branch (origin/HEAD)."
|
|
2505
|
+
},
|
|
2506
|
+
target: {
|
|
2507
|
+
type: "string",
|
|
2508
|
+
alias: "t",
|
|
2509
|
+
description: "Switch every repo to this branch, falling back to the principal one."
|
|
2510
|
+
},
|
|
2511
|
+
pull: {
|
|
2512
|
+
type: "boolean",
|
|
2513
|
+
description: "Pull the checked-out branch after reset without asking."
|
|
2514
|
+
},
|
|
2515
|
+
yes: {
|
|
2516
|
+
type: "boolean",
|
|
2517
|
+
alias: "y",
|
|
2518
|
+
description: "Non-interactive: switch and delete without confirmation."
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
async run({ args }) {
|
|
2522
|
+
const assumeYes = Boolean(args.yes);
|
|
2523
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !assumeYes;
|
|
2524
|
+
const options = {
|
|
2525
|
+
principal: Boolean(args.principal),
|
|
2526
|
+
target: typeof args.target === "string" ? args.target.trim() : "",
|
|
2527
|
+
pull: Boolean(args.pull)
|
|
2528
|
+
};
|
|
2529
|
+
const path = args.path || ".";
|
|
2530
|
+
if (interactive) intro("zapdev reset");
|
|
2531
|
+
const repos = await findChildRepos(path);
|
|
2532
|
+
if (repos.length === 0) {
|
|
2533
|
+
log.warn(`No child git repository found under ${path}.`);
|
|
2534
|
+
if (interactive) outro("Nothing to do.");
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
for (const repo of repos) {
|
|
2538
|
+
try {
|
|
2539
|
+
const cancelled = await resetRepo(repo, options, interactive, assumeYes);
|
|
2540
|
+
if (cancelled) {
|
|
2541
|
+
cancel("Cancelled.");
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
} catch (error) {
|
|
2545
|
+
log.error(`Skipped ${repo}: ${errorMessage(error)}`);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (interactive) outro(`Done (${repos.length} repo${repos.length > 1 ? "s" : ""}).`);
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
async function resetRepo(repo, options, interactive, assumeYes) {
|
|
2552
|
+
log.step(`Resetting ${repo}`);
|
|
2553
|
+
const fetchLoader = interactive ? spinner() : void 0;
|
|
2554
|
+
fetchLoader?.start("Fetching + pruning\u2026");
|
|
2555
|
+
try {
|
|
2556
|
+
await fetchPrune(repo);
|
|
2557
|
+
fetchLoader?.stop("Fetched + pruned");
|
|
2558
|
+
} catch (error) {
|
|
2559
|
+
fetchLoader?.error("Fetch failed");
|
|
2560
|
+
log.warn(` Fetch failed, continuing: ${errorMessage(error)}`);
|
|
2561
|
+
}
|
|
2562
|
+
const [principal, current] = await Promise.all([defaultBranch(repo), currentBranchAt(repo)]);
|
|
2563
|
+
const endBranch = await resolveEndBranch(repo, principal, current, options, interactive);
|
|
2564
|
+
if (endBranch === CANCEL) return true;
|
|
2565
|
+
if (endBranch === null) return false;
|
|
2566
|
+
const [branches, worktrees] = await Promise.all([localBranches(repo), listWorktrees(repo)]);
|
|
2567
|
+
const branchesToDelete = deletableBranches(branches, [principal ?? "", endBranch]);
|
|
2568
|
+
const worktreesToRemove = worktrees.slice(1);
|
|
2569
|
+
if (branchesToDelete.length > 0 || worktreesToRemove.length > 0) {
|
|
2570
|
+
if (!assumeYes) {
|
|
2571
|
+
if (!interactive) {
|
|
2572
|
+
log.warn(" Refusing to delete non-interactively; pass --yes.");
|
|
2573
|
+
return false;
|
|
2574
|
+
}
|
|
2575
|
+
announcePlan(endBranch, branchesToDelete, worktreesToRemove);
|
|
2576
|
+
const ok = await confirm({ message: "Delete permanently?", initialValue: false });
|
|
2577
|
+
if (isCancel(ok)) return true;
|
|
2578
|
+
if (!ok) {
|
|
2579
|
+
log.info(" Skipped.");
|
|
2580
|
+
return false;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
await removeWorktrees(repo, worktreesToRemove);
|
|
2584
|
+
}
|
|
2585
|
+
if (!await switchTo(repo, current, endBranch)) return false;
|
|
2586
|
+
if (branchesToDelete.length > 0) await deleteBranches(repo, branchesToDelete);
|
|
2587
|
+
return maybePull(repo, endBranch, options, interactive);
|
|
2588
|
+
}
|
|
2589
|
+
async function resolveEndBranch(repo, principal, current, options, interactive) {
|
|
2590
|
+
if (options.target) {
|
|
2591
|
+
if (await branchExists(repo, options.target)) return options.target;
|
|
2592
|
+
if (principal) return principal;
|
|
2593
|
+
log.warn(` Target "${options.target}" not found and no origin/HEAD; skipped.`);
|
|
2594
|
+
return null;
|
|
2595
|
+
}
|
|
2596
|
+
if (options.principal || !interactive) {
|
|
2597
|
+
if (principal) return principal;
|
|
2598
|
+
log.warn(" No origin/HEAD to resolve the principal branch; skipped.");
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
const branches = await localBranches(repo);
|
|
2602
|
+
if (branches.length === 0) return principal;
|
|
2603
|
+
const choice = await select({
|
|
2604
|
+
message: "Switch to:",
|
|
2605
|
+
initialValue: principal ?? current ?? branches[0],
|
|
2606
|
+
options: branches.map((branch) => ({
|
|
2607
|
+
value: branch,
|
|
2608
|
+
label: branch === principal ? `${branch} (principal)` : branch
|
|
2609
|
+
}))
|
|
2610
|
+
});
|
|
2611
|
+
if (isCancel(choice)) return CANCEL;
|
|
2612
|
+
return choice;
|
|
2613
|
+
}
|
|
2614
|
+
function announcePlan(endBranch, branches, worktrees) {
|
|
2615
|
+
log.info(` Switch to ${endBranch}`);
|
|
2616
|
+
if (branches.length > 0) {
|
|
2617
|
+
const label = branches.length > 1 ? "branches" : "branch";
|
|
2618
|
+
log.warn(` Delete ${branches.length} ${label}: ${branches.join(", ")}`);
|
|
2619
|
+
}
|
|
2620
|
+
if (worktrees.length > 0) {
|
|
2621
|
+
const label = worktrees.length > 1 ? "worktrees" : "worktree";
|
|
2622
|
+
const paths = worktrees.map((worktree) => worktree.path).join(", ");
|
|
2623
|
+
log.warn(` Remove ${worktrees.length} ${label}: ${paths}`);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
async function removeWorktrees(repo, worktrees) {
|
|
2627
|
+
let removed = 0;
|
|
2628
|
+
for (const worktree of worktrees) {
|
|
2629
|
+
try {
|
|
2630
|
+
await removeWorktree(repo, worktree.path);
|
|
2631
|
+
removed += 1;
|
|
2632
|
+
} catch (error) {
|
|
2633
|
+
log.error(` Could not remove worktree ${worktree.path}: ${errorMessage(error)}`);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
if (removed > 0) log.success(` Removed ${removed} worktree${removed > 1 ? "s" : ""}.`);
|
|
2637
|
+
}
|
|
2638
|
+
async function switchTo(repo, current, endBranch) {
|
|
2639
|
+
if (current === endBranch) return true;
|
|
2640
|
+
try {
|
|
2641
|
+
await switchBranch(repo, endBranch);
|
|
2642
|
+
return true;
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
log.error(` Could not switch to ${endBranch}: ${errorMessage(error)}`);
|
|
2645
|
+
return false;
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
async function deleteBranches(repo, branches) {
|
|
2649
|
+
let deleted = 0;
|
|
2650
|
+
for (const branch of branches) {
|
|
2651
|
+
try {
|
|
2652
|
+
await deleteBranch(repo, branch);
|
|
2653
|
+
deleted += 1;
|
|
2654
|
+
} catch (error) {
|
|
2655
|
+
log.error(` Could not delete ${branch}: ${errorMessage(error)}`);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
if (deleted > 0) log.success(` Deleted ${deleted} branch${deleted > 1 ? "es" : ""}.`);
|
|
2659
|
+
}
|
|
2660
|
+
async function maybePull(repo, endBranch, options, interactive) {
|
|
2661
|
+
let shouldPull = options.pull;
|
|
2662
|
+
if (!shouldPull && interactive) {
|
|
2663
|
+
const answer = await confirm({ message: `Pull ${endBranch}?`, initialValue: false });
|
|
2664
|
+
if (isCancel(answer)) return true;
|
|
2665
|
+
shouldPull = answer;
|
|
2666
|
+
}
|
|
2667
|
+
if (shouldPull) {
|
|
2668
|
+
const loader = interactive ? spinner() : void 0;
|
|
2669
|
+
loader?.start(`Pulling ${endBranch}\u2026`);
|
|
2670
|
+
try {
|
|
2671
|
+
await pull(repo);
|
|
2672
|
+
loader?.stop(`\u2713 Pulled ${endBranch}`);
|
|
2673
|
+
} catch (error) {
|
|
2674
|
+
loader?.error("Pull failed");
|
|
2675
|
+
log.error(` Pull failed: ${errorMessage(error)}`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
return false;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2749
2681
|
// src/cli.ts
|
|
2750
2682
|
var commands = {
|
|
2751
2683
|
commit: commitCommand,
|
|
2752
|
-
|
|
2684
|
+
reset: resetCommand
|
|
2753
2685
|
};
|
|
2754
2686
|
var MENU = {
|
|
2755
2687
|
commit: "Stage all changes and commit with an AI-generated message",
|
|
2756
|
-
|
|
2688
|
+
reset: "Switch child repos to a branch and delete the other branches and worktrees"
|
|
2757
2689
|
};
|
|
2758
2690
|
var menuOptions = Object.keys(MENU).map((value) => ({
|
|
2759
2691
|
value,
|