arbors 0.1.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/.claude-plugin/plugin.json +7 -0
- package/.oxlintrc.json +9 -0
- package/README.ja.md +131 -0
- package/README.ko.md +131 -0
- package/README.md +131 -0
- package/bin/arbors.ts +278 -0
- package/dist/arbors.js +1094 -0
- package/dist/arbors.js.map +1 -0
- package/dist/bun-EMN2NS2M.js +48 -0
- package/dist/bun-EMN2NS2M.js.map +1 -0
- package/dist/ja-F4DBSAAZ.js +38 -0
- package/dist/ja-F4DBSAAZ.js.map +1 -0
- package/dist/ko-MTIAHJOR.js +38 -0
- package/dist/ko-MTIAHJOR.js.map +1 -0
- package/dist/node-LCODN3HC.js +56 -0
- package/dist/node-LCODN3HC.js.map +1 -0
- package/package.json +54 -0
- package/pnpm-workspace.yaml +1 -0
- package/shell/arbors-wrapper.sh +21 -0
- package/shell/arbors-wrapper.zsh +21 -0
- package/skills/arbors-usage/SKILL.md +129 -0
- package/src/config.ts +66 -0
- package/src/git/exclude.ts +63 -0
- package/src/git/safety.ts +40 -0
- package/src/git/worktree.ts +171 -0
- package/src/i18n/en.ts +63 -0
- package/src/i18n/index.ts +37 -0
- package/src/i18n/ja.ts +40 -0
- package/src/i18n/ko.ts +40 -0
- package/src/project/registry.ts +108 -0
- package/src/project/setup.ts +74 -0
- package/src/runtime/adapter.ts +16 -0
- package/src/runtime/bun.ts +49 -0
- package/src/runtime/index.ts +17 -0
- package/src/runtime/node.ts +58 -0
- package/src/tui/App.tsx +87 -0
- package/src/tui/FuzzyList.tsx +111 -0
- package/src/tui/ProjectSelector.tsx +48 -0
- package/src/tui/WorktreeSelector.tsx +46 -0
- package/tests/config.test.ts +108 -0
- package/tests/exclude.test.ts +120 -0
- package/tests/i18n.test.ts +75 -0
- package/tests/registry.test.ts +136 -0
- package/tests/safety.test.ts +58 -0
- package/tests/setup-detection.test.ts +105 -0
- package/tests/setup.test.ts +87 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +14 -0
- package/vitest.config.ts +8 -0
package/dist/arbors.js
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
4
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
5
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
6
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
7
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
8
|
+
var styles = {
|
|
9
|
+
modifier: {
|
|
10
|
+
reset: [0, 0],
|
|
11
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
12
|
+
bold: [1, 22],
|
|
13
|
+
dim: [2, 22],
|
|
14
|
+
italic: [3, 23],
|
|
15
|
+
underline: [4, 24],
|
|
16
|
+
overline: [53, 55],
|
|
17
|
+
inverse: [7, 27],
|
|
18
|
+
hidden: [8, 28],
|
|
19
|
+
strikethrough: [9, 29]
|
|
20
|
+
},
|
|
21
|
+
color: {
|
|
22
|
+
black: [30, 39],
|
|
23
|
+
red: [31, 39],
|
|
24
|
+
green: [32, 39],
|
|
25
|
+
yellow: [33, 39],
|
|
26
|
+
blue: [34, 39],
|
|
27
|
+
magenta: [35, 39],
|
|
28
|
+
cyan: [36, 39],
|
|
29
|
+
white: [37, 39],
|
|
30
|
+
// Bright color
|
|
31
|
+
blackBright: [90, 39],
|
|
32
|
+
gray: [90, 39],
|
|
33
|
+
// Alias of `blackBright`
|
|
34
|
+
grey: [90, 39],
|
|
35
|
+
// Alias of `blackBright`
|
|
36
|
+
redBright: [91, 39],
|
|
37
|
+
greenBright: [92, 39],
|
|
38
|
+
yellowBright: [93, 39],
|
|
39
|
+
blueBright: [94, 39],
|
|
40
|
+
magentaBright: [95, 39],
|
|
41
|
+
cyanBright: [96, 39],
|
|
42
|
+
whiteBright: [97, 39]
|
|
43
|
+
},
|
|
44
|
+
bgColor: {
|
|
45
|
+
bgBlack: [40, 49],
|
|
46
|
+
bgRed: [41, 49],
|
|
47
|
+
bgGreen: [42, 49],
|
|
48
|
+
bgYellow: [43, 49],
|
|
49
|
+
bgBlue: [44, 49],
|
|
50
|
+
bgMagenta: [45, 49],
|
|
51
|
+
bgCyan: [46, 49],
|
|
52
|
+
bgWhite: [47, 49],
|
|
53
|
+
// Bright color
|
|
54
|
+
bgBlackBright: [100, 49],
|
|
55
|
+
bgGray: [100, 49],
|
|
56
|
+
// Alias of `bgBlackBright`
|
|
57
|
+
bgGrey: [100, 49],
|
|
58
|
+
// Alias of `bgBlackBright`
|
|
59
|
+
bgRedBright: [101, 49],
|
|
60
|
+
bgGreenBright: [102, 49],
|
|
61
|
+
bgYellowBright: [103, 49],
|
|
62
|
+
bgBlueBright: [104, 49],
|
|
63
|
+
bgMagentaBright: [105, 49],
|
|
64
|
+
bgCyanBright: [106, 49],
|
|
65
|
+
bgWhiteBright: [107, 49]
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
69
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
70
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
71
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
72
|
+
function assembleStyles() {
|
|
73
|
+
const codes = /* @__PURE__ */ new Map();
|
|
74
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
75
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
76
|
+
styles[styleName] = {
|
|
77
|
+
open: `\x1B[${style[0]}m`,
|
|
78
|
+
close: `\x1B[${style[1]}m`
|
|
79
|
+
};
|
|
80
|
+
group[styleName] = styles[styleName];
|
|
81
|
+
codes.set(style[0], style[1]);
|
|
82
|
+
}
|
|
83
|
+
Object.defineProperty(styles, groupName, {
|
|
84
|
+
value: group,
|
|
85
|
+
enumerable: false
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
Object.defineProperty(styles, "codes", {
|
|
89
|
+
value: codes,
|
|
90
|
+
enumerable: false
|
|
91
|
+
});
|
|
92
|
+
styles.color.close = "\x1B[39m";
|
|
93
|
+
styles.bgColor.close = "\x1B[49m";
|
|
94
|
+
styles.color.ansi = wrapAnsi16();
|
|
95
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
96
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
97
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
98
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
99
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
100
|
+
Object.defineProperties(styles, {
|
|
101
|
+
rgbToAnsi256: {
|
|
102
|
+
value(red, green, blue) {
|
|
103
|
+
if (red === green && green === blue) {
|
|
104
|
+
if (red < 8) {
|
|
105
|
+
return 16;
|
|
106
|
+
}
|
|
107
|
+
if (red > 248) {
|
|
108
|
+
return 231;
|
|
109
|
+
}
|
|
110
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
111
|
+
}
|
|
112
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
113
|
+
},
|
|
114
|
+
enumerable: false
|
|
115
|
+
},
|
|
116
|
+
hexToRgb: {
|
|
117
|
+
value(hex) {
|
|
118
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
119
|
+
if (!matches) {
|
|
120
|
+
return [0, 0, 0];
|
|
121
|
+
}
|
|
122
|
+
let [colorString] = matches;
|
|
123
|
+
if (colorString.length === 3) {
|
|
124
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
125
|
+
}
|
|
126
|
+
const integer = Number.parseInt(colorString, 16);
|
|
127
|
+
return [
|
|
128
|
+
/* eslint-disable no-bitwise */
|
|
129
|
+
integer >> 16 & 255,
|
|
130
|
+
integer >> 8 & 255,
|
|
131
|
+
integer & 255
|
|
132
|
+
/* eslint-enable no-bitwise */
|
|
133
|
+
];
|
|
134
|
+
},
|
|
135
|
+
enumerable: false
|
|
136
|
+
},
|
|
137
|
+
hexToAnsi256: {
|
|
138
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
139
|
+
enumerable: false
|
|
140
|
+
},
|
|
141
|
+
ansi256ToAnsi: {
|
|
142
|
+
value(code) {
|
|
143
|
+
if (code < 8) {
|
|
144
|
+
return 30 + code;
|
|
145
|
+
}
|
|
146
|
+
if (code < 16) {
|
|
147
|
+
return 90 + (code - 8);
|
|
148
|
+
}
|
|
149
|
+
let red;
|
|
150
|
+
let green;
|
|
151
|
+
let blue;
|
|
152
|
+
if (code >= 232) {
|
|
153
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
154
|
+
green = red;
|
|
155
|
+
blue = red;
|
|
156
|
+
} else {
|
|
157
|
+
code -= 16;
|
|
158
|
+
const remainder = code % 36;
|
|
159
|
+
red = Math.floor(code / 36) / 5;
|
|
160
|
+
green = Math.floor(remainder / 6) / 5;
|
|
161
|
+
blue = remainder % 6 / 5;
|
|
162
|
+
}
|
|
163
|
+
const value = Math.max(red, green, blue) * 2;
|
|
164
|
+
if (value === 0) {
|
|
165
|
+
return 30;
|
|
166
|
+
}
|
|
167
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
168
|
+
if (value === 2) {
|
|
169
|
+
result += 60;
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
},
|
|
173
|
+
enumerable: false
|
|
174
|
+
},
|
|
175
|
+
rgbToAnsi: {
|
|
176
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
177
|
+
enumerable: false
|
|
178
|
+
},
|
|
179
|
+
hexToAnsi: {
|
|
180
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
181
|
+
enumerable: false
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return styles;
|
|
185
|
+
}
|
|
186
|
+
var ansiStyles = assembleStyles();
|
|
187
|
+
var ansi_styles_default = ansiStyles;
|
|
188
|
+
|
|
189
|
+
// node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
|
|
190
|
+
import process2 from "process";
|
|
191
|
+
import os from "os";
|
|
192
|
+
import tty from "tty";
|
|
193
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
194
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
195
|
+
const position = argv.indexOf(prefix + flag);
|
|
196
|
+
const terminatorPosition = argv.indexOf("--");
|
|
197
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
198
|
+
}
|
|
199
|
+
var { env } = process2;
|
|
200
|
+
var flagForceColor;
|
|
201
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
202
|
+
flagForceColor = 0;
|
|
203
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
204
|
+
flagForceColor = 1;
|
|
205
|
+
}
|
|
206
|
+
function envForceColor() {
|
|
207
|
+
if ("FORCE_COLOR" in env) {
|
|
208
|
+
if (env.FORCE_COLOR === "true") {
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
if (env.FORCE_COLOR === "false") {
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function translateLevel(level) {
|
|
218
|
+
if (level === 0) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
level,
|
|
223
|
+
hasBasic: true,
|
|
224
|
+
has256: level >= 2,
|
|
225
|
+
has16m: level >= 3
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
229
|
+
const noFlagForceColor = envForceColor();
|
|
230
|
+
if (noFlagForceColor !== void 0) {
|
|
231
|
+
flagForceColor = noFlagForceColor;
|
|
232
|
+
}
|
|
233
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
234
|
+
if (forceColor === 0) {
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
if (sniffFlags) {
|
|
238
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
239
|
+
return 3;
|
|
240
|
+
}
|
|
241
|
+
if (hasFlag("color=256")) {
|
|
242
|
+
return 2;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
246
|
+
return 1;
|
|
247
|
+
}
|
|
248
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
const min = forceColor || 0;
|
|
252
|
+
if (env.TERM === "dumb") {
|
|
253
|
+
return min;
|
|
254
|
+
}
|
|
255
|
+
if (process2.platform === "win32") {
|
|
256
|
+
const osRelease = os.release().split(".");
|
|
257
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
258
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
259
|
+
}
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
if ("CI" in env) {
|
|
263
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
264
|
+
return 3;
|
|
265
|
+
}
|
|
266
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
267
|
+
return 1;
|
|
268
|
+
}
|
|
269
|
+
return min;
|
|
270
|
+
}
|
|
271
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
272
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
273
|
+
}
|
|
274
|
+
if (env.COLORTERM === "truecolor") {
|
|
275
|
+
return 3;
|
|
276
|
+
}
|
|
277
|
+
if (env.TERM === "xterm-kitty") {
|
|
278
|
+
return 3;
|
|
279
|
+
}
|
|
280
|
+
if (env.TERM === "xterm-ghostty") {
|
|
281
|
+
return 3;
|
|
282
|
+
}
|
|
283
|
+
if (env.TERM === "wezterm") {
|
|
284
|
+
return 3;
|
|
285
|
+
}
|
|
286
|
+
if ("TERM_PROGRAM" in env) {
|
|
287
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
288
|
+
switch (env.TERM_PROGRAM) {
|
|
289
|
+
case "iTerm.app": {
|
|
290
|
+
return version >= 3 ? 3 : 2;
|
|
291
|
+
}
|
|
292
|
+
case "Apple_Terminal": {
|
|
293
|
+
return 2;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
298
|
+
return 2;
|
|
299
|
+
}
|
|
300
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
if ("COLORTERM" in env) {
|
|
304
|
+
return 1;
|
|
305
|
+
}
|
|
306
|
+
return min;
|
|
307
|
+
}
|
|
308
|
+
function createSupportsColor(stream, options = {}) {
|
|
309
|
+
const level = _supportsColor(stream, {
|
|
310
|
+
streamIsTTY: stream && stream.isTTY,
|
|
311
|
+
...options
|
|
312
|
+
});
|
|
313
|
+
return translateLevel(level);
|
|
314
|
+
}
|
|
315
|
+
var supportsColor = {
|
|
316
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
317
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
318
|
+
};
|
|
319
|
+
var supports_color_default = supportsColor;
|
|
320
|
+
|
|
321
|
+
// node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/utilities.js
|
|
322
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
323
|
+
let index = string.indexOf(substring);
|
|
324
|
+
if (index === -1) {
|
|
325
|
+
return string;
|
|
326
|
+
}
|
|
327
|
+
const substringLength = substring.length;
|
|
328
|
+
let endIndex = 0;
|
|
329
|
+
let returnValue = "";
|
|
330
|
+
do {
|
|
331
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
332
|
+
endIndex = index + substringLength;
|
|
333
|
+
index = string.indexOf(substring, endIndex);
|
|
334
|
+
} while (index !== -1);
|
|
335
|
+
returnValue += string.slice(endIndex);
|
|
336
|
+
return returnValue;
|
|
337
|
+
}
|
|
338
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
339
|
+
let endIndex = 0;
|
|
340
|
+
let returnValue = "";
|
|
341
|
+
do {
|
|
342
|
+
const gotCR = string[index - 1] === "\r";
|
|
343
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
344
|
+
endIndex = index + 1;
|
|
345
|
+
index = string.indexOf("\n", endIndex);
|
|
346
|
+
} while (index !== -1);
|
|
347
|
+
returnValue += string.slice(endIndex);
|
|
348
|
+
return returnValue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/index.js
|
|
352
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
353
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
354
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
355
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
356
|
+
var levelMapping = [
|
|
357
|
+
"ansi",
|
|
358
|
+
"ansi",
|
|
359
|
+
"ansi256",
|
|
360
|
+
"ansi16m"
|
|
361
|
+
];
|
|
362
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
363
|
+
var applyOptions = (object, options = {}) => {
|
|
364
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
365
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
366
|
+
}
|
|
367
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
368
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
369
|
+
};
|
|
370
|
+
var chalkFactory = (options) => {
|
|
371
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
372
|
+
applyOptions(chalk2, options);
|
|
373
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
374
|
+
return chalk2;
|
|
375
|
+
};
|
|
376
|
+
function createChalk(options) {
|
|
377
|
+
return chalkFactory(options);
|
|
378
|
+
}
|
|
379
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
380
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
381
|
+
styles2[styleName] = {
|
|
382
|
+
get() {
|
|
383
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
384
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
385
|
+
return builder;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
styles2.visible = {
|
|
390
|
+
get() {
|
|
391
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
392
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
393
|
+
return builder;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
397
|
+
if (model === "rgb") {
|
|
398
|
+
if (level === "ansi16m") {
|
|
399
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
400
|
+
}
|
|
401
|
+
if (level === "ansi256") {
|
|
402
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
403
|
+
}
|
|
404
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
405
|
+
}
|
|
406
|
+
if (model === "hex") {
|
|
407
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
408
|
+
}
|
|
409
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
410
|
+
};
|
|
411
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
412
|
+
for (const model of usedModels) {
|
|
413
|
+
styles2[model] = {
|
|
414
|
+
get() {
|
|
415
|
+
const { level } = this;
|
|
416
|
+
return function(...arguments_) {
|
|
417
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
418
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
423
|
+
styles2[bgModel] = {
|
|
424
|
+
get() {
|
|
425
|
+
const { level } = this;
|
|
426
|
+
return function(...arguments_) {
|
|
427
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
428
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
var proto = Object.defineProperties(() => {
|
|
434
|
+
}, {
|
|
435
|
+
...styles2,
|
|
436
|
+
level: {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
get() {
|
|
439
|
+
return this[GENERATOR].level;
|
|
440
|
+
},
|
|
441
|
+
set(level) {
|
|
442
|
+
this[GENERATOR].level = level;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
var createStyler = (open, close, parent) => {
|
|
447
|
+
let openAll;
|
|
448
|
+
let closeAll;
|
|
449
|
+
if (parent === void 0) {
|
|
450
|
+
openAll = open;
|
|
451
|
+
closeAll = close;
|
|
452
|
+
} else {
|
|
453
|
+
openAll = parent.openAll + open;
|
|
454
|
+
closeAll = close + parent.closeAll;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
open,
|
|
458
|
+
close,
|
|
459
|
+
openAll,
|
|
460
|
+
closeAll,
|
|
461
|
+
parent
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
465
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
466
|
+
Object.setPrototypeOf(builder, proto);
|
|
467
|
+
builder[GENERATOR] = self;
|
|
468
|
+
builder[STYLER] = _styler;
|
|
469
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
470
|
+
return builder;
|
|
471
|
+
};
|
|
472
|
+
var applyStyle = (self, string) => {
|
|
473
|
+
if (self.level <= 0 || !string) {
|
|
474
|
+
return self[IS_EMPTY] ? "" : string;
|
|
475
|
+
}
|
|
476
|
+
let styler = self[STYLER];
|
|
477
|
+
if (styler === void 0) {
|
|
478
|
+
return string;
|
|
479
|
+
}
|
|
480
|
+
const { openAll, closeAll } = styler;
|
|
481
|
+
if (string.includes("\x1B")) {
|
|
482
|
+
while (styler !== void 0) {
|
|
483
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
484
|
+
styler = styler.parent;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const lfIndex = string.indexOf("\n");
|
|
488
|
+
if (lfIndex !== -1) {
|
|
489
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
490
|
+
}
|
|
491
|
+
return openAll + string + closeAll;
|
|
492
|
+
};
|
|
493
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
494
|
+
var chalk = createChalk();
|
|
495
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
496
|
+
var source_default = chalk;
|
|
497
|
+
|
|
498
|
+
// src/config.ts
|
|
499
|
+
import { homedir } from "os";
|
|
500
|
+
import { join, resolve } from "path";
|
|
501
|
+
var DEFAULT_CONFIG = {
|
|
502
|
+
runtime: "node",
|
|
503
|
+
language: "en",
|
|
504
|
+
packageManager: "auto",
|
|
505
|
+
copyExcludes: true,
|
|
506
|
+
copySkip: ["node_modules"],
|
|
507
|
+
worktreeDir: "~/arbors/{repo}"
|
|
508
|
+
};
|
|
509
|
+
var GLOBAL_CONFIG_PATH = join(homedir(), ".arbors", "config.json");
|
|
510
|
+
var PROJECT_CONFIG_DIR = ".arbors";
|
|
511
|
+
var PROJECT_CONFIG_FILE = "config.json";
|
|
512
|
+
var mergeConfig = (base, override) => {
|
|
513
|
+
return { ...base, ...override };
|
|
514
|
+
};
|
|
515
|
+
var readJsonFile = async (readFile, exists, path) => {
|
|
516
|
+
if (!await exists(path)) return {};
|
|
517
|
+
try {
|
|
518
|
+
const content = await readFile(path);
|
|
519
|
+
return JSON.parse(content);
|
|
520
|
+
} catch {
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
var loadConfig = async (readFile, exists, projectRoot) => {
|
|
525
|
+
const globalOverride = await readJsonFile(readFile, exists, GLOBAL_CONFIG_PATH);
|
|
526
|
+
const merged = mergeConfig(DEFAULT_CONFIG, globalOverride);
|
|
527
|
+
if (!projectRoot) return merged;
|
|
528
|
+
const projectConfigPath = resolve(projectRoot, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
529
|
+
const projectOverride = await readJsonFile(readFile, exists, projectConfigPath);
|
|
530
|
+
return mergeConfig(merged, projectOverride);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/git/exclude.ts
|
|
534
|
+
import { join as join2 } from "path";
|
|
535
|
+
|
|
536
|
+
// src/git/worktree.ts
|
|
537
|
+
import { homedir as homedir2 } from "os";
|
|
538
|
+
import { basename, resolve as resolve2 } from "path";
|
|
539
|
+
var getRepoRoot = async (adapter) => {
|
|
540
|
+
const result = await adapter.exec("git", ["rev-parse", "--show-toplevel"]);
|
|
541
|
+
if (result.exitCode !== 0) throw new Error("Not a git repository");
|
|
542
|
+
return result.stdout;
|
|
543
|
+
};
|
|
544
|
+
var getRepoName = async (adapter) => {
|
|
545
|
+
const root = await getRepoRoot(adapter);
|
|
546
|
+
return basename(root);
|
|
547
|
+
};
|
|
548
|
+
var getDefaultBranch = async (adapter) => {
|
|
549
|
+
const result = await adapter.exec("git", ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
550
|
+
if (result.exitCode === 0) {
|
|
551
|
+
return result.stdout.replace("refs/remotes/origin/", "");
|
|
552
|
+
}
|
|
553
|
+
const mainCheck = await adapter.exec("git", ["rev-parse", "--verify", "main"]);
|
|
554
|
+
return mainCheck.exitCode === 0 ? "main" : "master";
|
|
555
|
+
};
|
|
556
|
+
var parseWorktreeBlock = (block, isFirst) => {
|
|
557
|
+
const lines = block.split("\n");
|
|
558
|
+
const pathLine = lines.find((l) => l.startsWith("worktree "));
|
|
559
|
+
const branchLine = lines.find((l) => l.startsWith("branch "));
|
|
560
|
+
if (!pathLine) return null;
|
|
561
|
+
const path = pathLine.slice(9);
|
|
562
|
+
const branch = branchLine?.slice(7).replace("refs/heads/", "") ?? "";
|
|
563
|
+
return { path, branch, isMain: isFirst };
|
|
564
|
+
};
|
|
565
|
+
var listWorktrees = async (adapter) => {
|
|
566
|
+
const result = await adapter.exec("git", ["worktree", "list", "--porcelain"]);
|
|
567
|
+
if (result.exitCode !== 0) return [];
|
|
568
|
+
return result.stdout.split("\n\n").map((block, index) => parseWorktreeBlock(block, index === 0)).filter((wt) => wt !== null);
|
|
569
|
+
};
|
|
570
|
+
var branchExists = async (adapter, branchName) => {
|
|
571
|
+
const result = await adapter.exec("git", ["rev-parse", "--verify", branchName]);
|
|
572
|
+
return result.exitCode === 0;
|
|
573
|
+
};
|
|
574
|
+
var remoteBranchExists = async (adapter, branchName) => {
|
|
575
|
+
const result = await adapter.exec("git", ["ls-remote", "--heads", "origin", branchName]);
|
|
576
|
+
return result.exitCode === 0 && result.stdout.length > 0;
|
|
577
|
+
};
|
|
578
|
+
var branchToDir = (branch) => branch.replaceAll("/", "-");
|
|
579
|
+
var resolveWorktreeDir = (worktreeDir, repoName) => {
|
|
580
|
+
const expanded = worktreeDir.replace(/^~/, homedir2()).replace("{repo}", repoName);
|
|
581
|
+
return resolve2(expanded);
|
|
582
|
+
};
|
|
583
|
+
var createWorktree = async (adapter, branch, worktreeDir, baseBranch) => {
|
|
584
|
+
const repoName = await getRepoName(adapter);
|
|
585
|
+
const base = baseBranch ?? await getDefaultBranch(adapter);
|
|
586
|
+
const worktreePath = resolve2(resolveWorktreeDir(worktreeDir, repoName), branchToDir(branch));
|
|
587
|
+
if (await branchExists(adapter, branch)) {
|
|
588
|
+
throw new Error(`Branch '${branch}' already exists`);
|
|
589
|
+
}
|
|
590
|
+
await adapter.exec("git", ["fetch", "origin", base]);
|
|
591
|
+
const result = await adapter.exec("git", [
|
|
592
|
+
"worktree",
|
|
593
|
+
"add",
|
|
594
|
+
"-b",
|
|
595
|
+
branch,
|
|
596
|
+
worktreePath,
|
|
597
|
+
`origin/${base}`
|
|
598
|
+
]);
|
|
599
|
+
if (result.exitCode !== 0) {
|
|
600
|
+
throw new Error(result.stderr || "Failed to create worktree");
|
|
601
|
+
}
|
|
602
|
+
return worktreePath;
|
|
603
|
+
};
|
|
604
|
+
var checkoutWorktree = async (adapter, branch, worktreeDir) => {
|
|
605
|
+
const existing = (await listWorktrees(adapter)).find((wt) => wt.branch === branch);
|
|
606
|
+
if (existing) return { path: existing.path, created: false };
|
|
607
|
+
const repoName = await getRepoName(adapter);
|
|
608
|
+
const worktreePath = resolve2(resolveWorktreeDir(worktreeDir, repoName), branchToDir(branch));
|
|
609
|
+
const result = await adapter.exec("git", ["worktree", "add", worktreePath, branch]);
|
|
610
|
+
if (result.exitCode !== 0) {
|
|
611
|
+
throw new Error(result.stderr || "Failed to checkout worktree");
|
|
612
|
+
}
|
|
613
|
+
return { path: worktreePath, created: true };
|
|
614
|
+
};
|
|
615
|
+
var checkoutRemoteWorktree = async (adapter, branch, worktreeDir) => {
|
|
616
|
+
const existing = (await listWorktrees(adapter)).find((wt) => wt.branch === branch);
|
|
617
|
+
if (existing) return { path: existing.path, created: false };
|
|
618
|
+
await adapter.exec("git", ["fetch", "origin", branch]);
|
|
619
|
+
const repoName = await getRepoName(adapter);
|
|
620
|
+
const worktreePath = resolve2(resolveWorktreeDir(worktreeDir, repoName), branchToDir(branch));
|
|
621
|
+
const result = await adapter.exec("git", [
|
|
622
|
+
"worktree",
|
|
623
|
+
"add",
|
|
624
|
+
"-b",
|
|
625
|
+
branch,
|
|
626
|
+
worktreePath,
|
|
627
|
+
`origin/${branch}`
|
|
628
|
+
]);
|
|
629
|
+
if (result.exitCode !== 0) {
|
|
630
|
+
throw new Error(result.stderr || "Failed to checkout remote worktree");
|
|
631
|
+
}
|
|
632
|
+
return { path: worktreePath, created: true };
|
|
633
|
+
};
|
|
634
|
+
var removeWorktree = async (adapter, worktreePath, branch) => {
|
|
635
|
+
const removeResult = await adapter.exec("git", ["worktree", "remove", "--force", worktreePath]);
|
|
636
|
+
if (removeResult.exitCode !== 0) {
|
|
637
|
+
throw new Error(removeResult.stderr || "Failed to remove worktree");
|
|
638
|
+
}
|
|
639
|
+
await adapter.exec("git", ["branch", "-D", branch]);
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/git/exclude.ts
|
|
643
|
+
var getExcludePatterns = async (adapter) => {
|
|
644
|
+
const repoRoot = await getRepoRoot(adapter);
|
|
645
|
+
const excludePath = join2(repoRoot, ".git", "info", "exclude");
|
|
646
|
+
if (!await adapter.exists(excludePath)) return [];
|
|
647
|
+
const content = await adapter.readFile(excludePath);
|
|
648
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
|
|
649
|
+
};
|
|
650
|
+
var findExcludedEntries = async (adapter, patterns) => {
|
|
651
|
+
const repoRoot = await getRepoRoot(adapter);
|
|
652
|
+
const cleaned = patterns.map((p) => p.replace(/^\//, ""));
|
|
653
|
+
const groups = await Promise.all(
|
|
654
|
+
cleaned.map((pattern) => adapter.glob(pattern, repoRoot))
|
|
655
|
+
);
|
|
656
|
+
return [...new Set(groups.flat())].sort();
|
|
657
|
+
};
|
|
658
|
+
var copyExcludedFiles = async (adapter, worktreePath, skipPatterns = []) => {
|
|
659
|
+
const patterns = await getExcludePatterns(adapter);
|
|
660
|
+
if (patterns.length === 0) return [];
|
|
661
|
+
const repoRoot = await getRepoRoot(adapter);
|
|
662
|
+
const allEntries = await findExcludedEntries(adapter, patterns);
|
|
663
|
+
const entries = allEntries.filter(
|
|
664
|
+
(e) => !skipPatterns.some((s) => e === s || e.startsWith(`${s}/`))
|
|
665
|
+
);
|
|
666
|
+
const copied = [];
|
|
667
|
+
await Promise.all(
|
|
668
|
+
entries.map(async (entry) => {
|
|
669
|
+
const src = join2(repoRoot, entry);
|
|
670
|
+
const dest = join2(worktreePath, entry);
|
|
671
|
+
try {
|
|
672
|
+
await adapter.copy(src, dest);
|
|
673
|
+
copied.push(entry);
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
return copied;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/git/safety.ts
|
|
682
|
+
var VALID_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._\/-]*$/;
|
|
683
|
+
var validateWorktreeName = (name) => VALID_NAME_PATTERN.test(name) && !name.includes("..");
|
|
684
|
+
var hasUncommittedChanges = async (adapter, worktreePath) => {
|
|
685
|
+
const result = await adapter.exec("git", ["-C", worktreePath, "status", "--porcelain"]);
|
|
686
|
+
return result.exitCode === 0 && result.stdout.length > 0;
|
|
687
|
+
};
|
|
688
|
+
var isMainWorktree = async (adapter, worktreePath) => {
|
|
689
|
+
const worktrees = await listWorktrees(adapter);
|
|
690
|
+
const target = worktrees.find((wt) => wt.path === worktreePath);
|
|
691
|
+
return target?.isMain ?? false;
|
|
692
|
+
};
|
|
693
|
+
var canSafelyRemove = async (adapter, worktreePath) => {
|
|
694
|
+
if (await isMainWorktree(adapter, worktreePath)) {
|
|
695
|
+
return { safe: false, reason: "cannotDeleteMain" };
|
|
696
|
+
}
|
|
697
|
+
if (await hasUncommittedChanges(adapter, worktreePath)) {
|
|
698
|
+
return { safe: false, reason: "uncommittedChanges" };
|
|
699
|
+
}
|
|
700
|
+
return { safe: true };
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/i18n/en.ts
|
|
704
|
+
var en = {
|
|
705
|
+
selectProject: "Select a project:",
|
|
706
|
+
noProjects: "No projects registered. Run arbors in a git repository first.",
|
|
707
|
+
recentProjects: "Recent projects",
|
|
708
|
+
selectWorktree: "Select a worktree:",
|
|
709
|
+
noWorktrees: "No worktrees found.",
|
|
710
|
+
createNew: "Create new worktree",
|
|
711
|
+
creating: "Creating worktree...",
|
|
712
|
+
removing: "Removing worktree...",
|
|
713
|
+
copying: "Copying excluded files...",
|
|
714
|
+
installing: "Installing dependencies...",
|
|
715
|
+
created: "Worktree created",
|
|
716
|
+
removed: "Worktree removed",
|
|
717
|
+
copied: "Excluded files copied",
|
|
718
|
+
installed: "Dependencies installed",
|
|
719
|
+
resultsFound: (count) => `${count} result${count === 1 ? "" : "s"} found`,
|
|
720
|
+
notGitRepo: "Not a git repository.",
|
|
721
|
+
worktreeExists: "Worktree already exists.",
|
|
722
|
+
worktreeNotFound: "Worktree not found.",
|
|
723
|
+
uncommittedChanges: "Worktree has uncommitted changes. Commit or stash them first.",
|
|
724
|
+
cannotDeleteMain: "Cannot delete the main worktree.",
|
|
725
|
+
invalidName: "Invalid worktree name.",
|
|
726
|
+
helpFooter: "Tab: autocomplete | Enter: select | Esc: cancel",
|
|
727
|
+
helpWorktree: "Ctrl+B: new branch | Ctrl+X: delete | Esc: back",
|
|
728
|
+
configSaved: "Configuration saved.",
|
|
729
|
+
configCurrent: "Current configuration:",
|
|
730
|
+
version: "arbors v0.1.0",
|
|
731
|
+
usage: "Usage: arbors [command] [options]",
|
|
732
|
+
commands: "Commands:",
|
|
733
|
+
options: "Options:"
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/i18n/index.ts
|
|
737
|
+
var LANG_MAP = {
|
|
738
|
+
ko: "ko",
|
|
739
|
+
"ko-kr": "ko",
|
|
740
|
+
ko_kr: "ko",
|
|
741
|
+
en: "en",
|
|
742
|
+
"en-us": "en",
|
|
743
|
+
en_us: "en",
|
|
744
|
+
"en-gb": "en",
|
|
745
|
+
en_gb: "en",
|
|
746
|
+
ja: "ja",
|
|
747
|
+
"ja-jp": "ja",
|
|
748
|
+
ja_jp: "ja"
|
|
749
|
+
};
|
|
750
|
+
var detectLanguage = () => {
|
|
751
|
+
const lang = (process.env.LANG ?? process.env.LANGUAGE ?? "en").split(".")[0].toLowerCase();
|
|
752
|
+
return LANG_MAP[lang] ?? LANG_MAP[lang.split(/[-_]/)[0]] ?? "en";
|
|
753
|
+
};
|
|
754
|
+
var loaders = {
|
|
755
|
+
en: async () => en,
|
|
756
|
+
ko: async () => (await import("./ko-MTIAHJOR.js")).ko,
|
|
757
|
+
ja: async () => (await import("./ja-F4DBSAAZ.js")).ja
|
|
758
|
+
};
|
|
759
|
+
var loadMessages = async (configLanguage) => {
|
|
760
|
+
const lang = configLanguage ?? detectLanguage();
|
|
761
|
+
return loaders[lang]();
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// src/project/registry.ts
|
|
765
|
+
import { homedir as homedir3 } from "os";
|
|
766
|
+
import { join as join3 } from "path";
|
|
767
|
+
var DB_PATH = join3(homedir3(), ".arbors", "db.json");
|
|
768
|
+
var readRegistry = async (adapter) => {
|
|
769
|
+
if (!await adapter.exists(DB_PATH)) return { projects: [], worktrees: [] };
|
|
770
|
+
try {
|
|
771
|
+
const content = await adapter.readFile(DB_PATH);
|
|
772
|
+
const data = JSON.parse(content);
|
|
773
|
+
return { projects: data.projects ?? [], worktrees: data.worktrees ?? [] };
|
|
774
|
+
} catch {
|
|
775
|
+
return { projects: [], worktrees: [] };
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
var writeRegistry = async (adapter, data) => {
|
|
779
|
+
await adapter.writeFile(DB_PATH, JSON.stringify(data, null, 2));
|
|
780
|
+
};
|
|
781
|
+
var registerProject = async (adapter, name, path) => {
|
|
782
|
+
const data = await readRegistry(adapter);
|
|
783
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
784
|
+
const existing = data.projects.findIndex((p) => p.path === path);
|
|
785
|
+
if (existing !== -1) {
|
|
786
|
+
data.projects[existing] = { ...data.projects[existing], lastAccessed: now };
|
|
787
|
+
} else {
|
|
788
|
+
data.projects.push({ name, path, lastAccessed: now });
|
|
789
|
+
}
|
|
790
|
+
await writeRegistry(adapter, data);
|
|
791
|
+
};
|
|
792
|
+
var registerWorktree = async (adapter, path, branch, projectPath) => {
|
|
793
|
+
const data = await readRegistry(adapter);
|
|
794
|
+
const existing = data.worktrees.findIndex((w) => w.path === path);
|
|
795
|
+
if (existing === -1) {
|
|
796
|
+
data.worktrees.push({ path, branch, projectPath });
|
|
797
|
+
}
|
|
798
|
+
await writeRegistry(adapter, data);
|
|
799
|
+
};
|
|
800
|
+
var getWorktrees = async (adapter, projectPath) => {
|
|
801
|
+
const data = await readRegistry(adapter);
|
|
802
|
+
return data.worktrees.filter((w) => w.projectPath === projectPath);
|
|
803
|
+
};
|
|
804
|
+
var unregisterWorktree = async (adapter, path) => {
|
|
805
|
+
const data = await readRegistry(adapter);
|
|
806
|
+
data.worktrees = data.worktrees.filter((w) => w.path !== path);
|
|
807
|
+
await writeRegistry(adapter, data);
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/project/setup.ts
|
|
811
|
+
import { join as join4 } from "path";
|
|
812
|
+
var LOCK_FILE_MAP = [
|
|
813
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
814
|
+
["yarn.lock", "yarn"],
|
|
815
|
+
["package-lock.json", "npm"]
|
|
816
|
+
];
|
|
817
|
+
var RUNTIME_MANAGER_MAP = [
|
|
818
|
+
["mise.toml", "mise"],
|
|
819
|
+
[".mise.toml", "mise"],
|
|
820
|
+
[".nvmrc", "nvm"]
|
|
821
|
+
];
|
|
822
|
+
var detectFile = async (adapter, cwd, entries) => {
|
|
823
|
+
const results = await Promise.all(
|
|
824
|
+
entries.map(async ([file, value]) => ({
|
|
825
|
+
value,
|
|
826
|
+
exists: await adapter.exists(join4(cwd, file))
|
|
827
|
+
}))
|
|
828
|
+
);
|
|
829
|
+
return results.find((r) => r.exists)?.value ?? null;
|
|
830
|
+
};
|
|
831
|
+
var detectPackageManager = async (adapter, cwd) => detectFile(adapter, cwd, LOCK_FILE_MAP);
|
|
832
|
+
var detectRuntimeManager = async (adapter, cwd) => detectFile(adapter, cwd, RUNTIME_MANAGER_MAP);
|
|
833
|
+
var PM_INSTALL_ARGS = {
|
|
834
|
+
pnpm: ["install"],
|
|
835
|
+
yarn: ["install"],
|
|
836
|
+
npm: ["install"]
|
|
837
|
+
};
|
|
838
|
+
var RM_INSTALL_ARGS = {
|
|
839
|
+
mise: ["mise", ["install"]],
|
|
840
|
+
nvm: ["bash", ["-c", "source ~/.nvm/nvm.sh && nvm install"]]
|
|
841
|
+
};
|
|
842
|
+
var runSetup = async (adapter, cwd, configPm) => {
|
|
843
|
+
const rm = await detectRuntimeManager(adapter, cwd);
|
|
844
|
+
if (rm) {
|
|
845
|
+
const [cmd, args] = RM_INSTALL_ARGS[rm];
|
|
846
|
+
await adapter.exec(cmd, args);
|
|
847
|
+
}
|
|
848
|
+
const pm = configPm && configPm !== "auto" ? configPm : await detectPackageManager(adapter, cwd);
|
|
849
|
+
if (pm) {
|
|
850
|
+
await adapter.exec(pm, PM_INSTALL_ARGS[pm]);
|
|
851
|
+
}
|
|
852
|
+
return { packageManager: pm, runtimeManager: rm };
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/runtime/index.ts
|
|
856
|
+
var isBun = () => Object.hasOwn(process.versions, "bun");
|
|
857
|
+
var createAdapter = async (runtime) => {
|
|
858
|
+
const resolved = runtime ?? (isBun() ? "bun" : "node");
|
|
859
|
+
if (resolved === "bun") {
|
|
860
|
+
const { createBunAdapter } = await import("./bun-EMN2NS2M.js");
|
|
861
|
+
return createBunAdapter();
|
|
862
|
+
}
|
|
863
|
+
const { createNodeAdapter } = await import("./node-LCODN3HC.js");
|
|
864
|
+
return createNodeAdapter();
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// bin/arbors.ts
|
|
868
|
+
var parseArgs = (argv) => {
|
|
869
|
+
const args = argv.slice(2);
|
|
870
|
+
const command = args[0];
|
|
871
|
+
const flags = args.reduce((acc, arg, i) => {
|
|
872
|
+
if (arg.startsWith("--") && args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
873
|
+
acc[arg.slice(2)] = args[i + 1];
|
|
874
|
+
}
|
|
875
|
+
if (arg === "--plain") acc.plain = "true";
|
|
876
|
+
if (arg === "--create" || arg === "-c") acc.create = "true";
|
|
877
|
+
if (arg === "--help" || arg === "-h") acc.help = "true";
|
|
878
|
+
if (arg === "--version" || arg === "-v") acc.version = "true";
|
|
879
|
+
return acc;
|
|
880
|
+
}, {});
|
|
881
|
+
const name = args.slice(1).find((a) => !a.startsWith("-"));
|
|
882
|
+
return { command, name, flags };
|
|
883
|
+
};
|
|
884
|
+
var printHelp = (msg) => {
|
|
885
|
+
console.log(source_default.cyan.bold(msg.version));
|
|
886
|
+
console.log();
|
|
887
|
+
console.log(source_default.white(msg.usage));
|
|
888
|
+
console.log();
|
|
889
|
+
console.log(source_default.white(msg.commands));
|
|
890
|
+
console.log(" add <branch> Checkout existing branch (local or remote)");
|
|
891
|
+
console.log(" add -c <branch> [--base <br>] Create a new branch worktree");
|
|
892
|
+
console.log(" remove <branch> Remove a worktree");
|
|
893
|
+
console.log(" list List worktrees");
|
|
894
|
+
console.log(" excluded Show exclude patterns");
|
|
895
|
+
console.log(" config Show current config");
|
|
896
|
+
console.log();
|
|
897
|
+
console.log(source_default.white(msg.options));
|
|
898
|
+
console.log(" --plain Machine-readable output");
|
|
899
|
+
console.log(" -h, --help Show help");
|
|
900
|
+
console.log(" -v, --version Show version");
|
|
901
|
+
};
|
|
902
|
+
var main = async () => {
|
|
903
|
+
const { command, name, flags } = parseArgs(process.argv);
|
|
904
|
+
const config = await loadConfig(
|
|
905
|
+
async (p) => {
|
|
906
|
+
const { readFile } = await import("fs/promises");
|
|
907
|
+
return readFile(p, "utf-8");
|
|
908
|
+
},
|
|
909
|
+
async (p) => {
|
|
910
|
+
const { stat } = await import("fs/promises");
|
|
911
|
+
try {
|
|
912
|
+
await stat(p);
|
|
913
|
+
return true;
|
|
914
|
+
} catch {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
const msg = await loadMessages(config.language);
|
|
920
|
+
const adapter = await createAdapter(config.runtime);
|
|
921
|
+
if (flags.version) {
|
|
922
|
+
console.log(msg.version);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (flags.help || !command) {
|
|
926
|
+
printHelp(msg);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
switch (command) {
|
|
930
|
+
case "add": {
|
|
931
|
+
if (!name) {
|
|
932
|
+
console.error(source_default.red("\u2717 Usage: arbors add [-c] <branch> [--base <branch>]"));
|
|
933
|
+
process.exitCode = 1;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (!validateWorktreeName(name)) {
|
|
937
|
+
console.error(source_default.red(`\u2717 ${msg.invalidName}`));
|
|
938
|
+
process.exitCode = 1;
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
console.log();
|
|
942
|
+
console.log(source_default.cyan.bold("arbors add"));
|
|
943
|
+
console.log();
|
|
944
|
+
let worktreePath;
|
|
945
|
+
let created = false;
|
|
946
|
+
let newBranch = false;
|
|
947
|
+
if (flags.create) {
|
|
948
|
+
console.log(source_default.gray(msg.creating));
|
|
949
|
+
worktreePath = await createWorktree(adapter, name, config.worktreeDir, flags.base);
|
|
950
|
+
created = true;
|
|
951
|
+
newBranch = true;
|
|
952
|
+
console.log(source_default.green(`\u2713 ${msg.created}: ${worktreePath}`));
|
|
953
|
+
console.log(source_default.gray(` Branch: ${name} (from ${flags.base ?? "default"})`));
|
|
954
|
+
} else if (await branchExists(adapter, name)) {
|
|
955
|
+
console.log(source_default.gray(`Checking out ${name}...`));
|
|
956
|
+
const result = await checkoutWorktree(adapter, name, config.worktreeDir);
|
|
957
|
+
worktreePath = result.path;
|
|
958
|
+
created = result.created;
|
|
959
|
+
console.log(source_default.green(`\u2713 ${msg.created}: ${worktreePath}`));
|
|
960
|
+
console.log(source_default.gray(` Branch: ${name}`));
|
|
961
|
+
} else if (await remoteBranchExists(adapter, name)) {
|
|
962
|
+
console.log(source_default.gray(`Fetching ${name} from origin...`));
|
|
963
|
+
const result = await checkoutRemoteWorktree(adapter, name, config.worktreeDir);
|
|
964
|
+
worktreePath = result.path;
|
|
965
|
+
created = result.created;
|
|
966
|
+
newBranch = result.created;
|
|
967
|
+
console.log(source_default.green(`\u2713 ${msg.created}: ${worktreePath}`));
|
|
968
|
+
console.log(source_default.gray(` Branch: ${name} (from origin/${name})`));
|
|
969
|
+
} else {
|
|
970
|
+
console.error(
|
|
971
|
+
source_default.red(`\u2717 Branch '${name}' not found locally or on origin. Use 'arbors add -c' to create.`)
|
|
972
|
+
);
|
|
973
|
+
process.exitCode = 1;
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
if (config.copyExcludes) {
|
|
978
|
+
console.log();
|
|
979
|
+
console.log(source_default.gray(msg.copying));
|
|
980
|
+
const copied = await copyExcludedFiles(adapter, worktreePath, config.copySkip);
|
|
981
|
+
console.log(source_default.green(`\u2713 ${msg.copied} (${copied.length} files)`));
|
|
982
|
+
}
|
|
983
|
+
console.log();
|
|
984
|
+
console.log(source_default.gray(msg.installing));
|
|
985
|
+
await runSetup(adapter, worktreePath, config.packageManager);
|
|
986
|
+
console.log(source_default.green(`\u2713 ${msg.installed}`));
|
|
987
|
+
const repoRoot = await getRepoRoot(adapter);
|
|
988
|
+
await registerProject(adapter, name, repoRoot);
|
|
989
|
+
await registerWorktree(adapter, worktreePath, name, repoRoot);
|
|
990
|
+
} catch (setupErr) {
|
|
991
|
+
console.error(source_default.red(`\u2717 ${setupErr.message}`));
|
|
992
|
+
if (created) {
|
|
993
|
+
console.log(source_default.gray("Rolling back worktree..."));
|
|
994
|
+
await adapter.exec("git", ["worktree", "remove", "--force", worktreePath]).catch(() => {
|
|
995
|
+
});
|
|
996
|
+
if (newBranch) {
|
|
997
|
+
await adapter.exec("git", ["branch", "-D", name]).catch(() => {
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
process.exitCode = 1;
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
console.log();
|
|
1005
|
+
console.log(source_default.gray(` cd ${worktreePath}`));
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
case "remove": {
|
|
1009
|
+
if (!name) {
|
|
1010
|
+
console.error(source_default.red("\u2717 Usage: arbors remove <branch>"));
|
|
1011
|
+
process.exitCode = 1;
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
console.log();
|
|
1015
|
+
console.log(source_default.cyan.bold("arbors remove"));
|
|
1016
|
+
console.log();
|
|
1017
|
+
const worktrees = await listWorktrees(adapter);
|
|
1018
|
+
const target = worktrees.find((wt) => wt.branch === name);
|
|
1019
|
+
if (!target) {
|
|
1020
|
+
console.error(source_default.red(`\u2717 No worktree found for branch '${name}'`));
|
|
1021
|
+
process.exitCode = 1;
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const { safe, reason } = await canSafelyRemove(adapter, target.path);
|
|
1025
|
+
if (!safe) {
|
|
1026
|
+
const errorMsg = reason ? msg[reason] : "Cannot remove";
|
|
1027
|
+
console.error(source_default.red(`\u2717 ${errorMsg}`));
|
|
1028
|
+
process.exitCode = 1;
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
console.log(source_default.gray(msg.removing));
|
|
1032
|
+
await removeWorktree(adapter, target.path, target.branch);
|
|
1033
|
+
await unregisterWorktree(adapter, target.path);
|
|
1034
|
+
console.log(source_default.green(`\u2713 ${msg.removed}: ${name}`));
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
case "list": {
|
|
1038
|
+
const repoRootForList = await getRepoRoot(adapter);
|
|
1039
|
+
const dbWorktrees = await getWorktrees(adapter, repoRootForList);
|
|
1040
|
+
const gitWorktrees = await listWorktrees(adapter);
|
|
1041
|
+
const gitPaths = new Set(gitWorktrees.map((wt) => wt.path));
|
|
1042
|
+
const stale = dbWorktrees.filter((w) => !gitPaths.has(w.path));
|
|
1043
|
+
for (const w of stale) {
|
|
1044
|
+
await unregisterWorktree(adapter, w.path);
|
|
1045
|
+
}
|
|
1046
|
+
const managedWorktrees = dbWorktrees.filter((w) => gitPaths.has(w.path));
|
|
1047
|
+
if (flags.plain) {
|
|
1048
|
+
managedWorktrees.forEach((wt) => console.log(`${wt.branch} ${wt.path}`));
|
|
1049
|
+
} else if (managedWorktrees.length === 0) {
|
|
1050
|
+
console.log(source_default.gray(msg.noWorktrees));
|
|
1051
|
+
} else {
|
|
1052
|
+
console.log();
|
|
1053
|
+
console.log(source_default.cyan.bold("arbors list"));
|
|
1054
|
+
console.log();
|
|
1055
|
+
managedWorktrees.forEach((wt) => {
|
|
1056
|
+
console.log(source_default.white(wt.branch));
|
|
1057
|
+
console.log(source_default.gray(` ${wt.path}`));
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
case "excluded": {
|
|
1063
|
+
const patterns = await getExcludePatterns(adapter);
|
|
1064
|
+
if (patterns.length === 0) {
|
|
1065
|
+
console.log(source_default.gray("No exclude patterns found in .git/info/exclude"));
|
|
1066
|
+
} else {
|
|
1067
|
+
console.log();
|
|
1068
|
+
console.log(source_default.cyan.bold("arbors excluded"));
|
|
1069
|
+
console.log();
|
|
1070
|
+
patterns.forEach((p) => console.log(` ${p}`));
|
|
1071
|
+
}
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
case "config": {
|
|
1075
|
+
console.log();
|
|
1076
|
+
console.log(source_default.cyan.bold("arbors config"));
|
|
1077
|
+
console.log();
|
|
1078
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
1079
|
+
console.log(` ${source_default.white(key)}: ${source_default.gray(String(value))}`);
|
|
1080
|
+
});
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
default: {
|
|
1084
|
+
console.error(source_default.red(`\u2717 Unknown command: ${command}`));
|
|
1085
|
+
printHelp(msg);
|
|
1086
|
+
process.exitCode = 1;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
main().catch((err) => {
|
|
1091
|
+
console.error(source_default.red(`\u2717 ${err.message}`));
|
|
1092
|
+
process.exitCode = 1;
|
|
1093
|
+
});
|
|
1094
|
+
//# sourceMappingURL=arbors.js.map
|