@ogpoyraz/wtx 0.7.0 → 0.8.1
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 +27 -6
- package/completions/_wtx.zsh +14 -2
- package/completions/wtx.bash +6 -5
- package/completions/wtx.fish +10 -5
- package/dist/cli.mjs +1187 -327
- package/package.json +1 -1
- package/skills/claude.md +18 -6
- package/skills/cursor.md +18 -6
- package/skills/opencode.md +18 -6
package/dist/cli.mjs
CHANGED
|
@@ -15331,6 +15331,11 @@ function verbose(message, isVerbose) {
|
|
|
15331
15331
|
function indented(message) {
|
|
15332
15332
|
console.log(` ${message}`);
|
|
15333
15333
|
}
|
|
15334
|
+
function terminalLink(url2, text = url2, stream = process.stdout) {
|
|
15335
|
+
if (!stream.isTTY)
|
|
15336
|
+
return text;
|
|
15337
|
+
return `\x1B]8;;${url2}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
15338
|
+
}
|
|
15334
15339
|
var c, quietMode = false;
|
|
15335
15340
|
var init_log = __esm(() => {
|
|
15336
15341
|
init_source();
|
|
@@ -23014,6 +23019,20 @@ async function getLatestCommit(repoPath, ref) {
|
|
|
23014
23019
|
subject: stdout.substring(splitIdx + 1)
|
|
23015
23020
|
};
|
|
23016
23021
|
}
|
|
23022
|
+
async function resolveCommitSha(repoPath, ref, opts = { verbose: false, dryRun: false }) {
|
|
23023
|
+
try {
|
|
23024
|
+
const stdout = await gitExec(["-C", repoPath, "rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`], opts);
|
|
23025
|
+
const sha = stdout.trim();
|
|
23026
|
+
if (!sha) {
|
|
23027
|
+
throw new Error("Git returned an empty commit id");
|
|
23028
|
+
}
|
|
23029
|
+
return sha;
|
|
23030
|
+
} catch (err) {
|
|
23031
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23032
|
+
throw new Error(`Ref '${ref}' does not resolve to a commit: ${message.split(`
|
|
23033
|
+
`)[0]}`);
|
|
23034
|
+
}
|
|
23035
|
+
}
|
|
23017
23036
|
async function getDirtyFiles(worktreePath) {
|
|
23018
23037
|
const stdout = await gitExec(["-C", worktreePath, "status", "--porcelain"]);
|
|
23019
23038
|
return stdout.split(`
|
|
@@ -23131,19 +23150,195 @@ var init_git = __esm(() => {
|
|
|
23131
23150
|
init_log();
|
|
23132
23151
|
});
|
|
23133
23152
|
|
|
23134
|
-
// src/lib/
|
|
23153
|
+
// src/lib/stack.ts
|
|
23135
23154
|
import fs7 from "fs";
|
|
23136
23155
|
import path15 from "path";
|
|
23156
|
+
function emptyMetadata() {
|
|
23157
|
+
return { version: 1, branches: {} };
|
|
23158
|
+
}
|
|
23159
|
+
function isRecord(value) {
|
|
23160
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23161
|
+
}
|
|
23162
|
+
function parseMetadata(value) {
|
|
23163
|
+
if (!isRecord(value) || value.version !== 1 || !isRecord(value.branches)) {
|
|
23164
|
+
throw new Error("invalid format or version");
|
|
23165
|
+
}
|
|
23166
|
+
const branches = {};
|
|
23167
|
+
for (const [branch, rawEntry] of Object.entries(value.branches)) {
|
|
23168
|
+
if (!isRecord(rawEntry))
|
|
23169
|
+
continue;
|
|
23170
|
+
if (typeof rawEntry.baseRef !== "string" || typeof rawEntry.baseSha !== "string")
|
|
23171
|
+
continue;
|
|
23172
|
+
branches[branch] = {
|
|
23173
|
+
baseRef: rawEntry.baseRef,
|
|
23174
|
+
baseSha: rawEntry.baseSha,
|
|
23175
|
+
explicit: rawEntry.explicit === true,
|
|
23176
|
+
createdAt: typeof rawEntry.createdAt === "string" ? rawEntry.createdAt : ""
|
|
23177
|
+
};
|
|
23178
|
+
}
|
|
23179
|
+
return { version: 1, branches };
|
|
23180
|
+
}
|
|
23181
|
+
async function metadataPath(repoPath, opts) {
|
|
23182
|
+
const commonDirOutput = await gitExec(["-C", repoPath, "rev-parse", "--git-common-dir"], { ...opts, dryRun: false });
|
|
23183
|
+
const commonDir = commonDirOutput.trim();
|
|
23184
|
+
if (!commonDir)
|
|
23185
|
+
return null;
|
|
23186
|
+
const resolvedCommonDir = path15.isAbsolute(commonDir) ? commonDir : path15.resolve(repoPath, commonDir);
|
|
23187
|
+
return path15.join(resolvedCommonDir, "wtx", "stack.json");
|
|
23188
|
+
}
|
|
23189
|
+
async function readStackMetadata(repoPath, opts = { verbose: false, dryRun: false }) {
|
|
23190
|
+
const filePath = await metadataPath(repoPath, opts);
|
|
23191
|
+
if (!filePath || !fs7.existsSync(filePath))
|
|
23192
|
+
return emptyMetadata();
|
|
23193
|
+
try {
|
|
23194
|
+
const raw = JSON.parse(fs7.readFileSync(filePath, "utf8"));
|
|
23195
|
+
return parseMetadata(raw);
|
|
23196
|
+
} catch (err) {
|
|
23197
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23198
|
+
throw new Error(`Failed to read stack metadata: ${message}`);
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
23201
|
+
async function writeStackMetadata(repoPath, metadata, opts) {
|
|
23202
|
+
if (opts.dryRun)
|
|
23203
|
+
return;
|
|
23204
|
+
const filePath = await metadataPath(repoPath, opts);
|
|
23205
|
+
if (!filePath)
|
|
23206
|
+
return;
|
|
23207
|
+
const dir = path15.dirname(filePath);
|
|
23208
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
23209
|
+
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
23210
|
+
fs7.writeFileSync(tempPath, `${JSON.stringify(metadata, null, 2)}
|
|
23211
|
+
`, "utf8");
|
|
23212
|
+
fs7.renameSync(tempPath, filePath);
|
|
23213
|
+
}
|
|
23214
|
+
async function recordStackEntry(repoPath, branch, entry, opts) {
|
|
23215
|
+
const metadata = await readStackMetadata(repoPath, opts);
|
|
23216
|
+
metadata.branches[branch] = entry;
|
|
23217
|
+
await writeStackMetadata(repoPath, metadata, opts);
|
|
23218
|
+
}
|
|
23219
|
+
async function removeStackEntry(repoPath, branch, opts) {
|
|
23220
|
+
const metadata = await readStackMetadata(repoPath, opts);
|
|
23221
|
+
if (!metadata.branches[branch])
|
|
23222
|
+
return;
|
|
23223
|
+
delete metadata.branches[branch];
|
|
23224
|
+
await writeStackMetadata(repoPath, metadata, opts);
|
|
23225
|
+
}
|
|
23226
|
+
async function renameStackEntry(repoPath, oldBranch, newBranch, opts) {
|
|
23227
|
+
const metadata = await readStackMetadata(repoPath, opts);
|
|
23228
|
+
const entry = metadata.branches[oldBranch];
|
|
23229
|
+
let changed = false;
|
|
23230
|
+
if (entry) {
|
|
23231
|
+
metadata.branches[newBranch] = entry;
|
|
23232
|
+
delete metadata.branches[oldBranch];
|
|
23233
|
+
changed = true;
|
|
23234
|
+
}
|
|
23235
|
+
for (const child of Object.values(metadata.branches)) {
|
|
23236
|
+
if (child.baseRef !== oldBranch && child.baseRef !== `refs/heads/${oldBranch}`)
|
|
23237
|
+
continue;
|
|
23238
|
+
child.baseRef = newBranch;
|
|
23239
|
+
changed = true;
|
|
23240
|
+
}
|
|
23241
|
+
if (changed)
|
|
23242
|
+
await writeStackMetadata(repoPath, metadata, opts);
|
|
23243
|
+
}
|
|
23244
|
+
function getStackChildren(metadata, branch) {
|
|
23245
|
+
return Object.entries(metadata.branches).filter(([, entry]) => entry.baseRef === branch || entry.baseRef === `refs/heads/${branch}`).map(([child]) => child).sort((a2, b) => a2.localeCompare(b));
|
|
23246
|
+
}
|
|
23247
|
+
function getStackAncestors(metadata, branch) {
|
|
23248
|
+
const ancestors = [branch];
|
|
23249
|
+
const seen = new Set([branch]);
|
|
23250
|
+
let current = branch;
|
|
23251
|
+
while (true) {
|
|
23252
|
+
const entry = metadata.branches[current];
|
|
23253
|
+
if (!entry || seen.has(entry.baseRef))
|
|
23254
|
+
break;
|
|
23255
|
+
ancestors.unshift(entry.baseRef);
|
|
23256
|
+
seen.add(entry.baseRef);
|
|
23257
|
+
current = entry.baseRef;
|
|
23258
|
+
}
|
|
23259
|
+
return ancestors;
|
|
23260
|
+
}
|
|
23261
|
+
function resolveParentBranch(baseRef, branches) {
|
|
23262
|
+
if (!baseRef)
|
|
23263
|
+
return null;
|
|
23264
|
+
if (branches.has(baseRef))
|
|
23265
|
+
return baseRef;
|
|
23266
|
+
const headRef = baseRef.replace(/^refs\/heads\//, "");
|
|
23267
|
+
if (branches.has(headRef))
|
|
23268
|
+
return headRef;
|
|
23269
|
+
const remoteRef = baseRef.replace(/^refs\/remotes\/[^/]+\//, "");
|
|
23270
|
+
if (branches.has(remoteRef))
|
|
23271
|
+
return remoteRef;
|
|
23272
|
+
const slash = baseRef.indexOf("/");
|
|
23273
|
+
if (slash > 0) {
|
|
23274
|
+
const shortRef = baseRef.substring(slash + 1);
|
|
23275
|
+
if (branches.has(shortRef))
|
|
23276
|
+
return shortRef;
|
|
23277
|
+
}
|
|
23278
|
+
return null;
|
|
23279
|
+
}
|
|
23280
|
+
function buildStackHierarchy(items, getBranch, getBase, compare) {
|
|
23281
|
+
const branchNames = new Set(items.map(getBranch).filter((branch) => branch !== undefined));
|
|
23282
|
+
const byBranch = new Map;
|
|
23283
|
+
const children = new Map;
|
|
23284
|
+
const roots = [];
|
|
23285
|
+
for (const item of items) {
|
|
23286
|
+
const branch = getBranch(item);
|
|
23287
|
+
if (branch)
|
|
23288
|
+
byBranch.set(branch, item);
|
|
23289
|
+
}
|
|
23290
|
+
for (const item of items) {
|
|
23291
|
+
const branch = getBranch(item);
|
|
23292
|
+
const parent = resolveParentBranch(getBase(item), branchNames);
|
|
23293
|
+
if (!parent || parent === branch || !byBranch.has(parent)) {
|
|
23294
|
+
roots.push(item);
|
|
23295
|
+
continue;
|
|
23296
|
+
}
|
|
23297
|
+
const siblings = children.get(parent) ?? [];
|
|
23298
|
+
siblings.push(item);
|
|
23299
|
+
children.set(parent, siblings);
|
|
23300
|
+
}
|
|
23301
|
+
const result = [];
|
|
23302
|
+
const visited = new Set;
|
|
23303
|
+
const visit = (item, depth, ancestorPrefix, isLast) => {
|
|
23304
|
+
const branch = getBranch(item);
|
|
23305
|
+
if (branch && visited.has(branch))
|
|
23306
|
+
return;
|
|
23307
|
+
if (branch)
|
|
23308
|
+
visited.add(branch);
|
|
23309
|
+
const prefix = depth === 0 ? "" : `${ancestorPrefix}${isLast ? "└─ " : "├─ "}`;
|
|
23310
|
+
result.push({ item, depth, prefix });
|
|
23311
|
+
const childRows = [...branch ? children.get(branch) ?? [] : []].sort(compare);
|
|
23312
|
+
const childPrefix = depth === 0 ? "" : `${ancestorPrefix}${isLast ? " " : "│ "}`;
|
|
23313
|
+
childRows.forEach((child, index) => {
|
|
23314
|
+
visit(child, depth + 1, childPrefix, index === childRows.length - 1);
|
|
23315
|
+
});
|
|
23316
|
+
};
|
|
23317
|
+
roots.sort(compare).forEach((root) => visit(root, 0, "", true));
|
|
23318
|
+
for (const item of items) {
|
|
23319
|
+
const branch = getBranch(item);
|
|
23320
|
+
if (!branch || !visited.has(branch))
|
|
23321
|
+
visit(item, 0, "", true);
|
|
23322
|
+
}
|
|
23323
|
+
return result;
|
|
23324
|
+
}
|
|
23325
|
+
var init_stack = __esm(() => {
|
|
23326
|
+
init_git();
|
|
23327
|
+
});
|
|
23328
|
+
|
|
23329
|
+
// src/lib/resolver.ts
|
|
23330
|
+
import fs8 from "fs";
|
|
23331
|
+
import path16 from "path";
|
|
23137
23332
|
function detectRepoFromCwd(config2) {
|
|
23138
23333
|
const cwd = process.cwd();
|
|
23139
23334
|
const root = expandTilde(config2.root);
|
|
23140
23335
|
for (const name of Object.keys(config2.repos)) {
|
|
23141
|
-
const mainPath =
|
|
23142
|
-
const wtRoot =
|
|
23143
|
-
if (cwd === mainPath || cwd.startsWith(mainPath +
|
|
23336
|
+
const mainPath = path16.join(root, name);
|
|
23337
|
+
const wtRoot = path16.join(root, `${name}${config2.postfix}`);
|
|
23338
|
+
if (cwd === mainPath || cwd.startsWith(mainPath + path16.sep)) {
|
|
23144
23339
|
return name;
|
|
23145
23340
|
}
|
|
23146
|
-
if (cwd === wtRoot || cwd.startsWith(wtRoot +
|
|
23341
|
+
if (cwd === wtRoot || cwd.startsWith(wtRoot + path16.sep)) {
|
|
23147
23342
|
return name;
|
|
23148
23343
|
}
|
|
23149
23344
|
}
|
|
@@ -23173,11 +23368,11 @@ function resolveRepos(config2, repoFilter) {
|
|
|
23173
23368
|
return targetRepos.map((name) => {
|
|
23174
23369
|
const mainPath = `${expandTilde(config2.root)}/${name}`;
|
|
23175
23370
|
const wtRoot = `${expandTilde(config2.root)}/${name}${config2.postfix}`;
|
|
23176
|
-
if (!
|
|
23371
|
+
if (!fs8.existsSync(mainPath)) {
|
|
23177
23372
|
throw new Error(`Repo directory not found at ${mainPath}`);
|
|
23178
23373
|
}
|
|
23179
|
-
const gitDir =
|
|
23180
|
-
if (!
|
|
23374
|
+
const gitDir = path16.join(mainPath, ".git");
|
|
23375
|
+
if (!fs8.existsSync(gitDir)) {
|
|
23181
23376
|
throw new Error(`Not a git repository: ${mainPath}`);
|
|
23182
23377
|
}
|
|
23183
23378
|
return {
|
|
@@ -23240,14 +23435,14 @@ function expandTemplate(template, vars) {
|
|
|
23240
23435
|
}
|
|
23241
23436
|
|
|
23242
23437
|
// src/lib/deps/workspaces.ts
|
|
23243
|
-
import
|
|
23244
|
-
import
|
|
23438
|
+
import fs11 from "fs";
|
|
23439
|
+
import path18 from "path";
|
|
23245
23440
|
function getWorkspaceDirs(rootDir) {
|
|
23246
23441
|
const dirs = new Set;
|
|
23247
|
-
const pkgPath =
|
|
23248
|
-
if (
|
|
23442
|
+
const pkgPath = path18.join(rootDir, "package.json");
|
|
23443
|
+
if (fs11.existsSync(pkgPath)) {
|
|
23249
23444
|
try {
|
|
23250
|
-
const pkg = JSON.parse(
|
|
23445
|
+
const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
|
|
23251
23446
|
if (pkg.workspaces) {
|
|
23252
23447
|
let ws = [];
|
|
23253
23448
|
if (Array.isArray(pkg.workspaces)) {
|
|
@@ -23261,10 +23456,10 @@ function getWorkspaceDirs(rootDir) {
|
|
|
23261
23456
|
}
|
|
23262
23457
|
} catch {}
|
|
23263
23458
|
}
|
|
23264
|
-
const pnpmPath =
|
|
23265
|
-
if (
|
|
23459
|
+
const pnpmPath = path18.join(rootDir, "pnpm-workspace.yaml");
|
|
23460
|
+
if (fs11.existsSync(pnpmPath)) {
|
|
23266
23461
|
try {
|
|
23267
|
-
const content =
|
|
23462
|
+
const content = fs11.readFileSync(pnpmPath, "utf-8");
|
|
23268
23463
|
const lines = content.split(/\r?\n/);
|
|
23269
23464
|
let inPackages = false;
|
|
23270
23465
|
for (let line of lines) {
|
|
@@ -23295,8 +23490,8 @@ function resolvePattern(rootDir, pattern, out) {
|
|
|
23295
23490
|
const normalized = pattern.replace(/\/+$/, "");
|
|
23296
23491
|
const hasGlob = normalized.includes("*");
|
|
23297
23492
|
if (!hasGlob) {
|
|
23298
|
-
const fullDir =
|
|
23299
|
-
if (
|
|
23493
|
+
const fullDir = path18.join(rootDir, normalized);
|
|
23494
|
+
if (fs11.existsSync(fullDir) && fs11.statSync(fullDir).isDirectory()) {
|
|
23300
23495
|
out.add(normalized);
|
|
23301
23496
|
}
|
|
23302
23497
|
return;
|
|
@@ -23311,12 +23506,12 @@ function globToRegex(pattern) {
|
|
|
23311
23506
|
function collectMatchingDirs(base2, rel, regex, out, depth) {
|
|
23312
23507
|
if (depth > 8)
|
|
23313
23508
|
return;
|
|
23314
|
-
if (rel && regex.test(rel) &&
|
|
23509
|
+
if (rel && regex.test(rel) && fs11.existsSync(path18.join(base2, rel, "package.json"))) {
|
|
23315
23510
|
out.add(rel);
|
|
23316
23511
|
}
|
|
23317
23512
|
let entries;
|
|
23318
23513
|
try {
|
|
23319
|
-
entries =
|
|
23514
|
+
entries = fs11.readdirSync(rel ? path18.join(base2, rel) : base2, { withFileTypes: true });
|
|
23320
23515
|
} catch {
|
|
23321
23516
|
return;
|
|
23322
23517
|
}
|
|
@@ -23324,7 +23519,7 @@ function collectMatchingDirs(base2, rel, regex, out, depth) {
|
|
|
23324
23519
|
if (!entry.isDirectory() || entry.name === "node_modules" || entry.name.startsWith(".")) {
|
|
23325
23520
|
continue;
|
|
23326
23521
|
}
|
|
23327
|
-
const nextRel = rel ?
|
|
23522
|
+
const nextRel = rel ? path18.posix.join(rel, entry.name) : entry.name;
|
|
23328
23523
|
if (!regex.test(nextRel) && !couldMatchDeeper(regex.source, nextRel))
|
|
23329
23524
|
continue;
|
|
23330
23525
|
collectMatchingDirs(base2, nextRel, regex, out, depth + 1);
|
|
@@ -23336,21 +23531,21 @@ function couldMatchDeeper(regexSource, rel) {
|
|
|
23336
23531
|
var init_workspaces = () => {};
|
|
23337
23532
|
|
|
23338
23533
|
// src/lib/deps/diff.ts
|
|
23339
|
-
import
|
|
23340
|
-
import
|
|
23534
|
+
import fs12 from "fs";
|
|
23535
|
+
import path19 from "path";
|
|
23341
23536
|
function filesMatch(wtPath, mainPath, fileNames) {
|
|
23342
23537
|
for (const name of fileNames) {
|
|
23343
|
-
const wtFile =
|
|
23344
|
-
const mainFile =
|
|
23345
|
-
const wtExists =
|
|
23346
|
-
const mainExists =
|
|
23538
|
+
const wtFile = path19.join(wtPath, name);
|
|
23539
|
+
const mainFile = path19.join(mainPath, name);
|
|
23540
|
+
const wtExists = fs12.existsSync(wtFile);
|
|
23541
|
+
const mainExists = fs12.existsSync(mainFile);
|
|
23347
23542
|
if (wtExists !== mainExists)
|
|
23348
23543
|
return false;
|
|
23349
23544
|
if (!wtExists)
|
|
23350
23545
|
continue;
|
|
23351
23546
|
try {
|
|
23352
|
-
const wtContent =
|
|
23353
|
-
const mainContent =
|
|
23547
|
+
const wtContent = fs12.readFileSync(wtFile);
|
|
23548
|
+
const mainContent = fs12.readFileSync(mainFile);
|
|
23354
23549
|
if (!wtContent.equals(mainContent))
|
|
23355
23550
|
return false;
|
|
23356
23551
|
} catch {
|
|
@@ -23368,7 +23563,7 @@ function getWorkspaceDelta(wtPath, mainPath, lockfileNames) {
|
|
|
23368
23563
|
const changedWorkspaces = [];
|
|
23369
23564
|
for (const ws of allWorkspaces) {
|
|
23370
23565
|
const wsFiles = ["package.json"];
|
|
23371
|
-
if (!filesMatch(
|
|
23566
|
+
if (!filesMatch(path19.join(wtPath, ws), path19.join(mainPath, ws), wsFiles)) {
|
|
23372
23567
|
changedWorkspaces.push(ws);
|
|
23373
23568
|
}
|
|
23374
23569
|
}
|
|
@@ -23379,8 +23574,8 @@ var init_diff = __esm(() => {
|
|
|
23379
23574
|
});
|
|
23380
23575
|
|
|
23381
23576
|
// src/lib/deps/adapters/bun.ts
|
|
23382
|
-
import
|
|
23383
|
-
import
|
|
23577
|
+
import fs13 from "fs";
|
|
23578
|
+
import path20 from "path";
|
|
23384
23579
|
var bunAdapter;
|
|
23385
23580
|
var init_bun = __esm(() => {
|
|
23386
23581
|
init_diff();
|
|
@@ -23388,7 +23583,7 @@ var init_bun = __esm(() => {
|
|
|
23388
23583
|
bunAdapter = {
|
|
23389
23584
|
id: "bun",
|
|
23390
23585
|
displayName: "bun",
|
|
23391
|
-
detect: (dir) =>
|
|
23586
|
+
detect: (dir) => fs13.existsSync(path20.join(dir, "bun.lockb")) || fs13.existsSync(path20.join(dir, "bun.lock")),
|
|
23392
23587
|
lockfileNames: ["bun.lockb", "bun.lock"],
|
|
23393
23588
|
definitionsMatch: (wtPath, mainPath) => {
|
|
23394
23589
|
const delta = getWorkspaceDelta(wtPath, mainPath, ["bun.lockb", "bun.lock"]);
|
|
@@ -23416,8 +23611,8 @@ var init_bun = __esm(() => {
|
|
|
23416
23611
|
});
|
|
23417
23612
|
|
|
23418
23613
|
// src/lib/deps/adapters/pnpm.ts
|
|
23419
|
-
import
|
|
23420
|
-
import
|
|
23614
|
+
import fs14 from "fs";
|
|
23615
|
+
import path21 from "path";
|
|
23421
23616
|
var pnpmAdapter;
|
|
23422
23617
|
var init_pnpm = __esm(() => {
|
|
23423
23618
|
init_diff();
|
|
@@ -23425,7 +23620,7 @@ var init_pnpm = __esm(() => {
|
|
|
23425
23620
|
pnpmAdapter = {
|
|
23426
23621
|
id: "pnpm",
|
|
23427
23622
|
displayName: "pnpm",
|
|
23428
|
-
detect: (dir) =>
|
|
23623
|
+
detect: (dir) => fs14.existsSync(path21.join(dir, "pnpm-lock.yaml")),
|
|
23429
23624
|
lockfileNames: ["pnpm-lock.yaml"],
|
|
23430
23625
|
definitionsMatch: (wtPath, mainPath) => {
|
|
23431
23626
|
const delta = getWorkspaceDelta(wtPath, mainPath, ["pnpm-lock.yaml"]);
|
|
@@ -23453,8 +23648,8 @@ var init_pnpm = __esm(() => {
|
|
|
23453
23648
|
});
|
|
23454
23649
|
|
|
23455
23650
|
// src/lib/deps/adapters/yarn.ts
|
|
23456
|
-
import
|
|
23457
|
-
import
|
|
23651
|
+
import fs15 from "fs";
|
|
23652
|
+
import path22 from "path";
|
|
23458
23653
|
var yarnAdapter;
|
|
23459
23654
|
var init_yarn = __esm(() => {
|
|
23460
23655
|
init_diff();
|
|
@@ -23462,7 +23657,7 @@ var init_yarn = __esm(() => {
|
|
|
23462
23657
|
yarnAdapter = {
|
|
23463
23658
|
id: "yarn",
|
|
23464
23659
|
displayName: "yarn",
|
|
23465
|
-
detect: (dir) =>
|
|
23660
|
+
detect: (dir) => fs15.existsSync(path22.join(dir, "yarn.lock")),
|
|
23466
23661
|
lockfileNames: ["yarn.lock"],
|
|
23467
23662
|
definitionsMatch: (wtPath, mainPath) => {
|
|
23468
23663
|
const delta = getWorkspaceDelta(wtPath, mainPath, ["yarn.lock"]);
|
|
@@ -23484,8 +23679,8 @@ var init_yarn = __esm(() => {
|
|
|
23484
23679
|
});
|
|
23485
23680
|
|
|
23486
23681
|
// src/lib/deps/adapters/npm.ts
|
|
23487
|
-
import
|
|
23488
|
-
import
|
|
23682
|
+
import fs16 from "fs";
|
|
23683
|
+
import path23 from "path";
|
|
23489
23684
|
var npmAdapter;
|
|
23490
23685
|
var init_npm = __esm(() => {
|
|
23491
23686
|
init_diff();
|
|
@@ -23493,7 +23688,7 @@ var init_npm = __esm(() => {
|
|
|
23493
23688
|
npmAdapter = {
|
|
23494
23689
|
id: "npm",
|
|
23495
23690
|
displayName: "npm",
|
|
23496
|
-
detect: (dir) =>
|
|
23691
|
+
detect: (dir) => fs16.existsSync(path23.join(dir, "package-lock.json")),
|
|
23497
23692
|
lockfileNames: ["package-lock.json"],
|
|
23498
23693
|
definitionsMatch: (wtPath, mainPath) => {
|
|
23499
23694
|
const delta = getWorkspaceDelta(wtPath, mainPath, ["package-lock.json"]);
|
|
@@ -23850,47 +24045,47 @@ var init_registry = __esm(() => {
|
|
|
23850
24045
|
});
|
|
23851
24046
|
|
|
23852
24047
|
// src/lib/deps/linking.ts
|
|
23853
|
-
import
|
|
23854
|
-
import
|
|
24048
|
+
import fs17 from "fs";
|
|
24049
|
+
import path24 from "path";
|
|
23855
24050
|
function performSafeLink(wtPath, mainPath, dryRun, quiet) {
|
|
23856
|
-
const wtNm =
|
|
23857
|
-
const mainNm =
|
|
23858
|
-
if (!
|
|
24051
|
+
const wtNm = path24.join(wtPath, "node_modules");
|
|
24052
|
+
const mainNm = path24.join(mainPath, "node_modules");
|
|
24053
|
+
if (!fs17.existsSync(mainNm)) {
|
|
23859
24054
|
return;
|
|
23860
24055
|
}
|
|
23861
24056
|
let existingLink = false;
|
|
23862
24057
|
try {
|
|
23863
|
-
const stat =
|
|
24058
|
+
const stat = fs17.lstatSync(wtNm);
|
|
23864
24059
|
existingLink = stat.isSymbolicLink();
|
|
23865
24060
|
} catch {}
|
|
23866
24061
|
if (existingLink) {
|
|
23867
24062
|
if (!quiet)
|
|
23868
24063
|
stepProgress("Removing whole-directory symlink to prepare for safe linking...");
|
|
23869
24064
|
if (!dryRun) {
|
|
23870
|
-
|
|
24065
|
+
fs17.unlinkSync(wtNm);
|
|
23871
24066
|
}
|
|
23872
24067
|
}
|
|
23873
|
-
if (!dryRun && !
|
|
23874
|
-
|
|
24068
|
+
if (!dryRun && !fs17.existsSync(wtNm)) {
|
|
24069
|
+
fs17.mkdirSync(wtNm, { recursive: true });
|
|
23875
24070
|
}
|
|
23876
24071
|
const entriesToLink = [];
|
|
23877
24072
|
try {
|
|
23878
|
-
const mainEntries =
|
|
24073
|
+
const mainEntries = fs17.readdirSync(mainNm, { withFileTypes: true });
|
|
23879
24074
|
for (const entry of mainEntries) {
|
|
23880
24075
|
if (entry.name === ".bin") {
|
|
23881
24076
|
entriesToLink.push({
|
|
23882
24077
|
name: ".bin",
|
|
23883
|
-
target:
|
|
24078
|
+
target: path24.join(mainNm, ".bin"),
|
|
23884
24079
|
isBin: true
|
|
23885
24080
|
});
|
|
23886
24081
|
} else if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
23887
|
-
const scopePath =
|
|
24082
|
+
const scopePath = path24.join(mainNm, entry.name);
|
|
23888
24083
|
try {
|
|
23889
|
-
const scopedEntries =
|
|
24084
|
+
const scopedEntries = fs17.readdirSync(scopePath, { withFileTypes: true });
|
|
23890
24085
|
for (const scopedEntry of scopedEntries) {
|
|
23891
24086
|
entriesToLink.push({
|
|
23892
|
-
name:
|
|
23893
|
-
target:
|
|
24087
|
+
name: path24.join(entry.name, scopedEntry.name),
|
|
24088
|
+
target: path24.join(scopePath, scopedEntry.name),
|
|
23894
24089
|
isBin: false
|
|
23895
24090
|
});
|
|
23896
24091
|
}
|
|
@@ -23898,7 +24093,7 @@ function performSafeLink(wtPath, mainPath, dryRun, quiet) {
|
|
|
23898
24093
|
} else {
|
|
23899
24094
|
entriesToLink.push({
|
|
23900
24095
|
name: entry.name,
|
|
23901
|
-
target:
|
|
24096
|
+
target: path24.join(mainNm, entry.name),
|
|
23902
24097
|
isBin: false
|
|
23903
24098
|
});
|
|
23904
24099
|
}
|
|
@@ -23908,7 +24103,7 @@ function performSafeLink(wtPath, mainPath, dryRun, quiet) {
|
|
|
23908
24103
|
let failedCount = 0;
|
|
23909
24104
|
const failedNames = [];
|
|
23910
24105
|
for (const { name, target, isBin } of entriesToLink) {
|
|
23911
|
-
const linkPath =
|
|
24106
|
+
const linkPath = path24.join(wtNm, name);
|
|
23912
24107
|
if (dryRun) {
|
|
23913
24108
|
if (!quiet)
|
|
23914
24109
|
info(` [dry-run] Would link ${name}`);
|
|
@@ -23916,41 +24111,41 @@ function performSafeLink(wtPath, mainPath, dryRun, quiet) {
|
|
|
23916
24111
|
continue;
|
|
23917
24112
|
}
|
|
23918
24113
|
try {
|
|
23919
|
-
const parentDir =
|
|
23920
|
-
if (!
|
|
23921
|
-
|
|
24114
|
+
const parentDir = path24.dirname(linkPath);
|
|
24115
|
+
if (!fs17.existsSync(parentDir)) {
|
|
24116
|
+
fs17.mkdirSync(parentDir, { recursive: true });
|
|
23922
24117
|
}
|
|
23923
24118
|
let shouldLink = true;
|
|
23924
24119
|
let backupPath = null;
|
|
23925
24120
|
try {
|
|
23926
|
-
const stat =
|
|
24121
|
+
const stat = fs17.lstatSync(linkPath);
|
|
23927
24122
|
if (stat.isSymbolicLink()) {
|
|
23928
|
-
const existingTarget =
|
|
23929
|
-
const resolvedExisting =
|
|
24123
|
+
const existingTarget = fs17.readlinkSync(linkPath);
|
|
24124
|
+
const resolvedExisting = path24.resolve(path24.dirname(linkPath), existingTarget);
|
|
23930
24125
|
if (resolvedExisting === target) {
|
|
23931
24126
|
shouldLink = false;
|
|
23932
24127
|
} else {
|
|
23933
|
-
|
|
24128
|
+
fs17.unlinkSync(linkPath);
|
|
23934
24129
|
}
|
|
23935
24130
|
} else {
|
|
23936
24131
|
backupPath = `${linkPath}.wtx-old`;
|
|
23937
|
-
|
|
24132
|
+
fs17.renameSync(linkPath, backupPath);
|
|
23938
24133
|
}
|
|
23939
24134
|
} catch {}
|
|
23940
24135
|
if (shouldLink) {
|
|
23941
24136
|
try {
|
|
23942
|
-
const relTarget =
|
|
23943
|
-
|
|
24137
|
+
const relTarget = path24.relative(path24.dirname(linkPath), target);
|
|
24138
|
+
fs17.symlinkSync(relTarget, linkPath, isBin ? "dir" : fs17.statSync(target).isDirectory() ? "dir" : "file");
|
|
23944
24139
|
createdCount++;
|
|
23945
24140
|
} catch (err) {
|
|
23946
24141
|
if (backupPath) {
|
|
23947
|
-
|
|
24142
|
+
fs17.renameSync(backupPath, linkPath);
|
|
23948
24143
|
}
|
|
23949
24144
|
throw err;
|
|
23950
24145
|
}
|
|
23951
24146
|
}
|
|
23952
|
-
if (backupPath &&
|
|
23953
|
-
|
|
24147
|
+
if (backupPath && fs17.existsSync(backupPath)) {
|
|
24148
|
+
fs17.rmSync(backupPath, { recursive: true, force: true });
|
|
23954
24149
|
}
|
|
23955
24150
|
} catch {
|
|
23956
24151
|
failedCount++;
|
|
@@ -23969,15 +24164,15 @@ var init_linking = __esm(() => {
|
|
|
23969
24164
|
});
|
|
23970
24165
|
|
|
23971
24166
|
// src/lib/deps/engine.ts
|
|
23972
|
-
import
|
|
23973
|
-
import
|
|
24167
|
+
import fs18 from "fs";
|
|
24168
|
+
import path25 from "path";
|
|
23974
24169
|
function resolveAdapter2(dir, managerOverride) {
|
|
23975
24170
|
return resolveAdapter(dir, managerOverride);
|
|
23976
24171
|
}
|
|
23977
24172
|
function performLegacySymlink(wtPath, mainPath, dryRun, quiet) {
|
|
23978
|
-
const nmPath =
|
|
23979
|
-
const mainNm =
|
|
23980
|
-
if (!
|
|
24173
|
+
const nmPath = path25.join(wtPath, "node_modules");
|
|
24174
|
+
const mainNm = path25.join(mainPath, "node_modules");
|
|
24175
|
+
if (!fs18.existsSync(mainNm)) {
|
|
23981
24176
|
if (!quiet)
|
|
23982
24177
|
stepWarning("Main repo has no node_modules to symlink to");
|
|
23983
24178
|
return;
|
|
@@ -23987,7 +24182,7 @@ function performLegacySymlink(wtPath, mainPath, dryRun, quiet) {
|
|
|
23987
24182
|
let existingLink = false;
|
|
23988
24183
|
let shouldRemove = false;
|
|
23989
24184
|
try {
|
|
23990
|
-
const stat =
|
|
24185
|
+
const stat = fs18.lstatSync(nmPath);
|
|
23991
24186
|
existingLink = stat.isSymbolicLink();
|
|
23992
24187
|
shouldRemove = true;
|
|
23993
24188
|
} catch {}
|
|
@@ -24001,9 +24196,9 @@ function performLegacySymlink(wtPath, mainPath, dryRun, quiet) {
|
|
|
24001
24196
|
}
|
|
24002
24197
|
if (!dryRun) {
|
|
24003
24198
|
if (existingLink) {
|
|
24004
|
-
|
|
24199
|
+
fs18.unlinkSync(nmPath);
|
|
24005
24200
|
} else {
|
|
24006
|
-
|
|
24201
|
+
fs18.rmSync(nmPath, { recursive: true, force: true });
|
|
24007
24202
|
}
|
|
24008
24203
|
}
|
|
24009
24204
|
}
|
|
@@ -24011,22 +24206,22 @@ function performLegacySymlink(wtPath, mainPath, dryRun, quiet) {
|
|
|
24011
24206
|
if (!quiet)
|
|
24012
24207
|
info(` [dry-run] Would symlink ${mainNm} to ${nmPath}`);
|
|
24013
24208
|
} else {
|
|
24014
|
-
|
|
24209
|
+
fs18.symlinkSync(mainNm, nmPath);
|
|
24015
24210
|
}
|
|
24016
24211
|
if (!quiet)
|
|
24017
24212
|
stepSuccess("Symlinked node_modules", mainNm);
|
|
24018
24213
|
}
|
|
24019
24214
|
function detectCommonLinkageState(wtPath, mainPath) {
|
|
24020
|
-
const nodeModulesPath =
|
|
24215
|
+
const nodeModulesPath = path25.join(wtPath, "node_modules");
|
|
24021
24216
|
try {
|
|
24022
|
-
const stat =
|
|
24217
|
+
const stat = fs18.lstatSync(nodeModulesPath);
|
|
24023
24218
|
if (stat.isSymbolicLink()) {
|
|
24024
|
-
const target =
|
|
24025
|
-
const resolvedTarget =
|
|
24219
|
+
const target = fs18.readlinkSync(nodeModulesPath);
|
|
24220
|
+
const resolvedTarget = path25.resolve(wtPath, target);
|
|
24026
24221
|
const resolvedMain = safeResolve(mainPath);
|
|
24027
24222
|
let targetExists = false;
|
|
24028
24223
|
try {
|
|
24029
|
-
|
|
24224
|
+
fs18.statSync(resolvedTarget);
|
|
24030
24225
|
targetExists = true;
|
|
24031
24226
|
} catch {
|
|
24032
24227
|
targetExists = false;
|
|
@@ -24034,14 +24229,14 @@ function detectCommonLinkageState(wtPath, mainPath) {
|
|
|
24034
24229
|
if (!targetExists) {
|
|
24035
24230
|
return { state: "broken", target };
|
|
24036
24231
|
}
|
|
24037
|
-
if (!isWithin(resolvedMain, resolvedTarget) && resolvedTarget !==
|
|
24232
|
+
if (!isWithin(resolvedMain, resolvedTarget) && resolvedTarget !== path25.join(resolvedMain, "node_modules")) {
|
|
24038
24233
|
return { state: "external", target };
|
|
24039
24234
|
}
|
|
24040
24235
|
return { state: "linked-whole", target };
|
|
24041
24236
|
} else if (stat.isDirectory()) {
|
|
24042
24237
|
let isLinkedPackages = false;
|
|
24043
24238
|
try {
|
|
24044
|
-
const entries =
|
|
24239
|
+
const entries = fs18.readdirSync(nodeModulesPath, { withFileTypes: true });
|
|
24045
24240
|
for (const entry of entries) {
|
|
24046
24241
|
if (entry.isSymbolicLink()) {
|
|
24047
24242
|
isLinkedPackages = true;
|
|
@@ -24055,7 +24250,7 @@ function detectCommonLinkageState(wtPath, mainPath) {
|
|
|
24055
24250
|
return { state: "installed" };
|
|
24056
24251
|
}
|
|
24057
24252
|
} catch {
|
|
24058
|
-
if (!
|
|
24253
|
+
if (!fs18.existsSync(nodeModulesPath)) {
|
|
24059
24254
|
return { state: "missing" };
|
|
24060
24255
|
}
|
|
24061
24256
|
}
|
|
@@ -24169,7 +24364,7 @@ function findRepoDepsContext(wtPath) {
|
|
|
24169
24364
|
try {
|
|
24170
24365
|
const config2 = loadConfig();
|
|
24171
24366
|
const repos = resolveRepos(config2, []);
|
|
24172
|
-
const repo = repos.find((r) => wtPath === r.wtRoot || wtPath.startsWith(r.wtRoot + "/"));
|
|
24367
|
+
const repo = repos.find((r) => wtPath === r.mainPath || wtPath === r.wtRoot || wtPath.startsWith(r.wtRoot + "/"));
|
|
24173
24368
|
if (repo) {
|
|
24174
24369
|
return {
|
|
24175
24370
|
name: repo.name,
|
|
@@ -24250,18 +24445,18 @@ var init_deps = __esm(() => {
|
|
|
24250
24445
|
});
|
|
24251
24446
|
|
|
24252
24447
|
// src/lib/forge/map.ts
|
|
24253
|
-
function
|
|
24448
|
+
function isRecord2(value) {
|
|
24254
24449
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24255
24450
|
}
|
|
24256
24451
|
function asCheckItem(value) {
|
|
24257
|
-
if (!
|
|
24452
|
+
if (!isRecord2(value))
|
|
24258
24453
|
return null;
|
|
24259
24454
|
return {
|
|
24260
24455
|
__typename: typeof value.__typename === "string" ? value.__typename : undefined,
|
|
24261
24456
|
status: typeof value.status === "string" ? value.status : null,
|
|
24262
24457
|
conclusion: typeof value.conclusion === "string" ? value.conclusion : null,
|
|
24263
24458
|
state: typeof value.state === "string" ? value.state : null,
|
|
24264
|
-
commit:
|
|
24459
|
+
commit: isRecord2(value.commit) ? value.commit : null
|
|
24265
24460
|
};
|
|
24266
24461
|
}
|
|
24267
24462
|
function collectCheckItems(raw) {
|
|
@@ -24335,7 +24530,7 @@ function mapReviewDecision(raw) {
|
|
|
24335
24530
|
return null;
|
|
24336
24531
|
}
|
|
24337
24532
|
function mapGithubPr(raw) {
|
|
24338
|
-
if (!
|
|
24533
|
+
if (!isRecord2(raw))
|
|
24339
24534
|
return null;
|
|
24340
24535
|
const number4 = typeof raw.number === "number" ? raw.number : null;
|
|
24341
24536
|
const headRefName = typeof raw.headRefName === "string" ? raw.headRefName : null;
|
|
@@ -24346,9 +24541,10 @@ function mapGithubPr(raw) {
|
|
|
24346
24541
|
const url2 = typeof raw.url === "string" ? raw.url : "";
|
|
24347
24542
|
const isDraft = raw.isDraft === true;
|
|
24348
24543
|
const updatedAt = typeof raw.updatedAt === "string" ? raw.updatedAt : "";
|
|
24544
|
+
const baseRefName = typeof raw.baseRefName === "string" ? raw.baseRefName : undefined;
|
|
24349
24545
|
const checks3 = bucketChecks(collectCheckItems(raw.statusCheckRollup));
|
|
24350
24546
|
const author = raw.author;
|
|
24351
|
-
const authorLogin =
|
|
24547
|
+
const authorLogin = isRecord2(author) && typeof author.login === "string" ? author.login : null;
|
|
24352
24548
|
return {
|
|
24353
24549
|
number: number4,
|
|
24354
24550
|
authorLogin,
|
|
@@ -24359,12 +24555,13 @@ function mapGithubPr(raw) {
|
|
|
24359
24555
|
mergeable: mapMergeable(raw.mergeable),
|
|
24360
24556
|
checks: checks3,
|
|
24361
24557
|
reviewDecision: mapReviewDecision(raw.reviewDecision),
|
|
24558
|
+
...baseRefName ? { baseRefName } : {},
|
|
24362
24559
|
unresolvedThreads: 0,
|
|
24363
24560
|
updatedAt
|
|
24364
24561
|
};
|
|
24365
24562
|
}
|
|
24366
24563
|
function mapPrHead(raw) {
|
|
24367
|
-
if (!
|
|
24564
|
+
if (!isRecord2(raw))
|
|
24368
24565
|
return null;
|
|
24369
24566
|
const number4 = typeof raw.number === "number" ? raw.number : null;
|
|
24370
24567
|
const headRefName = typeof raw.headRefName === "string" ? raw.headRefName : null;
|
|
@@ -24375,12 +24572,13 @@ function mapPrHead(raw) {
|
|
|
24375
24572
|
const url2 = typeof raw.url === "string" ? raw.url : "";
|
|
24376
24573
|
const isDraft = raw.isDraft === true;
|
|
24377
24574
|
const isCrossRepository = raw.isCrossRepository === true;
|
|
24575
|
+
const baseRefName = typeof raw.baseRefName === "string" ? raw.baseRefName : undefined;
|
|
24378
24576
|
let headOwnerLogin = null;
|
|
24379
|
-
if (
|
|
24577
|
+
if (isRecord2(raw.headRepositoryOwner) && typeof raw.headRepositoryOwner.login === "string") {
|
|
24380
24578
|
headOwnerLogin = raw.headRepositoryOwner.login;
|
|
24381
24579
|
}
|
|
24382
24580
|
let headRepoName = null;
|
|
24383
|
-
if (
|
|
24581
|
+
if (isRecord2(raw.headRepository) && typeof raw.headRepository.name === "string") {
|
|
24384
24582
|
headRepoName = raw.headRepository.name;
|
|
24385
24583
|
}
|
|
24386
24584
|
return {
|
|
@@ -24390,6 +24588,7 @@ function mapPrHead(raw) {
|
|
|
24390
24588
|
state,
|
|
24391
24589
|
isDraft,
|
|
24392
24590
|
headRefName,
|
|
24591
|
+
...baseRefName ? { baseRefName } : {},
|
|
24393
24592
|
isCrossRepository,
|
|
24394
24593
|
headOwnerLogin,
|
|
24395
24594
|
headRepoName
|
|
@@ -24485,7 +24684,7 @@ async function ghExec(args, opts = {}) {
|
|
|
24485
24684
|
}
|
|
24486
24685
|
function mapWithBranch(raw) {
|
|
24487
24686
|
const pr = mapGithubPr(raw);
|
|
24488
|
-
if (!pr || !
|
|
24687
|
+
if (!pr || !isRecord2(raw) || typeof raw.headRefName !== "string")
|
|
24489
24688
|
return null;
|
|
24490
24689
|
return { branch: raw.headRefName, pr };
|
|
24491
24690
|
}
|
|
@@ -24530,12 +24729,12 @@ async function fetchBranchPrs(ctx) {
|
|
|
24530
24729
|
return results.filter((entry) => entry !== null);
|
|
24531
24730
|
}
|
|
24532
24731
|
function countUnresolvedThreads(result) {
|
|
24533
|
-
if (!
|
|
24732
|
+
if (!isRecord2(result))
|
|
24534
24733
|
return 0;
|
|
24535
24734
|
const threads = result.reviewThreads;
|
|
24536
|
-
if (!
|
|
24735
|
+
if (!isRecord2(threads) || !Array.isArray(threads.nodes))
|
|
24537
24736
|
return 0;
|
|
24538
|
-
return threads.nodes.filter((node) =>
|
|
24737
|
+
return threads.nodes.filter((node) => isRecord2(node) && node.isResolved === false).length;
|
|
24539
24738
|
}
|
|
24540
24739
|
function buildThreadQuery(numbers) {
|
|
24541
24740
|
const aliases = numbers.map((n2, i2) => `pr${i2}: pullRequest(number: ${n2}) { reviewThreads(first: ${THREADS_PER_PR}) { nodes { isResolved } } }`).join(" ");
|
|
@@ -24556,10 +24755,10 @@ async function enrichUnresolvedThreads(slug, prs, verboseFlag) {
|
|
|
24556
24755
|
`name=${slug.name}`
|
|
24557
24756
|
], { verbose: verboseFlag });
|
|
24558
24757
|
const payload = parseJson(stdout);
|
|
24559
|
-
if (!
|
|
24758
|
+
if (!isRecord2(payload) || !isRecord2(payload.data))
|
|
24560
24759
|
return;
|
|
24561
24760
|
const repoData = payload.data.repository;
|
|
24562
|
-
if (!
|
|
24761
|
+
if (!isRecord2(repoData))
|
|
24563
24762
|
return;
|
|
24564
24763
|
prs.forEach((pr, i2) => {
|
|
24565
24764
|
pr.unresolvedThreads = countUnresolvedThreads(repoData[`pr${i2}`]);
|
|
@@ -24589,7 +24788,7 @@ function createGithubAdapter(slug) {
|
|
|
24589
24788
|
"-R",
|
|
24590
24789
|
`${slug.owner}/${slug.name}`,
|
|
24591
24790
|
"--json",
|
|
24592
|
-
"number,title,url,state,isDraft,headRefName,isCrossRepository,headRepositoryOwner,headRepository"
|
|
24791
|
+
"number,title,url,state,isDraft,headRefName,baseRefName,isCrossRepository,headRepositoryOwner,headRepository"
|
|
24593
24792
|
];
|
|
24594
24793
|
const stdout = await ghExec(args, opts);
|
|
24595
24794
|
const parsed = parseJson(stdout);
|
|
@@ -24629,6 +24828,7 @@ var init_github = __esm(() => {
|
|
|
24629
24828
|
"statusCheckRollup",
|
|
24630
24829
|
"reviewDecision",
|
|
24631
24830
|
"headRefName",
|
|
24831
|
+
"baseRefName",
|
|
24632
24832
|
"updatedAt"
|
|
24633
24833
|
].join(",");
|
|
24634
24834
|
REMOTE_URL_PATTERNS = [
|
|
@@ -24639,8 +24839,8 @@ var init_github = __esm(() => {
|
|
|
24639
24839
|
});
|
|
24640
24840
|
|
|
24641
24841
|
// src/lib/forge/index.ts
|
|
24642
|
-
import
|
|
24643
|
-
import
|
|
24842
|
+
import fs19 from "fs";
|
|
24843
|
+
import path26 from "path";
|
|
24644
24844
|
function parsePrLink(link) {
|
|
24645
24845
|
for (const forge of FORGES) {
|
|
24646
24846
|
const ref = forge.parsePrLink(link);
|
|
@@ -24653,10 +24853,10 @@ function descriptorFor(id) {
|
|
|
24653
24853
|
return FORGES.find((f) => f.id === id) ?? null;
|
|
24654
24854
|
}
|
|
24655
24855
|
function readOriginUrl(mainPath) {
|
|
24656
|
-
const gitConfigPath =
|
|
24657
|
-
if (!
|
|
24856
|
+
const gitConfigPath = path26.join(mainPath, ".git", "config");
|
|
24857
|
+
if (!fs19.existsSync(gitConfigPath))
|
|
24658
24858
|
return null;
|
|
24659
|
-
const content =
|
|
24859
|
+
const content = fs19.readFileSync(gitConfigPath, "utf-8");
|
|
24660
24860
|
const originSection = content.match(/\[remote "origin"\]([^[]*)/);
|
|
24661
24861
|
if (!originSection)
|
|
24662
24862
|
return null;
|
|
@@ -24706,7 +24906,7 @@ var init_forge = __esm(() => {
|
|
|
24706
24906
|
});
|
|
24707
24907
|
|
|
24708
24908
|
// src/lib/owner.ts
|
|
24709
|
-
import
|
|
24909
|
+
import fs20 from "fs";
|
|
24710
24910
|
function deriveOwnership(input) {
|
|
24711
24911
|
if (input.hasLocalChanges) {
|
|
24712
24912
|
return { mine: true, author: null };
|
|
@@ -24733,7 +24933,7 @@ async function resolveOwnership(input) {
|
|
|
24733
24933
|
}
|
|
24734
24934
|
const { mainPath, branch, verbose: verbose2 } = input;
|
|
24735
24935
|
let hasLocalChanges = false;
|
|
24736
|
-
if (input.wtPath &&
|
|
24936
|
+
if (input.wtPath && fs20.existsSync(input.wtPath)) {
|
|
24737
24937
|
try {
|
|
24738
24938
|
const dirtyFiles = await getDirtyFiles(input.wtPath);
|
|
24739
24939
|
hasLocalChanges = dirtyFiles.length > 0;
|
|
@@ -24995,6 +25195,13 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
24995
25195
|
try {
|
|
24996
25196
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
24997
25197
|
const wts = await getWorktreeList(repo.mainPath);
|
|
25198
|
+
let stackMetadata = { version: 1, branches: {} };
|
|
25199
|
+
try {
|
|
25200
|
+
stackMetadata = await readStackMetadata(repo.mainPath, opts);
|
|
25201
|
+
} catch (err) {
|
|
25202
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25203
|
+
warnings.push({ repoName: repo.name, message });
|
|
25204
|
+
}
|
|
24998
25205
|
const forge = resolveForge(repo);
|
|
24999
25206
|
const branches = wts.map((w) => w.branch).filter(Boolean);
|
|
25000
25207
|
let prMap = new Map;
|
|
@@ -25022,6 +25229,10 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25022
25229
|
if (isMainCheckout && !branch) {
|
|
25023
25230
|
branch = mainBranch;
|
|
25024
25231
|
}
|
|
25232
|
+
const stackEntry = branch ? stackMetadata.branches[branch] : undefined;
|
|
25233
|
+
const pr = branch ? prMap.get(branch) : undefined;
|
|
25234
|
+
const base2 = stackEntry?.baseRef ?? pr?.baseRefName;
|
|
25235
|
+
const comparisonBase = stackEntry?.explicit ? stackEntry.baseRef : pr?.baseRefName && pr.baseRefName !== mainBranch ? pr.baseRefName : `origin/${mainBranch}`;
|
|
25025
25236
|
const row = {
|
|
25026
25237
|
repoName: repo.name,
|
|
25027
25238
|
branch: branch || "(detached)",
|
|
@@ -25040,7 +25251,9 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25040
25251
|
prUrl: null,
|
|
25041
25252
|
owner: null,
|
|
25042
25253
|
rebaseStatus: null,
|
|
25043
|
-
depsStrategy: "none"
|
|
25254
|
+
depsStrategy: "none",
|
|
25255
|
+
base: base2,
|
|
25256
|
+
baseChanged: false
|
|
25044
25257
|
};
|
|
25045
25258
|
if (isMainCheckout) {
|
|
25046
25259
|
allRows.push(row);
|
|
@@ -25052,7 +25265,7 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25052
25265
|
})(),
|
|
25053
25266
|
(async () => {
|
|
25054
25267
|
try {
|
|
25055
|
-
const stdout = await gitExec(["-C", wt.path, "rev-list", "--left-right", "--count",
|
|
25268
|
+
const stdout = await gitExec(["-C", wt.path, "rev-list", "--left-right", "--count", `${comparisonBase}...HEAD`], { dryRun: opts.dryRun });
|
|
25056
25269
|
if (stdout) {
|
|
25057
25270
|
const parts = stdout.trim().split(/\s+/);
|
|
25058
25271
|
if (parts.length === 2) {
|
|
@@ -25062,6 +25275,16 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25062
25275
|
}
|
|
25063
25276
|
} catch {}
|
|
25064
25277
|
})(),
|
|
25278
|
+
(async () => {
|
|
25279
|
+
if (!stackEntry?.explicit || opts.dryRun)
|
|
25280
|
+
return;
|
|
25281
|
+
try {
|
|
25282
|
+
const currentBaseSha = await resolveCommitSha(repo.mainPath, stackEntry.baseRef, opts);
|
|
25283
|
+
row.baseChanged = currentBaseSha !== stackEntry.baseSha;
|
|
25284
|
+
} catch {
|
|
25285
|
+
row.baseChanged = true;
|
|
25286
|
+
}
|
|
25287
|
+
})(),
|
|
25065
25288
|
(async () => {
|
|
25066
25289
|
row.rebaseStatus = detectInProgressRebase(wt.path);
|
|
25067
25290
|
})(),
|
|
@@ -25073,7 +25296,6 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25073
25296
|
})(),
|
|
25074
25297
|
(async () => {
|
|
25075
25298
|
if (branch && config2.user) {
|
|
25076
|
-
const pr = prMap.get(branch);
|
|
25077
25299
|
try {
|
|
25078
25300
|
const owner = await resolveOwnership({
|
|
25079
25301
|
configUser: config2.user,
|
|
@@ -25090,7 +25312,6 @@ async function fetchWorktreeData(opts, scope) {
|
|
|
25090
25312
|
})()
|
|
25091
25313
|
]);
|
|
25092
25314
|
if (branch) {
|
|
25093
|
-
const pr = prMap.get(branch);
|
|
25094
25315
|
if (pr) {
|
|
25095
25316
|
row.prNumber = pr.number;
|
|
25096
25317
|
row.prUrl = pr.url;
|
|
@@ -25127,6 +25348,7 @@ var init_data = __esm(() => {
|
|
|
25127
25348
|
init_forge();
|
|
25128
25349
|
init_owner();
|
|
25129
25350
|
init_types2();
|
|
25351
|
+
init_stack();
|
|
25130
25352
|
prCache = new Map;
|
|
25131
25353
|
});
|
|
25132
25354
|
|
|
@@ -25147,14 +25369,16 @@ function matchesFilter(entry, term) {
|
|
|
25147
25369
|
return true;
|
|
25148
25370
|
if (entry.prUrl?.toLowerCase().includes(lower))
|
|
25149
25371
|
return true;
|
|
25372
|
+
if (entry.base?.toLowerCase().includes(lower))
|
|
25373
|
+
return true;
|
|
25150
25374
|
return false;
|
|
25151
25375
|
}
|
|
25152
|
-
function toggleSelection(current,
|
|
25376
|
+
function toggleSelection(current, path36) {
|
|
25153
25377
|
const next = new Set(current);
|
|
25154
|
-
if (next.has(
|
|
25155
|
-
next.delete(
|
|
25378
|
+
if (next.has(path36)) {
|
|
25379
|
+
next.delete(path36);
|
|
25156
25380
|
} else {
|
|
25157
|
-
next.add(
|
|
25381
|
+
next.add(path36);
|
|
25158
25382
|
}
|
|
25159
25383
|
return next;
|
|
25160
25384
|
}
|
|
@@ -25181,9 +25405,38 @@ function rowSort(a2, b) {
|
|
|
25181
25405
|
return 1;
|
|
25182
25406
|
return a2.branch.localeCompare(b.branch);
|
|
25183
25407
|
}
|
|
25408
|
+
function sortRowsHierarchically(rows) {
|
|
25409
|
+
return buildStackHierarchy(rows, (row) => row.branch, (row) => row.base, rowSort).map(({ item, depth, prefix }) => ({
|
|
25410
|
+
...item,
|
|
25411
|
+
hierarchyDepth: depth,
|
|
25412
|
+
hierarchyPrefix: prefix
|
|
25413
|
+
}));
|
|
25414
|
+
}
|
|
25184
25415
|
function sortBlocks(blocks) {
|
|
25185
25416
|
return [...blocks].sort((a2, b) => a2.repoName.localeCompare(b.repoName));
|
|
25186
25417
|
}
|
|
25418
|
+
function wrapText(text, width) {
|
|
25419
|
+
const lines = [];
|
|
25420
|
+
for (const paragraph of text.split(`
|
|
25421
|
+
`)) {
|
|
25422
|
+
let current = "";
|
|
25423
|
+
for (const word of paragraph.split(/\s+/).filter(Boolean)) {
|
|
25424
|
+
if (!current) {
|
|
25425
|
+
current = word;
|
|
25426
|
+
} else if (current.length + 1 + word.length <= width) {
|
|
25427
|
+
current += ` ${word}`;
|
|
25428
|
+
} else {
|
|
25429
|
+
lines.push(current);
|
|
25430
|
+
current = word;
|
|
25431
|
+
}
|
|
25432
|
+
}
|
|
25433
|
+
lines.push(current);
|
|
25434
|
+
}
|
|
25435
|
+
return lines;
|
|
25436
|
+
}
|
|
25437
|
+
function isTapWithoutDrag(down, up) {
|
|
25438
|
+
return Math.abs(up.x - down.x) <= 1 && Math.abs(up.y - down.y) <= 1;
|
|
25439
|
+
}
|
|
25187
25440
|
function mergeBlocks(prev, next, scope) {
|
|
25188
25441
|
if (!scope)
|
|
25189
25442
|
return sortBlocks(next);
|
|
@@ -25236,9 +25489,12 @@ function withCreatePlaceholders(blocks, creating) {
|
|
|
25236
25489
|
if (!branches)
|
|
25237
25490
|
return block;
|
|
25238
25491
|
const placeholders = branches.map((br) => makePlaceholderRow(block.repoName, br));
|
|
25239
|
-
return { ...block, rows: [...block.rows, ...placeholders]
|
|
25492
|
+
return { ...block, rows: sortRowsHierarchically([...block.rows, ...placeholders]) };
|
|
25240
25493
|
});
|
|
25241
25494
|
}
|
|
25495
|
+
var init_utils = __esm(() => {
|
|
25496
|
+
init_stack();
|
|
25497
|
+
});
|
|
25242
25498
|
|
|
25243
25499
|
// src/tui/hooks/useWorktrees.ts
|
|
25244
25500
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
@@ -25278,8 +25534,7 @@ function useWorktrees(opts) {
|
|
|
25278
25534
|
}
|
|
25279
25535
|
const newBlocks = [];
|
|
25280
25536
|
for (const [repoName, rows] of byRepo.entries()) {
|
|
25281
|
-
|
|
25282
|
-
newBlocks.push({ repoName, rows });
|
|
25537
|
+
newBlocks.push({ repoName, rows: sortRowsHierarchically(rows) });
|
|
25283
25538
|
}
|
|
25284
25539
|
newBlocks.sort((a2, b) => a2.repoName.localeCompare(b.repoName));
|
|
25285
25540
|
const scopeSet = scope ? new Set(scope) : undefined;
|
|
@@ -25306,6 +25561,7 @@ function useWorktrees(opts) {
|
|
|
25306
25561
|
var init_useWorktrees = __esm(() => {
|
|
25307
25562
|
init_data();
|
|
25308
25563
|
init_config();
|
|
25564
|
+
init_utils();
|
|
25309
25565
|
});
|
|
25310
25566
|
|
|
25311
25567
|
// src/tui/theme.ts
|
|
@@ -25339,9 +25595,91 @@ var init_theme = __esm(() => {
|
|
|
25339
25595
|
};
|
|
25340
25596
|
});
|
|
25341
25597
|
|
|
25598
|
+
// src/tui/hooks/use-tap.ts
|
|
25599
|
+
import { useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
25600
|
+
function useTapHandler(onTap) {
|
|
25601
|
+
const down = useRef2(null);
|
|
25602
|
+
const onMouseDown = useCallback2((e) => {
|
|
25603
|
+
down.current = { x: e.x, y: e.y };
|
|
25604
|
+
}, []);
|
|
25605
|
+
const onMouseUp = useCallback2((e) => {
|
|
25606
|
+
const start = down.current;
|
|
25607
|
+
down.current = null;
|
|
25608
|
+
if (!start || !isTapWithoutDrag(start, e))
|
|
25609
|
+
return;
|
|
25610
|
+
onTap();
|
|
25611
|
+
}, [onTap]);
|
|
25612
|
+
return { onMouseDown, onMouseUp };
|
|
25613
|
+
}
|
|
25614
|
+
var init_use_tap = __esm(() => {
|
|
25615
|
+
init_utils();
|
|
25616
|
+
});
|
|
25617
|
+
|
|
25618
|
+
// src/tui/platform.ts
|
|
25619
|
+
function clipboardCandidatesFor(platform2, env2 = process.env) {
|
|
25620
|
+
if (platform2 === "darwin")
|
|
25621
|
+
return [{ cmd: "pbcopy", args: [] }];
|
|
25622
|
+
if (platform2 === "win32")
|
|
25623
|
+
return [{ cmd: "clip", args: [] }];
|
|
25624
|
+
const candidates = [];
|
|
25625
|
+
if (env2.WAYLAND_DISPLAY)
|
|
25626
|
+
candidates.push({ cmd: "wl-copy", args: [] });
|
|
25627
|
+
if (env2.DISPLAY) {
|
|
25628
|
+
candidates.push({ cmd: "xclip", args: ["-selection", "clipboard"] });
|
|
25629
|
+
candidates.push({ cmd: "xsel", args: ["--clipboard", "--input"] });
|
|
25630
|
+
}
|
|
25631
|
+
return candidates;
|
|
25632
|
+
}
|
|
25633
|
+
async function copyTextToClipboard(renderer, text) {
|
|
25634
|
+
if (!text)
|
|
25635
|
+
return false;
|
|
25636
|
+
const viaTerminal = renderer.copyToClipboardOSC52(text);
|
|
25637
|
+
let viaSystem = false;
|
|
25638
|
+
for (const candidate of clipboardCandidatesFor(process.platform)) {
|
|
25639
|
+
try {
|
|
25640
|
+
const proc = Bun.spawn([candidate.cmd, ...candidate.args], {
|
|
25641
|
+
stdin: "pipe",
|
|
25642
|
+
stdout: "ignore",
|
|
25643
|
+
stderr: "ignore"
|
|
25644
|
+
});
|
|
25645
|
+
proc.stdin.write(text);
|
|
25646
|
+
proc.stdin.end();
|
|
25647
|
+
if (await proc.exited === 0) {
|
|
25648
|
+
viaSystem = true;
|
|
25649
|
+
break;
|
|
25650
|
+
}
|
|
25651
|
+
} catch {}
|
|
25652
|
+
}
|
|
25653
|
+
return viaSystem || viaTerminal;
|
|
25654
|
+
}
|
|
25655
|
+
function browserCommandFor(platform2, url2) {
|
|
25656
|
+
if (!/^https?:\/\//.test(url2))
|
|
25657
|
+
return null;
|
|
25658
|
+
if (platform2 === "darwin")
|
|
25659
|
+
return { cmd: "open", args: [url2] };
|
|
25660
|
+
if (platform2 === "win32")
|
|
25661
|
+
return { cmd: "cmd", args: ["/c", "start", "", url2] };
|
|
25662
|
+
return { cmd: "xdg-open", args: [url2] };
|
|
25663
|
+
}
|
|
25664
|
+
async function openInBrowser(url2) {
|
|
25665
|
+
const command = browserCommandFor(process.platform, url2);
|
|
25666
|
+
if (!command)
|
|
25667
|
+
return false;
|
|
25668
|
+
try {
|
|
25669
|
+
const proc = Bun.spawn([command.cmd, ...command.args], {
|
|
25670
|
+
stdin: "ignore",
|
|
25671
|
+
stdout: "ignore",
|
|
25672
|
+
stderr: "ignore"
|
|
25673
|
+
});
|
|
25674
|
+
return await proc.exited === 0;
|
|
25675
|
+
} catch {
|
|
25676
|
+
return false;
|
|
25677
|
+
}
|
|
25678
|
+
}
|
|
25679
|
+
|
|
25342
25680
|
// src/tui/components/WorktreeTable.tsx
|
|
25343
|
-
import { useEffect as useEffect2, useRef as
|
|
25344
|
-
import { jsx, jsxs } from "@opentui/react/jsx-runtime";
|
|
25681
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
25682
|
+
import { jsx, jsxs, Fragment } from "@opentui/react/jsx-runtime";
|
|
25345
25683
|
function statusBadge(row) {
|
|
25346
25684
|
if (row.isMainCheckout)
|
|
25347
25685
|
return { text: "[main]", fg: tokens.accent };
|
|
@@ -25356,22 +25694,25 @@ function statusBadge(row) {
|
|
|
25356
25694
|
return { text: "clean", fg: tokens.dim };
|
|
25357
25695
|
}
|
|
25358
25696
|
function WorktreeItem({ row, isSelected, isMultiSelected, indicator, frame, id }) {
|
|
25697
|
+
const prTap = useTapHandler(() => {
|
|
25698
|
+
if (row.prUrl)
|
|
25699
|
+
openInBrowser(row.prUrl);
|
|
25700
|
+
});
|
|
25359
25701
|
const badge = indicator ? indicator.running ? { text: `${frame} ${indicator.verb}…`, fg: tokens.accent } : { text: `◌ ${indicator.verb}`, fg: tokens.dim } : row.isPendingCreate ? { text: `${frame} creating…`, fg: tokens.accent } : statusBadge(row);
|
|
25360
25702
|
const disabled = indicator !== undefined || row.isPendingCreate === true;
|
|
25361
25703
|
const primary = isSelected ? tokens.bright : disabled ? tokens.dim : tokens.fg;
|
|
25362
25704
|
const divergence = row.ahead !== null && row.behind !== null && (row.ahead > 0 || row.behind > 0) ? ` · ↑${row.ahead} ↓${row.behind}` : "";
|
|
25363
|
-
const prSegment = row.prNumber ? [
|
|
25364
|
-
`· #${row.prNumber}`,
|
|
25365
|
-
row.prState ?? "",
|
|
25366
|
-
row.prChecks ? `(${row.prChecks})` : ""
|
|
25367
|
-
].filter(Boolean).join(" ") : "";
|
|
25368
25705
|
const ownerSegment = row.owner ? ` · by ${row.owner}` : "";
|
|
25706
|
+
const baseSegment = row.base ? ` · base ${row.base}` : "";
|
|
25369
25707
|
const rebaseSegment = !row.isMainCheckout && row.rebaseStatus && !row.isPrunable ? ` · ${row.rebaseStatus}` : "";
|
|
25708
|
+
const baseChangedSegment = row.baseChanged ? " · base moved" : "";
|
|
25709
|
+
const hierarchyPrefix = row.hierarchyPrefix ?? "";
|
|
25710
|
+
const secondaryIndent = " ".repeat(SECONDARY_INDENT.length + hierarchyPrefix.length);
|
|
25370
25711
|
const secondary = [
|
|
25371
|
-
`${
|
|
25712
|
+
`${secondaryIndent}${row.commitShort}`,
|
|
25372
25713
|
divergence,
|
|
25373
|
-
|
|
25374
|
-
|
|
25714
|
+
ownerSegment,
|
|
25715
|
+
baseSegment
|
|
25375
25716
|
].filter(Boolean).join(" ").trimEnd();
|
|
25376
25717
|
return /* @__PURE__ */ jsxs("box", {
|
|
25377
25718
|
id,
|
|
@@ -25389,6 +25730,10 @@ function WorktreeItem({ row, isSelected, isMultiSelected, indicator, frame, id }
|
|
|
25389
25730
|
fg: tokens.accent,
|
|
25390
25731
|
children: isMultiSelected ? "✓ " : " "
|
|
25391
25732
|
}),
|
|
25733
|
+
/* @__PURE__ */ jsx("span", {
|
|
25734
|
+
fg: tokens.dim,
|
|
25735
|
+
children: hierarchyPrefix
|
|
25736
|
+
}),
|
|
25392
25737
|
/* @__PURE__ */ jsx("span", {
|
|
25393
25738
|
fg: primary,
|
|
25394
25739
|
children: truncateBranch(row.branch)
|
|
@@ -25400,11 +25745,40 @@ function WorktreeItem({ row, isSelected, isMultiSelected, indicator, frame, id }
|
|
|
25400
25745
|
]
|
|
25401
25746
|
}),
|
|
25402
25747
|
/* @__PURE__ */ jsxs("text", {
|
|
25748
|
+
...row.prUrl ? prTap : {},
|
|
25403
25749
|
children: [
|
|
25404
25750
|
/* @__PURE__ */ jsx("span", {
|
|
25405
25751
|
fg: tokens.dim,
|
|
25406
25752
|
children: secondary
|
|
25407
25753
|
}),
|
|
25754
|
+
row.prNumber !== null && /* @__PURE__ */ jsxs(Fragment, {
|
|
25755
|
+
children: [
|
|
25756
|
+
/* @__PURE__ */ jsx("span", {
|
|
25757
|
+
fg: tokens.dim,
|
|
25758
|
+
children: secondary ? " · " : ""
|
|
25759
|
+
}),
|
|
25760
|
+
/* @__PURE__ */ jsx("span", {
|
|
25761
|
+
fg: tokens.accent,
|
|
25762
|
+
children: `#${row.prNumber}`
|
|
25763
|
+
}),
|
|
25764
|
+
row.prState && /* @__PURE__ */ jsx("span", {
|
|
25765
|
+
fg: tokens.dim,
|
|
25766
|
+
children: ` ${row.prState}`
|
|
25767
|
+
}),
|
|
25768
|
+
row.prChecks && /* @__PURE__ */ jsx("span", {
|
|
25769
|
+
fg: tokens.dim,
|
|
25770
|
+
children: ` (${row.prChecks})`
|
|
25771
|
+
}),
|
|
25772
|
+
row.prUrl && /* @__PURE__ */ jsx("span", {
|
|
25773
|
+
fg: tokens.dim,
|
|
25774
|
+
children: " ↗"
|
|
25775
|
+
})
|
|
25776
|
+
]
|
|
25777
|
+
}),
|
|
25778
|
+
baseChangedSegment && /* @__PURE__ */ jsx("span", {
|
|
25779
|
+
fg: tokens.warning,
|
|
25780
|
+
children: baseChangedSegment
|
|
25781
|
+
}),
|
|
25408
25782
|
rebaseSegment && /* @__PURE__ */ jsx("span", {
|
|
25409
25783
|
fg: tokens.error,
|
|
25410
25784
|
children: rebaseSegment
|
|
@@ -25415,7 +25789,7 @@ function WorktreeItem({ row, isSelected, isMultiSelected, indicator, frame, id }
|
|
|
25415
25789
|
});
|
|
25416
25790
|
}
|
|
25417
25791
|
function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repoVerbs, rowVerbs }) {
|
|
25418
|
-
const scrollRef =
|
|
25792
|
+
const scrollRef = useRef3(null);
|
|
25419
25793
|
useEffect2(() => {
|
|
25420
25794
|
if (scrollRef.current?.scrollChildIntoView) {
|
|
25421
25795
|
scrollRef.current.scrollChildIntoView("selected-row");
|
|
@@ -25485,11 +25859,16 @@ function WorktreeTable({ blocks, selectedIndex, selection = new Set, frame, repo
|
|
|
25485
25859
|
var SECONDARY_INDENT = " ";
|
|
25486
25860
|
var init_WorktreeTable = __esm(() => {
|
|
25487
25861
|
init_theme();
|
|
25862
|
+
init_use_tap();
|
|
25488
25863
|
});
|
|
25489
25864
|
|
|
25490
25865
|
// src/tui/components/DetailPane.tsx
|
|
25491
|
-
import { jsx as jsx2, jsxs as jsxs2, Fragment } from "@opentui/react/jsx-runtime";
|
|
25866
|
+
import { jsx as jsx2, jsxs as jsxs2, Fragment as Fragment2 } from "@opentui/react/jsx-runtime";
|
|
25492
25867
|
function DetailPane({ selectedRow }) {
|
|
25868
|
+
const prTap = useTapHandler(() => {
|
|
25869
|
+
if (selectedRow?.prUrl)
|
|
25870
|
+
openInBrowser(selectedRow.prUrl);
|
|
25871
|
+
});
|
|
25493
25872
|
if (!selectedRow) {
|
|
25494
25873
|
return /* @__PURE__ */ jsx2("box", {
|
|
25495
25874
|
id: "detail-pane",
|
|
@@ -25510,7 +25889,7 @@ function DetailPane({ selectedRow }) {
|
|
|
25510
25889
|
const {
|
|
25511
25890
|
repoName,
|
|
25512
25891
|
branch,
|
|
25513
|
-
path:
|
|
25892
|
+
path: path36,
|
|
25514
25893
|
commitShort,
|
|
25515
25894
|
isMainCheckout,
|
|
25516
25895
|
isLocked,
|
|
@@ -25524,7 +25903,9 @@ function DetailPane({ selectedRow }) {
|
|
|
25524
25903
|
prUrl,
|
|
25525
25904
|
owner,
|
|
25526
25905
|
rebaseStatus,
|
|
25527
|
-
depsStrategy
|
|
25906
|
+
depsStrategy,
|
|
25907
|
+
base: base2,
|
|
25908
|
+
baseChanged
|
|
25528
25909
|
} = selectedRow;
|
|
25529
25910
|
const aheadBehindStr = ahead !== null && behind !== null ? `${ahead} ahead, ${behind} behind` : "unknown";
|
|
25530
25911
|
return /* @__PURE__ */ jsxs2("scrollbox", {
|
|
@@ -25579,7 +25960,7 @@ function DetailPane({ selectedRow }) {
|
|
|
25579
25960
|
fg: tokens.fg,
|
|
25580
25961
|
children: [
|
|
25581
25962
|
" ",
|
|
25582
|
-
|
|
25963
|
+
path36
|
|
25583
25964
|
]
|
|
25584
25965
|
})
|
|
25585
25966
|
]
|
|
@@ -25629,12 +26010,39 @@ function DetailPane({ selectedRow }) {
|
|
|
25629
26010
|
})
|
|
25630
26011
|
]
|
|
25631
26012
|
}),
|
|
26013
|
+
base2 && /* @__PURE__ */ jsxs2("text", {
|
|
26014
|
+
children: [
|
|
26015
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26016
|
+
fg: tokens.dim,
|
|
26017
|
+
children: "Base:"
|
|
26018
|
+
}),
|
|
26019
|
+
/* @__PURE__ */ jsxs2("span", {
|
|
26020
|
+
fg: tokens.accent,
|
|
26021
|
+
children: [
|
|
26022
|
+
" ",
|
|
26023
|
+
base2
|
|
26024
|
+
]
|
|
26025
|
+
})
|
|
26026
|
+
]
|
|
26027
|
+
}),
|
|
26028
|
+
baseChanged && /* @__PURE__ */ jsxs2("text", {
|
|
26029
|
+
children: [
|
|
26030
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26031
|
+
fg: tokens.dim,
|
|
26032
|
+
children: "Base state:"
|
|
26033
|
+
}),
|
|
26034
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26035
|
+
fg: tokens.warning,
|
|
26036
|
+
children: " moved since recorded"
|
|
26037
|
+
})
|
|
26038
|
+
]
|
|
26039
|
+
}),
|
|
25632
26040
|
/* @__PURE__ */ jsxs2("text", {
|
|
25633
26041
|
style: { marginTop: 1 },
|
|
25634
26042
|
children: [
|
|
25635
26043
|
/* @__PURE__ */ jsx2("span", {
|
|
25636
26044
|
fg: tokens.dim,
|
|
25637
|
-
children: "vs main:"
|
|
26045
|
+
children: base2 ? "vs base:" : "vs main:"
|
|
25638
26046
|
}),
|
|
25639
26047
|
/* @__PURE__ */ jsxs2("span", {
|
|
25640
26048
|
fg: tokens.fg,
|
|
@@ -25675,7 +26083,7 @@ function DetailPane({ selectedRow }) {
|
|
|
25675
26083
|
})
|
|
25676
26084
|
]
|
|
25677
26085
|
}),
|
|
25678
|
-
prNumber !== null && /* @__PURE__ */ jsxs2(
|
|
26086
|
+
prNumber !== null && /* @__PURE__ */ jsxs2(Fragment2, {
|
|
25679
26087
|
children: [
|
|
25680
26088
|
/* @__PURE__ */ jsxs2("text", {
|
|
25681
26089
|
style: { marginTop: 1 },
|
|
@@ -25712,20 +26120,25 @@ function DetailPane({ selectedRow }) {
|
|
|
25712
26120
|
})
|
|
25713
26121
|
]
|
|
25714
26122
|
}),
|
|
25715
|
-
prUrl && /* @__PURE__ */
|
|
25716
|
-
|
|
25717
|
-
|
|
25718
|
-
|
|
25719
|
-
|
|
25720
|
-
|
|
25721
|
-
|
|
25722
|
-
|
|
25723
|
-
|
|
25724
|
-
|
|
25725
|
-
|
|
25726
|
-
|
|
25727
|
-
|
|
25728
|
-
|
|
26123
|
+
prUrl && /* @__PURE__ */ jsx2("box", {
|
|
26124
|
+
...prTap,
|
|
26125
|
+
children: /* @__PURE__ */ jsxs2("text", {
|
|
26126
|
+
selectable: false,
|
|
26127
|
+
children: [
|
|
26128
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26129
|
+
fg: tokens.dim,
|
|
26130
|
+
children: "URL:"
|
|
26131
|
+
}),
|
|
26132
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26133
|
+
fg: tokens.accent,
|
|
26134
|
+
children: ` ${prUrl}`
|
|
26135
|
+
}),
|
|
26136
|
+
/* @__PURE__ */ jsx2("span", {
|
|
26137
|
+
fg: tokens.dim,
|
|
26138
|
+
children: " ↗ click"
|
|
26139
|
+
})
|
|
26140
|
+
]
|
|
26141
|
+
})
|
|
25729
26142
|
})
|
|
25730
26143
|
]
|
|
25731
26144
|
}),
|
|
@@ -25766,7 +26179,7 @@ function DetailPane({ selectedRow }) {
|
|
|
25766
26179
|
fg: tokens.dim,
|
|
25767
26180
|
children: "Actions:"
|
|
25768
26181
|
}),
|
|
25769
|
-
!isMainCheckout && /* @__PURE__ */ jsxs2(
|
|
26182
|
+
!isMainCheckout && /* @__PURE__ */ jsxs2(Fragment2, {
|
|
25770
26183
|
children: [
|
|
25771
26184
|
/* @__PURE__ */ jsxs2("text", {
|
|
25772
26185
|
fg: tokens.dim,
|
|
@@ -25797,6 +26210,7 @@ function DetailPane({ selectedRow }) {
|
|
|
25797
26210
|
}
|
|
25798
26211
|
var init_DetailPane = __esm(() => {
|
|
25799
26212
|
init_theme();
|
|
26213
|
+
init_use_tap();
|
|
25800
26214
|
});
|
|
25801
26215
|
|
|
25802
26216
|
// src/tui/components/Footer.tsx
|
|
@@ -25843,11 +26257,15 @@ function Footer({ loading, lastRefreshed, errorCount, message, busyText, spinner
|
|
|
25843
26257
|
]
|
|
25844
26258
|
}) : null,
|
|
25845
26259
|
errorCount > 0 ? /* @__PURE__ */ jsxs3("text", {
|
|
25846
|
-
fg: tokens.error,
|
|
25847
26260
|
children: [
|
|
25848
|
-
|
|
25849
|
-
|
|
25850
|
-
|
|
26261
|
+
/* @__PURE__ */ jsx3("span", {
|
|
26262
|
+
fg: tokens.error,
|
|
26263
|
+
children: `${errorCount} error${errorCount !== 1 ? "s" : ""}`
|
|
26264
|
+
}),
|
|
26265
|
+
/* @__PURE__ */ jsx3("span", {
|
|
26266
|
+
fg: tokens.dim,
|
|
26267
|
+
children: " · e view"
|
|
26268
|
+
})
|
|
25851
26269
|
]
|
|
25852
26270
|
}) : null,
|
|
25853
26271
|
/* @__PURE__ */ jsx3("text", {
|
|
@@ -25960,15 +26378,18 @@ var init_HelpOverlay = __esm(() => {
|
|
|
25960
26378
|
["n", "Create new worktree (pick dependency strategy)"],
|
|
25961
26379
|
["f", "Fetch main for selected repo(s)"],
|
|
25962
26380
|
["p", "Pull latest changes for selected branch(es)"],
|
|
25963
|
-
["b", "Rebase selected onto main"],
|
|
26381
|
+
["b", "Rebase selected onto base (or main)"],
|
|
25964
26382
|
["s", "Sync selected (env files + hooks)"],
|
|
25965
|
-
["i", "Install dependencies in
|
|
26383
|
+
["i", "Install dependencies in selection (worktrees and main)"],
|
|
25966
26384
|
["m", "Rename selected worktree (branch + directory)"],
|
|
25967
26385
|
["o", "Open selected in IDE"],
|
|
25968
26386
|
["a", "Spawn agent in selected"],
|
|
25969
26387
|
["d", "Remove selected worktree(s)"],
|
|
26388
|
+
["e", "View data warnings (when count > 0)"],
|
|
25970
26389
|
["r", "Refresh data"],
|
|
25971
26390
|
["H", "Action history"],
|
|
26391
|
+
["cmd+c / ctrl+shift+c", "Copy selected text to clipboard"],
|
|
26392
|
+
["click PR #/URL", "Open pull request in browser"],
|
|
25972
26393
|
["?", "Toggle help"],
|
|
25973
26394
|
["q/esc", "Quit"]
|
|
25974
26395
|
];
|
|
@@ -26136,38 +26557,38 @@ var init_ActionLogModal = __esm(() => {
|
|
|
26136
26557
|
});
|
|
26137
26558
|
|
|
26138
26559
|
// src/lib/history.ts
|
|
26139
|
-
import
|
|
26140
|
-
import
|
|
26560
|
+
import fs31 from "fs";
|
|
26561
|
+
import path36 from "path";
|
|
26141
26562
|
import os4 from "os";
|
|
26142
26563
|
function getHistoryDir() {
|
|
26143
|
-
const stateHome = process.env.XDG_STATE_HOME ??
|
|
26144
|
-
return
|
|
26564
|
+
const stateHome = process.env.XDG_STATE_HOME ?? path36.join(os4.homedir(), ".local", "state");
|
|
26565
|
+
return path36.join(stateHome, "wtx");
|
|
26145
26566
|
}
|
|
26146
26567
|
function getHistoryPath() {
|
|
26147
|
-
return
|
|
26568
|
+
return path36.join(getHistoryDir(), "history.jsonl");
|
|
26148
26569
|
}
|
|
26149
26570
|
function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_KEEP_LINES) {
|
|
26150
26571
|
const historyPath = getHistoryPath();
|
|
26151
26572
|
let size = 0;
|
|
26152
26573
|
try {
|
|
26153
|
-
size =
|
|
26574
|
+
size = fs31.statSync(historyPath).size;
|
|
26154
26575
|
} catch {
|
|
26155
26576
|
return;
|
|
26156
26577
|
}
|
|
26157
26578
|
if (size <= maxBytes)
|
|
26158
26579
|
return;
|
|
26159
|
-
const lines =
|
|
26580
|
+
const lines = fs31.readFileSync(historyPath, "utf-8").split(`
|
|
26160
26581
|
`).filter(Boolean);
|
|
26161
26582
|
const kept = lines.slice(-keepLines);
|
|
26162
26583
|
const tmpPath = `${historyPath}.tmp.${process.pid}`;
|
|
26163
26584
|
try {
|
|
26164
|
-
|
|
26585
|
+
fs31.writeFileSync(tmpPath, kept.length > 0 ? `${kept.join(`
|
|
26165
26586
|
`)}
|
|
26166
26587
|
` : "", "utf-8");
|
|
26167
|
-
|
|
26588
|
+
fs31.renameSync(tmpPath, historyPath);
|
|
26168
26589
|
} catch (err) {
|
|
26169
|
-
if (
|
|
26170
|
-
|
|
26590
|
+
if (fs31.existsSync(tmpPath)) {
|
|
26591
|
+
fs31.unlinkSync(tmpPath);
|
|
26171
26592
|
}
|
|
26172
26593
|
throw err;
|
|
26173
26594
|
}
|
|
@@ -26175,18 +26596,18 @@ function rotateHistory(maxBytes = HISTORY_MAX_BYTES, keepLines = HISTORY_ROTATE_
|
|
|
26175
26596
|
function appendHistory(entry) {
|
|
26176
26597
|
try {
|
|
26177
26598
|
const dir = getHistoryDir();
|
|
26178
|
-
if (!
|
|
26179
|
-
|
|
26599
|
+
if (!fs31.existsSync(dir)) {
|
|
26600
|
+
fs31.mkdirSync(dir, { recursive: true });
|
|
26180
26601
|
}
|
|
26181
26602
|
rotateHistory();
|
|
26182
|
-
|
|
26603
|
+
fs31.appendFileSync(getHistoryPath(), `${JSON.stringify(entry)}
|
|
26183
26604
|
`, "utf-8");
|
|
26184
26605
|
} catch {}
|
|
26185
26606
|
}
|
|
26186
26607
|
function readRecentHistory(limit = 50) {
|
|
26187
26608
|
let content;
|
|
26188
26609
|
try {
|
|
26189
|
-
content =
|
|
26610
|
+
content = fs31.readFileSync(getHistoryPath(), "utf-8");
|
|
26190
26611
|
} catch {
|
|
26191
26612
|
return [];
|
|
26192
26613
|
}
|
|
@@ -26424,7 +26845,7 @@ var init_ChoiceModal = __esm(() => {
|
|
|
26424
26845
|
// src/tui/components/ConfigOverlay.tsx
|
|
26425
26846
|
import { useState as useState5, useEffect as useEffect4 } from "react";
|
|
26426
26847
|
import { useKeyboard as useKeyboard2 } from "@opentui/react";
|
|
26427
|
-
import { jsx as jsx11, jsxs as jsxs10, Fragment as
|
|
26848
|
+
import { jsx as jsx11, jsxs as jsxs10, Fragment as Fragment3 } from "@opentui/react/jsx-runtime";
|
|
26428
26849
|
function ConfigOverlay({ onClose, onSaved, onError }) {
|
|
26429
26850
|
const [config2, setConfig] = useState5(null);
|
|
26430
26851
|
const [viewState, setViewState] = useState5({ type: "main" });
|
|
@@ -26687,7 +27108,7 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
|
|
|
26687
27108
|
hint = "Remove this repository from config";
|
|
26688
27109
|
}
|
|
26689
27110
|
}
|
|
26690
|
-
return /* @__PURE__ */ jsxs10(
|
|
27111
|
+
return /* @__PURE__ */ jsxs10(Fragment3, {
|
|
26691
27112
|
children: [
|
|
26692
27113
|
/* @__PURE__ */ jsx11(Overlay, {
|
|
26693
27114
|
title: "Configuration",
|
|
@@ -26787,12 +27208,54 @@ function ConfigOverlay({ onClose, onSaved, onError }) {
|
|
|
26787
27208
|
}
|
|
26788
27209
|
var init_ConfigOverlay = __esm(() => {
|
|
26789
27210
|
init_Overlay();
|
|
27211
|
+
init_utils();
|
|
26790
27212
|
init_theme();
|
|
26791
27213
|
init_config();
|
|
26792
27214
|
init_InputModal();
|
|
26793
27215
|
init_ConfirmModal();
|
|
26794
27216
|
});
|
|
26795
27217
|
|
|
27218
|
+
// src/tui/components/WarningsOverlay.tsx
|
|
27219
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "@opentui/react/jsx-runtime";
|
|
27220
|
+
function WarningsOverlay({ warnings }) {
|
|
27221
|
+
return /* @__PURE__ */ jsxs11(Overlay, {
|
|
27222
|
+
title: `Warnings (${warnings.length})`,
|
|
27223
|
+
borderColor: tokens.warning,
|
|
27224
|
+
children: [
|
|
27225
|
+
/* @__PURE__ */ jsx12("box", {
|
|
27226
|
+
flexDirection: "column",
|
|
27227
|
+
style: { maxHeight: 30 },
|
|
27228
|
+
children: /* @__PURE__ */ jsx12("scrollbox", {
|
|
27229
|
+
flexGrow: 1,
|
|
27230
|
+
children: warnings.map((warning, i2) => /* @__PURE__ */ jsxs11("box", {
|
|
27231
|
+
flexDirection: "column",
|
|
27232
|
+
style: { marginBottom: 1 },
|
|
27233
|
+
children: [
|
|
27234
|
+
/* @__PURE__ */ jsx12("text", {
|
|
27235
|
+
fg: tokens.warning,
|
|
27236
|
+
children: `⚠ ${warning.repoName}`
|
|
27237
|
+
}),
|
|
27238
|
+
wrapText(warning.message, 56).map((line, j) => /* @__PURE__ */ jsx12("text", {
|
|
27239
|
+
fg: tokens.fg,
|
|
27240
|
+
children: ` ${line}`
|
|
27241
|
+
}, j))
|
|
27242
|
+
]
|
|
27243
|
+
}, i2))
|
|
27244
|
+
})
|
|
27245
|
+
}),
|
|
27246
|
+
/* @__PURE__ */ jsx12("text", {
|
|
27247
|
+
style: { marginTop: 1, fg: tokens.dim },
|
|
27248
|
+
children: "Press any key to close"
|
|
27249
|
+
})
|
|
27250
|
+
]
|
|
27251
|
+
});
|
|
27252
|
+
}
|
|
27253
|
+
var init_WarningsOverlay = __esm(() => {
|
|
27254
|
+
init_Overlay();
|
|
27255
|
+
init_theme();
|
|
27256
|
+
init_utils();
|
|
27257
|
+
});
|
|
27258
|
+
|
|
26796
27259
|
// src/tui/hooks/useSpinnerFrame.ts
|
|
26797
27260
|
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
26798
27261
|
function useSpinnerFrame(active) {
|
|
@@ -26811,9 +27274,9 @@ var init_useSpinnerFrame = __esm(() => {
|
|
|
26811
27274
|
});
|
|
26812
27275
|
|
|
26813
27276
|
// src/tui/components/App.tsx
|
|
26814
|
-
import { useState as useState7, useEffect as useEffect6, useMemo, useRef as
|
|
27277
|
+
import { useState as useState7, useEffect as useEffect6, useMemo, useRef as useRef4, useCallback as useCallback3 } from "react";
|
|
26815
27278
|
import { useKeyboard as useKeyboard3, useRenderer } from "@opentui/react";
|
|
26816
|
-
import { jsx as
|
|
27279
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "@opentui/react/jsx-runtime";
|
|
26817
27280
|
function getWorktreePathFor(repoName, branch) {
|
|
26818
27281
|
try {
|
|
26819
27282
|
const config2 = loadConfig();
|
|
@@ -26829,12 +27292,14 @@ function App({ opts }) {
|
|
|
26829
27292
|
const [selectedIndex, setSelectedIndex] = useState7(0);
|
|
26830
27293
|
const [modal, setModal] = useState7({ type: "none" });
|
|
26831
27294
|
const [actionMessage, setActionMessage] = useState7();
|
|
26832
|
-
const messageTimer =
|
|
27295
|
+
const messageTimer = useRef4(null);
|
|
26833
27296
|
const [ops, setOps] = useState7([]);
|
|
26834
27297
|
const [failedLogs, setFailedLogs] = useState7([]);
|
|
26835
|
-
const nextOpId =
|
|
27298
|
+
const nextOpId = useRef4(1);
|
|
26836
27299
|
const [createModal, setCreateModal] = useState7(false);
|
|
26837
27300
|
const [createError, setCreateError] = useState7();
|
|
27301
|
+
const [createBaseModal, setCreateBaseModal] = useState7(null);
|
|
27302
|
+
const [createBaseError, setCreateBaseError] = useState7();
|
|
26838
27303
|
const [createDepsChoice, setCreateDepsChoice] = useState7(null);
|
|
26839
27304
|
const [renameModal, setRenameModal] = useState7(false);
|
|
26840
27305
|
const [renameError, setRenameError] = useState7();
|
|
@@ -26843,7 +27308,7 @@ function App({ opts }) {
|
|
|
26843
27308
|
const [filterText, setFilterText] = useState7("");
|
|
26844
27309
|
const [isFiltering, setIsFiltering] = useState7(false);
|
|
26845
27310
|
const [selection, setSelection] = useState7(new Set);
|
|
26846
|
-
const doRefresh =
|
|
27311
|
+
const doRefresh = useCallback3(async (scope) => {
|
|
26847
27312
|
const targets = scope ?? [
|
|
26848
27313
|
...new Set([...blocks.map((b) => b.repoName), ...pendingRepos])
|
|
26849
27314
|
];
|
|
@@ -26859,7 +27324,7 @@ function App({ opts }) {
|
|
|
26859
27324
|
setModal({ type: "error", message: error52 });
|
|
26860
27325
|
}
|
|
26861
27326
|
}, [error52]);
|
|
26862
|
-
const flash =
|
|
27327
|
+
const flash = useCallback3((message, ms = 3000) => {
|
|
26863
27328
|
if (messageTimer.current)
|
|
26864
27329
|
clearTimeout(messageTimer.current);
|
|
26865
27330
|
setActionMessage(message);
|
|
@@ -26990,7 +27455,7 @@ function App({ opts }) {
|
|
|
26990
27455
|
setOps((prev) => [...prev, op]);
|
|
26991
27456
|
executeOp(op, ["fetch", "--repo", repoNames.join(",")], repoNames).then(() => setSelection(new Set));
|
|
26992
27457
|
};
|
|
26993
|
-
const startCreate = (branch, repoName, deps) => {
|
|
27458
|
+
const startCreate = (branch, repoName, deps, base2) => {
|
|
26994
27459
|
const op = {
|
|
26995
27460
|
id: nextOpId.current++,
|
|
26996
27461
|
kind: "create",
|
|
@@ -27003,6 +27468,8 @@ function App({ opts }) {
|
|
|
27003
27468
|
};
|
|
27004
27469
|
setOps((prev) => [...prev, op]);
|
|
27005
27470
|
const args = ["create", branch, "--repo", repoName];
|
|
27471
|
+
if (base2)
|
|
27472
|
+
args.push("--base", base2);
|
|
27006
27473
|
if (deps && deps !== "auto")
|
|
27007
27474
|
args.push("--deps", deps);
|
|
27008
27475
|
executeOp(op, args, [repoName]);
|
|
@@ -27031,6 +27498,13 @@ function App({ opts }) {
|
|
|
27031
27498
|
}
|
|
27032
27499
|
return;
|
|
27033
27500
|
}
|
|
27501
|
+
if (createBaseModal) {
|
|
27502
|
+
if (key.name === "escape") {
|
|
27503
|
+
setCreateBaseModal(null);
|
|
27504
|
+
setCreateBaseError(undefined);
|
|
27505
|
+
}
|
|
27506
|
+
return;
|
|
27507
|
+
}
|
|
27034
27508
|
if (createDepsChoice)
|
|
27035
27509
|
return;
|
|
27036
27510
|
if (renameModal) {
|
|
@@ -27041,7 +27515,7 @@ function App({ opts }) {
|
|
|
27041
27515
|
return;
|
|
27042
27516
|
}
|
|
27043
27517
|
if (modal.type !== "none") {
|
|
27044
|
-
if (modal.type === "error" || modal.type === "help" || modal.type === "history") {
|
|
27518
|
+
if (modal.type === "error" || modal.type === "help" || modal.type === "history" || modal.type === "warnings") {
|
|
27045
27519
|
setModal({ type: "none" });
|
|
27046
27520
|
if (modal.type === "error" && error52) {
|
|
27047
27521
|
renderer.destroy();
|
|
@@ -27089,6 +27563,15 @@ function App({ opts }) {
|
|
|
27089
27563
|
return;
|
|
27090
27564
|
}
|
|
27091
27565
|
}
|
|
27566
|
+
if ((key.super || key.meta || key.ctrl && key.shift) && key.name === "c") {
|
|
27567
|
+
const text = renderer.getSelection()?.getSelectedText() ?? "";
|
|
27568
|
+
if (!text) {
|
|
27569
|
+
flash("Nothing selected to copy");
|
|
27570
|
+
return;
|
|
27571
|
+
}
|
|
27572
|
+
copyTextToClipboard(renderer, text).then((ok) => flash(ok ? `Copied ${text.length} character${text.length !== 1 ? "s" : ""}` : "Copy failed"));
|
|
27573
|
+
return;
|
|
27574
|
+
}
|
|
27092
27575
|
if (key.name === "q" || key.name === "escape" || key.name === "c" && key.ctrl) {
|
|
27093
27576
|
if (key.name === "escape" && selection.size > 0) {
|
|
27094
27577
|
setSelection(new Set);
|
|
@@ -27131,6 +27614,10 @@ function App({ opts }) {
|
|
|
27131
27614
|
}
|
|
27132
27615
|
return;
|
|
27133
27616
|
}
|
|
27617
|
+
if (key.name === "e" && warnings.length > 0) {
|
|
27618
|
+
setModal({ type: "warnings" });
|
|
27619
|
+
return;
|
|
27620
|
+
}
|
|
27134
27621
|
if (key.name === "n") {
|
|
27135
27622
|
if (!selectedRow)
|
|
27136
27623
|
return;
|
|
@@ -27154,16 +27641,12 @@ function App({ opts }) {
|
|
|
27154
27641
|
const targets = getSelectedRows();
|
|
27155
27642
|
if (targets.length === 0)
|
|
27156
27643
|
return;
|
|
27157
|
-
if (targets.some((r) => r.isMainCheckout)) {
|
|
27158
|
-
flash("Cannot install deps on main checkout");
|
|
27159
|
-
return;
|
|
27160
|
-
}
|
|
27161
27644
|
const conflict = findConflict(targets);
|
|
27162
27645
|
if (conflict) {
|
|
27163
27646
|
flash(conflict);
|
|
27164
27647
|
return;
|
|
27165
27648
|
}
|
|
27166
|
-
startBatchActions("install", targets, (r) => ["deps", r.branch, "--repo", r.repoName, "--install"]);
|
|
27649
|
+
startBatchActions("install", targets, (r) => r.isMainCheckout ? ["deps", "--repo", r.repoName, "--install"] : ["deps", r.branch, "--repo", r.repoName, "--install"]);
|
|
27167
27650
|
return;
|
|
27168
27651
|
}
|
|
27169
27652
|
if (key.name === "m") {
|
|
@@ -27312,17 +27795,17 @@ function App({ opts }) {
|
|
|
27312
27795
|
})();
|
|
27313
27796
|
}
|
|
27314
27797
|
});
|
|
27315
|
-
return /* @__PURE__ */
|
|
27798
|
+
return /* @__PURE__ */ jsxs12("box", {
|
|
27316
27799
|
flexDirection: "column",
|
|
27317
27800
|
width: "100%",
|
|
27318
27801
|
height: "100%",
|
|
27319
27802
|
children: [
|
|
27320
|
-
/* @__PURE__ */
|
|
27803
|
+
/* @__PURE__ */ jsxs12("box", {
|
|
27321
27804
|
flexDirection: "row",
|
|
27322
27805
|
width: "100%",
|
|
27323
27806
|
flexGrow: 1,
|
|
27324
27807
|
children: [
|
|
27325
|
-
/* @__PURE__ */
|
|
27808
|
+
/* @__PURE__ */ jsx13(WorktreeTable, {
|
|
27326
27809
|
blocks: displayBlocks,
|
|
27327
27810
|
selectedIndex,
|
|
27328
27811
|
selection,
|
|
@@ -27330,28 +27813,28 @@ function App({ opts }) {
|
|
|
27330
27813
|
repoVerbs,
|
|
27331
27814
|
rowVerbs
|
|
27332
27815
|
}),
|
|
27333
|
-
/* @__PURE__ */
|
|
27816
|
+
/* @__PURE__ */ jsx13(DetailPane, {
|
|
27334
27817
|
selectedRow
|
|
27335
27818
|
})
|
|
27336
27819
|
]
|
|
27337
27820
|
}),
|
|
27338
|
-
isFiltering && /* @__PURE__ */
|
|
27821
|
+
isFiltering && /* @__PURE__ */ jsxs12("box", {
|
|
27339
27822
|
flexDirection: "row",
|
|
27340
27823
|
paddingX: 1,
|
|
27341
27824
|
border: true,
|
|
27342
27825
|
borderColor: "magenta",
|
|
27343
27826
|
children: [
|
|
27344
|
-
/* @__PURE__ */
|
|
27827
|
+
/* @__PURE__ */ jsx13("text", {
|
|
27345
27828
|
children: "filter: "
|
|
27346
27829
|
}),
|
|
27347
|
-
/* @__PURE__ */
|
|
27830
|
+
/* @__PURE__ */ jsx13("input", {
|
|
27348
27831
|
focused: true,
|
|
27349
27832
|
placeholder: "Type to filter...",
|
|
27350
27833
|
onInput: (v) => setFilterText(v)
|
|
27351
27834
|
})
|
|
27352
27835
|
]
|
|
27353
27836
|
}),
|
|
27354
|
-
/* @__PURE__ */
|
|
27837
|
+
/* @__PURE__ */ jsx13(Footer, {
|
|
27355
27838
|
loading: loading || refreshing,
|
|
27356
27839
|
lastRefreshed,
|
|
27357
27840
|
errorCount: warnings.length,
|
|
@@ -27360,13 +27843,16 @@ function App({ opts }) {
|
|
|
27360
27843
|
spinnerFrame,
|
|
27361
27844
|
filter: filterText ? { term: filterText, matches: flatRows.length, total: totalRows } : undefined
|
|
27362
27845
|
}),
|
|
27363
|
-
modal.type === "help" && /* @__PURE__ */
|
|
27364
|
-
modal.type === "history" && /* @__PURE__ */
|
|
27365
|
-
modal.type === "
|
|
27846
|
+
modal.type === "help" && /* @__PURE__ */ jsx13(HelpOverlay, {}),
|
|
27847
|
+
modal.type === "history" && /* @__PURE__ */ jsx13(HistoryOverlay, {}),
|
|
27848
|
+
modal.type === "warnings" && /* @__PURE__ */ jsx13(WarningsOverlay, {
|
|
27849
|
+
warnings
|
|
27850
|
+
}),
|
|
27851
|
+
modal.type === "error" && /* @__PURE__ */ jsx13(ConfirmModal, {
|
|
27366
27852
|
title: "Error",
|
|
27367
27853
|
message: modal.message
|
|
27368
27854
|
}),
|
|
27369
|
-
modal.type === "confirm_remove" && /* @__PURE__ */
|
|
27855
|
+
modal.type === "confirm_remove" && /* @__PURE__ */ jsx13(ConfirmModal, {
|
|
27370
27856
|
title: `Remove ${modal.rows.length} Worktree(s)`,
|
|
27371
27857
|
message: (() => {
|
|
27372
27858
|
const count2 = modal.rows.length;
|
|
@@ -27383,15 +27869,15 @@ function App({ opts }) {
|
|
|
27383
27869
|
`);
|
|
27384
27870
|
})()
|
|
27385
27871
|
}),
|
|
27386
|
-
modal.type === "confirm_rebase" && /* @__PURE__ */
|
|
27872
|
+
modal.type === "confirm_rebase" && /* @__PURE__ */ jsx13(ConfirmModal, {
|
|
27387
27873
|
title: `Rebase ${modal.rows.length} Worktree(s)`,
|
|
27388
27874
|
message: `Are you sure you want to fetch and rebase ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`
|
|
27389
27875
|
}),
|
|
27390
|
-
modal.type === "confirm_sync" && /* @__PURE__ */
|
|
27876
|
+
modal.type === "confirm_sync" && /* @__PURE__ */ jsx13(ConfirmModal, {
|
|
27391
27877
|
title: `Sync ${modal.rows.length} Worktree(s)`,
|
|
27392
27878
|
message: `Are you sure you want to sync ${modal.rows.length === 1 ? modal.rows[0]?.branch : modal.rows.length + " worktrees"}?`
|
|
27393
27879
|
}),
|
|
27394
|
-
createModal && /* @__PURE__ */
|
|
27880
|
+
createModal && /* @__PURE__ */ jsx13(InputModal, {
|
|
27395
27881
|
title: `New worktree branch in ${selectedRow?.repoName ?? ""}`,
|
|
27396
27882
|
placeholder: "Branch name (empty to cancel)",
|
|
27397
27883
|
errorMessage: createError,
|
|
@@ -27414,20 +27900,37 @@ function App({ opts }) {
|
|
|
27414
27900
|
}
|
|
27415
27901
|
setCreateModal(false);
|
|
27416
27902
|
setCreateError(undefined);
|
|
27417
|
-
|
|
27903
|
+
setCreateBaseError(undefined);
|
|
27904
|
+
setCreateBaseModal({ branch, repoName });
|
|
27905
|
+
}
|
|
27906
|
+
}),
|
|
27907
|
+
createBaseModal && /* @__PURE__ */ jsx13(InputModal, {
|
|
27908
|
+
title: `Base ref for ${createBaseModal.branch}`,
|
|
27909
|
+
placeholder: "origin/main (empty for default main)",
|
|
27910
|
+
errorMessage: createBaseError,
|
|
27911
|
+
onSubmit: (value) => {
|
|
27912
|
+
const base2 = value.trim();
|
|
27913
|
+
if (base2 && !validateSafeBranchName(base2)) {
|
|
27914
|
+
setCreateBaseError("Invalid base ref");
|
|
27915
|
+
return;
|
|
27916
|
+
}
|
|
27917
|
+
const { branch, repoName } = createBaseModal;
|
|
27918
|
+
setCreateBaseModal(null);
|
|
27919
|
+
setCreateBaseError(undefined);
|
|
27920
|
+
setCreateDepsChoice({ branch, repoName, base: base2 || undefined });
|
|
27418
27921
|
}
|
|
27419
27922
|
}),
|
|
27420
|
-
createDepsChoice && /* @__PURE__ */
|
|
27923
|
+
createDepsChoice && /* @__PURE__ */ jsx13(ChoiceModal, {
|
|
27421
27924
|
title: `Dependencies for ${createDepsChoice.branch}`,
|
|
27422
27925
|
options: DEPS_CHOICES,
|
|
27423
27926
|
onSubmit: (choice) => {
|
|
27424
|
-
const { branch, repoName } = createDepsChoice;
|
|
27927
|
+
const { branch, repoName, base: base2 } = createDepsChoice;
|
|
27425
27928
|
setCreateDepsChoice(null);
|
|
27426
|
-
startCreate(branch, repoName, choice);
|
|
27929
|
+
startCreate(branch, repoName, choice, base2);
|
|
27427
27930
|
},
|
|
27428
27931
|
onCancel: () => setCreateDepsChoice(null)
|
|
27429
27932
|
}),
|
|
27430
|
-
renameModal && selectedRow && /* @__PURE__ */
|
|
27933
|
+
renameModal && selectedRow && /* @__PURE__ */ jsx13(InputModal, {
|
|
27431
27934
|
title: `Rename branch ${selectedRow.branch}`,
|
|
27432
27935
|
placeholder: `New branch name (${selectedRow.branch})`,
|
|
27433
27936
|
errorMessage: renameError,
|
|
@@ -27451,7 +27954,7 @@ function App({ opts }) {
|
|
|
27451
27954
|
setModal({ type: "confirm_rename", row: target, to });
|
|
27452
27955
|
}
|
|
27453
27956
|
}),
|
|
27454
|
-
modal.type === "confirm_rename" && /* @__PURE__ */
|
|
27957
|
+
modal.type === "confirm_rename" && /* @__PURE__ */ jsx13(ConfirmModal, {
|
|
27455
27958
|
title: "Rename Worktree",
|
|
27456
27959
|
message: [
|
|
27457
27960
|
`${modal.row.branch} → ${modal.to}`,
|
|
@@ -27464,12 +27967,12 @@ function App({ opts }) {
|
|
|
27464
27967
|
].join(`
|
|
27465
27968
|
`)
|
|
27466
27969
|
}),
|
|
27467
|
-
configOpen && /* @__PURE__ */
|
|
27970
|
+
configOpen && /* @__PURE__ */ jsx13(ConfigOverlay, {
|
|
27468
27971
|
onClose: () => setConfigOpen(false),
|
|
27469
27972
|
onSaved: () => void doRefresh(),
|
|
27470
27973
|
onError: (msg) => setModal({ type: "error", message: msg })
|
|
27471
27974
|
}),
|
|
27472
|
-
failedLogs.length > 0 && /* @__PURE__ */
|
|
27975
|
+
failedLogs.length > 0 && /* @__PURE__ */ jsx13(ActionLogModal, {
|
|
27473
27976
|
title: failedLogs[0].title,
|
|
27474
27977
|
lines: failedLogs[0].lines,
|
|
27475
27978
|
done: true,
|
|
@@ -27493,6 +27996,8 @@ var init_App = __esm(() => {
|
|
|
27493
27996
|
init_InputModal();
|
|
27494
27997
|
init_ChoiceModal();
|
|
27495
27998
|
init_ConfigOverlay();
|
|
27999
|
+
init_WarningsOverlay();
|
|
28000
|
+
init_utils();
|
|
27496
28001
|
init_agents();
|
|
27497
28002
|
init_config();
|
|
27498
28003
|
init_history();
|
|
@@ -27522,14 +28027,14 @@ __export(exports_tui, {
|
|
|
27522
28027
|
});
|
|
27523
28028
|
import { createCliRenderer, TextTableRenderable } from "@opentui/core";
|
|
27524
28029
|
import { createRoot, extend as extend2 } from "@opentui/react";
|
|
27525
|
-
import { jsx as
|
|
28030
|
+
import { jsx as jsx14 } from "@opentui/react/jsx-runtime";
|
|
27526
28031
|
async function runTerminal(opts) {
|
|
27527
28032
|
const renderer = await createCliRenderer({
|
|
27528
28033
|
exitOnCtrlC: false
|
|
27529
28034
|
});
|
|
27530
28035
|
const root = createRoot(renderer);
|
|
27531
28036
|
try {
|
|
27532
|
-
root.render(/* @__PURE__ */
|
|
28037
|
+
root.render(/* @__PURE__ */ jsx14(App, {
|
|
27533
28038
|
opts
|
|
27534
28039
|
}));
|
|
27535
28040
|
} catch (err) {
|
|
@@ -29661,8 +30166,8 @@ Run 'wtx config show' to see available repos.`);
|
|
|
29661
30166
|
init_config();
|
|
29662
30167
|
init_log();
|
|
29663
30168
|
init_git();
|
|
29664
|
-
import
|
|
29665
|
-
import
|
|
30169
|
+
import fs21 from "fs";
|
|
30170
|
+
import path27 from "path";
|
|
29666
30171
|
|
|
29667
30172
|
// src/lib/remotes.ts
|
|
29668
30173
|
init_execa();
|
|
@@ -29727,6 +30232,7 @@ function resolveBranchTarget(input) {
|
|
|
29727
30232
|
}
|
|
29728
30233
|
|
|
29729
30234
|
// src/commands/create.ts
|
|
30235
|
+
init_stack();
|
|
29730
30236
|
init_resolver();
|
|
29731
30237
|
|
|
29732
30238
|
// src/lib/ide.ts
|
|
@@ -29742,14 +30248,14 @@ function spawnIde(ide, wtPath) {
|
|
|
29742
30248
|
// src/lib/worktree-setup.ts
|
|
29743
30249
|
init_execa();
|
|
29744
30250
|
init_log();
|
|
29745
|
-
import
|
|
29746
|
-
import
|
|
30251
|
+
import fs10 from "fs";
|
|
30252
|
+
import path17 from "path";
|
|
29747
30253
|
|
|
29748
30254
|
// src/lib/ports.ts
|
|
29749
30255
|
init_git();
|
|
29750
30256
|
init_path_safety();
|
|
29751
30257
|
init_config();
|
|
29752
|
-
import
|
|
30258
|
+
import fs9 from "fs";
|
|
29753
30259
|
function hashPort(key, min, max) {
|
|
29754
30260
|
let h2 = 2166136261;
|
|
29755
30261
|
for (let i2 = 0;i2 < key.length; i2++) {
|
|
@@ -29784,7 +30290,7 @@ async function getWorktreePort(repoName, branch, config2, currentWtPath) {
|
|
|
29784
30290
|
const taken = new Set;
|
|
29785
30291
|
for (const name of allRepos) {
|
|
29786
30292
|
const mainPath = `${root}/${name}`;
|
|
29787
|
-
if (!
|
|
30293
|
+
if (!fs9.existsSync(mainPath)) {
|
|
29788
30294
|
continue;
|
|
29789
30295
|
}
|
|
29790
30296
|
let wts = [];
|
|
@@ -29815,12 +30321,12 @@ async function runPostCreateSetup(params) {
|
|
|
29815
30321
|
let hooks = [];
|
|
29816
30322
|
if (repo.config.sync_files && repo.config.sync_files.length > 0) {
|
|
29817
30323
|
for (const file2 of repo.config.sync_files) {
|
|
29818
|
-
const src =
|
|
29819
|
-
const dest =
|
|
29820
|
-
if (
|
|
30324
|
+
const src = path17.join(repo.mainPath, file2);
|
|
30325
|
+
const dest = path17.join(wtPath, file2);
|
|
30326
|
+
if (fs10.existsSync(src)) {
|
|
29821
30327
|
if (!globalOpts.dryRun) {
|
|
29822
|
-
|
|
29823
|
-
|
|
30328
|
+
fs10.mkdirSync(path17.dirname(dest), { recursive: true });
|
|
30329
|
+
fs10.copyFileSync(src, dest);
|
|
29824
30330
|
copiedFiles.push(file2);
|
|
29825
30331
|
}
|
|
29826
30332
|
stepSuccess(`Synced ${file2}`);
|
|
@@ -29948,9 +30454,6 @@ function registerCreateCommand(program2) {
|
|
|
29948
30454
|
openWorktree(wtPath);
|
|
29949
30455
|
continue;
|
|
29950
30456
|
}
|
|
29951
|
-
if (!globalOpts.dryRun) {
|
|
29952
|
-
fs20.mkdirSync(path26.dirname(wtPath), { recursive: true });
|
|
29953
|
-
}
|
|
29954
30457
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
29955
30458
|
const resolvedRemote = await resolveBaseRemote(repo.mainPath, mainBranch);
|
|
29956
30459
|
if (repo.config.fetch_main_on_create) {
|
|
@@ -29961,6 +30464,19 @@ function registerCreateCommand(program2) {
|
|
|
29961
30464
|
}
|
|
29962
30465
|
stepProgress("Checking branch status...");
|
|
29963
30466
|
const baseRef = options.base || `${resolvedRemote}/${mainBranch}`;
|
|
30467
|
+
if (baseRef === branch || baseRef === `refs/heads/${branch}`) {
|
|
30468
|
+
throw new Error(`Base ref '${baseRef}' cannot be the new branch '${branch}'`);
|
|
30469
|
+
}
|
|
30470
|
+
let baseSha = null;
|
|
30471
|
+
if (!globalOpts.dryRun) {
|
|
30472
|
+
baseSha = await resolveCommitSha(repo.mainPath, baseRef, globalOpts);
|
|
30473
|
+
stepSuccess("Base resolved", `${baseRef} at ${baseSha.substring(0, 7)}`);
|
|
30474
|
+
} else {
|
|
30475
|
+
stepProgress("Using base", baseRef);
|
|
30476
|
+
}
|
|
30477
|
+
if (!globalOpts.dryRun) {
|
|
30478
|
+
fs21.mkdirSync(path27.dirname(wtPath), { recursive: true });
|
|
30479
|
+
}
|
|
29964
30480
|
const localSha = await getLocalBranchSha(repo.mainPath, branch, globalOpts);
|
|
29965
30481
|
const remoteSha = await getRemoteBranchSha(repo.mainPath, resolvedRemote, branch, globalOpts);
|
|
29966
30482
|
const localExists = localSha !== null;
|
|
@@ -30021,6 +30537,21 @@ function registerCreateCommand(program2) {
|
|
|
30021
30537
|
throw err;
|
|
30022
30538
|
}
|
|
30023
30539
|
stepSuccess("Worktree created", wtPath);
|
|
30540
|
+
if (!globalOpts.dryRun && baseSha && resolvedAction.kind === "create-new") {
|
|
30541
|
+
try {
|
|
30542
|
+
const metadataBaseRef = options.base ? baseRef : mainBranch;
|
|
30543
|
+
await recordStackEntry(repo.mainPath, branch, {
|
|
30544
|
+
baseRef: metadataBaseRef,
|
|
30545
|
+
baseSha,
|
|
30546
|
+
explicit: options.base !== undefined,
|
|
30547
|
+
createdAt: new Date().toISOString()
|
|
30548
|
+
}, globalOpts);
|
|
30549
|
+
stepSuccess("Base recorded", metadataBaseRef);
|
|
30550
|
+
} catch (err) {
|
|
30551
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30552
|
+
stepWarning("Base metadata not recorded", message);
|
|
30553
|
+
}
|
|
30554
|
+
}
|
|
30024
30555
|
const setupResult = await runPostCreateSetup({ config: config2, repo, wtPath, branch, globalOpts });
|
|
30025
30556
|
if (options.deps && options.deps !== "auto" && options.deps !== "link" && options.deps !== "off") {
|
|
30026
30557
|
const ok = await applyDepsStrategy(options.deps, wtPath, repo.mainPath, globalOpts);
|
|
@@ -30088,9 +30619,10 @@ init_config();
|
|
|
30088
30619
|
init_log();
|
|
30089
30620
|
init_git();
|
|
30090
30621
|
init_resolver();
|
|
30091
|
-
import
|
|
30092
|
-
import
|
|
30622
|
+
import fs22 from "fs";
|
|
30623
|
+
import path28 from "path";
|
|
30093
30624
|
init_forge();
|
|
30625
|
+
init_stack();
|
|
30094
30626
|
function registerPullCommand(program2) {
|
|
30095
30627
|
program2.command("pull <link>").description("Fetch a forge PR and create its worktree").option("-r, --repo <repos...>", "Target specific repo").action(async (link, options) => {
|
|
30096
30628
|
const globalOpts = program2.opts();
|
|
@@ -30112,7 +30644,7 @@ function registerPullCommand(program2) {
|
|
|
30112
30644
|
for (const name of Object.keys(config2.repos).sort()) {
|
|
30113
30645
|
if (config2.repos[name].check_prs === false)
|
|
30114
30646
|
continue;
|
|
30115
|
-
const mainPath =
|
|
30647
|
+
const mainPath = path28.join(expandTilde(config2.root), name);
|
|
30116
30648
|
const detected = detectRepoForge(mainPath);
|
|
30117
30649
|
if (!detected)
|
|
30118
30650
|
continue;
|
|
@@ -30210,10 +30742,22 @@ function registerPullCommand(program2) {
|
|
|
30210
30742
|
summaryWarning(`Nothing pulled — branch '${branch}' already exists`);
|
|
30211
30743
|
return;
|
|
30212
30744
|
}
|
|
30213
|
-
const
|
|
30745
|
+
const mainBranch = await resolveMainBranch(target, config2);
|
|
30746
|
+
const baseRemote = await resolveBaseRemote(target.mainPath, mainBranch);
|
|
30747
|
+
const baseBranch = head.baseRefName || mainBranch;
|
|
30748
|
+
const baseRef = `${baseRemote}/${baseBranch}`;
|
|
30214
30749
|
const fetch = adapter.buildHeadFetch(head);
|
|
30215
30750
|
stepProgress(fetch.url ? `Fetching ${fetch.refspec} from fork...` : `Fetching pull/${head.number}/head from ${baseRemote}...`);
|
|
30216
30751
|
try {
|
|
30752
|
+
if (baseBranch !== mainBranch) {
|
|
30753
|
+
try {
|
|
30754
|
+
await gitExec(["-C", target.mainPath, "fetch", baseRemote, "--", baseBranch], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
|
|
30755
|
+
} catch (err) {
|
|
30756
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30757
|
+
stepWarning("PR base was not fetched", message.split(`
|
|
30758
|
+
`)[0] ?? message);
|
|
30759
|
+
}
|
|
30760
|
+
}
|
|
30217
30761
|
await gitExec([
|
|
30218
30762
|
"-C",
|
|
30219
30763
|
target.mainPath,
|
|
@@ -30227,7 +30771,7 @@ function registerPullCommand(program2) {
|
|
|
30227
30771
|
}
|
|
30228
30772
|
stepSuccess("Fetched");
|
|
30229
30773
|
if (!globalOpts.dryRun) {
|
|
30230
|
-
|
|
30774
|
+
fs22.mkdirSync(path28.dirname(wtPath), { recursive: true });
|
|
30231
30775
|
}
|
|
30232
30776
|
try {
|
|
30233
30777
|
await gitExec(["-C", target.mainPath, "worktree", "add", "-b", branch, wtPath, "FETCH_HEAD"], { verbose: globalOpts.verbose, dryRun: globalOpts.dryRun });
|
|
@@ -30241,6 +30785,22 @@ function registerPullCommand(program2) {
|
|
|
30241
30785
|
throw err;
|
|
30242
30786
|
}
|
|
30243
30787
|
stepSuccess("Worktree created", wtPath);
|
|
30788
|
+
if (!globalOpts.dryRun) {
|
|
30789
|
+
try {
|
|
30790
|
+
const baseSha = await resolveCommitSha(target.mainPath, baseRef, globalOpts);
|
|
30791
|
+
const metadataBaseRef = baseBranch === mainBranch ? mainBranch : baseRef;
|
|
30792
|
+
await recordStackEntry(target.mainPath, branch, {
|
|
30793
|
+
baseRef: metadataBaseRef,
|
|
30794
|
+
baseSha,
|
|
30795
|
+
explicit: baseBranch !== mainBranch,
|
|
30796
|
+
createdAt: new Date().toISOString()
|
|
30797
|
+
}, globalOpts);
|
|
30798
|
+
stepSuccess("Base recorded", metadataBaseRef);
|
|
30799
|
+
} catch (err) {
|
|
30800
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30801
|
+
stepWarning("Base metadata not recorded", message);
|
|
30802
|
+
}
|
|
30803
|
+
}
|
|
30244
30804
|
const setupResult = await runPostCreateSetup({ config: config2, repo: target, wtPath, branch, globalOpts });
|
|
30245
30805
|
const failedHooks = setupResult.hooks.filter((h2) => !h2.ok);
|
|
30246
30806
|
if (failedHooks.length > 0) {
|
|
@@ -30346,7 +30906,7 @@ init_log();
|
|
|
30346
30906
|
init_git();
|
|
30347
30907
|
init_resolver();
|
|
30348
30908
|
init_path_safety();
|
|
30349
|
-
import
|
|
30909
|
+
import path29 from "path";
|
|
30350
30910
|
|
|
30351
30911
|
// src/lib/prompts.ts
|
|
30352
30912
|
import * as readline2 from "readline";
|
|
@@ -30389,6 +30949,7 @@ async function confirm(message, io) {
|
|
|
30389
30949
|
}
|
|
30390
30950
|
|
|
30391
30951
|
// src/commands/remove.ts
|
|
30952
|
+
init_stack();
|
|
30392
30953
|
function registerRemoveCommand(program2) {
|
|
30393
30954
|
program2.command("remove <branch>").description("Remove worktree(s)").option("-r, --repo <repos...>", "Target specific repo(s)").option("-f, --force", "Force removal even if there are uncommitted changes").option("-y, --yes", "Skip confirmation prompt").action(async (branch, options) => {
|
|
30394
30955
|
const globalOpts = program2.opts();
|
|
@@ -30413,6 +30974,16 @@ function registerRemoveCommand(program2) {
|
|
|
30413
30974
|
continue;
|
|
30414
30975
|
}
|
|
30415
30976
|
const wtPath = target.path;
|
|
30977
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, globalOpts);
|
|
30978
|
+
const children = getStackChildren(stackMetadata, branch);
|
|
30979
|
+
if (children.length > 0 && !options.force) {
|
|
30980
|
+
stepError("Worktree has dependent branches", `${children.join(", ")} — retarget or remove them first (use --force to override)`);
|
|
30981
|
+
skipCount++;
|
|
30982
|
+
continue;
|
|
30983
|
+
}
|
|
30984
|
+
if (children.length > 0) {
|
|
30985
|
+
stepWarning("Removing a parent with dependent branches", children.join(", "));
|
|
30986
|
+
}
|
|
30416
30987
|
if (!options.force && !globalOpts.dryRun) {
|
|
30417
30988
|
try {
|
|
30418
30989
|
const dirtyFiles = await getDirtyFiles(wtPath);
|
|
@@ -30443,7 +31014,7 @@ function registerRemoveCommand(program2) {
|
|
|
30443
31014
|
const toClean = planEmptyParentRemoval(repo.wtRoot, repo.mainPath, wtPath);
|
|
30444
31015
|
indented(`Will remove worktree: ${wtPath}`);
|
|
30445
31016
|
for (const dir of toClean) {
|
|
30446
|
-
indented(`Will clean up empty dir: ${
|
|
31017
|
+
indented(`Will clean up empty dir: ${path29.relative(repo.wtRoot, dir)}/`);
|
|
30447
31018
|
}
|
|
30448
31019
|
const proceed = await confirm("Are you sure you want to delete these?");
|
|
30449
31020
|
if (!proceed) {
|
|
@@ -30473,9 +31044,18 @@ function registerRemoveCommand(program2) {
|
|
|
30473
31044
|
if (!globalOpts.dryRun) {
|
|
30474
31045
|
const removedDirs = cleanupEmptyParents(repo.wtRoot, repo.mainPath, wtPath);
|
|
30475
31046
|
for (const dir of removedDirs) {
|
|
30476
|
-
stepSuccess("Cleaned up empty directory",
|
|
31047
|
+
stepSuccess("Cleaned up empty directory", path29.relative(repo.wtRoot, dir) + "/");
|
|
30477
31048
|
}
|
|
30478
31049
|
}
|
|
31050
|
+
try {
|
|
31051
|
+
await removeStackEntry(repo.mainPath, branch, globalOpts);
|
|
31052
|
+
} catch (err) {
|
|
31053
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31054
|
+
stepWarning("Stack metadata not removed", message);
|
|
31055
|
+
}
|
|
31056
|
+
if (children.length > 0) {
|
|
31057
|
+
stepWarning("Dependent base metadata retained", children.join(", "));
|
|
31058
|
+
}
|
|
30479
31059
|
successCount++;
|
|
30480
31060
|
} catch (err) {
|
|
30481
31061
|
stepError("Failed to remove worktree", err.message);
|
|
@@ -30499,7 +31079,7 @@ init_log();
|
|
|
30499
31079
|
init_git();
|
|
30500
31080
|
init_resolver();
|
|
30501
31081
|
init_forge();
|
|
30502
|
-
import
|
|
31082
|
+
import path30 from "path";
|
|
30503
31083
|
|
|
30504
31084
|
// src/lib/prune.ts
|
|
30505
31085
|
function selectMergedCandidates(worktrees, mainPath, prMap) {
|
|
@@ -30517,6 +31097,7 @@ function selectMergedCandidates(worktrees, mainPath, prMap) {
|
|
|
30517
31097
|
|
|
30518
31098
|
// src/commands/prune.ts
|
|
30519
31099
|
init_path_safety();
|
|
31100
|
+
init_stack();
|
|
30520
31101
|
function registerPruneCommand(program2) {
|
|
30521
31102
|
program2.command("prune").description("Remove worktrees whose branch has a merged PR").option("-r, --repo <repos...>", "Target specific repo(s)").option("-f, --force", "Remove even if there are uncommitted changes").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
30522
31103
|
const globalOpts = program2.opts();
|
|
@@ -30540,6 +31121,7 @@ function registerPruneCommand(program2) {
|
|
|
30540
31121
|
}
|
|
30541
31122
|
try {
|
|
30542
31123
|
const worktrees = await getWorktreeList(repo.mainPath);
|
|
31124
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, globalOpts);
|
|
30543
31125
|
const branches = worktrees.filter((wt) => wt.path !== repo.mainPath && wt.branch).map((wt) => wt.branch);
|
|
30544
31126
|
if (branches.length === 0) {
|
|
30545
31127
|
continue;
|
|
@@ -30564,6 +31146,15 @@ function registerPruneCommand(program2) {
|
|
|
30564
31146
|
continue;
|
|
30565
31147
|
}
|
|
30566
31148
|
const wtInfo = worktrees.find((wt) => wt.path === candidate.path);
|
|
31149
|
+
const children = getStackChildren(stackMetadata, candidate.branch);
|
|
31150
|
+
if (children.length > 0 && !options.force) {
|
|
31151
|
+
stepWarning("Skipped — branch has dependent worktrees", `${label}: ${children.join(", ")}`);
|
|
31152
|
+
skippedCount++;
|
|
31153
|
+
continue;
|
|
31154
|
+
}
|
|
31155
|
+
if (children.length > 0) {
|
|
31156
|
+
stepWarning("Pruning parent with dependent worktrees", `${label}: ${children.join(", ")}`);
|
|
31157
|
+
}
|
|
30567
31158
|
if (wtInfo?.isLocked && !options.force) {
|
|
30568
31159
|
stepWarning("Skipped — worktree is locked \uD83D\uDD12", label);
|
|
30569
31160
|
skippedCount++;
|
|
@@ -30644,9 +31235,15 @@ function registerPruneCommand(program2) {
|
|
|
30644
31235
|
if (!globalOpts.dryRun) {
|
|
30645
31236
|
const removedDirs = cleanupEmptyParents(repo.wtRoot, repo.mainPath, candidate.path);
|
|
30646
31237
|
for (const dir of removedDirs) {
|
|
30647
|
-
stepSuccess("Cleaned up empty directory",
|
|
31238
|
+
stepSuccess("Cleaned up empty directory", path30.relative(repo.wtRoot, dir) + "/");
|
|
30648
31239
|
}
|
|
30649
31240
|
}
|
|
31241
|
+
try {
|
|
31242
|
+
await removeStackEntry(repo.mainPath, candidate.branch, globalOpts);
|
|
31243
|
+
} catch (err) {
|
|
31244
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31245
|
+
stepWarning("Stack metadata not removed", message);
|
|
31246
|
+
}
|
|
30650
31247
|
removedCount++;
|
|
30651
31248
|
}
|
|
30652
31249
|
if (removedCount === 0 && skippedCount === 0) {
|
|
@@ -30713,13 +31310,15 @@ function formatRelativeTime(isoTimestamp) {
|
|
|
30713
31310
|
// src/commands/ls.ts
|
|
30714
31311
|
init_owner();
|
|
30715
31312
|
init_source();
|
|
30716
|
-
|
|
30717
|
-
|
|
31313
|
+
init_stack();
|
|
31314
|
+
init_stack();
|
|
31315
|
+
import path31 from "path";
|
|
31316
|
+
import fs23 from "fs";
|
|
30718
31317
|
function buildLsJson(reposData) {
|
|
30719
31318
|
const result = [];
|
|
30720
31319
|
for (const repo of reposData) {
|
|
30721
31320
|
for (const wt of repo.worktrees) {
|
|
30722
|
-
const branch = wt.branch ||
|
|
31321
|
+
const branch = wt.branch || path31.basename(wt.path);
|
|
30723
31322
|
const sha = (wt.commit || "0000000").substring(0, 7);
|
|
30724
31323
|
let status = "clean";
|
|
30725
31324
|
if (wt.path === repo.mainPath) {
|
|
@@ -30746,6 +31345,8 @@ function buildLsJson(reposData) {
|
|
|
30746
31345
|
}
|
|
30747
31346
|
}
|
|
30748
31347
|
const entry = { repo: repo.name, branch, sha, status };
|
|
31348
|
+
if (wt.base)
|
|
31349
|
+
entry.base = wt.base;
|
|
30749
31350
|
if (pr)
|
|
30750
31351
|
entry.pr = pr;
|
|
30751
31352
|
if (ownerStr)
|
|
@@ -30784,7 +31385,7 @@ function registerLsCommand(program2) {
|
|
|
30784
31385
|
};
|
|
30785
31386
|
try {
|
|
30786
31387
|
const worktrees = await getWorktreeList(repo.mainPath);
|
|
30787
|
-
const
|
|
31388
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, globalOpts);
|
|
30788
31389
|
let prMap = null;
|
|
30789
31390
|
if (options.pr) {
|
|
30790
31391
|
const branches = worktrees.filter((wt) => wt.path !== repo.mainPath && wt.branch).map((wt) => wt.branch);
|
|
@@ -30799,9 +31400,20 @@ function registerLsCommand(program2) {
|
|
|
30799
31400
|
}
|
|
30800
31401
|
}
|
|
30801
31402
|
}
|
|
30802
|
-
|
|
30803
|
-
const branch = wt.branch ||
|
|
30804
|
-
|
|
31403
|
+
const displayItems = buildStackHierarchy(worktrees, (wt) => wt.branch || path31.basename(wt.path), (wt) => {
|
|
31404
|
+
const branch = wt.branch || path31.basename(wt.path);
|
|
31405
|
+
return stackMetadata.branches[branch]?.baseRef ?? prMap?.get(branch)?.baseRefName;
|
|
31406
|
+
}, (a2, b) => {
|
|
31407
|
+
if (a2.path === repo.mainPath && b.path !== repo.mainPath)
|
|
31408
|
+
return -1;
|
|
31409
|
+
if (a2.path !== repo.mainPath && b.path === repo.mainPath)
|
|
31410
|
+
return 1;
|
|
31411
|
+
return (a2.branch || path31.basename(a2.path)).localeCompare(b.branch || path31.basename(b.path));
|
|
31412
|
+
});
|
|
31413
|
+
const maxBranchLen = Math.max(0, ...displayItems.map(({ item, prefix }) => `${prefix}${item.branch || path31.basename(item.path)}`.length));
|
|
31414
|
+
for (const { item: wt, prefix } of displayItems) {
|
|
31415
|
+
const branch = wt.branch || path31.basename(wt.path);
|
|
31416
|
+
const paddedBranch = `${prefix}${branch}`.padEnd(maxBranchLen + 2);
|
|
30805
31417
|
const hash2 = (wt.commit || "0000000").substring(0, 7);
|
|
30806
31418
|
let statusStr = source_default.dim("clean");
|
|
30807
31419
|
let isMissing = false;
|
|
@@ -30811,7 +31423,7 @@ function registerLsCommand(program2) {
|
|
|
30811
31423
|
statusStr = source_default.blue("[main checkout]");
|
|
30812
31424
|
} else if (wt.isLocked) {
|
|
30813
31425
|
statusStr = source_default.red("locked \uD83D\uDD12");
|
|
30814
|
-
} else if (
|
|
31426
|
+
} else if (fs23.existsSync(wt.path)) {
|
|
30815
31427
|
try {
|
|
30816
31428
|
dirtyFiles = await getDirtyFiles(wt.path);
|
|
30817
31429
|
if (dirtyFiles.length > 0) {
|
|
@@ -30829,9 +31441,11 @@ function registerLsCommand(program2) {
|
|
|
30829
31441
|
const prInfo = prMap?.get(branch);
|
|
30830
31442
|
if (prInfo) {
|
|
30831
31443
|
const display = derivePrDisplay(prInfo);
|
|
30832
|
-
prSegment = ` #${prInfo.number} ${renderDisplayState(display)} ${source_default.dim(prInfo.url)}`;
|
|
31444
|
+
prSegment = ` #${prInfo.number} ${renderDisplayState(display)} ${source_default.dim(terminalLink(prInfo.url))}`;
|
|
30833
31445
|
}
|
|
30834
31446
|
let ownerSuffix = "";
|
|
31447
|
+
const baseRef = wt.branch ? stackMetadata.branches[wt.branch]?.baseRef ?? prInfo?.baseRefName : prInfo?.baseRefName;
|
|
31448
|
+
const baseSuffix = baseRef ? ` base ${baseRef}` : "";
|
|
30835
31449
|
let ownership = null;
|
|
30836
31450
|
if (wt.path !== repo.mainPath) {
|
|
30837
31451
|
ownership = await resolveOwnership({
|
|
@@ -30851,13 +31465,14 @@ function registerLsCommand(program2) {
|
|
|
30851
31465
|
path: wt.path,
|
|
30852
31466
|
branch: wt.branch,
|
|
30853
31467
|
commit: wt.commit,
|
|
31468
|
+
base: baseRef,
|
|
30854
31469
|
isLocked: wt.isLocked,
|
|
30855
31470
|
dirtyFiles,
|
|
30856
31471
|
isMissing,
|
|
30857
31472
|
isError
|
|
30858
31473
|
});
|
|
30859
31474
|
if (!options.json) {
|
|
30860
|
-
info(` ${paddedBranch} ${hash2} ${statusStr}${prSegment}${ownerSuffix}`);
|
|
31475
|
+
info(` ${paddedBranch} ${hash2} ${statusStr}${baseSuffix}${prSegment}${ownerSuffix}`);
|
|
30861
31476
|
}
|
|
30862
31477
|
}
|
|
30863
31478
|
} catch (err) {
|
|
@@ -30934,10 +31549,11 @@ end
|
|
|
30934
31549
|
init_config();
|
|
30935
31550
|
init_log();
|
|
30936
31551
|
init_git();
|
|
30937
|
-
import
|
|
31552
|
+
import fs24 from "fs";
|
|
30938
31553
|
init_resolver();
|
|
31554
|
+
init_stack();
|
|
30939
31555
|
function registerRebaseCommand(program2) {
|
|
30940
|
-
program2.command("rebase <branch>").description("fetch + rebase
|
|
31556
|
+
program2.command("rebase <branch>").description("fetch + rebase worktree onto its base").option("--repo <repos...>", "comma-separated list of repos to target").option("--onto <ref>", "Override the recorded base ref for this rebase").action(async (branch, options, cmd) => {
|
|
30941
31557
|
const opts = cmd.optsWithGlobals();
|
|
30942
31558
|
const config2 = loadConfig();
|
|
30943
31559
|
const targetRepos = parseRepoFlag(opts.repo);
|
|
@@ -30948,30 +31564,58 @@ function registerRebaseCommand(program2) {
|
|
|
30948
31564
|
repoHeader(repo.name);
|
|
30949
31565
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
30950
31566
|
const wtPath = getWorktreePath(repo, branch);
|
|
30951
|
-
if (!
|
|
31567
|
+
if (!fs24.existsSync(wtPath)) {
|
|
30952
31568
|
stepError("No worktree found", `${wtPath} (skipped)`);
|
|
30953
31569
|
failCount++;
|
|
30954
31570
|
continue;
|
|
30955
31571
|
}
|
|
30956
31572
|
let rebaseStarted = false;
|
|
30957
31573
|
try {
|
|
30958
|
-
const
|
|
30959
|
-
|
|
30960
|
-
const
|
|
30961
|
-
|
|
30962
|
-
|
|
31574
|
+
const metadata = await readStackMetadata(repo.mainPath, opts);
|
|
31575
|
+
const recorded = metadata.branches[branch];
|
|
31576
|
+
const resolvedRemote = !options.onto && !recorded?.explicit ? await resolveBaseRemote(repo.mainPath, mainBranch) : undefined;
|
|
31577
|
+
const defaultBase = resolvedRemote ? `${resolvedRemote}/${mainBranch}` : mainBranch;
|
|
31578
|
+
const baseRef = options.onto || (recorded?.explicit ? recorded.baseRef : defaultBase);
|
|
31579
|
+
const shouldFetchMain = !options.onto && (!recorded || !recorded.explicit);
|
|
31580
|
+
if (shouldFetchMain) {
|
|
31581
|
+
if (!resolvedRemote) {
|
|
31582
|
+
throw new Error(`Could not determine the remote for base branch '${mainBranch}'`);
|
|
31583
|
+
}
|
|
31584
|
+
await gitExec(["-C", repo.mainPath, "fetch", resolvedRemote, "--", mainBranch], opts);
|
|
31585
|
+
const commit = await getLatestCommit(repo.mainPath, defaultBase);
|
|
31586
|
+
stepProgress(`Fetching ${resolvedRemote}/${mainBranch}...`, `${commit.hash} "${commit.subject}"`);
|
|
31587
|
+
} else {
|
|
31588
|
+
stepProgress("Using recorded base", baseRef);
|
|
31589
|
+
}
|
|
31590
|
+
const baseSha = await resolveCommitSha(repo.mainPath, baseRef, opts);
|
|
31591
|
+
stepProgress(`Rebasing ${branch} onto ${baseRef}...`);
|
|
30963
31592
|
rebaseStarted = true;
|
|
30964
|
-
const rebaseOut = await gitExec(["-C", wtPath, "rebase", "--",
|
|
31593
|
+
const rebaseOut = await gitExec(["-C", wtPath, "rebase", "--", baseRef], opts);
|
|
30965
31594
|
if (rebaseOut.includes("is up to date") || rebaseOut.includes("up-to-date")) {
|
|
30966
31595
|
stepSuccess("Up to date", "0 commits replayed");
|
|
30967
31596
|
} else {
|
|
30968
|
-
const count2 = await gitExec(["-C", wtPath, "rev-list", "--count", `${
|
|
31597
|
+
const count2 = await gitExec(["-C", wtPath, "rev-list", "--count", `${baseRef}..HEAD`], opts).then((s) => s.trim());
|
|
30969
31598
|
stepSuccess("Rebased", `${count2} commits replayed`);
|
|
30970
31599
|
}
|
|
31600
|
+
if (recorded || options.onto) {
|
|
31601
|
+
const metadataBaseRef = options.onto || (recorded?.explicit ? baseRef : mainBranch);
|
|
31602
|
+
try {
|
|
31603
|
+
await recordStackEntry(repo.mainPath, branch, {
|
|
31604
|
+
baseRef: metadataBaseRef,
|
|
31605
|
+
baseSha,
|
|
31606
|
+
explicit: options.onto ? true : recorded?.explicit ?? true,
|
|
31607
|
+
createdAt: recorded?.createdAt || new Date().toISOString()
|
|
31608
|
+
}, opts);
|
|
31609
|
+
stepSuccess("Base recorded", metadataBaseRef);
|
|
31610
|
+
} catch (err) {
|
|
31611
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
31612
|
+
stepWarning("Base metadata not updated", message);
|
|
31613
|
+
}
|
|
31614
|
+
}
|
|
30971
31615
|
successCount++;
|
|
30972
31616
|
} catch (err) {
|
|
30973
31617
|
if (!rebaseStarted) {
|
|
30974
|
-
stepError("Rebase skipped — could not
|
|
31618
|
+
stepError("Rebase skipped — could not resolve base:", err.message.split(`
|
|
30975
31619
|
`)[0] ?? err.message);
|
|
30976
31620
|
failCount++;
|
|
30977
31621
|
continue;
|
|
@@ -31046,8 +31690,8 @@ init_execa();
|
|
|
31046
31690
|
init_config();
|
|
31047
31691
|
init_log();
|
|
31048
31692
|
init_resolver();
|
|
31049
|
-
import
|
|
31050
|
-
import
|
|
31693
|
+
import fs25 from "fs";
|
|
31694
|
+
import path32 from "path";
|
|
31051
31695
|
function registerSyncCommand(program2) {
|
|
31052
31696
|
program2.command("sync <branch>").description("re-copy sync files + run post_sync").option("--repo <repos...>", "comma-separated list of repos to target").action(async (branch, _options, cmd) => {
|
|
31053
31697
|
const opts = cmd.optsWithGlobals();
|
|
@@ -31058,19 +31702,19 @@ function registerSyncCommand(program2) {
|
|
|
31058
31702
|
for (const repo of repos) {
|
|
31059
31703
|
repoHeader(repo.name);
|
|
31060
31704
|
const wtPath = getWorktreePath(repo, branch);
|
|
31061
|
-
if (!
|
|
31705
|
+
if (!fs25.existsSync(wtPath)) {
|
|
31062
31706
|
stepWarning("No worktree found", `${wtPath} (skipped)`);
|
|
31063
31707
|
continue;
|
|
31064
31708
|
}
|
|
31065
31709
|
try {
|
|
31066
31710
|
if (repo.config.sync_files) {
|
|
31067
31711
|
for (const file2 of repo.config.sync_files) {
|
|
31068
|
-
const src =
|
|
31069
|
-
const dest =
|
|
31070
|
-
if (
|
|
31712
|
+
const src = path32.join(repo.mainPath, file2);
|
|
31713
|
+
const dest = path32.join(wtPath, file2);
|
|
31714
|
+
if (fs25.existsSync(src)) {
|
|
31071
31715
|
if (!opts.dryRun) {
|
|
31072
|
-
|
|
31073
|
-
|
|
31716
|
+
fs25.mkdirSync(path32.dirname(dest), { recursive: true });
|
|
31717
|
+
fs25.copyFileSync(src, dest);
|
|
31074
31718
|
}
|
|
31075
31719
|
stepSuccess(`Synced ${file2}`);
|
|
31076
31720
|
}
|
|
@@ -31120,15 +31764,15 @@ function registerSyncCommand(program2) {
|
|
|
31120
31764
|
process.exit(1);
|
|
31121
31765
|
}
|
|
31122
31766
|
}
|
|
31123
|
-
const wtNodeModules =
|
|
31124
|
-
if (
|
|
31767
|
+
const wtNodeModules = path32.join(wtPath, "node_modules");
|
|
31768
|
+
if (fs25.existsSync(wtNodeModules) && fs25.lstatSync(wtNodeModules).isSymbolicLink()) {
|
|
31125
31769
|
const lockfiles = ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lockb", "bun.lock"];
|
|
31126
31770
|
for (const lock of lockfiles) {
|
|
31127
|
-
const mainLock =
|
|
31128
|
-
const wtLock =
|
|
31129
|
-
if (
|
|
31130
|
-
const mainContent =
|
|
31131
|
-
const wtContent =
|
|
31771
|
+
const mainLock = path32.join(repo.mainPath, lock);
|
|
31772
|
+
const wtLock = path32.join(wtPath, lock);
|
|
31773
|
+
if (fs25.existsSync(mainLock) && fs25.existsSync(wtLock)) {
|
|
31774
|
+
const mainContent = fs25.readFileSync(mainLock);
|
|
31775
|
+
const wtContent = fs25.readFileSync(wtLock);
|
|
31132
31776
|
if (!mainContent.equals(wtContent)) {
|
|
31133
31777
|
stepWarning(`${lock} differs from main — node_modules is symlinked`);
|
|
31134
31778
|
indented(`Run: wtx deps ${branch} --repo ${repo.name} --install`);
|
|
@@ -31157,7 +31801,7 @@ init_config();
|
|
|
31157
31801
|
init_log();
|
|
31158
31802
|
init_resolver();
|
|
31159
31803
|
init_deps();
|
|
31160
|
-
import
|
|
31804
|
+
import fs26 from "fs";
|
|
31161
31805
|
function registerDepsCommand(program2) {
|
|
31162
31806
|
program2.command("deps [branch]").description("Manage node_modules strategy per worktree").option("-r, --repo <repos...>", "Target specific repo(s)").option("--install", "Switch to independent node_modules (run install)").option("--symlink", "Switch to legacy symlinked node_modules").option("--json", "Output machine-readable JSON state").action(async (branch, options) => {
|
|
31163
31807
|
const globalOpts = program2.opts();
|
|
@@ -31172,16 +31816,29 @@ function registerDepsCommand(program2) {
|
|
|
31172
31816
|
if (!options.json)
|
|
31173
31817
|
repoHeader(repo.name);
|
|
31174
31818
|
if (!branch) {
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31819
|
+
if (options.install) {
|
|
31820
|
+
const ok = await switchToInstall(repo.mainPath, globalOpts);
|
|
31821
|
+
if (!ok)
|
|
31822
|
+
process.exitCode = 1;
|
|
31823
|
+
if (options.json) {
|
|
31824
|
+
jsonResults[repo.name] = detectDepsState(repo.mainPath, repo.mainPath);
|
|
31825
|
+
}
|
|
31826
|
+
} else if (options.symlink) {
|
|
31827
|
+
if (!options.json) {
|
|
31828
|
+
stepWarning("Symlink strategy requires a worktree", "usage: wtx deps <branch> --symlink");
|
|
31829
|
+
}
|
|
31178
31830
|
} else {
|
|
31179
|
-
|
|
31831
|
+
const state = detectDepsState(repo.mainPath, repo.mainPath);
|
|
31832
|
+
if (options.json) {
|
|
31833
|
+
jsonResults[repo.name] = { main: state };
|
|
31834
|
+
} else {
|
|
31835
|
+
info(` Main repo package manager: ${state.packageManager ?? "none detected"}`);
|
|
31836
|
+
}
|
|
31180
31837
|
}
|
|
31181
31838
|
continue;
|
|
31182
31839
|
}
|
|
31183
31840
|
const wtPath = getWorktreePath(repo, branch);
|
|
31184
|
-
if (!
|
|
31841
|
+
if (!fs26.existsSync(wtPath)) {
|
|
31185
31842
|
if (options.json) {
|
|
31186
31843
|
jsonResults[repo.name] = { error: "No worktree found", branch };
|
|
31187
31844
|
} else {
|
|
@@ -31239,7 +31896,7 @@ function registerDepsCommand(program2) {
|
|
|
31239
31896
|
init_config();
|
|
31240
31897
|
init_log();
|
|
31241
31898
|
init_resolver();
|
|
31242
|
-
import
|
|
31899
|
+
import fs27 from "fs";
|
|
31243
31900
|
init_git();
|
|
31244
31901
|
async function resolveMainCheckoutPath(repoCtx, branch) {
|
|
31245
31902
|
try {
|
|
@@ -31264,7 +31921,7 @@ function registerOpenCommand(program2) {
|
|
|
31264
31921
|
for (const repo of repos) {
|
|
31265
31922
|
const wtPath = getWorktreePath(repo, branch);
|
|
31266
31923
|
let targetPath = wtPath;
|
|
31267
|
-
if (!
|
|
31924
|
+
if (!fs27.existsSync(wtPath)) {
|
|
31268
31925
|
const mainCheckoutPath = await resolveMainCheckoutPath(repo, branch);
|
|
31269
31926
|
if (!mainCheckoutPath) {
|
|
31270
31927
|
continue;
|
|
@@ -31294,14 +31951,14 @@ init_config();
|
|
|
31294
31951
|
init_log();
|
|
31295
31952
|
init_git();
|
|
31296
31953
|
init_resolver();
|
|
31297
|
-
import
|
|
31954
|
+
import path34 from "path";
|
|
31298
31955
|
|
|
31299
31956
|
// src/lib/rename-worktree.ts
|
|
31300
31957
|
init_git();
|
|
31301
31958
|
init_resolver();
|
|
31302
31959
|
init_path_safety();
|
|
31303
|
-
import
|
|
31304
|
-
import
|
|
31960
|
+
import fs28 from "fs";
|
|
31961
|
+
import path33 from "path";
|
|
31305
31962
|
async function getUpstream(wtPath) {
|
|
31306
31963
|
try {
|
|
31307
31964
|
const out = await gitExec(["-C", wtPath, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {});
|
|
@@ -31321,7 +31978,7 @@ async function planRename(repo, oldBranch, newBranch, opts) {
|
|
|
31321
31978
|
throw new Error(`Worktree '${oldBranch}' is locked — unlock it before renaming`);
|
|
31322
31979
|
}
|
|
31323
31980
|
const newPath = `${repo.wtRoot}/${newBranch}`;
|
|
31324
|
-
if (
|
|
31981
|
+
if (fs28.existsSync(newPath)) {
|
|
31325
31982
|
throw new Error(`Target path already exists: ${newPath}`);
|
|
31326
31983
|
}
|
|
31327
31984
|
if (!opts.dryRun && await localBranchExists(repo.mainPath, newBranch, opts)) {
|
|
@@ -31345,7 +32002,7 @@ async function renameWorktree(params) {
|
|
|
31345
32002
|
}
|
|
31346
32003
|
await gitExec(["-C", planned.worktreePath, "branch", "-m", oldBranch, newBranch], opts);
|
|
31347
32004
|
try {
|
|
31348
|
-
|
|
32005
|
+
fs28.mkdirSync(path33.dirname(planned.newPath), { recursive: true });
|
|
31349
32006
|
await gitExec(["-C", repo.mainPath, "worktree", "move", planned.worktreePath, planned.newPath], opts);
|
|
31350
32007
|
} catch (err) {
|
|
31351
32008
|
try {
|
|
@@ -31369,6 +32026,7 @@ async function renameWorktree(params) {
|
|
|
31369
32026
|
}
|
|
31370
32027
|
|
|
31371
32028
|
// src/commands/rename.ts
|
|
32029
|
+
init_stack();
|
|
31372
32030
|
function registerRenameCommand(program2) {
|
|
31373
32031
|
program2.command("rename <old-branch> <new-branch>").description("Rename a worktree's branch and move its checkout to the new location").option("-r, --repo <repos...>", "Target specific repo(s)").action(async (oldBranch, newBranch, options) => {
|
|
31374
32032
|
const globalOpts = program2.opts();
|
|
@@ -31406,12 +32064,19 @@ function registerRenameCommand(program2) {
|
|
|
31406
32064
|
stepProgress(`Renaming ${oldBranch} → ${newBranch}...`);
|
|
31407
32065
|
const outcome = await renameWorktree({ repo, oldBranch, newBranch, opts: globalOpts });
|
|
31408
32066
|
for (const dir of outcome.cleanedDirs) {
|
|
31409
|
-
stepSuccess("Cleaned up empty directory",
|
|
32067
|
+
stepSuccess("Cleaned up empty directory", path34.relative(repo.wtRoot, dir) + "/");
|
|
31410
32068
|
}
|
|
31411
32069
|
stepSuccess("Renamed", `${outcome.oldPath} → ${outcome.newPath}`);
|
|
31412
32070
|
if (outcome.upstream) {
|
|
31413
32071
|
indented(`Upstream still tracks '${outcome.upstream}' — after pushing run: git push -u origin ${newBranch}`);
|
|
31414
32072
|
}
|
|
32073
|
+
try {
|
|
32074
|
+
await renameStackEntry(repo.mainPath, oldBranch, newBranch, globalOpts);
|
|
32075
|
+
stepSuccess("Updated stack metadata", newBranch);
|
|
32076
|
+
} catch (err) {
|
|
32077
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32078
|
+
stepWarning("Stack metadata not updated", message);
|
|
32079
|
+
}
|
|
31415
32080
|
summary(`Done — renamed ${oldBranch} to ${newBranch}`);
|
|
31416
32081
|
} catch (err) {
|
|
31417
32082
|
stepError("Rename failed", err.message);
|
|
@@ -31428,8 +32093,9 @@ init_resolver();
|
|
|
31428
32093
|
init_deps();
|
|
31429
32094
|
init_forge();
|
|
31430
32095
|
init_types2();
|
|
31431
|
-
import
|
|
32096
|
+
import fs29 from "fs";
|
|
31432
32097
|
init_owner();
|
|
32098
|
+
init_stack();
|
|
31433
32099
|
init_source();
|
|
31434
32100
|
function buildStatusJson(item) {
|
|
31435
32101
|
const entry = {
|
|
@@ -31446,11 +32112,14 @@ function buildStatusJson(item) {
|
|
|
31446
32112
|
entry.behind = item.behind;
|
|
31447
32113
|
entry.deps = item.deps;
|
|
31448
32114
|
if (item.prInfo) {
|
|
31449
|
-
|
|
32115
|
+
const prEntry = {
|
|
31450
32116
|
number: item.prInfo.number,
|
|
31451
32117
|
state: item.prInfo.state,
|
|
31452
32118
|
url: item.prInfo.url
|
|
31453
32119
|
};
|
|
32120
|
+
if (item.prInfo.baseRefName)
|
|
32121
|
+
prEntry.base = item.prInfo.baseRefName;
|
|
32122
|
+
entry.pr = prEntry;
|
|
31454
32123
|
}
|
|
31455
32124
|
if (item.ownership && !item.ownership.mine && item.ownership.author) {
|
|
31456
32125
|
entry.owner = item.ownership.author;
|
|
@@ -31458,10 +32127,14 @@ function buildStatusJson(item) {
|
|
|
31458
32127
|
if (item.rebase) {
|
|
31459
32128
|
entry.rebase = item.rebase;
|
|
31460
32129
|
}
|
|
32130
|
+
if (item.base)
|
|
32131
|
+
entry.base = item.base;
|
|
32132
|
+
if (item.baseChanged)
|
|
32133
|
+
entry.baseChanged = true;
|
|
31461
32134
|
return entry;
|
|
31462
32135
|
}
|
|
31463
32136
|
function registerStatusCommand(program2) {
|
|
31464
|
-
program2.command("status <branch>").description("Show worktree status across repos").option("-r, --repo <repos...>", "Target specific repo(s)").option("--json", "Output machine-readable JSON").action(async (branch, options) => {
|
|
32137
|
+
program2.command("status <branch>").description("Show worktree status across repos").option("-r, --repo <repos...>", "Target specific repo(s)").option("--json", "Output machine-readable JSON").option("--base <ref>", "Override the recorded base ref for this status check").action(async (branch, options) => {
|
|
31465
32138
|
const globalOpts = program2.opts();
|
|
31466
32139
|
const config2 = loadConfig();
|
|
31467
32140
|
const repoFilter = parseRepoFlag(options.repo);
|
|
@@ -31470,17 +32143,33 @@ function registerStatusCommand(program2) {
|
|
|
31470
32143
|
const jsonOutputs = [];
|
|
31471
32144
|
for (const repo of repos) {
|
|
31472
32145
|
const wtPath = getWorktreePath(repo, branch);
|
|
31473
|
-
if (!
|
|
32146
|
+
if (!fs29.existsSync(wtPath)) {
|
|
31474
32147
|
continue;
|
|
31475
32148
|
}
|
|
31476
32149
|
found++;
|
|
31477
32150
|
const dirtyFiles = await getDirtyFiles(wtPath);
|
|
31478
32151
|
let ahead = null;
|
|
31479
32152
|
let behind = null;
|
|
32153
|
+
let baseRef;
|
|
32154
|
+
let baseChanged = false;
|
|
32155
|
+
let usingStackBase = false;
|
|
31480
32156
|
try {
|
|
31481
32157
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
31482
|
-
const
|
|
31483
|
-
const
|
|
32158
|
+
const metadata = await readStackMetadata(repo.mainPath, globalOpts);
|
|
32159
|
+
const recorded = metadata.branches[branch];
|
|
32160
|
+
const resolvedRemote = !options.base && !recorded?.explicit ? await resolveBaseRemote(repo.mainPath, mainBranch) : undefined;
|
|
32161
|
+
const defaultBase = resolvedRemote ? `${resolvedRemote}/${mainBranch}` : mainBranch;
|
|
32162
|
+
baseRef = options.base || (recorded?.explicit ? recorded.baseRef : defaultBase);
|
|
32163
|
+
usingStackBase = Boolean(options.base || recorded?.explicit);
|
|
32164
|
+
if (recorded?.explicit && !options.base && !globalOpts.dryRun) {
|
|
32165
|
+
try {
|
|
32166
|
+
const currentBaseSha = await resolveCommitSha(repo.mainPath, recorded.baseRef, globalOpts);
|
|
32167
|
+
baseChanged = currentBaseSha !== recorded.baseSha;
|
|
32168
|
+
} catch {
|
|
32169
|
+
baseChanged = true;
|
|
32170
|
+
}
|
|
32171
|
+
}
|
|
32172
|
+
const countOutput = await gitExec(["-C", wtPath, "rev-list", "--left-right", "--count", `${baseRef}...HEAD`], { verbose: globalOpts.verbose });
|
|
31484
32173
|
const parts = countOutput.trim().split(/\s+/);
|
|
31485
32174
|
behind = parts[0] ? parseInt(parts[0], 10) : null;
|
|
31486
32175
|
ahead = parts[1] ? parseInt(parts[1], 10) : null;
|
|
@@ -31519,7 +32208,9 @@ function registerStatusCommand(program2) {
|
|
|
31519
32208
|
prInfo,
|
|
31520
32209
|
ownership,
|
|
31521
32210
|
deps: depsState,
|
|
31522
|
-
rebase: rebaseStatus
|
|
32211
|
+
rebase: rebaseStatus,
|
|
32212
|
+
base: usingStackBase ? baseRef : undefined,
|
|
32213
|
+
baseChanged
|
|
31523
32214
|
}));
|
|
31524
32215
|
continue;
|
|
31525
32216
|
}
|
|
@@ -31533,10 +32224,16 @@ function registerStatusCommand(program2) {
|
|
|
31533
32224
|
indented(` ${f}`);
|
|
31534
32225
|
}
|
|
31535
32226
|
}
|
|
32227
|
+
if (usingStackBase && baseRef) {
|
|
32228
|
+
info(` Base: ${baseRef}`);
|
|
32229
|
+
if (baseChanged) {
|
|
32230
|
+
info(` Base state: moved since stack entry`);
|
|
32231
|
+
}
|
|
32232
|
+
}
|
|
31536
32233
|
if (ahead !== null && behind !== null) {
|
|
31537
|
-
info(` vs main: ${ahead} ahead, ${behind} behind`);
|
|
32234
|
+
info(` ${usingStackBase ? "vs base" : "vs main"}: ${ahead} ahead, ${behind} behind`);
|
|
31538
32235
|
} else {
|
|
31539
|
-
info(` vs main: unknown`);
|
|
32236
|
+
info(` ${usingStackBase ? "vs base" : "vs main"}: unknown`);
|
|
31540
32237
|
}
|
|
31541
32238
|
if (prInfo) {
|
|
31542
32239
|
const display = derivePrDisplay(prInfo);
|
|
@@ -31583,6 +32280,7 @@ init_git();
|
|
|
31583
32280
|
init_resolver();
|
|
31584
32281
|
init_forge();
|
|
31585
32282
|
init_owner();
|
|
32283
|
+
init_stack();
|
|
31586
32284
|
init_types2();
|
|
31587
32285
|
var ATTENTION_STATES = new Set([
|
|
31588
32286
|
PR_DISPLAY_STATES.CONFLICTED,
|
|
@@ -31614,6 +32312,7 @@ async function collectPrRows(repos, config2, verboseFlag) {
|
|
|
31614
32312
|
continue;
|
|
31615
32313
|
try {
|
|
31616
32314
|
const worktrees = await getWorktreeList(repo.mainPath);
|
|
32315
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, { verbose: verboseFlag, dryRun: false });
|
|
31617
32316
|
const branches = worktrees.filter((wt) => wt.path !== repo.mainPath && wt.branch).map((wt) => wt.branch);
|
|
31618
32317
|
if (branches.length === 0)
|
|
31619
32318
|
continue;
|
|
@@ -31643,7 +32342,8 @@ async function collectPrRows(repos, config2, verboseFlag) {
|
|
|
31643
32342
|
unresolvedThreads: pr.unresolvedThreads,
|
|
31644
32343
|
updatedAt: pr.updatedAt,
|
|
31645
32344
|
authorLogin: pr.authorLogin ?? null,
|
|
31646
|
-
ownership
|
|
32345
|
+
ownership,
|
|
32346
|
+
baseRef: pr.baseRefName ?? stackMetadata.branches[branch]?.baseRef ?? null
|
|
31647
32347
|
});
|
|
31648
32348
|
}
|
|
31649
32349
|
} catch (err) {
|
|
@@ -31669,8 +32369,9 @@ function renderTable(rows) {
|
|
|
31669
32369
|
const threads = row.unresolvedThreads > 0 ? `${row.unresolvedThreads} thread${row.unresolvedThreads > 1 ? "s" : ""}` : null;
|
|
31670
32370
|
const details = [row.checksSummary, threads].filter(Boolean).join(" · ");
|
|
31671
32371
|
const detailSuffix = details ? ` ${details}` : "";
|
|
32372
|
+
const baseSuffix = row.baseRef ? ` → ${row.baseRef}` : "";
|
|
31672
32373
|
const authorTag = row.ownership && !row.ownership.mine && row.ownership.author ? ` ${source_default.dim(row.ownership.author)}` : "";
|
|
31673
|
-
info(` #${row.prNumber} ${paddedBranch} ${renderDisplayState(row.prDisplay)}${detailSuffix}${authorTag} ${formatRelativeTime(row.updatedAt)} ${source_default.dim(row.url)}`);
|
|
32374
|
+
info(` #${row.prNumber} ${paddedBranch} ${renderDisplayState(row.prDisplay)}${baseSuffix}${detailSuffix}${authorTag} ${formatRelativeTime(row.updatedAt)} ${source_default.dim(terminalLink(row.url))}`);
|
|
31674
32375
|
}
|
|
31675
32376
|
}
|
|
31676
32377
|
}
|
|
@@ -31683,7 +32384,8 @@ function toJsonOutput(rows) {
|
|
|
31683
32384
|
awaitingReview: row.prDisplay.awaitingReview,
|
|
31684
32385
|
approved: row.prDisplay.approved,
|
|
31685
32386
|
prNumber: row.prNumber,
|
|
31686
|
-
author: row.authorLogin
|
|
32387
|
+
author: row.authorLogin,
|
|
32388
|
+
base: row.baseRef
|
|
31687
32389
|
}));
|
|
31688
32390
|
}
|
|
31689
32391
|
function registerPrsCommand(program2) {
|
|
@@ -31741,8 +32443,8 @@ function registerPrsCommand(program2) {
|
|
|
31741
32443
|
|
|
31742
32444
|
// src/commands/skill.ts
|
|
31743
32445
|
init_log();
|
|
31744
|
-
import
|
|
31745
|
-
import
|
|
32446
|
+
import fs30 from "fs";
|
|
32447
|
+
import path35 from "path";
|
|
31746
32448
|
import { URL as URL2 } from "url";
|
|
31747
32449
|
var __dirname2 = new URL2(".", import.meta.url).pathname;
|
|
31748
32450
|
var COMMON_MARKDOWN = `# wtx — Worktree Manager
|
|
@@ -31771,8 +32473,12 @@ Lists all worktrees across repositories.
|
|
|
31771
32473
|
Shows git statuses for all worktrees.
|
|
31772
32474
|
|
|
31773
32475
|
### \`wtx rebase <branch>\`
|
|
31774
|
-
Fetches the main branch
|
|
31775
|
-
- **Flags**: \`-r, --repo <repos
|
|
32476
|
+
Fetches the configured main branch for an independent worktree, or rebases onto its recorded base for a stacked worktree.
|
|
32477
|
+
- **Flags**: \`-r, --repo <repos...>\`, \`--onto <ref>\` to override the base.
|
|
32478
|
+
|
|
32479
|
+
### \`wtx stack <branch>\`
|
|
32480
|
+
Shows the recorded parent and descendant branches for a worktree.
|
|
32481
|
+
- **Flags**: \`-r, --repo <repos...>\`, \`--json\`
|
|
31776
32482
|
|
|
31777
32483
|
### \`wtx sync <branch>\`
|
|
31778
32484
|
Re-copies \`sync_files\` from the main checkout to the worktree and runs \`post_sync\` hooks. Also checks for package lockfile differences if \`node_modules\` is symlinked.
|
|
@@ -31837,15 +32543,23 @@ Hooks (\`post_create\` and \`post_sync\`) support the following template variabl
|
|
|
31837
32543
|
\`\`\`bash
|
|
31838
32544
|
wtx rebase feature-xyz
|
|
31839
32545
|
\`\`\`
|
|
31840
|
-
*Fetches the latest main branch
|
|
32546
|
+
*Fetches the latest main branch for independent work, or uses the recorded parent for a stacked branch.*
|
|
32547
|
+
|
|
32548
|
+
3. **Creating a stacked branch**:
|
|
32549
|
+
\`\`\`bash
|
|
32550
|
+
wtx create feature-api
|
|
32551
|
+
wtx create feature-ui --base feature-api
|
|
32552
|
+
wtx stack feature-ui
|
|
32553
|
+
\`\`\`
|
|
32554
|
+
*Open the child PR against \`feature-api\`; after the parent merges, retarget the child to main before rebasing it onto main.*
|
|
31841
32555
|
|
|
31842
|
-
|
|
32556
|
+
4. **Syncing environment variables**:
|
|
31843
32557
|
If the \`.env\` file in the main checkout was updated, run:
|
|
31844
32558
|
\`\`\`bash
|
|
31845
32559
|
wtx sync feature-xyz
|
|
31846
32560
|
\`\`\`
|
|
31847
32561
|
|
|
31848
|
-
|
|
32562
|
+
5. **Managing node_modules dependencies**:
|
|
31849
32563
|
If a worktree requires different dependencies than the main branch (e.g. you're testing an upgrade):
|
|
31850
32564
|
\`\`\`bash
|
|
31851
32565
|
wtx deps feature-xyz --install
|
|
@@ -31884,9 +32598,9 @@ function registerSkillCommand(program2) {
|
|
|
31884
32598
|
error51(`Unknown platform: ${platform2}`);
|
|
31885
32599
|
process.exit(1);
|
|
31886
32600
|
}
|
|
31887
|
-
let projectRoot =
|
|
31888
|
-
let skillPath =
|
|
31889
|
-
if (
|
|
32601
|
+
let projectRoot = path35.resolve(__dirname2, "..", "..");
|
|
32602
|
+
let skillPath = path35.join(projectRoot, "skills", `${platform2}.md`);
|
|
32603
|
+
if (fs30.existsSync(skillPath)) {
|
|
31890
32604
|
process.stdout.write(skillPath + `
|
|
31891
32605
|
`);
|
|
31892
32606
|
} else {
|
|
@@ -31932,7 +32646,8 @@ init_git();
|
|
|
31932
32646
|
init_deps();
|
|
31933
32647
|
import readline3 from "readline";
|
|
31934
32648
|
init_path_safety();
|
|
31935
|
-
|
|
32649
|
+
init_stack();
|
|
32650
|
+
import fs32 from "fs";
|
|
31936
32651
|
async function runMcpServer(opts = {}) {
|
|
31937
32652
|
const input = opts.input ?? process.stdin;
|
|
31938
32653
|
const output = opts.output ?? process.stdout;
|
|
@@ -32025,7 +32740,7 @@ async function handleRequest(req, send, config2, opts) {
|
|
|
32025
32740
|
description: "Get status of a specific worktree",
|
|
32026
32741
|
inputSchema: {
|
|
32027
32742
|
type: "object",
|
|
32028
|
-
properties: { repo: { type: "string" }, branch: { type: "string" } },
|
|
32743
|
+
properties: { repo: { type: "string" }, branch: { type: "string" }, base: { type: "string" } },
|
|
32029
32744
|
required: ["repo", "branch"]
|
|
32030
32745
|
}
|
|
32031
32746
|
},
|
|
@@ -32049,10 +32764,10 @@ async function handleRequest(req, send, config2, opts) {
|
|
|
32049
32764
|
},
|
|
32050
32765
|
{
|
|
32051
32766
|
name: "rebase_worktree",
|
|
32052
|
-
description: "Rebase a worktree against
|
|
32767
|
+
description: "Rebase a worktree against its recorded base or an explicit ref",
|
|
32053
32768
|
inputSchema: {
|
|
32054
32769
|
type: "object",
|
|
32055
|
-
properties: { repo: { type: "string" }, branch: { type: "string" } },
|
|
32770
|
+
properties: { repo: { type: "string" }, branch: { type: "string" }, onto: { type: "string" } },
|
|
32056
32771
|
required: ["repo", "branch"]
|
|
32057
32772
|
}
|
|
32058
32773
|
}
|
|
@@ -32111,16 +32826,18 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32111
32826
|
const items = [];
|
|
32112
32827
|
for (const repo of repos) {
|
|
32113
32828
|
const wts = await getWorktreeList(repo.mainPath);
|
|
32829
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, { verbose: _opts.verbose === true, dryRun: false });
|
|
32114
32830
|
for (const wt of wts) {
|
|
32115
32831
|
if (!wt.path.startsWith(repo.wtRoot))
|
|
32116
32832
|
continue;
|
|
32117
|
-
const dirtyCount =
|
|
32833
|
+
const dirtyCount = fs32.existsSync(wt.path) ? (await getDirtyFiles(wt.path)).length : 0;
|
|
32118
32834
|
items.push({
|
|
32119
32835
|
repo: repo.name,
|
|
32120
32836
|
branch: wt.branch || null,
|
|
32121
32837
|
path: wt.path,
|
|
32122
32838
|
sha: wt.commit ? wt.commit.substring(0, 7) : null,
|
|
32123
|
-
dirtyFiles: dirtyCount
|
|
32839
|
+
dirtyFiles: dirtyCount,
|
|
32840
|
+
base: wt.branch && stackMetadata.branches[wt.branch]?.explicit ? stackMetadata.branches[wt.branch]?.baseRef : null
|
|
32124
32841
|
});
|
|
32125
32842
|
}
|
|
32126
32843
|
}
|
|
@@ -32133,18 +32850,27 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32133
32850
|
if (!validateSafeBranchName(args.branch)) {
|
|
32134
32851
|
throw { isToolError: true, message: "unsafe branch name" };
|
|
32135
32852
|
}
|
|
32853
|
+
if (args.base !== undefined && typeof args.base !== "string") {
|
|
32854
|
+
throw { isSchemaError: true, message: "base must be a string" };
|
|
32855
|
+
}
|
|
32856
|
+
if (typeof args.base === "string" && !validateSafeBranchName(args.base)) {
|
|
32857
|
+
throw { isToolError: true, message: "unsafe base ref" };
|
|
32858
|
+
}
|
|
32136
32859
|
const repo = resolveRepos(config2, [args.repo])[0];
|
|
32137
32860
|
const wtPath = getWorktreePath(repo, args.branch);
|
|
32138
|
-
if (!
|
|
32861
|
+
if (!fs32.existsSync(wtPath)) {
|
|
32139
32862
|
throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
|
|
32140
32863
|
}
|
|
32141
32864
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
32142
32865
|
const resolvedRemote = await resolveBaseRemote(repo.mainPath, mainBranch);
|
|
32866
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, { verbose: _opts.verbose === true, dryRun: false });
|
|
32867
|
+
const recorded = stackMetadata.branches[args.branch];
|
|
32868
|
+
const baseRef = typeof args.base === "string" ? args.base : recorded?.explicit ? recorded.baseRef : `${resolvedRemote}/${mainBranch}`;
|
|
32143
32869
|
const dirtyFiles = await getDirtyFiles(wtPath);
|
|
32144
32870
|
let ahead = null;
|
|
32145
32871
|
let behind = null;
|
|
32146
32872
|
try {
|
|
32147
|
-
const countOutput = await gitExec(["-C", wtPath, "rev-list", "--left-right", "--count", `${
|
|
32873
|
+
const countOutput = await gitExec(["-C", wtPath, "rev-list", "--left-right", "--count", `${baseRef}...HEAD`]);
|
|
32148
32874
|
const parts = countOutput.trim().split(/\s+/);
|
|
32149
32875
|
behind = parts[0] ? parseInt(parts[0], 10) : null;
|
|
32150
32876
|
ahead = parts[1] ? parseInt(parts[1], 10) : null;
|
|
@@ -32160,6 +32886,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32160
32886
|
dirtyCount: dirtyFiles.length,
|
|
32161
32887
|
ahead,
|
|
32162
32888
|
behind,
|
|
32889
|
+
base: recorded?.explicit || typeof args.base === "string" ? baseRef : null,
|
|
32163
32890
|
depsStrategy: depsState.strategy
|
|
32164
32891
|
})
|
|
32165
32892
|
}]
|
|
@@ -32187,9 +32914,11 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32187
32914
|
}
|
|
32188
32915
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
32189
32916
|
const resolvedRemote = await resolveBaseRemote(repo.mainPath, mainBranch);
|
|
32917
|
+
const baseRef = args.base || `${resolvedRemote}/${mainBranch}`;
|
|
32190
32918
|
if (repo.config.fetch_main_on_create !== false) {
|
|
32191
32919
|
await gitExec(["-C", repo.mainPath, "fetch", resolvedRemote, "--", mainBranch]);
|
|
32192
32920
|
}
|
|
32921
|
+
const baseSha = await resolveCommitSha(repo.mainPath, baseRef, { verbose: _opts.verbose === true, dryRun: false });
|
|
32193
32922
|
const localExists = await localBranchExists(repo.mainPath, args.branch, { verbose: false, dryRun: false });
|
|
32194
32923
|
const remoteExists = await branchExistsOnRemote(repo.mainPath, args.branch, { verbose: false, dryRun: false }, resolvedRemote);
|
|
32195
32924
|
const localSha = localExists ? await getLocalBranchSha(repo.mainPath, args.branch, { verbose: false, dryRun: false }) : null;
|
|
@@ -32207,6 +32936,14 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32207
32936
|
gitArgs.push(wtPath, args.branch);
|
|
32208
32937
|
}
|
|
32209
32938
|
await gitExec(gitArgs);
|
|
32939
|
+
if (resolution.kind === "create-new") {
|
|
32940
|
+
await recordStackEntry(repo.mainPath, args.branch, {
|
|
32941
|
+
baseRef: args.base || mainBranch,
|
|
32942
|
+
baseSha,
|
|
32943
|
+
explicit: args.base !== undefined,
|
|
32944
|
+
createdAt: new Date().toISOString()
|
|
32945
|
+
}, { verbose: _opts.verbose === true, dryRun: false });
|
|
32946
|
+
}
|
|
32210
32947
|
return {
|
|
32211
32948
|
content: [{ type: "text", text: JSON.stringify({ path: wtPath }) }]
|
|
32212
32949
|
};
|
|
@@ -32235,7 +32972,12 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32235
32972
|
throw { isToolError: true, message: `Worktree for ${args.branch} is not registered` };
|
|
32236
32973
|
}
|
|
32237
32974
|
const wtPath = target.path;
|
|
32238
|
-
|
|
32975
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, { verbose: _opts.verbose === true, dryRun: false });
|
|
32976
|
+
const children = getStackChildren(stackMetadata, args.branch);
|
|
32977
|
+
if (children.length > 0 && args.force !== true) {
|
|
32978
|
+
throw { isToolError: true, message: `Branch has dependent worktrees: ${children.join(", ")}. Use force:true to override.` };
|
|
32979
|
+
}
|
|
32980
|
+
if (!args.force && fs32.existsSync(wtPath)) {
|
|
32239
32981
|
const dirty = await getDirtyFiles(wtPath);
|
|
32240
32982
|
if (dirty.length > 0) {
|
|
32241
32983
|
throw { isToolError: true, message: `Worktree is dirty. Use force:true to remove it.` };
|
|
@@ -32243,6 +32985,7 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32243
32985
|
}
|
|
32244
32986
|
await gitExec(["-C", repo.mainPath, "worktree", "remove", args.force ? "--force" : "", wtPath].filter(Boolean));
|
|
32245
32987
|
cleanupEmptyParents(repo.wtRoot, repo.mainPath, wtPath);
|
|
32988
|
+
await removeStackEntry(repo.mainPath, args.branch, { verbose: _opts.verbose === true, dryRun: false });
|
|
32246
32989
|
return {
|
|
32247
32990
|
content: [{ type: "text", text: JSON.stringify({ removed: true }) }]
|
|
32248
32991
|
};
|
|
@@ -32253,18 +32996,46 @@ async function handleToolCall(name, args, config2, _opts) {
|
|
|
32253
32996
|
}
|
|
32254
32997
|
const repo = resolveRepos(config2, [args.repo])[0];
|
|
32255
32998
|
const wtPath = getWorktreePath(repo, args.branch);
|
|
32256
|
-
if (!
|
|
32999
|
+
if (!fs32.existsSync(wtPath)) {
|
|
32257
33000
|
throw { isToolError: true, message: `Worktree not found for branch ${args.branch}` };
|
|
32258
33001
|
}
|
|
32259
33002
|
const mainBranch = await resolveMainBranch(repo, config2);
|
|
32260
33003
|
const resolvedRemote = await resolveBaseRemote(repo.mainPath, mainBranch);
|
|
32261
|
-
|
|
33004
|
+
if (args.onto !== undefined && typeof args.onto !== "string") {
|
|
33005
|
+
throw { isSchemaError: true, message: "onto must be a string" };
|
|
33006
|
+
}
|
|
33007
|
+
if (typeof args.onto === "string" && !validateSafeBranchName(args.onto)) {
|
|
33008
|
+
throw { isToolError: true, message: "unsafe base ref" };
|
|
33009
|
+
}
|
|
33010
|
+
const stackMetadata = await readStackMetadata(repo.mainPath, { verbose: _opts.verbose === true, dryRun: false });
|
|
33011
|
+
const recorded = stackMetadata.branches[args.branch];
|
|
33012
|
+
const baseRef = args.onto || (recorded?.explicit ? recorded.baseRef : `${resolvedRemote}/${mainBranch}`);
|
|
33013
|
+
if (!args.onto && (!recorded || !recorded.explicit)) {
|
|
33014
|
+
await gitExec(["-C", repo.mainPath, "fetch", resolvedRemote, "--", mainBranch]);
|
|
33015
|
+
}
|
|
33016
|
+
const baseSha = await resolveCommitSha(repo.mainPath, baseRef, { verbose: _opts.verbose === true, dryRun: false });
|
|
32262
33017
|
try {
|
|
32263
|
-
const rebaseOut = await gitExec(["-C", wtPath, "rebase", "--",
|
|
33018
|
+
const rebaseOut = await gitExec(["-C", wtPath, "rebase", "--", baseRef]);
|
|
32264
33019
|
if (rebaseOut.includes("is up to date") || rebaseOut.includes("up-to-date")) {
|
|
33020
|
+
if (recorded || args.onto) {
|
|
33021
|
+
await recordStackEntry(repo.mainPath, args.branch, {
|
|
33022
|
+
baseRef: args.onto || (recorded?.explicit ? baseRef : mainBranch),
|
|
33023
|
+
baseSha,
|
|
33024
|
+
explicit: args.onto ? true : recorded?.explicit ?? true,
|
|
33025
|
+
createdAt: recorded?.createdAt || new Date().toISOString()
|
|
33026
|
+
}, { verbose: _opts.verbose === true, dryRun: false });
|
|
33027
|
+
}
|
|
32265
33028
|
return { content: [{ type: "text", text: JSON.stringify({ status: "up-to-date" }) }] };
|
|
32266
33029
|
} else {
|
|
32267
|
-
const count2 = await gitExec(["-C", wtPath, "rev-list", "--count", `${
|
|
33030
|
+
const count2 = await gitExec(["-C", wtPath, "rev-list", "--count", `${baseRef}..HEAD`]).then((s) => s.trim());
|
|
33031
|
+
if (recorded || args.onto) {
|
|
33032
|
+
await recordStackEntry(repo.mainPath, args.branch, {
|
|
33033
|
+
baseRef: args.onto || (recorded?.explicit ? baseRef : mainBranch),
|
|
33034
|
+
baseSha,
|
|
33035
|
+
explicit: args.onto ? true : recorded?.explicit ?? true,
|
|
33036
|
+
createdAt: recorded?.createdAt || new Date().toISOString()
|
|
33037
|
+
}, { verbose: _opts.verbose === true, dryRun: false });
|
|
33038
|
+
}
|
|
32268
33039
|
return { content: [{ type: "text", text: JSON.stringify({ status: "rebased", commits: count2 }) }] };
|
|
32269
33040
|
}
|
|
32270
33041
|
} catch (err) {
|
|
@@ -32354,12 +33125,100 @@ function registerHistoryCommand(program2) {
|
|
|
32354
33125
|
});
|
|
32355
33126
|
}
|
|
32356
33127
|
|
|
33128
|
+
// src/commands/stack.ts
|
|
33129
|
+
init_config();
|
|
33130
|
+
init_git();
|
|
33131
|
+
init_resolver();
|
|
33132
|
+
init_stack();
|
|
33133
|
+
init_log();
|
|
33134
|
+
function nodeFor(branch, metadata, worktrees) {
|
|
33135
|
+
const entry = metadata.branches[branch];
|
|
33136
|
+
const worktree = worktrees.find((wt) => wt.branch === branch)?.path ?? null;
|
|
33137
|
+
return {
|
|
33138
|
+
branch,
|
|
33139
|
+
base: entry?.baseRef ?? null,
|
|
33140
|
+
explicit: entry?.explicit ?? false,
|
|
33141
|
+
baseSha: entry?.baseSha ?? null,
|
|
33142
|
+
worktree
|
|
33143
|
+
};
|
|
33144
|
+
}
|
|
33145
|
+
function collectDescendants(metadata, branch, worktrees, seen) {
|
|
33146
|
+
const nodes = [];
|
|
33147
|
+
for (const child of getStackChildren(metadata, branch)) {
|
|
33148
|
+
if (seen.has(child))
|
|
33149
|
+
continue;
|
|
33150
|
+
seen.add(child);
|
|
33151
|
+
nodes.push(nodeFor(child, metadata, worktrees));
|
|
33152
|
+
nodes.push(...collectDescendants(metadata, child, worktrees, seen));
|
|
33153
|
+
}
|
|
33154
|
+
return nodes;
|
|
33155
|
+
}
|
|
33156
|
+
function renderNode(node, prefix) {
|
|
33157
|
+
const base2 = node.base ? ` base ${node.base}` : " base not recorded";
|
|
33158
|
+
const location = node.worktree ? ` ${node.worktree}` : " no local worktree";
|
|
33159
|
+
info(` ${prefix}${node.branch}`);
|
|
33160
|
+
indented(`${" ".repeat(prefix.length)}${base2} · ${location}`);
|
|
33161
|
+
}
|
|
33162
|
+
function registerStackCommand(program2) {
|
|
33163
|
+
program2.command("stack <branch>").description("Show the recorded branch stack").option("-r, --repo <repos...>", "Target specific repo(s)").option("--json", "Output machine-readable JSON").action(async (branch, options) => {
|
|
33164
|
+
const globalOpts = program2.opts();
|
|
33165
|
+
if (!validateSafeBranchName(branch)) {
|
|
33166
|
+
stepError(`Invalid branch name: '${branch}'`);
|
|
33167
|
+
process.exit(1);
|
|
33168
|
+
}
|
|
33169
|
+
const config2 = loadConfig();
|
|
33170
|
+
const repoFilter = parseRepoFlag(options.repo);
|
|
33171
|
+
const repos = resolveRepos(config2, repoFilter);
|
|
33172
|
+
const jsonResults = [];
|
|
33173
|
+
let successCount = 0;
|
|
33174
|
+
for (const repo of repos) {
|
|
33175
|
+
try {
|
|
33176
|
+
const metadata = await readStackMetadata(repo.mainPath, globalOpts);
|
|
33177
|
+
const worktrees = await getWorktreeList(repo.mainPath);
|
|
33178
|
+
const ancestors = getStackAncestors(metadata, branch);
|
|
33179
|
+
const nodes = ancestors.map((item) => nodeFor(item, metadata, worktrees));
|
|
33180
|
+
const descendants = collectDescendants(metadata, branch, worktrees, new Set(ancestors));
|
|
33181
|
+
const allNodes = [...nodes, ...descendants];
|
|
33182
|
+
const displayNodes = buildStackHierarchy(allNodes, (node) => node.branch, (node) => node.base ?? undefined, (a2, b) => a2.branch.localeCompare(b.branch));
|
|
33183
|
+
if (options.json) {
|
|
33184
|
+
jsonResults.push({ repo: repo.name, branch, nodes: allNodes });
|
|
33185
|
+
} else {
|
|
33186
|
+
repoHeader(repo.name);
|
|
33187
|
+
info(` Stack for ${branch}`);
|
|
33188
|
+
if (allNodes.length === 1 && !metadata.branches[branch]) {
|
|
33189
|
+
const mainBranch = await resolveMainBranch(repo, config2);
|
|
33190
|
+
const defaultRemote = await resolveBaseRemote(repo.mainPath, mainBranch);
|
|
33191
|
+
const defaultBase = `${defaultRemote}/${mainBranch}`;
|
|
33192
|
+
indented(`No recorded parent; default base is ${defaultBase}`);
|
|
33193
|
+
}
|
|
33194
|
+
displayNodes.forEach(({ item, prefix }) => renderNode(item, prefix));
|
|
33195
|
+
}
|
|
33196
|
+
successCount++;
|
|
33197
|
+
} catch (err) {
|
|
33198
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33199
|
+
if (options.json) {
|
|
33200
|
+
console.error(JSON.stringify({ repo: repo.name, error: message }));
|
|
33201
|
+
} else {
|
|
33202
|
+
stepError("Failed to read stack", `${repo.name}: ${message}`);
|
|
33203
|
+
}
|
|
33204
|
+
}
|
|
33205
|
+
}
|
|
33206
|
+
if (options.json) {
|
|
33207
|
+
console.log(JSON.stringify(jsonResults.length === 1 ? jsonResults[0] : jsonResults, null, 2));
|
|
33208
|
+
} else if (successCount === 0) {
|
|
33209
|
+
summaryWarning("No stacks found");
|
|
33210
|
+
} else {
|
|
33211
|
+
summary(`Done — ${successCount} repo${successCount > 1 ? "s" : ""} checked`);
|
|
33212
|
+
}
|
|
33213
|
+
});
|
|
33214
|
+
}
|
|
33215
|
+
|
|
32357
33216
|
// src/index.ts
|
|
32358
33217
|
init_config();
|
|
32359
33218
|
init_resolver();
|
|
32360
33219
|
init_log();
|
|
32361
33220
|
init_history();
|
|
32362
|
-
import
|
|
33221
|
+
import fs33 from "fs";
|
|
32363
33222
|
var program2 = new Command;
|
|
32364
33223
|
program2.name("wtx").description("Multi-repo git worktree manager").version(VERSION).option("-q, --quiet", "Suppress progress indicators", false).option("--verbose", "Show git commands as they run", false).option("--dry-run", "Show what would happen", false);
|
|
32365
33224
|
var MUTATING_COMMANDS = new Set([
|
|
@@ -32451,6 +33310,7 @@ registerSkillCommand(program2);
|
|
|
32451
33310
|
registerTerminalCommand(program2);
|
|
32452
33311
|
registerMcpCommand(program2);
|
|
32453
33312
|
registerHistoryCommand(program2);
|
|
33313
|
+
registerStackCommand(program2);
|
|
32454
33314
|
program2.command("_resolve-path <repo> <branch>", { hidden: true }).description("Internal command to resolve worktree path for shell wrapper cd").action((repoName, branch) => {
|
|
32455
33315
|
try {
|
|
32456
33316
|
const config2 = loadConfig();
|
|
@@ -32462,7 +33322,7 @@ program2.command("_resolve-path <repo> <branch>", { hidden: true }).description(
|
|
|
32462
33322
|
}
|
|
32463
33323
|
const repo = repos[0];
|
|
32464
33324
|
const wtPath = getWorktreePath(repo, branch);
|
|
32465
|
-
if (!
|
|
33325
|
+
if (!fs33.existsSync(wtPath)) {
|
|
32466
33326
|
process.stderr.write(`✗ No worktree at ${wtPath}
|
|
32467
33327
|
`);
|
|
32468
33328
|
process.exit(1);
|