webmux 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/dist/server.js +124 -32
- package/bin/webmux.js +171 -36
- package/frontend/dist/assets/index-Bi9DHlpD.js +34 -0
- package/frontend/dist/assets/index-FwEUWC9Q.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-DIWwx16E.css +0 -32
- package/frontend/dist/assets/index-Pz_SK2_d.js +0 -32
package/bin/webmux.js
CHANGED
|
@@ -164,6 +164,11 @@ function listGitWorktrees(cwd) {
|
|
|
164
164
|
const output = runGit(["worktree", "list", "--porcelain"], cwd);
|
|
165
165
|
return parseGitWorktreePorcelain(output);
|
|
166
166
|
}
|
|
167
|
+
function listLocalGitBranches(cwd) {
|
|
168
|
+
const output = runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"], cwd);
|
|
169
|
+
return output.split(`
|
|
170
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
171
|
+
}
|
|
167
172
|
function readGitWorktreeStatus(cwd) {
|
|
168
173
|
const dirtyOutput = runGit(["status", "--porcelain"], cwd);
|
|
169
174
|
const commit = tryRunGit(["rev-parse", "HEAD"], cwd);
|
|
@@ -205,13 +210,21 @@ class BunGitGateway {
|
|
|
205
210
|
listWorktrees(cwd) {
|
|
206
211
|
return listGitWorktrees(cwd);
|
|
207
212
|
}
|
|
213
|
+
listLocalBranches(cwd) {
|
|
214
|
+
return listLocalGitBranches(cwd);
|
|
215
|
+
}
|
|
208
216
|
readWorktreeStatus(cwd) {
|
|
209
217
|
return readGitWorktreeStatus(cwd);
|
|
210
218
|
}
|
|
211
219
|
createWorktree(opts) {
|
|
212
|
-
const args = ["worktree", "add"
|
|
213
|
-
if (opts.
|
|
214
|
-
args.push(opts.
|
|
220
|
+
const args = ["worktree", "add"];
|
|
221
|
+
if (opts.mode === "new") {
|
|
222
|
+
args.push("-b", opts.branch, opts.worktreePath);
|
|
223
|
+
if (opts.baseBranch)
|
|
224
|
+
args.push(opts.baseBranch);
|
|
225
|
+
} else {
|
|
226
|
+
args.push(opts.worktreePath, opts.branch);
|
|
227
|
+
}
|
|
215
228
|
runGit(args, opts.repoRoot);
|
|
216
229
|
}
|
|
217
230
|
removeWorktree(opts) {
|
|
@@ -350,6 +363,7 @@ _webmux() {
|
|
|
350
363
|
'close:Close a worktree session'
|
|
351
364
|
'remove:Remove a worktree'
|
|
352
365
|
'merge:Merge a worktree into main'
|
|
366
|
+
'prune:Remove all worktrees in the current project'
|
|
353
367
|
'completion:Generate shell completion script'
|
|
354
368
|
)
|
|
355
369
|
|
|
@@ -397,7 +411,7 @@ compdef _webmux webmux`, BASH_SCRIPT = `_webmux() {
|
|
|
397
411
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
398
412
|
|
|
399
413
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
400
|
-
COMPREPLY=($(compgen -W "serve init service update add list open close remove merge completion" -- "\${cur}"))
|
|
414
|
+
COMPREPLY=($(compgen -W "serve init service update add list open close remove merge prune completion" -- "\${cur}"))
|
|
401
415
|
return
|
|
402
416
|
fi
|
|
403
417
|
|
|
@@ -2625,7 +2639,6 @@ class BunTmuxGateway {
|
|
|
2625
2639
|
if (check.exitCode !== 0) {
|
|
2626
2640
|
assertTmuxOk(["new-session", "-d", "-s", sessionName, "-c", cwd], `create tmux session ${sessionName}`);
|
|
2627
2641
|
}
|
|
2628
|
-
assertTmuxOk(["set-option", "-t", sessionName, "pane-base-index", "0"], `set pane-base-index on ${sessionName}`);
|
|
2629
2642
|
}
|
|
2630
2643
|
hasWindow(sessionName, windowName) {
|
|
2631
2644
|
const result = runTmux(["list-windows", "-t", sessionName, "-F", "#{window_name}"]);
|
|
@@ -10730,15 +10743,22 @@ function buildRuntimeBootstrap(runtimeEnvPath) {
|
|
|
10730
10743
|
return `set -a; . ${quoteShell(runtimeEnvPath)}; set +a`;
|
|
10731
10744
|
}
|
|
10732
10745
|
function buildAgentInvocation(input) {
|
|
10733
|
-
const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
10734
10746
|
if (input.agent === "codex") {
|
|
10735
10747
|
const yoloFlag2 = input.yolo ? " --yolo" : "";
|
|
10748
|
+
if (input.launchMode === "resume") {
|
|
10749
|
+
return `codex${yoloFlag2} resume --last`;
|
|
10750
|
+
}
|
|
10751
|
+
const promptSuffix2 = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
10736
10752
|
if (input.systemPrompt) {
|
|
10737
|
-
return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${
|
|
10753
|
+
return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${promptSuffix2}`;
|
|
10738
10754
|
}
|
|
10739
|
-
return `codex${yoloFlag2}${
|
|
10755
|
+
return `codex${yoloFlag2}${promptSuffix2}`;
|
|
10740
10756
|
}
|
|
10741
10757
|
const yoloFlag = input.yolo ? " --dangerously-skip-permissions" : "";
|
|
10758
|
+
if (input.launchMode === "resume") {
|
|
10759
|
+
return `claude${yoloFlag} --continue`;
|
|
10760
|
+
}
|
|
10761
|
+
const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
10742
10762
|
if (input.systemPrompt) {
|
|
10743
10763
|
return `claude${yoloFlag} --append-system-prompt ${quoteShell(input.systemPrompt)}${promptSuffix}`;
|
|
10744
10764
|
}
|
|
@@ -10821,6 +10841,7 @@ function ensureSessionLayout(tmux, plan) {
|
|
|
10821
10841
|
cwd: rootPane.cwd,
|
|
10822
10842
|
command: plan.shellCommand
|
|
10823
10843
|
});
|
|
10844
|
+
tmux.setWindowOption(plan.sessionName, plan.windowName, "pane-base-index", "0");
|
|
10824
10845
|
tmux.setWindowOption(plan.sessionName, plan.windowName, "automatic-rename", "off");
|
|
10825
10846
|
tmux.setWindowOption(plan.sessionName, plan.windowName, "allow-rename", "off");
|
|
10826
10847
|
for (const pane of plan.panes.slice(1)) {
|
|
@@ -10879,10 +10900,12 @@ function rollbackManagedWorktreeCreation(opts, sessionLayoutPlan, git, deps2) {
|
|
|
10879
10900
|
} catch (error) {
|
|
10880
10901
|
cleanupErrors.push(`worktree rollback failed: ${toErrorMessage(error)}`);
|
|
10881
10902
|
}
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10903
|
+
if (opts.deleteBranchOnRollback ?? true) {
|
|
10904
|
+
try {
|
|
10905
|
+
git.deleteBranch(opts.repoRoot, opts.branch, true);
|
|
10906
|
+
} catch (error) {
|
|
10907
|
+
cleanupErrors.push(`branch rollback failed: ${toErrorMessage(error)}`);
|
|
10908
|
+
}
|
|
10886
10909
|
}
|
|
10887
10910
|
return cleanupErrors.length > 0 ? joinErrorMessages(cleanupErrors) : null;
|
|
10888
10911
|
}
|
|
@@ -10932,6 +10955,7 @@ async function createManagedWorktree(opts, deps2 = {}) {
|
|
|
10932
10955
|
repoRoot: opts.repoRoot,
|
|
10933
10956
|
worktreePath: opts.worktreePath,
|
|
10934
10957
|
branch: opts.branch,
|
|
10958
|
+
mode: opts.mode,
|
|
10935
10959
|
baseBranch: opts.baseBranch
|
|
10936
10960
|
});
|
|
10937
10961
|
worktreeCreated = true;
|
|
@@ -11011,11 +11035,13 @@ class LifecycleService {
|
|
|
11011
11035
|
this.deps = deps2;
|
|
11012
11036
|
}
|
|
11013
11037
|
async createWorktree(input) {
|
|
11014
|
-
const
|
|
11015
|
-
this.
|
|
11038
|
+
const mode = input.mode ?? "new";
|
|
11039
|
+
const branch = await this.resolveBranch(input.branch, input.prompt, mode);
|
|
11040
|
+
this.ensureBranchAvailable(branch, mode);
|
|
11016
11041
|
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
11017
11042
|
const agent = this.resolveAgent(input.agent);
|
|
11018
11043
|
const worktreePath = this.resolveWorktreePath(branch);
|
|
11044
|
+
const deleteBranchOnRollback = mode === "new";
|
|
11019
11045
|
let initialized = null;
|
|
11020
11046
|
try {
|
|
11021
11047
|
await this.reportCreateProgress({
|
|
@@ -11030,7 +11056,8 @@ class LifecycleService {
|
|
|
11030
11056
|
repoRoot: this.deps.projectRoot,
|
|
11031
11057
|
worktreePath,
|
|
11032
11058
|
branch,
|
|
11033
|
-
|
|
11059
|
+
mode,
|
|
11060
|
+
...mode === "new" ? { baseBranch: this.deps.config.workspace.mainBranch } : {},
|
|
11034
11061
|
profile: profileName,
|
|
11035
11062
|
agent,
|
|
11036
11063
|
runtime: profile.runtime,
|
|
@@ -11038,7 +11065,8 @@ class LifecycleService {
|
|
|
11038
11065
|
allocatedPorts: await this.allocatePorts(),
|
|
11039
11066
|
runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
|
|
11040
11067
|
controlUrl: this.controlUrl(),
|
|
11041
|
-
controlToken: await this.deps.getControlToken()
|
|
11068
|
+
controlToken: await this.deps.getControlToken(),
|
|
11069
|
+
deleteBranchOnRollback
|
|
11042
11070
|
}, {
|
|
11043
11071
|
git: this.deps.git
|
|
11044
11072
|
});
|
|
@@ -11084,7 +11112,8 @@ class LifecycleService {
|
|
|
11084
11112
|
agent,
|
|
11085
11113
|
initialized,
|
|
11086
11114
|
worktreePath,
|
|
11087
|
-
prompt: input.prompt
|
|
11115
|
+
prompt: input.prompt,
|
|
11116
|
+
launchMode: "fresh"
|
|
11088
11117
|
});
|
|
11089
11118
|
await this.reportCreateProgress({
|
|
11090
11119
|
branch,
|
|
@@ -11100,7 +11129,7 @@ class LifecycleService {
|
|
|
11100
11129
|
};
|
|
11101
11130
|
} catch (error) {
|
|
11102
11131
|
if (initialized) {
|
|
11103
|
-
const cleanupError = await this.cleanupFailedCreate(branch, worktreePath, profile.runtime);
|
|
11132
|
+
const cleanupError = await this.cleanupFailedCreate(branch, worktreePath, profile.runtime, deleteBranchOnRollback);
|
|
11104
11133
|
if (cleanupError) {
|
|
11105
11134
|
throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
|
|
11106
11135
|
}
|
|
@@ -11113,6 +11142,7 @@ class LifecycleService {
|
|
|
11113
11142
|
async openWorktree(branch) {
|
|
11114
11143
|
try {
|
|
11115
11144
|
const resolved = await this.resolveExistingWorktree(branch);
|
|
11145
|
+
const launchMode = resolved.meta ? "resume" : "fresh";
|
|
11116
11146
|
const initialized = resolved.meta ? await this.refreshManagedArtifacts(resolved) : await this.initializeUnmanagedWorktree(resolved);
|
|
11117
11147
|
const { profile } = this.resolveProfile(initialized.meta.profile);
|
|
11118
11148
|
await ensureAgentRuntimeArtifacts({
|
|
@@ -11124,7 +11154,8 @@ class LifecycleService {
|
|
|
11124
11154
|
profile,
|
|
11125
11155
|
agent: initialized.meta.agent,
|
|
11126
11156
|
initialized,
|
|
11127
|
-
worktreePath: resolved.entry.path
|
|
11157
|
+
worktreePath: resolved.entry.path,
|
|
11158
|
+
launchMode
|
|
11128
11159
|
});
|
|
11129
11160
|
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
11130
11161
|
return {
|
|
@@ -11152,6 +11183,20 @@ class LifecycleService {
|
|
|
11152
11183
|
throw this.wrapOperationError(error);
|
|
11153
11184
|
}
|
|
11154
11185
|
}
|
|
11186
|
+
async pruneWorktrees() {
|
|
11187
|
+
try {
|
|
11188
|
+
const resolvedWorktrees = await this.resolveAllWorktrees();
|
|
11189
|
+
const removedBranches = [];
|
|
11190
|
+
for (const resolved of resolvedWorktrees) {
|
|
11191
|
+
const branch = resolved.entry.branch ?? resolved.entry.path;
|
|
11192
|
+
await this.removeResolvedWorktree(resolved);
|
|
11193
|
+
removedBranches.push(branch);
|
|
11194
|
+
}
|
|
11195
|
+
return { removedBranches };
|
|
11196
|
+
} catch (error) {
|
|
11197
|
+
throw this.wrapOperationError(error);
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
11155
11200
|
async mergeWorktree(branch) {
|
|
11156
11201
|
try {
|
|
11157
11202
|
const resolved = await this.resolveExistingWorktree(branch);
|
|
@@ -11170,9 +11215,17 @@ class LifecycleService {
|
|
|
11170
11215
|
throw this.wrapOperationError(error);
|
|
11171
11216
|
}
|
|
11172
11217
|
}
|
|
11173
|
-
|
|
11218
|
+
listAvailableBranches() {
|
|
11219
|
+
const localBranches = this.listLocalBranches().filter((branch) => isValidBranchName(branch));
|
|
11220
|
+
const checkedOutBranches = this.listCheckedOutBranches();
|
|
11221
|
+
return localBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
|
|
11222
|
+
}
|
|
11223
|
+
async resolveBranch(rawBranch, prompt, mode) {
|
|
11174
11224
|
const explicitBranch = rawBranch?.trim();
|
|
11175
|
-
const branch = explicitBranch || await this.generateAutoName(prompt) || generateBranchName();
|
|
11225
|
+
const branch = mode === "existing" ? explicitBranch : explicitBranch || await this.generateAutoName(prompt) || generateBranchName();
|
|
11226
|
+
if (!branch) {
|
|
11227
|
+
throw new LifecycleError("Existing branch is required", 400);
|
|
11228
|
+
}
|
|
11176
11229
|
if (!isValidBranchName(branch)) {
|
|
11177
11230
|
throw new LifecycleError(`Invalid branch name: ${branch}`, 400);
|
|
11178
11231
|
}
|
|
@@ -11184,10 +11237,19 @@ class LifecycleService {
|
|
|
11184
11237
|
}
|
|
11185
11238
|
return await this.deps.autoName.generateBranchName(this.deps.config.autoName, prompt);
|
|
11186
11239
|
}
|
|
11187
|
-
ensureBranchAvailable(branch) {
|
|
11188
|
-
const
|
|
11189
|
-
if (
|
|
11190
|
-
|
|
11240
|
+
ensureBranchAvailable(branch, mode) {
|
|
11241
|
+
const localBranches = new Set(this.listLocalBranches());
|
|
11242
|
+
if (mode === "new") {
|
|
11243
|
+
if (localBranches.has(branch)) {
|
|
11244
|
+
throw new LifecycleError(`Branch already exists: ${branch}`, 409);
|
|
11245
|
+
}
|
|
11246
|
+
return;
|
|
11247
|
+
}
|
|
11248
|
+
if (!localBranches.has(branch)) {
|
|
11249
|
+
throw new LifecycleError(`Branch not found: ${branch}`, 404);
|
|
11250
|
+
}
|
|
11251
|
+
if (this.listCheckedOutBranches().has(branch)) {
|
|
11252
|
+
throw new LifecycleError(`Branch already has a worktree: ${branch}`, 409);
|
|
11191
11253
|
}
|
|
11192
11254
|
}
|
|
11193
11255
|
resolveProfile(profileName) {
|
|
@@ -11226,6 +11288,12 @@ class LifecycleService {
|
|
|
11226
11288
|
resolveWorktreePath(branch) {
|
|
11227
11289
|
return resolve4(this.deps.projectRoot, this.deps.config.workspace.worktreeRoot, branch);
|
|
11228
11290
|
}
|
|
11291
|
+
listLocalBranches() {
|
|
11292
|
+
return this.deps.git.listLocalBranches(resolve4(this.deps.projectRoot));
|
|
11293
|
+
}
|
|
11294
|
+
listCheckedOutBranches() {
|
|
11295
|
+
return new Set(this.deps.git.listWorktrees(resolve4(this.deps.projectRoot)).filter((entry) => !entry.bare && entry.branch !== null).map((entry) => entry.branch));
|
|
11296
|
+
}
|
|
11229
11297
|
listProjectWorktrees() {
|
|
11230
11298
|
const projectRoot = resolve4(this.deps.projectRoot);
|
|
11231
11299
|
return this.deps.git.listWorktrees(projectRoot).filter((entry) => !entry.bare && resolve4(entry.path) !== projectRoot);
|
|
@@ -11246,6 +11314,14 @@ class LifecycleService {
|
|
|
11246
11314
|
const meta = await readWorktreeMeta(gitDir);
|
|
11247
11315
|
return { entry, gitDir, meta };
|
|
11248
11316
|
}
|
|
11317
|
+
async resolveAllWorktrees() {
|
|
11318
|
+
const entries = this.listProjectWorktrees().sort((left, right) => (left.branch ?? left.path).localeCompare(right.branch ?? right.path));
|
|
11319
|
+
return await Promise.all(entries.map(async (entry) => {
|
|
11320
|
+
const gitDir = this.deps.git.resolveWorktreeGitDir(entry.path);
|
|
11321
|
+
const meta = await readWorktreeMeta(gitDir);
|
|
11322
|
+
return { entry, gitDir, meta };
|
|
11323
|
+
}));
|
|
11324
|
+
}
|
|
11249
11325
|
async initializeUnmanagedWorktree(resolved) {
|
|
11250
11326
|
const { profileName, profile } = this.resolveProfile(undefined);
|
|
11251
11327
|
const dotenvValues = await loadDotenvLocal(resolved.entry.path);
|
|
@@ -11311,6 +11387,7 @@ class LifecycleService {
|
|
|
11311
11387
|
initialized: input.initialized,
|
|
11312
11388
|
worktreePath: input.worktreePath,
|
|
11313
11389
|
prompt: input.prompt,
|
|
11390
|
+
launchMode: input.launchMode,
|
|
11314
11391
|
containerName: containerName2
|
|
11315
11392
|
}));
|
|
11316
11393
|
return;
|
|
@@ -11321,11 +11398,12 @@ class LifecycleService {
|
|
|
11321
11398
|
agent: input.agent,
|
|
11322
11399
|
initialized: input.initialized,
|
|
11323
11400
|
worktreePath: input.worktreePath,
|
|
11324
|
-
prompt: input.prompt
|
|
11401
|
+
prompt: input.prompt,
|
|
11402
|
+
launchMode: input.launchMode
|
|
11325
11403
|
}));
|
|
11326
11404
|
}
|
|
11327
11405
|
buildSessionLayout(input) {
|
|
11328
|
-
const systemPrompt = input.profile.systemPrompt ? expandTemplate(input.profile.systemPrompt, input.initialized.runtimeEnv) : undefined;
|
|
11406
|
+
const systemPrompt = input.launchMode === "fresh" && input.profile.systemPrompt ? expandTemplate(input.profile.systemPrompt, input.initialized.runtimeEnv) : undefined;
|
|
11329
11407
|
const containerName2 = input.containerName;
|
|
11330
11408
|
return planSessionLayout(this.deps.projectRoot, input.branch, input.profile.panes, {
|
|
11331
11409
|
repoRoot: this.deps.projectRoot,
|
|
@@ -11338,7 +11416,8 @@ class LifecycleService {
|
|
|
11338
11416
|
runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
|
|
11339
11417
|
yolo: input.profile.yolo === true,
|
|
11340
11418
|
systemPrompt,
|
|
11341
|
-
prompt: input.prompt
|
|
11419
|
+
prompt: input.launchMode === "fresh" ? input.prompt : undefined,
|
|
11420
|
+
launchMode: input.launchMode
|
|
11342
11421
|
}),
|
|
11343
11422
|
shell: buildDockerShellCommand(containerName2, input.worktreePath, input.initialized.paths.runtimeEnvPath)
|
|
11344
11423
|
} : {
|
|
@@ -11347,7 +11426,8 @@ class LifecycleService {
|
|
|
11347
11426
|
runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
|
|
11348
11427
|
yolo: input.profile.yolo === true,
|
|
11349
11428
|
systemPrompt,
|
|
11350
|
-
prompt: input.prompt
|
|
11429
|
+
prompt: input.launchMode === "fresh" ? input.prompt : undefined,
|
|
11430
|
+
launchMode: input.launchMode
|
|
11351
11431
|
}),
|
|
11352
11432
|
shell: buildManagedShellCommand(input.initialized.paths.runtimeEnvPath)
|
|
11353
11433
|
}
|
|
@@ -11359,7 +11439,7 @@ class LifecycleService {
|
|
|
11359
11439
|
}
|
|
11360
11440
|
return profile;
|
|
11361
11441
|
}
|
|
11362
|
-
async cleanupFailedCreate(branch, worktreePath, runtime) {
|
|
11442
|
+
async cleanupFailedCreate(branch, worktreePath, runtime, deleteBranch) {
|
|
11363
11443
|
const cleanupErrors = [];
|
|
11364
11444
|
if (runtime === "docker") {
|
|
11365
11445
|
try {
|
|
@@ -11379,8 +11459,8 @@ class LifecycleService {
|
|
|
11379
11459
|
worktreePath,
|
|
11380
11460
|
branch,
|
|
11381
11461
|
force: true,
|
|
11382
|
-
deleteBranch
|
|
11383
|
-
deleteBranchForce:
|
|
11462
|
+
deleteBranch,
|
|
11463
|
+
deleteBranchForce: deleteBranch
|
|
11384
11464
|
}, this.deps.git);
|
|
11385
11465
|
} catch (error) {
|
|
11386
11466
|
cleanupErrors.push(`worktree cleanup failed: ${toErrorMessage2(error)}`);
|
|
@@ -11986,6 +12066,9 @@ function getWorktreeCommandUsage(command) {
|
|
|
11986
12066
|
case "merge":
|
|
11987
12067
|
return `Usage:
|
|
11988
12068
|
webmux merge <branch>`;
|
|
12069
|
+
case "prune":
|
|
12070
|
+
return `Usage:
|
|
12071
|
+
webmux prune`;
|
|
11989
12072
|
}
|
|
11990
12073
|
}
|
|
11991
12074
|
function readOptionValue(args, index, flag) {
|
|
@@ -12088,6 +12171,29 @@ function parseBranchCommandArgs(args) {
|
|
|
12088
12171
|
}
|
|
12089
12172
|
return branch;
|
|
12090
12173
|
}
|
|
12174
|
+
function parsePruneCommandArgs(args) {
|
|
12175
|
+
for (const arg of args) {
|
|
12176
|
+
if (arg === "--help" || arg === "-h") {
|
|
12177
|
+
return false;
|
|
12178
|
+
}
|
|
12179
|
+
if (arg.startsWith("-")) {
|
|
12180
|
+
throw new CommandUsageError(`Unknown option: ${arg}`);
|
|
12181
|
+
}
|
|
12182
|
+
throw new CommandUsageError(`Unexpected argument: ${arg}`);
|
|
12183
|
+
}
|
|
12184
|
+
return true;
|
|
12185
|
+
}
|
|
12186
|
+
function listProjectWorktrees(runtime) {
|
|
12187
|
+
const projectDir = resolve6(runtime.projectDir);
|
|
12188
|
+
return runtime.git.listWorktrees(projectDir).filter((entry) => !entry.bare && resolve6(entry.path) !== projectDir);
|
|
12189
|
+
}
|
|
12190
|
+
async function defaultConfirmPrune(worktreeCount) {
|
|
12191
|
+
const response = await Rt({
|
|
12192
|
+
message: `Prune all ${worktreeCount} worktree${worktreeCount === 1 ? "" : "s"}? This action cannot be undone.`,
|
|
12193
|
+
initialValue: false
|
|
12194
|
+
});
|
|
12195
|
+
return !Ct(response) && response;
|
|
12196
|
+
}
|
|
12091
12197
|
function defaultSwitchToTmuxWindow(projectDir, branch) {
|
|
12092
12198
|
const sessionName = buildProjectSessionName(resolve6(projectDir));
|
|
12093
12199
|
const windowName = buildWorktreeWindowName(branch);
|
|
@@ -12119,7 +12225,7 @@ function defaultSwitchToTmuxWindow(projectDir, branch) {
|
|
|
12119
12225
|
}
|
|
12120
12226
|
async function listWorktrees(runtime, stdout) {
|
|
12121
12227
|
const projectDir = resolve6(runtime.projectDir);
|
|
12122
|
-
const entries = runtime
|
|
12228
|
+
const entries = listProjectWorktrees(runtime);
|
|
12123
12229
|
if (entries.length === 0) {
|
|
12124
12230
|
stdout("No worktrees found.");
|
|
12125
12231
|
return;
|
|
@@ -12152,6 +12258,7 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12152
12258
|
const stdout = deps2.stdout ?? ((message) => console.log(message));
|
|
12153
12259
|
const stderr = deps2.stderr ?? ((message) => console.error(message));
|
|
12154
12260
|
const switchToTmuxWindow = deps2.switchToTmuxWindow ?? defaultSwitchToTmuxWindow;
|
|
12261
|
+
const confirmPrune = deps2.confirmPrune ?? defaultConfirmPrune;
|
|
12155
12262
|
try {
|
|
12156
12263
|
if (context.command === "add") {
|
|
12157
12264
|
const input = parseAddCommandArgs(context.args);
|
|
@@ -12180,6 +12287,32 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12180
12287
|
await listWorktrees(runtime2, stdout);
|
|
12181
12288
|
return 0;
|
|
12182
12289
|
}
|
|
12290
|
+
if (context.command === "prune") {
|
|
12291
|
+
if (!parsePruneCommandArgs(context.args)) {
|
|
12292
|
+
stdout(getWorktreeCommandUsage("prune"));
|
|
12293
|
+
return 0;
|
|
12294
|
+
}
|
|
12295
|
+
const runtime2 = createRuntime({
|
|
12296
|
+
projectDir: context.projectDir,
|
|
12297
|
+
port: context.port
|
|
12298
|
+
});
|
|
12299
|
+
const worktrees = listProjectWorktrees(runtime2);
|
|
12300
|
+
if (worktrees.length === 0) {
|
|
12301
|
+
stdout("No worktrees found.");
|
|
12302
|
+
return 0;
|
|
12303
|
+
}
|
|
12304
|
+
if (!await confirmPrune(worktrees.length)) {
|
|
12305
|
+
stdout("Aborted.");
|
|
12306
|
+
return 0;
|
|
12307
|
+
}
|
|
12308
|
+
const result = await runtime2.lifecycleService.pruneWorktrees();
|
|
12309
|
+
if (result.removedBranches.length === 0) {
|
|
12310
|
+
stdout("No worktrees found.");
|
|
12311
|
+
return 0;
|
|
12312
|
+
}
|
|
12313
|
+
stdout(`Pruned ${result.removedBranches.length} worktree${result.removedBranches.length === 1 ? "" : "s"}: ${result.removedBranches.join(", ")}`);
|
|
12314
|
+
return 0;
|
|
12315
|
+
}
|
|
12183
12316
|
const command = context.command;
|
|
12184
12317
|
const branch = parseBranchCommandArgs(context.args);
|
|
12185
12318
|
if (!branch) {
|
|
@@ -12215,6 +12348,7 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12215
12348
|
}
|
|
12216
12349
|
var CommandUsageError;
|
|
12217
12350
|
var init_worktree_commands = __esm(() => {
|
|
12351
|
+
init_dist2();
|
|
12218
12352
|
init_fs();
|
|
12219
12353
|
init_tmux();
|
|
12220
12354
|
init_policies();
|
|
@@ -12230,7 +12364,7 @@ import { fileURLToPath } from "url";
|
|
|
12230
12364
|
// package.json
|
|
12231
12365
|
var package_default = {
|
|
12232
12366
|
name: "webmux",
|
|
12233
|
-
version: "0.
|
|
12367
|
+
version: "0.13.0",
|
|
12234
12368
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
12235
12369
|
type: "module",
|
|
12236
12370
|
repository: {
|
|
@@ -12300,6 +12434,7 @@ Usage:
|
|
|
12300
12434
|
webmux close Close a worktree session without removing it
|
|
12301
12435
|
webmux remove Remove a worktree
|
|
12302
12436
|
webmux merge Merge a worktree into the main branch and remove it
|
|
12437
|
+
webmux prune Remove all worktrees in the current project
|
|
12303
12438
|
webmux completion Generate shell completion script (bash, zsh)
|
|
12304
12439
|
|
|
12305
12440
|
Options:
|
|
@@ -12313,7 +12448,7 @@ Environment:
|
|
|
12313
12448
|
`);
|
|
12314
12449
|
}
|
|
12315
12450
|
function isRootCommand(value) {
|
|
12316
|
-
return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "list" || value === "open" || value === "close" || value === "remove" || value === "merge" || value === "completion";
|
|
12451
|
+
return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "list" || value === "open" || value === "close" || value === "remove" || value === "merge" || value === "prune" || value === "completion";
|
|
12317
12452
|
}
|
|
12318
12453
|
function isServeRootOption(value) {
|
|
12319
12454
|
return value === "--port" || value === "--debug" || value === "--help" || value === "-h" || value === "--version" || value === "-V";
|
|
@@ -12372,7 +12507,7 @@ Run webmux --help for usage.`);
|
|
|
12372
12507
|
};
|
|
12373
12508
|
}
|
|
12374
12509
|
function isWorktreeCommand(command) {
|
|
12375
|
-
return command === "add" || command === "list" || command === "open" || command === "close" || command === "remove" || command === "merge";
|
|
12510
|
+
return command === "add" || command === "list" || command === "open" || command === "close" || command === "remove" || command === "merge" || command === "prune";
|
|
12376
12511
|
}
|
|
12377
12512
|
async function loadEnvFile(path) {
|
|
12378
12513
|
if (!existsSync5(path))
|