webmux 0.40.0 → 0.41.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 +2 -2
- package/bin/webmux.js +247 -132
- package/frontend/dist/assets/{DiffDialog-CqFJ8AzJ.js → DiffDialog-DSiqODW5.js} +1 -1
- package/frontend/dist/assets/{index-CghYughj.css → index-C4i4-u87.css} +1 -1
- package/frontend/dist/assets/index-C_8-_vLc.js +39 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BnMSlB_K.js +0 -39
package/bin/webmux.js
CHANGED
|
@@ -5997,7 +5997,7 @@ var init_zod = __esm(() => {
|
|
|
5997
5997
|
init_external();
|
|
5998
5998
|
});
|
|
5999
5999
|
|
|
6000
|
-
// node_modules/.bun/@ts-rest+core@3.52.1+
|
|
6000
|
+
// node_modules/.bun/@ts-rest+core@3.52.1+38b2c5aebf202339/node_modules/@ts-rest/core/index.esm.mjs
|
|
6001
6001
|
var isZodType = (obj) => {
|
|
6002
6002
|
return typeof (obj === null || obj === undefined ? undefined : obj.safeParse) === "function";
|
|
6003
6003
|
}, isZodObjectStrict = (obj) => {
|
|
@@ -8339,21 +8339,110 @@ ${result.stderr.trim()}` : ""
|
|
|
8339
8339
|
console.log();
|
|
8340
8340
|
});
|
|
8341
8341
|
|
|
8342
|
+
// backend/src/domain/projects.ts
|
|
8343
|
+
function isProjectEntry(value) {
|
|
8344
|
+
if (typeof value !== "object" || value === null)
|
|
8345
|
+
return false;
|
|
8346
|
+
const v = value;
|
|
8347
|
+
return typeof v.path === "string" && v.path.length > 0 && typeof v.name === "string" && typeof v.addedAt === "number";
|
|
8348
|
+
}
|
|
8349
|
+
|
|
8350
|
+
// backend/src/lib/log.ts
|
|
8351
|
+
function ts() {
|
|
8352
|
+
return new Date().toISOString().slice(11, 23);
|
|
8353
|
+
}
|
|
8354
|
+
var DEBUG, log2;
|
|
8355
|
+
var init_log = __esm(() => {
|
|
8356
|
+
DEBUG = Bun.env.WEBMUX_DEBUG === "1";
|
|
8357
|
+
log2 = {
|
|
8358
|
+
info(msg) {
|
|
8359
|
+
console.log(`[${ts()}] ${msg}`);
|
|
8360
|
+
},
|
|
8361
|
+
debug(msg) {
|
|
8362
|
+
if (DEBUG)
|
|
8363
|
+
console.log(`[${ts()}] ${msg}`);
|
|
8364
|
+
},
|
|
8365
|
+
warn(msg) {
|
|
8366
|
+
console.warn(`[${ts()}] ${msg}`);
|
|
8367
|
+
},
|
|
8368
|
+
error(msg, err) {
|
|
8369
|
+
err !== undefined ? console.error(`[${ts()}] ${msg}`, err) : console.error(`[${ts()}] ${msg}`);
|
|
8370
|
+
}
|
|
8371
|
+
};
|
|
8372
|
+
});
|
|
8373
|
+
|
|
8374
|
+
// backend/src/adapters/projects-registry.ts
|
|
8375
|
+
import { mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync } from "fs";
|
|
8376
|
+
import { homedir } from "os";
|
|
8377
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
8378
|
+
function defaultRegistryFile() {
|
|
8379
|
+
return join5(homedir(), ".webmux", "projects.json");
|
|
8380
|
+
}
|
|
8381
|
+
function createProjectsRegistry(file = defaultRegistryFile()) {
|
|
8382
|
+
function read() {
|
|
8383
|
+
let raw;
|
|
8384
|
+
try {
|
|
8385
|
+
raw = readFileSync3(file, "utf8");
|
|
8386
|
+
} catch {
|
|
8387
|
+
return [];
|
|
8388
|
+
}
|
|
8389
|
+
let parsed;
|
|
8390
|
+
try {
|
|
8391
|
+
parsed = JSON.parse(raw);
|
|
8392
|
+
} catch {
|
|
8393
|
+
log2.debug(`[projects-registry] ignoring malformed ${file}`);
|
|
8394
|
+
return [];
|
|
8395
|
+
}
|
|
8396
|
+
if (!Array.isArray(parsed))
|
|
8397
|
+
return [];
|
|
8398
|
+
return parsed.filter(isProjectEntry);
|
|
8399
|
+
}
|
|
8400
|
+
function write(entries) {
|
|
8401
|
+
mkdirSync(dirname3(file), { recursive: true });
|
|
8402
|
+
const tmpPath = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
8403
|
+
writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}
|
|
8404
|
+
`);
|
|
8405
|
+
renameSync(tmpPath, file);
|
|
8406
|
+
}
|
|
8407
|
+
return {
|
|
8408
|
+
list() {
|
|
8409
|
+
return read();
|
|
8410
|
+
},
|
|
8411
|
+
add(entry) {
|
|
8412
|
+
const entries = read().filter((existing) => existing.path !== entry.path);
|
|
8413
|
+
entries.push(entry);
|
|
8414
|
+
write(entries);
|
|
8415
|
+
},
|
|
8416
|
+
remove(path) {
|
|
8417
|
+
const entries = read();
|
|
8418
|
+
const next = entries.filter((entry) => entry.path !== path);
|
|
8419
|
+
if (next.length !== entries.length)
|
|
8420
|
+
write(next);
|
|
8421
|
+
}
|
|
8422
|
+
};
|
|
8423
|
+
}
|
|
8424
|
+
var init_projects_registry = __esm(() => {
|
|
8425
|
+
init_log();
|
|
8426
|
+
});
|
|
8427
|
+
|
|
8342
8428
|
// bin/src/service.ts
|
|
8343
8429
|
var exports_service = {};
|
|
8344
8430
|
__export(exports_service, {
|
|
8431
|
+
shouldPersistProject: () => shouldPersistProject,
|
|
8345
8432
|
resolveEnvVars: () => resolveEnvVars,
|
|
8433
|
+
resolveConfirmDecision: () => resolveConfirmDecision,
|
|
8346
8434
|
readPortFromUnit: () => readPortFromUnit,
|
|
8347
8435
|
readEnvVarsFromUnit: () => readEnvVarsFromUnit,
|
|
8348
8436
|
parseInstalledServiceConfig: () => parseInstalledServiceConfig,
|
|
8349
8437
|
parseEnvCliArgs: () => parseEnvCliArgs,
|
|
8438
|
+
migrateServedRepoFromUnit: () => migrateServedRepoFromUnit,
|
|
8350
8439
|
generateServiceFile: () => generateServiceFile,
|
|
8351
8440
|
default: () => service,
|
|
8352
8441
|
AUTO_PICKUP_ENV_VARS: () => AUTO_PICKUP_ENV_VARS
|
|
8353
8442
|
});
|
|
8354
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
8355
|
-
import { basename as basename3, join as
|
|
8356
|
-
import { homedir } from "os";
|
|
8443
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
8444
|
+
import { basename as basename3, join as join6 } from "path";
|
|
8445
|
+
import { homedir as homedir2 } from "os";
|
|
8357
8446
|
function getPlatform() {
|
|
8358
8447
|
const plat = process.platform;
|
|
8359
8448
|
if (plat === "linux" || plat === "darwin")
|
|
@@ -8366,6 +8455,33 @@ function resolveWebmuxPath() {
|
|
|
8366
8455
|
return null;
|
|
8367
8456
|
return result.stdout.toString().trim();
|
|
8368
8457
|
}
|
|
8458
|
+
function shouldPersistProject(root, hasWebmuxConfig, existingPaths) {
|
|
8459
|
+
if (!root || !hasWebmuxConfig)
|
|
8460
|
+
return false;
|
|
8461
|
+
return !existingPaths.includes(root);
|
|
8462
|
+
}
|
|
8463
|
+
function hasWebmuxConfig(root) {
|
|
8464
|
+
return existsSync4(join6(root, ".webmux.yaml")) || existsSync4(join6(root, ".webmux.local.yaml"));
|
|
8465
|
+
}
|
|
8466
|
+
function persistProject(root, registry) {
|
|
8467
|
+
if (!root)
|
|
8468
|
+
return null;
|
|
8469
|
+
const existingPaths = registry.list().map((entry) => entry.path);
|
|
8470
|
+
if (!shouldPersistProject(root, hasWebmuxConfig(root), existingPaths))
|
|
8471
|
+
return null;
|
|
8472
|
+
registry.add({ path: root, name: detectProjectName(root), addedAt: Date.now() });
|
|
8473
|
+
return root;
|
|
8474
|
+
}
|
|
8475
|
+
function cwdProjectToPersist() {
|
|
8476
|
+
const gitRoot2 = getGitRoot();
|
|
8477
|
+
if (!gitRoot2)
|
|
8478
|
+
return null;
|
|
8479
|
+
const existingPaths = createProjectsRegistry().list().map((entry) => entry.path);
|
|
8480
|
+
return shouldPersistProject(gitRoot2, hasWebmuxConfig(gitRoot2), existingPaths) ? gitRoot2 : null;
|
|
8481
|
+
}
|
|
8482
|
+
function migrateServedRepoFromUnit(filePath, platform, registry = createProjectsRegistry()) {
|
|
8483
|
+
return persistProject(readWorkingDirFromUnit(filePath, platform), registry);
|
|
8484
|
+
}
|
|
8369
8485
|
function formatCommand([bin, args]) {
|
|
8370
8486
|
return [bin, ...args].join(" ");
|
|
8371
8487
|
}
|
|
@@ -8379,10 +8495,10 @@ function printRunResult(result) {
|
|
|
8379
8495
|
console.error(err);
|
|
8380
8496
|
}
|
|
8381
8497
|
function systemdUnitPath(serviceName) {
|
|
8382
|
-
return
|
|
8498
|
+
return join6(homedir2(), ".config", "systemd", "user", `${serviceName}.service`);
|
|
8383
8499
|
}
|
|
8384
8500
|
function launchdPlistPath(serviceName) {
|
|
8385
|
-
return
|
|
8501
|
+
return join6(homedir2(), "Library", "LaunchAgents", `com.webmux.${serviceName}.plist`);
|
|
8386
8502
|
}
|
|
8387
8503
|
function serviceFilePath(config) {
|
|
8388
8504
|
if (config.platform === "linux")
|
|
@@ -8393,16 +8509,15 @@ function generateSystemdUnit(config) {
|
|
|
8393
8509
|
const extra = Object.keys(config.envVars).sort().map((key) => `Environment=${key}=${config.envVars[key]}`).join(`
|
|
8394
8510
|
`);
|
|
8395
8511
|
return `[Unit]
|
|
8396
|
-
Description=webmux dashboard
|
|
8512
|
+
Description=webmux dashboard
|
|
8397
8513
|
|
|
8398
8514
|
[Service]
|
|
8399
8515
|
Type=simple
|
|
8400
8516
|
ExecStart=${config.webmuxPath} serve --port ${config.port}
|
|
8401
|
-
WorkingDirectory=${
|
|
8517
|
+
WorkingDirectory=${homedir2()}
|
|
8402
8518
|
Restart=on-failure
|
|
8403
8519
|
RestartSec=5
|
|
8404
8520
|
Environment=PORT=${config.port}
|
|
8405
|
-
Environment=WEBMUX_PROJECT_DIR=${config.projectDir}
|
|
8406
8521
|
Environment=PATH=${process.env.PATH}${extra ? `
|
|
8407
8522
|
` + extra : ""}
|
|
8408
8523
|
|
|
@@ -8414,7 +8529,7 @@ function escapePlistText(value) {
|
|
|
8414
8529
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8415
8530
|
}
|
|
8416
8531
|
function generateLaunchdPlist(config) {
|
|
8417
|
-
const logPath =
|
|
8532
|
+
const logPath = join6(homedir2(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
8418
8533
|
const extra = Object.keys(config.envVars).sort().map((key) => ` <key>${escapePlistText(key)}</key>
|
|
8419
8534
|
<string>${escapePlistText(config.envVars[key])}</string>`).join(`
|
|
8420
8535
|
`);
|
|
@@ -8432,7 +8547,7 @@ function generateLaunchdPlist(config) {
|
|
|
8432
8547
|
<string>${config.port}</string>
|
|
8433
8548
|
</array>
|
|
8434
8549
|
<key>WorkingDirectory</key>
|
|
8435
|
-
<string>${
|
|
8550
|
+
<string>${homedir2()}</string>
|
|
8436
8551
|
<key>RunAtLoad</key>
|
|
8437
8552
|
<true/>
|
|
8438
8553
|
<key>KeepAlive</key>
|
|
@@ -8448,8 +8563,6 @@ function generateLaunchdPlist(config) {
|
|
|
8448
8563
|
<dict>
|
|
8449
8564
|
<key>PORT</key>
|
|
8450
8565
|
<string>${config.port}</string>
|
|
8451
|
-
<key>WEBMUX_PROJECT_DIR</key>
|
|
8452
|
-
<string>${config.projectDir}</string>
|
|
8453
8566
|
<key>PATH</key>
|
|
8454
8567
|
<string>${process.env.PATH}</string>${extra ? `
|
|
8455
8568
|
` + extra : ""}
|
|
@@ -8466,7 +8579,7 @@ function generateServiceFile(config) {
|
|
|
8466
8579
|
function readWorkingDirFromUnit(filePath, platform) {
|
|
8467
8580
|
let text;
|
|
8468
8581
|
try {
|
|
8469
|
-
text =
|
|
8582
|
+
text = readFileSync4(filePath, "utf8");
|
|
8470
8583
|
} catch {
|
|
8471
8584
|
return null;
|
|
8472
8585
|
}
|
|
@@ -8477,7 +8590,7 @@ function readWorkingDirFromUnit(filePath, platform) {
|
|
|
8477
8590
|
function readPortFromUnit(filePath) {
|
|
8478
8591
|
let text;
|
|
8479
8592
|
try {
|
|
8480
|
-
text =
|
|
8593
|
+
text = readFileSync4(filePath, "utf8");
|
|
8481
8594
|
} catch {
|
|
8482
8595
|
return null;
|
|
8483
8596
|
}
|
|
@@ -8491,7 +8604,7 @@ function unescapePlistText(value) {
|
|
|
8491
8604
|
function readEnvVarsFromUnit(filePath, platform) {
|
|
8492
8605
|
let text;
|
|
8493
8606
|
try {
|
|
8494
|
-
text =
|
|
8607
|
+
text = readFileSync4(filePath, "utf8");
|
|
8495
8608
|
} catch {
|
|
8496
8609
|
return {};
|
|
8497
8610
|
}
|
|
@@ -8520,19 +8633,13 @@ function parseInstalledServiceConfig(filePath, platform, webmuxPath) {
|
|
|
8520
8633
|
const port = readPortFromUnit(filePath);
|
|
8521
8634
|
if (port === null)
|
|
8522
8635
|
return null;
|
|
8523
|
-
const projectDir = readWorkingDirFromUnit(filePath, platform);
|
|
8524
|
-
if (projectDir === null)
|
|
8525
|
-
return null;
|
|
8526
8636
|
const fileBase = basename3(filePath);
|
|
8527
8637
|
const serviceName = platform === "linux" ? fileBase.replace(/\.service$/, "") : fileBase.replace(/^com\.webmux\./, "").replace(/\.plist$/, "");
|
|
8528
|
-
const projectName = detectProjectName(projectDir);
|
|
8529
8638
|
const envVars = readEnvVarsFromUnit(filePath, platform);
|
|
8530
8639
|
return {
|
|
8531
8640
|
platform,
|
|
8532
|
-
projectName,
|
|
8533
8641
|
serviceName,
|
|
8534
8642
|
webmuxPath,
|
|
8535
|
-
projectDir,
|
|
8536
8643
|
port,
|
|
8537
8644
|
envVars
|
|
8538
8645
|
};
|
|
@@ -8627,19 +8734,19 @@ function redactSecretsInUnit(content, envVars) {
|
|
|
8627
8734
|
}
|
|
8628
8735
|
return out;
|
|
8629
8736
|
}
|
|
8630
|
-
|
|
8737
|
+
function isInteractive() {
|
|
8738
|
+
return Boolean(process.stdin.isTTY);
|
|
8739
|
+
}
|
|
8740
|
+
function resolveConfirmDecision(autoConfirm, interactive) {
|
|
8741
|
+
if (autoConfirm)
|
|
8742
|
+
return "proceed";
|
|
8743
|
+
if (!interactive)
|
|
8744
|
+
return "abort-noninteractive";
|
|
8745
|
+
return "prompt";
|
|
8746
|
+
}
|
|
8747
|
+
async function install(config, portExplicit, envVarNotes, autoConfirm) {
|
|
8631
8748
|
const filePath = serviceFilePath(config);
|
|
8632
8749
|
const alreadyInstalled = isInstalled(config);
|
|
8633
|
-
if (alreadyInstalled) {
|
|
8634
|
-
const reinstall = await confirm({ message: "Service is already installed. Reinstall?" });
|
|
8635
|
-
if (isCancel(reinstall) || !reinstall) {
|
|
8636
|
-
log.info("Aborted.");
|
|
8637
|
-
return;
|
|
8638
|
-
}
|
|
8639
|
-
for (const cmd of uninstallCommands(config)) {
|
|
8640
|
-
runCommand(cmd);
|
|
8641
|
-
}
|
|
8642
|
-
}
|
|
8643
8750
|
const requestedPort = config.port;
|
|
8644
8751
|
let chosenPort = requestedPort;
|
|
8645
8752
|
let portNote = null;
|
|
@@ -8653,14 +8760,17 @@ async function install(config, portExplicit, envVarNotes) {
|
|
|
8653
8760
|
config = { ...config, port: chosenPort };
|
|
8654
8761
|
const content = generateServiceFile(config);
|
|
8655
8762
|
const commands = installCommands(config);
|
|
8763
|
+
const persistPath = cwdProjectToPersist();
|
|
8656
8764
|
const displayContent = redactSecretsInUnit(content, config.envVars);
|
|
8657
8765
|
note([
|
|
8766
|
+
...alreadyInstalled ? ["Service is already installed \u2014 this will reinstall it.", ""] : [],
|
|
8658
8767
|
`File: ${filePath}`,
|
|
8659
8768
|
"",
|
|
8660
8769
|
"Contents:",
|
|
8661
8770
|
displayContent,
|
|
8662
8771
|
"Commands to run:",
|
|
8663
|
-
...commands.map((c4) => ` $ ${formatCommand(c4)}`)
|
|
8772
|
+
...commands.map((c4) => ` $ ${formatCommand(c4)}`),
|
|
8773
|
+
...persistPath ? ["", `Will also register this project: ${persistPath}`] : []
|
|
8664
8774
|
].join(`
|
|
8665
8775
|
`), "Install service");
|
|
8666
8776
|
if (Object.keys(config.envVars).length > 0) {
|
|
@@ -8670,12 +8780,28 @@ ${envVarNotes.join(`
|
|
|
8670
8780
|
}
|
|
8671
8781
|
if (portNote)
|
|
8672
8782
|
log.info(portNote);
|
|
8673
|
-
const
|
|
8674
|
-
if (
|
|
8675
|
-
log.info("
|
|
8783
|
+
const decision = resolveConfirmDecision(autoConfirm, isInteractive());
|
|
8784
|
+
if (decision === "abort-noninteractive") {
|
|
8785
|
+
log.info(`Non-interactive environment \u2014 not ${alreadyInstalled ? "reinstalling" : "installing"}. ` + "Re-run with --yes to confirm and apply the plan above.");
|
|
8676
8786
|
return;
|
|
8677
8787
|
}
|
|
8678
|
-
|
|
8788
|
+
if (decision === "prompt") {
|
|
8789
|
+
const ok = await confirm({ message: alreadyInstalled ? "Reinstall?" : "Proceed?" });
|
|
8790
|
+
if (isCancel(ok) || !ok) {
|
|
8791
|
+
log.info("Aborted.");
|
|
8792
|
+
return;
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
if (alreadyInstalled) {
|
|
8796
|
+
const migrated = migrateServedRepoFromUnit(filePath, config.platform);
|
|
8797
|
+
if (migrated) {
|
|
8798
|
+
log.success(`Migrated previously-served project ${detectProjectName(migrated)} (${migrated})`);
|
|
8799
|
+
}
|
|
8800
|
+
for (const cmd of uninstallCommands(config)) {
|
|
8801
|
+
runCommand(cmd);
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
8804
|
+
mkdirSync2(filePath.substring(0, filePath.lastIndexOf("/")), { recursive: true });
|
|
8679
8805
|
await Bun.write(filePath, content);
|
|
8680
8806
|
if (Object.keys(config.envVars).length > 0) {
|
|
8681
8807
|
try {
|
|
@@ -8685,6 +8811,14 @@ ${envVarNotes.join(`
|
|
|
8685
8811
|
}
|
|
8686
8812
|
}
|
|
8687
8813
|
log.success(`Wrote ${filePath}`);
|
|
8814
|
+
if (persistPath) {
|
|
8815
|
+
createProjectsRegistry().add({
|
|
8816
|
+
path: persistPath,
|
|
8817
|
+
name: detectProjectName(persistPath),
|
|
8818
|
+
addedAt: Date.now()
|
|
8819
|
+
});
|
|
8820
|
+
log.success(`Registered project ${detectProjectName(persistPath)} (${persistPath})`);
|
|
8821
|
+
}
|
|
8688
8822
|
for (const cmd of commands) {
|
|
8689
8823
|
const result = runCommand(cmd);
|
|
8690
8824
|
if (!result.success) {
|
|
@@ -8756,7 +8890,7 @@ function logs(config) {
|
|
|
8756
8890
|
if (config.platform === "linux") {
|
|
8757
8891
|
proc = Bun.spawn(["journalctl", "--user", "-u", config.serviceName, "-f", "--no-pager"], { stdout: "inherit", stderr: "inherit" });
|
|
8758
8892
|
} else {
|
|
8759
|
-
const logPath =
|
|
8893
|
+
const logPath = join6(homedir2(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
8760
8894
|
if (!existsSync4(logPath)) {
|
|
8761
8895
|
log.error(`Log file not found: ${logPath}`);
|
|
8762
8896
|
return;
|
|
@@ -8785,6 +8919,9 @@ Usage:
|
|
|
8785
8919
|
Options:
|
|
8786
8920
|
--port N Pin the service to a port (default: 5111). On
|
|
8787
8921
|
reinstall without --port the existing port is kept.
|
|
8922
|
+
--yes, -y Skip the confirmation prompt and install. In a
|
|
8923
|
+
non-interactive shell (CI, pipe) install prints the
|
|
8924
|
+
plan and stops unless --yes is passed.
|
|
8788
8925
|
--env KEY=VALUE Bake an environment variable into the service
|
|
8789
8926
|
unit (repeatable). Reserved keys PORT,
|
|
8790
8927
|
WEBMUX_PROJECT_DIR, and PATH are rejected.
|
|
@@ -8813,11 +8950,6 @@ async function service(args) {
|
|
|
8813
8950
|
log.error(`Unsupported platform: ${process.platform}. Only linux and macOS are supported.`);
|
|
8814
8951
|
return;
|
|
8815
8952
|
}
|
|
8816
|
-
const gitRoot2 = getGitRoot();
|
|
8817
|
-
if (!gitRoot2) {
|
|
8818
|
-
log.error("Not inside a git repository.");
|
|
8819
|
-
return;
|
|
8820
|
-
}
|
|
8821
8953
|
const serviceManager = platform === "linux" ? "systemctl" : "launchctl";
|
|
8822
8954
|
const smResult = run("which", [serviceManager]);
|
|
8823
8955
|
if (!smResult.success) {
|
|
@@ -8832,6 +8964,7 @@ async function service(args) {
|
|
|
8832
8964
|
let port = parseInt(process.env.PORT || "5111");
|
|
8833
8965
|
let portExplicit = false;
|
|
8834
8966
|
let autoPickup = true;
|
|
8967
|
+
let autoConfirm = false;
|
|
8835
8968
|
for (let i2 = 1;i2 < args.length; i2++) {
|
|
8836
8969
|
if (args[i2] === "--port" && args[i2 + 1]) {
|
|
8837
8970
|
const parsed = parseInt(args[++i2]);
|
|
@@ -8843,6 +8976,8 @@ async function service(args) {
|
|
|
8843
8976
|
portExplicit = true;
|
|
8844
8977
|
} else if (args[i2] === "--no-auto-env") {
|
|
8845
8978
|
autoPickup = false;
|
|
8979
|
+
} else if (args[i2] === "--yes" || args[i2] === "-y") {
|
|
8980
|
+
autoConfirm = true;
|
|
8846
8981
|
}
|
|
8847
8982
|
}
|
|
8848
8983
|
const cliEnv = parseEnvCliArgs(args.slice(1));
|
|
@@ -8851,7 +8986,6 @@ async function service(args) {
|
|
|
8851
8986
|
log.error(err);
|
|
8852
8987
|
return;
|
|
8853
8988
|
}
|
|
8854
|
-
const projectName = detectProjectName(gitRoot2);
|
|
8855
8989
|
const serviceName = "webmux";
|
|
8856
8990
|
let envVars = {};
|
|
8857
8991
|
let envVarNotes = [];
|
|
@@ -8868,16 +9002,14 @@ async function service(args) {
|
|
|
8868
9002
|
}
|
|
8869
9003
|
const config = {
|
|
8870
9004
|
platform,
|
|
8871
|
-
projectName,
|
|
8872
9005
|
serviceName,
|
|
8873
9006
|
webmuxPath,
|
|
8874
|
-
projectDir: gitRoot2,
|
|
8875
9007
|
port,
|
|
8876
9008
|
envVars
|
|
8877
9009
|
};
|
|
8878
9010
|
switch (action) {
|
|
8879
9011
|
case "install":
|
|
8880
|
-
await install(config, portExplicit, envVarNotes);
|
|
9012
|
+
await install(config, portExplicit, envVarNotes, autoConfirm);
|
|
8881
9013
|
break;
|
|
8882
9014
|
case "uninstall":
|
|
8883
9015
|
await uninstall(config);
|
|
@@ -8898,6 +9030,7 @@ var AUTO_PICKUP_ENV_VARS, RESERVED_ENV_KEYS, SYSTEMD_WORKDIR_RE, LAUNCHD_WORKDIR
|
|
|
8898
9030
|
var init_service = __esm(() => {
|
|
8899
9031
|
init_dist4();
|
|
8900
9032
|
init_shared();
|
|
9033
|
+
init_projects_registry();
|
|
8901
9034
|
AUTO_PICKUP_ENV_VARS = ["LINEAR_API_KEY"];
|
|
8902
9035
|
RESERVED_ENV_KEYS = new Set(["PORT", "WEBMUX_PROJECT_DIR", "PATH"]);
|
|
8903
9036
|
SYSTEMD_WORKDIR_RE = /^WorkingDirectory=(.+)$/m;
|
|
@@ -8917,9 +9050,9 @@ __export(exports_service_restart, {
|
|
|
8917
9050
|
restartCommand: () => restartCommand,
|
|
8918
9051
|
listInstalledServices: () => listInstalledServices
|
|
8919
9052
|
});
|
|
8920
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
8921
|
-
import { homedir as
|
|
8922
|
-
import { join as
|
|
9053
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
9054
|
+
import { homedir as homedir3 } from "os";
|
|
9055
|
+
import { join as join7 } from "path";
|
|
8923
9056
|
function listInstalledServices(opts = {}) {
|
|
8924
9057
|
const out = [];
|
|
8925
9058
|
const systemdDir = opts.systemdDir ?? DEFAULT_SYSTEMD_DIR;
|
|
@@ -8931,7 +9064,7 @@ function listInstalledServices(opts = {}) {
|
|
|
8931
9064
|
continue;
|
|
8932
9065
|
out.push({
|
|
8933
9066
|
name: name.slice(0, -".service".length),
|
|
8934
|
-
filePath:
|
|
9067
|
+
filePath: join7(systemdDir, name),
|
|
8935
9068
|
platform: "linux"
|
|
8936
9069
|
});
|
|
8937
9070
|
}
|
|
@@ -8944,7 +9077,7 @@ function listInstalledServices(opts = {}) {
|
|
|
8944
9077
|
continue;
|
|
8945
9078
|
out.push({
|
|
8946
9079
|
name: name.slice(0, -".plist".length),
|
|
8947
|
-
filePath:
|
|
9080
|
+
filePath: join7(launchdDir, name),
|
|
8948
9081
|
platform: "darwin"
|
|
8949
9082
|
});
|
|
8950
9083
|
}
|
|
@@ -8987,17 +9120,19 @@ function reloadAfterRegenerate(service2, runner) {
|
|
|
8987
9120
|
service is now unloaded \u2014 recover with: launchctl load -w "${service2.filePath}"`
|
|
8988
9121
|
};
|
|
8989
9122
|
}
|
|
8990
|
-
async function updateInstalledService(service2, webmuxPath, runner = defaultRunner) {
|
|
9123
|
+
async function updateInstalledService(service2, webmuxPath, runner = defaultRunner, migrate = migrateServedRepoFromUnit) {
|
|
8991
9124
|
const canRegenerate = webmuxPath.length > 0;
|
|
8992
9125
|
const config = canRegenerate ? parseInstalledServiceConfig(service2.filePath, service2.platform, webmuxPath) : null;
|
|
8993
9126
|
let regenerated = false;
|
|
9127
|
+
let migratedProject;
|
|
8994
9128
|
if (config !== null) {
|
|
8995
9129
|
let currentContent = "";
|
|
8996
9130
|
try {
|
|
8997
|
-
currentContent =
|
|
9131
|
+
currentContent = readFileSync5(service2.filePath, "utf8");
|
|
8998
9132
|
} catch {}
|
|
8999
9133
|
const expected = generateServiceFile(config);
|
|
9000
9134
|
if (currentContent !== expected) {
|
|
9135
|
+
migratedProject = migrate(service2.filePath, service2.platform) ?? undefined;
|
|
9001
9136
|
try {
|
|
9002
9137
|
await Bun.write(service2.filePath, expected);
|
|
9003
9138
|
regenerated = true;
|
|
@@ -9006,6 +9141,7 @@ async function updateInstalledService(service2, webmuxPath, runner = defaultRunn
|
|
|
9006
9141
|
service: service2,
|
|
9007
9142
|
regenerated: false,
|
|
9008
9143
|
restarted: false,
|
|
9144
|
+
migratedProject,
|
|
9009
9145
|
error: `could not rewrite ${service2.filePath}: ${String(err)}`
|
|
9010
9146
|
};
|
|
9011
9147
|
}
|
|
@@ -9014,16 +9150,17 @@ async function updateInstalledService(service2, webmuxPath, runner = defaultRunn
|
|
|
9014
9150
|
if (regenerated) {
|
|
9015
9151
|
const reload = reloadAfterRegenerate(service2, runner);
|
|
9016
9152
|
if (!reload.ok) {
|
|
9017
|
-
return { service: service2, regenerated, restarted: false, error: reload.error };
|
|
9153
|
+
return { service: service2, regenerated, restarted: false, migratedProject, error: reload.error };
|
|
9018
9154
|
}
|
|
9019
9155
|
if (service2.platform === "darwin") {
|
|
9020
|
-
return { service: service2, regenerated, restarted: true };
|
|
9156
|
+
return { service: service2, regenerated, restarted: true, migratedProject };
|
|
9021
9157
|
}
|
|
9022
9158
|
}
|
|
9023
9159
|
const outcome = restartInstalledService(service2, runner);
|
|
9024
9160
|
return {
|
|
9025
9161
|
service: service2,
|
|
9026
9162
|
regenerated,
|
|
9163
|
+
migratedProject,
|
|
9027
9164
|
restarted: outcome.ok,
|
|
9028
9165
|
error: outcome.error
|
|
9029
9166
|
};
|
|
@@ -9033,40 +9170,16 @@ var init_service_restart = __esm(() => {
|
|
|
9033
9170
|
init_shared();
|
|
9034
9171
|
init_service();
|
|
9035
9172
|
defaultRunner = { run };
|
|
9036
|
-
DEFAULT_SYSTEMD_DIR =
|
|
9037
|
-
DEFAULT_LAUNCHD_DIR =
|
|
9038
|
-
});
|
|
9039
|
-
|
|
9040
|
-
// backend/src/lib/log.ts
|
|
9041
|
-
function ts() {
|
|
9042
|
-
return new Date().toISOString().slice(11, 23);
|
|
9043
|
-
}
|
|
9044
|
-
var DEBUG, log2;
|
|
9045
|
-
var init_log = __esm(() => {
|
|
9046
|
-
DEBUG = Bun.env.WEBMUX_DEBUG === "1";
|
|
9047
|
-
log2 = {
|
|
9048
|
-
info(msg) {
|
|
9049
|
-
console.log(`[${ts()}] ${msg}`);
|
|
9050
|
-
},
|
|
9051
|
-
debug(msg) {
|
|
9052
|
-
if (DEBUG)
|
|
9053
|
-
console.log(`[${ts()}] ${msg}`);
|
|
9054
|
-
},
|
|
9055
|
-
warn(msg) {
|
|
9056
|
-
console.warn(`[${ts()}] ${msg}`);
|
|
9057
|
-
},
|
|
9058
|
-
error(msg, err) {
|
|
9059
|
-
err !== undefined ? console.error(`[${ts()}] ${msg}`, err) : console.error(`[${ts()}] ${msg}`);
|
|
9060
|
-
}
|
|
9061
|
-
};
|
|
9173
|
+
DEFAULT_SYSTEMD_DIR = join7(homedir3(), ".config", "systemd", "user");
|
|
9174
|
+
DEFAULT_LAUNCHD_DIR = join7(homedir3(), "Library", "LaunchAgents");
|
|
9062
9175
|
});
|
|
9063
9176
|
|
|
9064
9177
|
// backend/src/adapters/instance-registry.ts
|
|
9065
|
-
import { mkdirSync as
|
|
9066
|
-
import { homedir as
|
|
9067
|
-
import { join as
|
|
9178
|
+
import { mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9179
|
+
import { homedir as homedir4 } from "os";
|
|
9180
|
+
import { join as join8 } from "path";
|
|
9068
9181
|
function defaultRegistryDir() {
|
|
9069
|
-
return
|
|
9182
|
+
return join8(homedir4(), ".webmux", "instances");
|
|
9070
9183
|
}
|
|
9071
9184
|
function isAlive(pid) {
|
|
9072
9185
|
try {
|
|
@@ -9084,14 +9197,14 @@ function isInstanceEntry(value) {
|
|
|
9084
9197
|
}
|
|
9085
9198
|
function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
9086
9199
|
function ensureDir() {
|
|
9087
|
-
|
|
9200
|
+
mkdirSync3(dir, { recursive: true });
|
|
9088
9201
|
}
|
|
9089
9202
|
function entryPath(port) {
|
|
9090
|
-
return
|
|
9203
|
+
return join8(dir, `${port}.json`);
|
|
9091
9204
|
}
|
|
9092
9205
|
function readEntry(filename) {
|
|
9093
9206
|
try {
|
|
9094
|
-
const raw =
|
|
9207
|
+
const raw = readFileSync6(join8(dir, filename), "utf8");
|
|
9095
9208
|
const parsed = JSON.parse(raw);
|
|
9096
9209
|
return isInstanceEntry(parsed) ? parsed : null;
|
|
9097
9210
|
} catch {
|
|
@@ -9105,8 +9218,8 @@ function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
|
9105
9218
|
const tmpPath = `${finalPath}.${process.pid}.${Date.now()}.tmp`;
|
|
9106
9219
|
const text = `${JSON.stringify(entry, null, 2)}
|
|
9107
9220
|
`;
|
|
9108
|
-
|
|
9109
|
-
|
|
9221
|
+
writeFileSync2(tmpPath, text);
|
|
9222
|
+
renameSync2(tmpPath, finalPath);
|
|
9110
9223
|
},
|
|
9111
9224
|
deregister(port, expectedPid) {
|
|
9112
9225
|
if (expectedPid !== undefined) {
|
|
@@ -9139,7 +9252,7 @@ function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
|
9139
9252
|
continue;
|
|
9140
9253
|
if (!isAlive(entry.pid)) {
|
|
9141
9254
|
try {
|
|
9142
|
-
unlinkSync2(
|
|
9255
|
+
unlinkSync2(join8(dir, filename));
|
|
9143
9256
|
} catch {}
|
|
9144
9257
|
continue;
|
|
9145
9258
|
}
|
|
@@ -11322,7 +11435,7 @@ var WORKTREE_META_SCHEMA_VERSION = 1, WORKTREE_ARCHIVE_STATE_VERSION = 1, OPEN_S
|
|
|
11322
11435
|
|
|
11323
11436
|
// backend/src/adapters/fs.ts
|
|
11324
11437
|
import { mkdir } from "fs/promises";
|
|
11325
|
-
import { join as
|
|
11438
|
+
import { join as join9 } from "path";
|
|
11326
11439
|
function stringifyAllocatedPorts(ports) {
|
|
11327
11440
|
const entries = Object.entries(ports).map(([key, value]) => [key, String(value)]);
|
|
11328
11441
|
return Object.fromEntries(entries);
|
|
@@ -11354,28 +11467,28 @@ function parseDotenv(content) {
|
|
|
11354
11467
|
}
|
|
11355
11468
|
async function loadDotenvLocal(worktreePath) {
|
|
11356
11469
|
try {
|
|
11357
|
-
const content = await Bun.file(
|
|
11470
|
+
const content = await Bun.file(join9(worktreePath, ".env.local")).text();
|
|
11358
11471
|
return parseDotenv(content);
|
|
11359
11472
|
} catch {
|
|
11360
11473
|
return {};
|
|
11361
11474
|
}
|
|
11362
11475
|
}
|
|
11363
11476
|
function getWorktreeStoragePaths(gitDir) {
|
|
11364
|
-
const webmuxDir =
|
|
11477
|
+
const webmuxDir = join9(gitDir, "webmux");
|
|
11365
11478
|
return {
|
|
11366
11479
|
gitDir,
|
|
11367
11480
|
webmuxDir,
|
|
11368
|
-
metaPath:
|
|
11369
|
-
runtimeEnvPath:
|
|
11370
|
-
controlEnvPath:
|
|
11371
|
-
prsPath:
|
|
11481
|
+
metaPath: join9(webmuxDir, "meta.json"),
|
|
11482
|
+
runtimeEnvPath: join9(webmuxDir, "runtime.env"),
|
|
11483
|
+
controlEnvPath: join9(webmuxDir, "control.env"),
|
|
11484
|
+
prsPath: join9(webmuxDir, "prs.json")
|
|
11372
11485
|
};
|
|
11373
11486
|
}
|
|
11374
11487
|
function getProjectArchiveStatePath(gitDir) {
|
|
11375
|
-
return
|
|
11488
|
+
return join9(gitDir, "webmux", "archive.json");
|
|
11376
11489
|
}
|
|
11377
11490
|
function getProjectOpenSessionsStatePath(gitDir) {
|
|
11378
|
-
return
|
|
11491
|
+
return join9(gitDir, "webmux", "open-sessions.json");
|
|
11379
11492
|
}
|
|
11380
11493
|
async function ensureWorktreeStorageDirs(gitDir) {
|
|
11381
11494
|
const paths = getWorktreeStoragePaths(gitDir);
|
|
@@ -18783,8 +18896,8 @@ var init_dist5 = __esm(() => {
|
|
|
18783
18896
|
});
|
|
18784
18897
|
|
|
18785
18898
|
// backend/src/adapters/config.ts
|
|
18786
|
-
import { readFileSync as
|
|
18787
|
-
import { dirname as
|
|
18899
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
18900
|
+
import { dirname as dirname4, join as join10, resolve as resolve7 } from "path";
|
|
18788
18901
|
function DEFAULT_ONESHOT_SYSTEM_PROMPT() {
|
|
18789
18902
|
return [
|
|
18790
18903
|
"You are running in webmux ONESHOT mode. There is NO interactive user \u2014 nobody is watching the chat or will respond to questions, approvals, or status checks. Any message asking the user to review, approve, confirm, take a look, or 'let you know' is wasted output: it will not be answered.",
|
|
@@ -19013,10 +19126,10 @@ function getDefaultProfileName(config) {
|
|
|
19013
19126
|
return Object.keys(config.profiles)[0] ?? "default";
|
|
19014
19127
|
}
|
|
19015
19128
|
function readConfigFile(root) {
|
|
19016
|
-
return
|
|
19129
|
+
return readFileSync7(join10(root, ".webmux.yaml"), "utf8");
|
|
19017
19130
|
}
|
|
19018
19131
|
function readLocalConfigFile(root) {
|
|
19019
|
-
return
|
|
19132
|
+
return readFileSync7(join10(root, ".webmux.local.yaml"), "utf8");
|
|
19020
19133
|
}
|
|
19021
19134
|
function parseConfigDocument(text) {
|
|
19022
19135
|
const parsed = $parse(text);
|
|
@@ -19164,7 +19277,7 @@ function projectRoot(dir) {
|
|
|
19164
19277
|
if (result.exitCode !== 0)
|
|
19165
19278
|
return gitRoot2(dir);
|
|
19166
19279
|
const commonDir = new TextDecoder().decode(result.stdout).trim();
|
|
19167
|
-
return commonDir ?
|
|
19280
|
+
return commonDir ? dirname4(resolve7(dir, commonDir)) : gitRoot2(dir);
|
|
19168
19281
|
}
|
|
19169
19282
|
function loadConfig(dir, options = {}) {
|
|
19170
19283
|
const root = options.resolvedRoot ? dir : projectRoot(dir);
|
|
@@ -19243,7 +19356,7 @@ var init_config = __esm(() => {
|
|
|
19243
19356
|
|
|
19244
19357
|
// backend/src/adapters/control-token.ts
|
|
19245
19358
|
import { chmod, mkdir as mkdir2 } from "fs/promises";
|
|
19246
|
-
import { dirname as
|
|
19359
|
+
import { dirname as dirname5 } from "path";
|
|
19247
19360
|
async function loadControlToken() {
|
|
19248
19361
|
if (cachedToken)
|
|
19249
19362
|
return cachedToken;
|
|
@@ -19253,7 +19366,7 @@ async function loadControlToken() {
|
|
|
19253
19366
|
return cachedToken;
|
|
19254
19367
|
}
|
|
19255
19368
|
const controlToken = crypto.randomUUID();
|
|
19256
|
-
await mkdir2(
|
|
19369
|
+
await mkdir2(dirname5(CONTROL_TOKEN_PATH), { recursive: true });
|
|
19257
19370
|
await Bun.write(CONTROL_TOKEN_PATH, controlToken);
|
|
19258
19371
|
await chmod(CONTROL_TOKEN_PATH, 384);
|
|
19259
19372
|
cachedToken = controlToken;
|
|
@@ -19521,7 +19634,7 @@ var init_docker = __esm(() => {
|
|
|
19521
19634
|
});
|
|
19522
19635
|
|
|
19523
19636
|
// backend/src/adapters/hooks.ts
|
|
19524
|
-
import { join as
|
|
19637
|
+
import { join as join11 } from "path";
|
|
19525
19638
|
function buildErrorMessage(name, exitCode, stdout2, stderr) {
|
|
19526
19639
|
const output = stderr.trim() || stdout2.trim();
|
|
19527
19640
|
if (output) {
|
|
@@ -19546,7 +19659,7 @@ class BunLifecycleHookRunner {
|
|
|
19546
19659
|
return this.direnvAvailable;
|
|
19547
19660
|
}
|
|
19548
19661
|
async buildCommand(cwd, command) {
|
|
19549
|
-
if (this.checkDirenv() && await Bun.file(
|
|
19662
|
+
if (this.checkDirenv() && await Bun.file(join11(cwd, ".envrc")).exists()) {
|
|
19550
19663
|
Bun.spawnSync(["direnv", "allow"], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
19551
19664
|
return ["direnv", "exec", cwd, "bash", "-c", command];
|
|
19552
19665
|
}
|
|
@@ -19653,7 +19766,7 @@ var init_claude_cli = __esm(() => {
|
|
|
19653
19766
|
|
|
19654
19767
|
// backend/src/adapters/session-discovery.ts
|
|
19655
19768
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
19656
|
-
import { basename as basename5, join as
|
|
19769
|
+
import { basename as basename5, join as join12 } from "path";
|
|
19657
19770
|
function home() {
|
|
19658
19771
|
const value = Bun.env.HOME;
|
|
19659
19772
|
if (!value)
|
|
@@ -19664,10 +19777,10 @@ function newestFirst(sessions) {
|
|
|
19664
19777
|
return sessions.sort((left, right) => right.mtimeMs - left.mtimeMs).map((entry) => entry.sessionId);
|
|
19665
19778
|
}
|
|
19666
19779
|
async function listClaudeSessionIds(cwd) {
|
|
19667
|
-
const dir =
|
|
19780
|
+
const dir = join12(home(), ".claude", "projects", encodeClaudeProjectDir(cwd));
|
|
19668
19781
|
const names = await readdir(dir).catch(() => []);
|
|
19669
19782
|
const stamped = await Promise.all(names.filter((name) => name.endsWith(".jsonl")).map(async (name) => {
|
|
19670
|
-
const info = await stat2(
|
|
19783
|
+
const info = await stat2(join12(dir, name)).catch(() => null);
|
|
19671
19784
|
return info ? { sessionId: basename5(name, ".jsonl"), mtimeMs: info.mtimeMs } : null;
|
|
19672
19785
|
}));
|
|
19673
19786
|
return newestFirst(stamped.filter((entry) => entry !== null));
|
|
@@ -19689,14 +19802,14 @@ async function readCodexSessionCwdId(path) {
|
|
|
19689
19802
|
}
|
|
19690
19803
|
}
|
|
19691
19804
|
async function listCodexSessionIds(cwd) {
|
|
19692
|
-
const root =
|
|
19805
|
+
const root = join12(home(), ".codex", "sessions");
|
|
19693
19806
|
const relPaths = await readdir(root, { recursive: true }).catch(() => []);
|
|
19694
19807
|
const rollouts = relPaths.filter((rel) => {
|
|
19695
19808
|
const name = basename5(rel);
|
|
19696
19809
|
return name.startsWith("rollout-") && name.endsWith(".jsonl");
|
|
19697
19810
|
});
|
|
19698
19811
|
const stamped = await Promise.all(rollouts.map(async (rel) => {
|
|
19699
|
-
const path =
|
|
19812
|
+
const path = join12(root, rel);
|
|
19700
19813
|
const meta = await readCodexSessionCwdId(path);
|
|
19701
19814
|
if (!meta || meta.cwd !== cwd)
|
|
19702
19815
|
return null;
|
|
@@ -19886,7 +19999,7 @@ var init_archive_state_service = __esm(() => {
|
|
|
19886
19999
|
|
|
19887
20000
|
// backend/src/adapters/agent-runtime.ts
|
|
19888
20001
|
import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
|
|
19889
|
-
import { dirname as
|
|
20002
|
+
import { dirname as dirname6, join as join13, resolve as resolve8 } from "path";
|
|
19890
20003
|
function shellQuote(value) {
|
|
19891
20004
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
19892
20005
|
}
|
|
@@ -20305,7 +20418,7 @@ async function mergeCodexHooksFile(hooksPath, hookSettings, agentCtlPath) {
|
|
|
20305
20418
|
}
|
|
20306
20419
|
async function resolveGitCommonDir(gitDir) {
|
|
20307
20420
|
try {
|
|
20308
|
-
const commonDir = (await Bun.file(
|
|
20421
|
+
const commonDir = (await Bun.file(join13(gitDir, "commondir")).text()).trim();
|
|
20309
20422
|
if (!commonDir)
|
|
20310
20423
|
return gitDir;
|
|
20311
20424
|
return commonDir.startsWith("/") ? commonDir : resolve8(gitDir, commonDir);
|
|
@@ -20315,7 +20428,7 @@ async function resolveGitCommonDir(gitDir) {
|
|
|
20315
20428
|
}
|
|
20316
20429
|
async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
20317
20430
|
const commonDir = await resolveGitCommonDir(gitDir);
|
|
20318
|
-
const excludePath =
|
|
20431
|
+
const excludePath = join13(commonDir, "info", "exclude");
|
|
20319
20432
|
let existing = "";
|
|
20320
20433
|
try {
|
|
20321
20434
|
existing = await Bun.file(excludePath).text();
|
|
@@ -20325,7 +20438,7 @@ async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
|
20325
20438
|
const lines = existing.split(/\r?\n/).map((line) => line.trim());
|
|
20326
20439
|
if (lines.includes(GENERATED_CODEX_HOOKS_EXCLUDE))
|
|
20327
20440
|
return;
|
|
20328
|
-
await mkdir3(
|
|
20441
|
+
await mkdir3(dirname6(excludePath), { recursive: true });
|
|
20329
20442
|
const separator = existing.length > 0 && !existing.endsWith(`
|
|
20330
20443
|
`) ? `
|
|
20331
20444
|
` : "";
|
|
@@ -20335,12 +20448,12 @@ async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
|
20335
20448
|
async function ensureAgentRuntimeArtifacts(input) {
|
|
20336
20449
|
const storagePaths = getWorktreeStoragePaths(input.gitDir);
|
|
20337
20450
|
const artifacts = {
|
|
20338
|
-
agentCtlPath:
|
|
20339
|
-
claudeSettingsPath:
|
|
20340
|
-
codexHooksPath:
|
|
20451
|
+
agentCtlPath: join13(storagePaths.webmuxDir, "webmux-agentctl"),
|
|
20452
|
+
claudeSettingsPath: join13(input.worktreePath, ".claude", "settings.local.json"),
|
|
20453
|
+
codexHooksPath: join13(input.worktreePath, ".codex", "hooks.json")
|
|
20341
20454
|
};
|
|
20342
|
-
await mkdir3(
|
|
20343
|
-
await mkdir3(
|
|
20455
|
+
await mkdir3(dirname6(artifacts.claudeSettingsPath), { recursive: true });
|
|
20456
|
+
await mkdir3(dirname6(artifacts.codexHooksPath), { recursive: true });
|
|
20344
20457
|
await Bun.write(artifacts.agentCtlPath, buildAgentCtlScript());
|
|
20345
20458
|
await chmod2(artifacts.agentCtlPath, 493);
|
|
20346
20459
|
const hookSettings = buildClaudeHookSettings(artifacts);
|
|
@@ -20872,7 +20985,7 @@ var init_worktree_service = __esm(() => {
|
|
|
20872
20985
|
// backend/src/services/lifecycle-service.ts
|
|
20873
20986
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
20874
20987
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
20875
|
-
import { dirname as
|
|
20988
|
+
import { dirname as dirname7, resolve as resolve10 } from "path";
|
|
20876
20989
|
function toErrorMessage2(error) {
|
|
20877
20990
|
return error instanceof Error ? error.message : String(error);
|
|
20878
20991
|
}
|
|
@@ -21792,7 +21905,7 @@ ${oneshotPrompt}` : oneshotPrompt ?? baseSystemPrompt;
|
|
|
21792
21905
|
...createProgressBase,
|
|
21793
21906
|
phase: "creating_worktree"
|
|
21794
21907
|
});
|
|
21795
|
-
await mkdir4(
|
|
21908
|
+
await mkdir4(dirname7(worktreePath), { recursive: true });
|
|
21796
21909
|
initialized = await createManagedWorktree({
|
|
21797
21910
|
repoRoot: this.deps.projectRoot,
|
|
21798
21911
|
worktreePath,
|
|
@@ -23346,13 +23459,13 @@ var init_worktree_commands = __esm(() => {
|
|
|
23346
23459
|
});
|
|
23347
23460
|
|
|
23348
23461
|
// bin/src/webmux.ts
|
|
23349
|
-
import { resolve as resolve13, dirname as
|
|
23462
|
+
import { resolve as resolve13, dirname as dirname8, join as join14 } from "path";
|
|
23350
23463
|
import { existsSync as existsSync6 } from "fs";
|
|
23351
23464
|
import { fileURLToPath } from "url";
|
|
23352
23465
|
// package.json
|
|
23353
23466
|
var package_default = {
|
|
23354
23467
|
name: "webmux",
|
|
23355
|
-
version: "0.
|
|
23468
|
+
version: "0.41.0",
|
|
23356
23469
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
23357
23470
|
type: "module",
|
|
23358
23471
|
repository: {
|
|
@@ -23409,7 +23522,7 @@ var package_default = {
|
|
|
23409
23522
|
};
|
|
23410
23523
|
|
|
23411
23524
|
// bin/src/webmux.ts
|
|
23412
|
-
var PKG_ROOT = resolve13(
|
|
23525
|
+
var PKG_ROOT = resolve13(dirname8(fileURLToPath(import.meta.url)), "..");
|
|
23413
23526
|
function usage2() {
|
|
23414
23527
|
console.log(`
|
|
23415
23528
|
webmux \u2014 Dev dashboard for managing Git worktrees
|
|
@@ -23646,6 +23759,8 @@ Refreshing ${services.length} installed webmux service(s) to pick up the new ver
|
|
|
23646
23759
|
const parts = [];
|
|
23647
23760
|
if (outcome.regenerated)
|
|
23648
23761
|
parts.push("regenerated unit");
|
|
23762
|
+
if (outcome.migratedProject)
|
|
23763
|
+
parts.push(`migrated served project ${outcome.migratedProject}`);
|
|
23649
23764
|
if (outcome.restarted)
|
|
23650
23765
|
parts.push("restarted");
|
|
23651
23766
|
if (!outcome.regenerated && !outcome.restarted && !outcome.error)
|
|
@@ -23733,8 +23848,8 @@ Refreshing ${services.length} installed webmux service(s) to pick up the new ver
|
|
|
23733
23848
|
}
|
|
23734
23849
|
process.on("SIGINT", cleanup);
|
|
23735
23850
|
process.on("SIGTERM", cleanup);
|
|
23736
|
-
const backendEntry =
|
|
23737
|
-
const staticDir =
|
|
23851
|
+
const backendEntry = join14(PKG_ROOT, "backend", "dist", "server.js");
|
|
23852
|
+
const staticDir = join14(PKG_ROOT, "frontend", "dist");
|
|
23738
23853
|
if (!existsSync6(staticDir)) {
|
|
23739
23854
|
console.error(`Error: frontend/dist/ not found. Run 'bun run build' first.`);
|
|
23740
23855
|
process.exit(1);
|