webmux 0.15.0 → 0.17.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 +100 -2
- package/bin/webmux.js +16 -3
- package/frontend/dist/assets/index-CMdCoXme.js +148 -0
- package/frontend/dist/assets/index-nt-8CG4j.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-Bt0UjBTn.css +0 -32
- package/frontend/dist/assets/index-C9lpouvy.js +0 -35
package/backend/dist/server.js
CHANGED
|
@@ -6906,6 +6906,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6906
6906
|
|
|
6907
6907
|
// backend/src/server.ts
|
|
6908
6908
|
import { join as join6, resolve as resolve6 } from "path";
|
|
6909
|
+
import { mkdirSync } from "fs";
|
|
6909
6910
|
import { networkInterfaces } from "os";
|
|
6910
6911
|
|
|
6911
6912
|
// backend/src/lib/log.ts
|
|
@@ -7482,15 +7483,17 @@ function loadLocalProjectConfigOverlay(root) {
|
|
|
7482
7483
|
try {
|
|
7483
7484
|
const text = readLocalConfigFile(root).trim();
|
|
7484
7485
|
if (!text) {
|
|
7485
|
-
return { profiles: {}, lifecycleHooks: {} };
|
|
7486
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
7486
7487
|
}
|
|
7487
7488
|
const parsed = parseConfigDocument(text);
|
|
7489
|
+
const ws = isRecord(parsed.workspace) ? parsed.workspace : null;
|
|
7488
7490
|
return {
|
|
7491
|
+
worktreeRoot: ws && typeof ws.worktreeRoot === "string" ? ws.worktreeRoot : null,
|
|
7489
7492
|
profiles: parseProfiles(parsed.profiles, false),
|
|
7490
7493
|
lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks)
|
|
7491
7494
|
};
|
|
7492
7495
|
} catch {
|
|
7493
|
-
return { profiles: {}, lifecycleHooks: {} };
|
|
7496
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
7494
7497
|
}
|
|
7495
7498
|
}
|
|
7496
7499
|
function mergeHookCommand(projectCommand, localCommand) {
|
|
@@ -7534,6 +7537,9 @@ function loadConfig(dir, options = {}) {
|
|
|
7534
7537
|
const localOverlay = loadLocalProjectConfigOverlay(root);
|
|
7535
7538
|
return {
|
|
7536
7539
|
...projectConfig,
|
|
7540
|
+
...localOverlay.worktreeRoot !== null ? {
|
|
7541
|
+
workspace: { ...projectConfig.workspace, worktreeRoot: localOverlay.worktreeRoot }
|
|
7542
|
+
} : {},
|
|
7537
7543
|
profiles: {
|
|
7538
7544
|
...cloneProfiles(projectConfig.profiles),
|
|
7539
7545
|
...cloneProfiles(localOverlay.profiles)
|
|
@@ -8610,6 +8616,14 @@ class BunGitGateway {
|
|
|
8610
8616
|
currentBranch(repoRoot) {
|
|
8611
8617
|
return runGit(["branch", "--show-current"], repoRoot);
|
|
8612
8618
|
}
|
|
8619
|
+
readDiff(cwd) {
|
|
8620
|
+
const result = tryRunGit(["diff", "HEAD", "--no-color"], cwd);
|
|
8621
|
+
return result.ok ? result.stdout : "";
|
|
8622
|
+
}
|
|
8623
|
+
readUnpushedDiff(cwd) {
|
|
8624
|
+
const result = tryRunGit(["diff", "@{upstream}..HEAD", "--no-color"], cwd);
|
|
8625
|
+
return result.ok ? result.stdout : "";
|
|
8626
|
+
}
|
|
8613
8627
|
}
|
|
8614
8628
|
|
|
8615
8629
|
// backend/src/domain/model.ts
|
|
@@ -10999,6 +11013,27 @@ async function apiGetLinearIssues() {
|
|
|
10999
11013
|
return errorResponse(result.error, 502);
|
|
11000
11014
|
return jsonResponse(result.data);
|
|
11001
11015
|
}
|
|
11016
|
+
var MAX_DIFF_BYTES = 200 * 1024;
|
|
11017
|
+
async function apiGetWorktreeDiff(name) {
|
|
11018
|
+
await reconciliationService.reconcile(PROJECT_DIR);
|
|
11019
|
+
const state = projectRuntime.getWorktreeByBranch(name);
|
|
11020
|
+
if (!state)
|
|
11021
|
+
return errorResponse(`Worktree not found: ${name}`, 404);
|
|
11022
|
+
const uncommitted = git.readDiff(state.path);
|
|
11023
|
+
const unpushed = git.readUnpushedDiff(state.path);
|
|
11024
|
+
function cap(raw) {
|
|
11025
|
+
const truncated = raw.length > MAX_DIFF_BYTES;
|
|
11026
|
+
return { diff: truncated ? raw.slice(0, MAX_DIFF_BYTES) : raw, truncated };
|
|
11027
|
+
}
|
|
11028
|
+
const u = cap(uncommitted);
|
|
11029
|
+
const p = cap(unpushed);
|
|
11030
|
+
return jsonResponse({
|
|
11031
|
+
uncommitted: u.diff,
|
|
11032
|
+
uncommittedTruncated: u.truncated,
|
|
11033
|
+
unpushed: p.diff,
|
|
11034
|
+
unpushedTruncated: p.truncated
|
|
11035
|
+
});
|
|
11036
|
+
}
|
|
11002
11037
|
async function apiCiLogs(runId) {
|
|
11003
11038
|
if (!/^\d+$/.test(runId))
|
|
11004
11039
|
return errorResponse("Invalid run ID", 400);
|
|
@@ -11014,6 +11049,53 @@ async function apiCiLogs(runId) {
|
|
|
11014
11049
|
const stderr = (await new Response(proc.stderr).text()).trim();
|
|
11015
11050
|
return errorResponse(`Failed to fetch logs: ${stderr || "unknown error"}`, 502);
|
|
11016
11051
|
}
|
|
11052
|
+
var ALLOWED_IMAGE_TYPES = new Set([
|
|
11053
|
+
"image/png",
|
|
11054
|
+
"image/jpeg",
|
|
11055
|
+
"image/gif",
|
|
11056
|
+
"image/webp"
|
|
11057
|
+
]);
|
|
11058
|
+
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
11059
|
+
function sanitizeFilename(name) {
|
|
11060
|
+
const base = name.split("/").pop()?.split("\\").pop() ?? "upload";
|
|
11061
|
+
return base.replace(/[^a-zA-Z0-9._-]/g, "_") || "upload";
|
|
11062
|
+
}
|
|
11063
|
+
async function apiUploadFiles(name, req) {
|
|
11064
|
+
const state = projectRuntime.getWorktreeByBranch(name);
|
|
11065
|
+
if (!state)
|
|
11066
|
+
return errorResponse(`Worktree not found: ${name}`, 404);
|
|
11067
|
+
let formData;
|
|
11068
|
+
try {
|
|
11069
|
+
formData = await req.formData();
|
|
11070
|
+
} catch {
|
|
11071
|
+
return errorResponse("Invalid multipart form data", 400);
|
|
11072
|
+
}
|
|
11073
|
+
const entries = formData.getAll("files");
|
|
11074
|
+
if (entries.length === 0)
|
|
11075
|
+
return errorResponse("No files provided", 400);
|
|
11076
|
+
const uploadDir = `/tmp/webmux-uploads/${sanitizeFilename(name)}`;
|
|
11077
|
+
mkdirSync(uploadDir, { recursive: true });
|
|
11078
|
+
const results = [];
|
|
11079
|
+
for (const entry of entries) {
|
|
11080
|
+
if (!(entry instanceof File))
|
|
11081
|
+
continue;
|
|
11082
|
+
if (!ALLOWED_IMAGE_TYPES.has(entry.type)) {
|
|
11083
|
+
return errorResponse(`Unsupported file type: ${entry.type}`, 400);
|
|
11084
|
+
}
|
|
11085
|
+
if (entry.size > MAX_FILE_SIZE) {
|
|
11086
|
+
return errorResponse(`File too large: ${entry.name} (max 10MB)`, 400);
|
|
11087
|
+
}
|
|
11088
|
+
const safeName = `${Date.now()}_${sanitizeFilename(entry.name)}`;
|
|
11089
|
+
const destPath = join6(uploadDir, safeName);
|
|
11090
|
+
if (!resolve6(destPath).startsWith(uploadDir + "/")) {
|
|
11091
|
+
return errorResponse("Invalid filename", 400);
|
|
11092
|
+
}
|
|
11093
|
+
await Bun.write(destPath, entry);
|
|
11094
|
+
results.push({ path: destPath });
|
|
11095
|
+
}
|
|
11096
|
+
log.info(`[upload] branch=${name} files=${results.length}`);
|
|
11097
|
+
return jsonResponse({ files: results });
|
|
11098
|
+
}
|
|
11017
11099
|
Bun.serve({
|
|
11018
11100
|
port: PORT,
|
|
11019
11101
|
idleTimeout: 255,
|
|
@@ -11069,6 +11151,14 @@ Bun.serve({
|
|
|
11069
11151
|
return catching(`POST /api/worktrees/${name}/send`, () => apiSendPrompt(name, req));
|
|
11070
11152
|
}
|
|
11071
11153
|
},
|
|
11154
|
+
"/api/worktrees/:name/upload": {
|
|
11155
|
+
POST: (req) => {
|
|
11156
|
+
const name = decodeURIComponent(req.params.name);
|
|
11157
|
+
if (!isValidWorktreeName(name))
|
|
11158
|
+
return errorResponse("Invalid worktree name", 400);
|
|
11159
|
+
return catching(`POST /api/worktrees/${name}/upload`, () => apiUploadFiles(name, req));
|
|
11160
|
+
}
|
|
11161
|
+
},
|
|
11072
11162
|
"/api/worktrees/:name/merge": {
|
|
11073
11163
|
POST: (req) => {
|
|
11074
11164
|
const name = decodeURIComponent(req.params.name);
|
|
@@ -11077,6 +11167,14 @@ Bun.serve({
|
|
|
11077
11167
|
return catching(`POST /api/worktrees/${name}/merge`, () => apiMergeWorktree(name));
|
|
11078
11168
|
}
|
|
11079
11169
|
},
|
|
11170
|
+
"/api/worktrees/:name/diff": {
|
|
11171
|
+
GET: (req) => {
|
|
11172
|
+
const name = decodeURIComponent(req.params.name);
|
|
11173
|
+
if (!isValidWorktreeName(name))
|
|
11174
|
+
return errorResponse("Invalid worktree name", 400);
|
|
11175
|
+
return catching(`GET /api/worktrees/${name}/diff`, () => apiGetWorktreeDiff(name));
|
|
11176
|
+
}
|
|
11177
|
+
},
|
|
11080
11178
|
"/api/linear/issues": {
|
|
11081
11179
|
GET: () => catching("GET /api/linear/issues", () => apiGetLinearIssues())
|
|
11082
11180
|
},
|
package/bin/webmux.js
CHANGED
|
@@ -267,6 +267,14 @@ class BunGitGateway {
|
|
|
267
267
|
currentBranch(repoRoot) {
|
|
268
268
|
return runGit(["branch", "--show-current"], repoRoot);
|
|
269
269
|
}
|
|
270
|
+
readDiff(cwd) {
|
|
271
|
+
const result = tryRunGit(["diff", "HEAD", "--no-color"], cwd);
|
|
272
|
+
return result.ok ? result.stdout : "";
|
|
273
|
+
}
|
|
274
|
+
readUnpushedDiff(cwd) {
|
|
275
|
+
const result = tryRunGit(["diff", "@{upstream}..HEAD", "--no-color"], cwd);
|
|
276
|
+
return result.ok ? result.stdout : "";
|
|
277
|
+
}
|
|
270
278
|
}
|
|
271
279
|
var init_git = () => {};
|
|
272
280
|
|
|
@@ -9901,15 +9909,17 @@ function loadLocalProjectConfigOverlay(root) {
|
|
|
9901
9909
|
try {
|
|
9902
9910
|
const text = readLocalConfigFile(root).trim();
|
|
9903
9911
|
if (!text) {
|
|
9904
|
-
return { profiles: {}, lifecycleHooks: {} };
|
|
9912
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
9905
9913
|
}
|
|
9906
9914
|
const parsed = parseConfigDocument(text);
|
|
9915
|
+
const ws = isRecord3(parsed.workspace) ? parsed.workspace : null;
|
|
9907
9916
|
return {
|
|
9917
|
+
worktreeRoot: ws && typeof ws.worktreeRoot === "string" ? ws.worktreeRoot : null,
|
|
9908
9918
|
profiles: parseProfiles(parsed.profiles, false),
|
|
9909
9919
|
lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks)
|
|
9910
9920
|
};
|
|
9911
9921
|
} catch {
|
|
9912
|
-
return { profiles: {}, lifecycleHooks: {} };
|
|
9922
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
9913
9923
|
}
|
|
9914
9924
|
}
|
|
9915
9925
|
function mergeHookCommand(projectCommand, localCommand) {
|
|
@@ -9953,6 +9963,9 @@ function loadConfig(dir, options = {}) {
|
|
|
9953
9963
|
const localOverlay = loadLocalProjectConfigOverlay(root);
|
|
9954
9964
|
return {
|
|
9955
9965
|
...projectConfig,
|
|
9966
|
+
...localOverlay.worktreeRoot !== null ? {
|
|
9967
|
+
workspace: { ...projectConfig.workspace, worktreeRoot: localOverlay.worktreeRoot }
|
|
9968
|
+
} : {},
|
|
9956
9969
|
profiles: {
|
|
9957
9970
|
...cloneProfiles(projectConfig.profiles),
|
|
9958
9971
|
...cloneProfiles(localOverlay.profiles)
|
|
@@ -12444,7 +12457,7 @@ import { fileURLToPath } from "url";
|
|
|
12444
12457
|
// package.json
|
|
12445
12458
|
var package_default = {
|
|
12446
12459
|
name: "webmux",
|
|
12447
|
-
version: "0.
|
|
12460
|
+
version: "0.17.0",
|
|
12448
12461
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
12449
12462
|
type: "module",
|
|
12450
12463
|
repository: {
|