mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-12 19:06:06 +02:00
Worktree renderer ports are `5174 + cksum(path) % 1000`, a 5174-6173 window that contains exactly one port Chromium refuses to navigate to: 6000, the X11 port on its restricted list. A worktree whose path hashes to offset 826 gets a healthy Vite server on 6000 and an Electron window that fails the load with ERR_UNSAFE_PORT -- so it reads as a renderer bug, not a port one, and the only way out was setting DESKTOP_RENDERER_PORT by hand. Restricted ports in the window are now remapped into the block immediately above it (6000 -> 6174). Sending them past the end rather than shifting them by one keeps the offset -> port mapping injective, so two worktrees still cannot land on the same port and race for it. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
127 lines
4.8 KiB
JavaScript
127 lines
4.8 KiB
JavaScript
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
appSuffixForPath,
|
|
applyWorktreeDevEnv,
|
|
cksum,
|
|
offsetForPath,
|
|
rendererPortForPath,
|
|
} from "./worktree-dev-env.mjs";
|
|
|
|
const cleanups = [];
|
|
afterEach(() => {
|
|
while (cleanups.length) cleanups.pop()();
|
|
});
|
|
|
|
function tmpRoot(kind /* "file" | "dir" | "none" */) {
|
|
const root = mkdtempSync(join(tmpdir(), "wt-"));
|
|
cleanups.push(() => rmSync(root, { recursive: true, force: true }));
|
|
if (kind === "file") writeFileSync(join(root, ".git"), "gitdir: /elsewhere\n");
|
|
else if (kind === "dir") mkdirSync(join(root, ".git"));
|
|
return root;
|
|
}
|
|
|
|
describe("worktree-dev-env", () => {
|
|
it("cksum is byte-compatible with coreutils cksum(1)", () => {
|
|
// `printf '%s' "/tmp/foo" | cksum` → 427878967 8
|
|
expect(cksum(Buffer.from("/tmp/foo"))).toBe(427878967);
|
|
// `printf '' | cksum` → 4294967295 0
|
|
expect(cksum(Buffer.from(""))).toBe(4294967295);
|
|
});
|
|
|
|
it("derives the offset from the path, mod 1000", () => {
|
|
expect(offsetForPath("/tmp/foo")).toBe(427878967 % 1000);
|
|
});
|
|
|
|
it("renderer port is 5174 + offset (5173 reserved for the primary checkout)", () => {
|
|
expect(rendererPortForPath("/tmp/foo")).toBe(5174 + (427878967 % 1000));
|
|
});
|
|
|
|
it("never reuses 5173 even when the offset is 0", () => {
|
|
// POSIX cksum("/tmp/multica-3494") === 1189739000, % 1000 === 0
|
|
expect(offsetForPath("/tmp/multica-3494")).toBe(0);
|
|
expect(rendererPortForPath("/tmp/multica-3494")).toBe(5174);
|
|
expect(rendererPortForPath("/tmp/multica-3494")).not.toBe(5173);
|
|
});
|
|
|
|
it("skips 6000, which Chromium refuses to load (ERR_UNSAFE_PORT)", () => {
|
|
// POSIX cksum("/tmp/wt-570") === 109908826, % 1000 === 826 → 5174 + 826 === 6000
|
|
expect(offsetForPath("/tmp/wt-570")).toBe(826);
|
|
expect(rendererPortForPath("/tmp/wt-570")).toBe(6174);
|
|
});
|
|
|
|
it("stays collision-free across every offset while skipping restricted ports", () => {
|
|
// The remap must stay injective: two worktrees sharing a port means the
|
|
// second Electron dies on EADDRINUSE. Cover all 1000 offsets with real
|
|
// paths so this exercises rendererPortForPath rather than restating it.
|
|
const pathForOffset = new Map();
|
|
for (let i = 0; pathForOffset.size < 1000; i++) {
|
|
const path = `/tmp/wt-${i}`;
|
|
const offset = offsetForPath(path);
|
|
if (!pathForOffset.has(offset)) pathForOffset.set(offset, path);
|
|
}
|
|
|
|
const ports = new Set(
|
|
[...pathForOffset.values()].map((path) => rendererPortForPath(path)),
|
|
);
|
|
expect(ports.size).toBe(1000);
|
|
expect(ports.has(6000)).toBe(false);
|
|
expect(ports.has(5173)).toBe(false);
|
|
});
|
|
|
|
it("suffix is '<folder>-<offset>' so it stays recognizable and unique", () => {
|
|
expect(appSuffixForPath("/work/MUL-3724_Desktop")).toBe(
|
|
`mul-3724-desktop-${offsetForPath("/work/MUL-3724_Desktop")}`,
|
|
);
|
|
expect(appSuffixForPath("/work/feat/some thing")).toBe(
|
|
`some-thing-${offsetForPath("/work/feat/some thing")}`,
|
|
);
|
|
// empty/non-ascii slug falls back to "worktree", still disambiguated by offset
|
|
expect(appSuffixForPath("/work/___")).toBe(`worktree-${offsetForPath("/work/___")}`);
|
|
});
|
|
|
|
it("disambiguates worktrees that share a folder name at different paths", () => {
|
|
// Same basename "multica", different parent dirs → different offsets/suffixes,
|
|
// so each gets its own single-instance lock.
|
|
expect(offsetForPath("/tmp/a/multica")).not.toBe(offsetForPath("/tmp/b/multica"));
|
|
expect(appSuffixForPath("/tmp/a/multica")).not.toBe(
|
|
appSuffixForPath("/tmp/b/multica"),
|
|
);
|
|
});
|
|
|
|
it("auto-isolates a linked worktree (.git is a file)", () => {
|
|
const root = tmpRoot("file");
|
|
const env = {};
|
|
applyWorktreeDevEnv(env, { root });
|
|
expect(env.DESKTOP_RENDERER_PORT).toBe(String(rendererPortForPath(root)));
|
|
expect(env.DESKTOP_APP_SUFFIX).toBe(appSuffixForPath(root));
|
|
});
|
|
|
|
it("leaves the primary checkout untouched (.git is a dir)", () => {
|
|
const root = tmpRoot("dir");
|
|
const env = {};
|
|
applyWorktreeDevEnv(env, { root });
|
|
expect(env.DESKTOP_RENDERER_PORT).toBeUndefined();
|
|
expect(env.DESKTOP_APP_SUFFIX).toBeUndefined();
|
|
});
|
|
|
|
it("respects explicit env overrides", () => {
|
|
const root = tmpRoot("file");
|
|
const env = { DESKTOP_RENDERER_PORT: "9999", DESKTOP_APP_SUFFIX: "manual" };
|
|
applyWorktreeDevEnv(env, { root });
|
|
expect(env.DESKTOP_RENDERER_PORT).toBe("9999");
|
|
expect(env.DESKTOP_APP_SUFFIX).toBe("manual");
|
|
});
|
|
|
|
it("fills only the missing knob when one is set explicitly", () => {
|
|
const root = tmpRoot("file");
|
|
const env = { DESKTOP_RENDERER_PORT: "9999" };
|
|
applyWorktreeDevEnv(env, { root });
|
|
expect(env.DESKTOP_RENDERER_PORT).toBe("9999");
|
|
expect(env.DESKTOP_APP_SUFFIX).toBe(appSuffixForPath(root));
|
|
});
|
|
});
|