Loading files
312233141This file is pinned to the selected release. Download the original to open it in your design tool.
import { spawnSync } from "child_process";
import { join } from "path";
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, statSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import { describe, expect, it } from "vitest";
// vitest runs from cli/, so this is cli/scripts/install.sh — the file published to the CDN.
const installer = join(process.cwd(), "scripts", "install.sh");
const writeCommand = (directory: string, name: string, lines: string[]) => {
const path = join(directory, name);
writeFileSync(path, ["#!/bin/sh", ...lines, ""].join("\n"));
chmodSync(path, 0o700);
};
// What the CLI runs on Linux besides tmux — the only other command step 1 asks about.
const writeLinuxHostCommands = (directory: string) => {
writeCommand(directory, "ps", ["exit 0"]);
};
// Step 1 of the script — the host-requirements ladder — as one runnable unit.
// From the constants (RUNTIME_DIR, BIN_DIR, the manifest URLs) through the whole host step.
const hostSetupOf = (source: string) =>
source.slice(source.indexOf('METADATA_URL="${HARNESS_METADATA_URL'), source.indexOf("# 2. Resolve the Node"));
// The download-tools check that guards the Node download, with the helpers it calls.
const downloadToolsOf = (source: string) =>
source.slice(source.indexOf('METADATA_URL="${HARNESS_METADATA_URL'), source.indexOf("tmux_runs()")) +
source.slice(
source.indexOf(" # Fetching, unpacking and verifying the runtime"),
source.indexOf(' echo "▸ Installing the Harness Node runtime'),
);
// Steps 5 and 6 — the verification, the wordmark and the guide, and the PATH reminder — as one
// runnable unit, from the constants down so BIN_DIR and LAUNCHER resolve the way they do in the script.
const finaleOf = (source: string) =>
source.slice(source.indexOf('METADATA_URL="${HARNESS_METADATA_URL'), source.indexOf("# Shared by every download")) +
source.slice(source.indexOf("# 5. Final verification"));
// A ~/.local/bin with a launcher that answers `version`, plus the node and tmux the verification runs.
const installedFixture = (scratch: string) => {
const home = join(scratch, "home");
const bin = join(home, ".local", "bin");
mkdirSync(bin, { recursive: true });
writeCommand(bin, "harness", ['[ "$1" = version ] && echo 0.2.60', "exit 0"]);
writeCommand(scratch, "node", ["exit 0"]);
writeCommand(scratch, "tmux", ['echo "tmux 3.7"']);
return { home, node: join(scratch, "node") };
};
const runFinale = (mode: "standalone" | "desktop", pathHasBin: boolean) => {
const source = readFileSync(installer, "utf8");
const scratch = mkdtempSync(join(tmpdir(), "harness-finale-"));
try {
const { home, node } = installedFixture(scratch);
const bin = join(home, ".local", "bin");
return spawnSync("/bin/sh", ["-c", finaleOf(source)], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
INSTALL_MODE: mode,
NODE_BIN: node,
PATH: pathHasBin ? `${bin}:${scratch}:/usr/bin:/bin` : `${scratch}:/usr/bin:/bin`,
},
});
} finally {
rmSync(scratch, { recursive: true, force: true });
}
};
const LOGO = [
" _",
" | |__ __ _ _ __ _ __ ___ ___ ___",
" | '_ \\ / _` | '__| '_ \\ / _ \\/ __/ __|",
" | | | | (_| | | | | | | __/\\__ \\__ \\",
" |_| |_|\\__,_|_| |_| |_|\\___||___/___/",
];
describe("scripts/install.sh: what it says once it is done", () => {
it("ends with the wordmark and the guide — login, start, remote password, then harness remote — in that order", () => {
const result = runFinale("standalone", true);
expect(result.status).toBe(0);
const out = result.stdout;
for (const line of LOGO) expect(out).toContain(line);
expect(out).toContain("✓ harness 0.2.60 installed.");
const order = ["harness login", "harness start", "harness remote-password set", "harness remote ", "harness machines", "harness status", "harness --help"];
const at = order.map((command) => out.indexOf(command));
expect(at.every((index) => index >= 0)).toBe(true);
expect(at).toEqual([...at].sort((a, b) => a - b));
expect(out.indexOf(LOGO[1])).toBeLessThan(out.indexOf("harness login"));
// Every command is explained, and nothing is run: the fixture launcher only answered `version`.
expect(out).toContain("# 1. sign in");
expect(out).toContain("# 3. let your OTHER machines reach this one");
expect(out).not.toContain("'harness' is installed in ~/.local/bin");
}, 20_000);
it("the PATH reminder still comes after the guide when ~/.local/bin is not on PATH", () => {
const out = runFinale("standalone", false).stdout;
expect(out.indexOf("harness --help")).toBeLessThan(out.indexOf("'harness' is installed in ~/.local/bin"));
}, 20_000);
it("desktop mode gets the one line and no guide: the app takes the person through sign-in", () => {
const out = runFinale("desktop", true).stdout;
expect(out).toContain("harness 0.2.60 installed.");
expect(out).not.toContain("Get started");
expect(out).not.toContain(LOGO[1]);
}, 20_000);
});
describe("scripts/install.sh command contract", () => {
it("rejects a legacy machine-token argument before installing", () => {
const result = spawnSync("sh", [installer, "legacy-token"], { encoding: "utf8" });
expect(result.status).toBe(2);
expect(result.stderr).toContain("no longer accepts a machine token");
expect(result.stderr).toContain("harness login && harness start");
});
it("accepts only standalone default, explicit desktop mode or host mode", () => {
const source = readFileSync(installer, "utf8");
const parser = source.slice(0, source.indexOf('METADATA_URL='));
const standalone = spawnSync("/bin/sh", ["-s"], {
encoding: "utf8",
input: parser,
});
const desktop = spawnSync("/bin/sh", ["-s", "--", "--desktop"], {
encoding: "utf8",
input: parser,
});
const host = spawnSync("/bin/sh", ["-s", "--", "--host"], {
encoding: "utf8",
input: parser,
});
const extra = spawnSync(
"/bin/sh",
["-s", "--", "--desktop", "legacy-token"],
{ encoding: "utf8", input: parser },
);
expect(standalone.status).toBe(0);
expect(desktop.status).toBe(0);
expect(host.status).toBe(0);
expect(extra.status).toBe(2);
expect(extra.stderr).toContain("accepts no additional arguments");
});
it("accepts a desktop-supplied Node runtime and pins the launcher to it", () => {
const source = readFileSync(installer, "utf8");
expect(source).toContain('NODE_BIN="${HARNESS_NODE_BINARY:-}"');
expect(source).toContain('HARNESS_NODE_BINARY="$NODE_BIN"');
expect(source).toContain("const shellQuote");
expect(source).toContain("exec ' + shellQuote(NODE)");
});
// The CLI runs on the runtime under ~/.harness/runtime, never on whatever `node` PATH happens to
// resolve — the same rule the desktop app follows, so one cli.js is never driven by two Nodes.
it("installs its own Node runtime rather than requiring one", () => {
const source = readFileSync(installer, "utf8");
expect(source).toContain("harness/runtime/metadata.json");
expect(source).toContain('CURRENT_NODE_FILE="$RUNTIME_DIR/current-node"');
// The runtime is recorded for the desktop app to find, and only after it has answered.
expect(source).toContain('printf \'%s\\n\' "$NODE_BIN" > "$CURRENT_NODE_FILE"');
// No Node yet at that point, so the checksum has to be taken with the platform's own tool.
expect(source).toContain("shasum -a 256");
expect(source).toContain("sha256sum");
// The old behaviour — bail out and tell the user to go install Node — must not come back.
expect(source).not.toContain("brew install node");
expect(source).not.toContain("deb.nodesource.com");
});
it("refuses an unverified Node archive", () => {
const source = readFileSync(installer, "utf8");
expect(source).toContain('if [ "$node_got" != "$node_sha" ]; then');
expect(source).toContain("failed checksum verification");
});
it("prepares and verifies required tmux before installing Node or Harness", () => {
const source = readFileSync(installer, "utf8");
const tmuxStep = source.indexOf("# 1. Host requirements");
const nodeStep = source.indexOf("# 2. Resolve the Node");
const cliStep = source.indexOf("# 3. Fetch manifest");
expect(tmuxStep).toBeGreaterThan(-1);
expect(nodeStep).toBeGreaterThan(tmuxStep);
expect(cliStep).toBeGreaterThan(nodeStep);
expect(source).toContain("brew install tmux");
expect(source).toContain("install_managed_tmux");
expect(source).toContain("install_with_apt $missing_host_packages");
expect(source).toContain('if [ "$(id -u)" -eq 0 ]; then');
expect(source).toContain('sudo apt-get "$@"');
expect(source).not.toContain("sudo apt-get update &&");
expect(source).toContain("tmux is required but did not pass verification");
expect(source).toContain("exit 22");
// Obtaining tmux never needs a compiler, Xcode or the Homebrew installer any more.
expect(source).not.toContain("xcrun");
expect(source).not.toContain("xcode-select");
expect(source).not.toContain("Homebrew/install/HEAD/install.sh");
expect(source).not.toContain("exit 20");
expect(source).not.toContain("exit 21");
});
// The ladder, in source order: tmux is asked about first, Homebrew used only when tmux is missing
// and Homebrew is already there, the managed build for everything else.
it("asks about Homebrew only without tmux, and downloads the managed build without Homebrew", () => {
const source = readFileSync(installer, "utf8");
const hostStep = source.slice(source.indexOf("# 1. Host requirements"));
const darwin = hostStep.slice(hostStep.indexOf(" Darwin)"), hostStep.indexOf(" Linux)"));
const tmuxCheck = darwin.indexOf("if tmux_runs; then");
const brewCheck = darwin.indexOf("if command -v brew >/dev/null 2>&1; then");
const managed = darwin.indexOf("tmux_runs || install_managed_tmux");
expect(tmuxCheck).toBeGreaterThan(-1);
expect(brewCheck).toBeGreaterThan(tmuxCheck);
expect(managed).toBeGreaterThan(brewCheck);
// The managed archive is checksum-pinned by the manifest and recorded for the daemon.
expect(source).toContain("harness/runtime/tmux/metadata.json");
expect(source).toContain('CURRENT_TMUX_FILE="$RUNTIME_DIR/current-tmux"');
expect(source).toContain("tmux download failed checksum verification");
expect(source).toContain('ln -sfn "$tmux_target/bin/tmux" "$BIN_DIR/tmux"');
});
it("does nothing on a Mac that already runs tmux", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-mac-tmux-"));
const invoked = join(scratch, "brew-invoked");
try {
writeCommand(scratch, "uname", ["printf 'Darwin\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
writeCommand(scratch, "brew", [`: > '${invoked}'`, "exit 99"]);
writeCommand(scratch, "sudo", [`: > '${invoked}'`, "exit 99"]);
writeCommand(scratch, "xcode-select", [`: > '${invoked}'`, "exit 99"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: { ...process.env, INSTALL_MODE: "standalone", PATH: scratch },
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("tmux ready");
expect(result.stdout).not.toContain("Homebrew");
expect(() => readFileSync(invoked, "utf8")).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("installs tmux with an existing Homebrew and never fetches the managed build", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-mac-brew-"));
const invocations = join(scratch, "brew-invocations");
const fetched = join(scratch, "curl-invoked");
const fakeTmux = join(scratch, "tmux");
try {
writeCommand(scratch, "uname", ["printf 'Darwin\\n'"]);
writeCommand(scratch, "brew", [
`printf '%s\\n' "$*" >> '${invocations}'`,
`if [ "$1" = "shellenv" ]; then exit 0; fi`,
`printf '%s\\n' '#!/bin/sh' 'printf "tmux 3.7c\\\\n"' > '${fakeTmux}'`,
`/bin/chmod 700 '${fakeTmux}'`,
]);
writeCommand(scratch, "curl", [`: > '${fetched}'`, "exit 99"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
PATH: scratch,
HARNESS_HOMEBREW_PREFIXES: join(scratch, "no-homebrew"),
},
});
expect(result.status).toBe(0);
expect(readFileSync(invocations, "utf8")).toContain("install --force-bottle tmux\n");
expect(result.stdout).not.toContain("managed");
expect(() => readFileSync(fetched, "utf8")).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
// A fake CDN: `curl -o` writes the archive, plain `curl` prints the manifest. The archive wraps a
// fake tmux so the whole managed path — fetch, verify, unpack, record, link — runs for real.
const managedFixture = (scratch: string, tamperSha = false) => {
const home = join(scratch, "home");
const root = "tmux-9.9-darwin-arm64";
const stage = join(scratch, "stage", root, "bin");
mkdirSync(stage, { recursive: true });
writeCommand(stage, "tmux", ["printf 'tmux 9.9\\n'"]);
const archive = join(scratch, `${root}.tar.gz`);
spawnSync("tar", ["-czf", archive, "-C", join(scratch, "stage"), root]);
const sha = spawnSync("shasum", ["-a", "256", archive], { encoding: "utf8" }).stdout.split(" ")[0];
const manifest = JSON.stringify({
tmux: {
"darwin-arm64": {
version: "9.9",
url: "https://cdn.example/tmux.tar.gz",
sha256: tamperSha ? "0".repeat(64) : sha,
size: 1,
archiveRoot: root,
},
},
}, null, 2);
writeFileSync(join(scratch, "manifest.json"), manifest);
// The ladder's first rung is `command -v tmux && tmux -V`, and PATH below still includes /usr/bin
// — where ubuntu-latest (CI) ships a real tmux 3.4, so on that runner the script found it, said
// "tmux ready (tmux 3.4)" and never took the managed path these tests exist for. A broken tmux
// in the fixture dir (first on PATH) fails that rung everywhere; once the managed build is
// linked, the script puts BIN_DIR ahead of PATH, so the 9.9 fake wins from then on.
writeCommand(scratch, "tmux", ["exit 127"]);
writeCommand(scratch, "curl", [
`printf '%s\\n' "$*" >> '${join(scratch, "curl-invocations")}'`,
`case "$*" in *"-o "*) cp '${archive}' "$4" ;; *) cat '${join(scratch, "manifest.json")}' ;; esac`,
]);
writeCommand(scratch, "uname", [`if [ "$1" = "-m" ]; then printf 'arm64\\n'; else printf 'Darwin\\n'; fi`]);
mkdirSync(home, { recursive: true });
return { home, root };
};
it("downloads, verifies and links the managed tmux when there is no Homebrew", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-mac-managed-"));
try {
const { home, root } = managedFixture(scratch);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
INSTALL_MODE: "standalone",
// Only stock tools beyond the fakes: no brew, no tmux — and the fakes win.
PATH: `${scratch}:/usr/bin:/bin`,
HARNESS_TMUX_METADATA_URL: "https://cdn.example/manifest.json",
HARNESS_HOMEBREW_PREFIXES: join(scratch, "no-homebrew"),
},
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("downloading tmux 9.9 (darwin-arm64)");
expect(result.stdout).toContain("tmux ready (tmux 9.9)");
const binary = join(home, ".harness", "runtime", root, "bin", "tmux");
expect(readFileSync(join(home, ".harness", "runtime", "current-tmux"), "utf8").trim()).toBe(binary);
expect(readlinkSync(join(home, ".local", "bin", "tmux"))).toBe(binary);
expect(readFileSync(join(scratch, "curl-invocations"), "utf8")).toContain("https://cdn.example/manifest.json");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("refuses a managed tmux whose checksum does not match, and links nothing", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-mac-managed-bad-"));
try {
const { home } = managedFixture(scratch, true);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
INSTALL_MODE: "standalone",
PATH: `${scratch}:/usr/bin:/bin`,
HARNESS_TMUX_METADATA_URL: "https://cdn.example/manifest.json",
HARNESS_HOMEBREW_PREFIXES: join(scratch, "no-homebrew"),
},
});
expect(result.status).toBe(22);
expect(result.stderr).toContain("checksum verification");
expect(() => readlinkSync(join(home, ".local", "bin", "tmux"))).toThrow();
expect(() => readFileSync(join(home, ".harness", "runtime", "current-tmux"))).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("falls back to the managed build when Homebrew cannot install tmux", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-mac-brew-broken-"));
try {
const { home, root } = managedFixture(scratch);
writeCommand(scratch, "brew", [`if [ "$1" = "shellenv" ]; then exit 0; fi`, "exit 1"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
INSTALL_MODE: "standalone",
PATH: `${scratch}:/usr/bin:/bin`,
HARNESS_TMUX_METADATA_URL: "https://cdn.example/manifest.json",
HARNESS_HOMEBREW_PREFIXES: join(scratch, "no-homebrew"),
},
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("No Homebrew tmux bottle for this Mac; using the managed build instead.");
expect(readlinkSync(join(home, ".local", "bin", "tmux"))).toContain(root);
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("host mode puts ~/.local/bin on PATH for new shells", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-host-rc-"));
try {
const { home } = managedFixture(scratch);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
SHELL: "/bin/zsh",
INSTALL_MODE: "host",
PATH: `${scratch}:/usr/bin:/bin`,
HARNESS_TMUX_METADATA_URL: "https://cdn.example/manifest.json",
HARNESS_HOMEBREW_PREFIXES: join(scratch, "no-homebrew"),
},
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("Host requirements ready");
expect(readFileSync(join(home, ".zshrc"), "utf8")).toContain('export PATH="$HOME/.local/bin:$PATH"');
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("host mode stops after the host requirements", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-host-mode-"));
try {
writeCommand(scratch, "uname", ["printf 'Darwin\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
const result = spawnSync("/bin/sh", ["-c", `${hostSetup}\necho REACHED-NODE-STEP`], {
encoding: "utf8",
env: { ...process.env, INSTALL_MODE: "host", PATH: scratch },
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("Host requirements ready");
expect(result.stdout).not.toContain("REACHED-NODE-STEP");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("installs nothing on a Linux host that already runs tmux and ps", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-linux-ready-"));
const invoked = join(scratch, "apt-invoked");
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
// curl is a download tool, not a host requirement: its absence is no business of step 1.
writeCommand(scratch, "apt-get", [`: > '${invoked}'`, "exit 99"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: "",
WAYLAND_DISPLAY: "",
PATH: scratch,
},
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("tmux ready");
expect(() => readFileSync(invoked, "utf8")).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("installs the download tools only when the runtime is downloaded", () => {
const source = readFileSync(installer, "utf8");
// The check lives inside the download branch, after managed-runtime reuse is ruled out.
const download = source.indexOf('if [ -z "$NODE_BIN" ]; then\n\n # Fetching');
const reuse = source.indexOf('if [ -z "$NODE_BIN" ] && [ -r "$CURRENT_NODE_FILE" ]');
expect(reuse).toBeGreaterThan(-1);
expect(download).toBeGreaterThan(reuse);
expect(source.slice(0, download)).not.toContain("require_command curl");
const downloadTools = downloadToolsOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-download-tools-"));
const invocations = join(scratch, "apt-invocations");
const fakeCurl = join(scratch, "curl");
try {
for (const command of ["tar", "sed", "awk", "sha256sum"]) {
writeCommand(scratch, command, ["exit 0"]);
}
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "apt-get", [
`printf '%s\\n' "$*" >> '${invocations}'`,
`printf '%s\\n' '#!/bin/sh' 'exit 0' > '${fakeCurl}'`,
`/bin/chmod 700 '${fakeCurl}'`,
]);
const result = spawnSync("/bin/sh", ["-c", downloadTools], {
encoding: "utf8",
env: { ...process.env, PATH: scratch },
});
expect(result.status).toBe(0);
expect(readFileSync(invocations, "utf8")).toBe("install -y curl\n");
expect(result.stdout).toContain("tools needed to download the runtime");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("installs tmux through sudo without refreshing usable package indexes", () => {
const source = readFileSync(installer, "utf8");
const tmuxStep = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-tmux-user-"));
const invocation = join(scratch, "sudo-invocation");
const fakeTmux = join(scratch, "tmux");
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '1000\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "apt-get", ["exit 99"]);
writeCommand(scratch, "sudo", [
`printf '%s\\n' "$*" > '${invocation}'`,
`printf '%s\\n' '#!/bin/sh' 'printf "tmux 3.6\\\\n"' > '${fakeTmux}'`,
`/bin/chmod 700 '${fakeTmux}'`,
]);
const result = spawnSync("/bin/sh", ["-c", tmuxStep], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: "",
WAYLAND_DISPLAY: "",
PATH: scratch,
},
});
expect(result.status).toBe(0);
expect(readFileSync(invocation, "utf8")).toBe("apt-get install -y tmux\n");
expect(result.stdout).toContain("tmux ready");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("installs as root without sudo and retries after a failed index refresh", () => {
const source = readFileSync(installer, "utf8");
const tmuxStep = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-tmux-root-"));
const invocations = join(scratch, "apt-invocations");
const firstAttempt = join(scratch, "first-attempt");
const fakeTmux = join(scratch, "tmux");
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "apt-get", [
`printf '%s\\n' "$*" >> '${invocations}'`,
`if [ "$1" = "update" ]; then exit 77; fi`,
`if [ ! -f '${firstAttempt}' ]; then : > '${firstAttempt}'; exit 1; fi`,
`printf '%s\\n' '#!/bin/sh' 'printf "tmux 3.6\\\\n"' > '${fakeTmux}'`,
`/bin/chmod 700 '${fakeTmux}'`,
]);
const result = spawnSync("/bin/sh", ["-c", tmuxStep], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: "",
WAYLAND_DISPLAY: "",
PATH: scratch,
},
});
expect(result.status).toBe(0);
expect(readFileSync(invocations, "utf8")).toBe(
"install -y tmux\nupdate\ninstall -y tmux\n",
);
expect(result.stdout).toContain("tmux ready");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("requires only the active Linux desktop clipboard helper before Node", () => {
const source = readFileSync(installer, "utf8");
const wayland = source.indexOf('clipboard_command="wl-copy"');
const x11 = source.indexOf('clipboard_command="xclip"');
const nodeStep = source.indexOf("# 2. Resolve the Node");
expect(wayland).toBeGreaterThan(-1);
expect(x11).toBeGreaterThan(wayland);
expect(nodeStep).toBeGreaterThan(x11);
expect(source).toContain('if [ -n "${WAYLAND_DISPLAY:-}" ]; then');
expect(source).toContain('elif [ -n "${DISPLAY:-}" ]; then');
expect(source).toContain('exit 23');
expect(source).not.toContain("install_with_apt xclip wl-clipboard");
});
it("installs xclip for X11 and verifies it", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-xclip-root-"));
const invocations = join(scratch, "apt-invocations");
const fakeXclip = join(scratch, "xclip");
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
writeCommand(scratch, "apt-get", [
`printf '%s\\n' "$*" >> '${invocations}'`,
`printf '%s\\n' '#!/bin/sh' 'exit 0' > '${fakeXclip}'`,
`/bin/chmod 700 '${fakeXclip}'`,
]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: ":0",
WAYLAND_DISPLAY: "",
PATH: scratch,
},
});
expect(result.status).toBe(0);
expect(readFileSync(invocations, "utf8")).toBe("install -y xclip\n");
expect(result.stdout).toContain("Linux image clipboard ready (xclip)");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("does not install clipboard packages on a headless Linux host", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-headless-root-"));
const invoked = join(scratch, "apt-invoked");
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
writeCommand(scratch, "apt-get", [`: > '${invoked}'`, "exit 99"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: "",
WAYLAND_DISPLAY: "",
PATH: scratch,
},
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("native image clipboard is not applicable");
expect(() => readFileSync(invoked, "utf8")).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
it("fails before Node when the active Wayland helper cannot be installed", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-wayland-fail-"));
try {
writeLinuxHostCommands(scratch);
writeCommand(scratch, "id", ["printf '0\\n'"]);
writeCommand(scratch, "uname", ["printf 'Linux\\n'"]);
writeCommand(scratch, "tmux", ["printf 'tmux 3.6\\n'"]);
writeCommand(scratch, "apt-get", ["exit 88"]);
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: {
...process.env,
INSTALL_MODE: "standalone",
DISPLAY: ":0",
WAYLAND_DISPLAY: "wayland-0",
PATH: scratch,
},
});
expect(result.status).toBe(23);
expect(result.stderr).toContain("Could not install wl-clipboard");
expect(result.stderr).not.toContain("xclip");
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
// ── the managed grid ──────────────────────────────────────────────────────────────────────────
//
// The grid CLI the release PINS, laid down beside Node and tmux — but unlike them, optional: the
// harness works without it, the daemon follows the pin on every start, so this step may fail and
// the install still succeeds. And never linked into ~/.local/bin: that path is grid's own
// installer's (uv's, on a Mac); the daemon puts the managed grid on an agent pane's PATH itself.
// The helpers plus the grid step alone, as one runnable unit.
const gridStepOf = (source: string) =>
source.slice(source.indexOf('METADATA_URL="${HARNESS_METADATA_URL'), source.indexOf("# 1. Host requirements")) +
source.slice(source.indexOf("# 3b. The managed grid"), source.indexOf("# 4. Ensure ~/.local/bin"));
it("installs the managed grid after the CLI and before PATH, as a step the install can survive", () => {
const source = readFileSync(installer, "utf8");
const cliStep = source.indexOf("# 3. Fetch manifest");
const gridStep = source.indexOf("# 3b. The managed grid");
const pathStep = source.indexOf("# 4. Ensure ~/.local/bin");
expect(cliStep).toBeGreaterThan(0);
expect(gridStep).toBeGreaterThan(cliStep);
expect(pathStep).toBeGreaterThan(gridStep);
expect(source).toContain('GRID_METADATA_URL="${HARNESS_GRID_METADATA_URL:-');
expect(source).toContain('CURRENT_GRID_FILE="$RUNTIME_DIR/current-grid"');
// Optional, by construction: the call is guarded, and the function returns instead of exiting.
expect(source.slice(gridStep, pathStep)).toMatch(/install_managed_grid \|\|/);
const fn = source.slice(source.indexOf("install_managed_grid() {"), source.indexOf("# 1. Host requirements"));
expect(fn).not.toContain("exit ");
expect(fn).not.toContain("ln -s");
});
const gridFixture = (scratch: string, tamperSha = false) => {
const home = join(scratch, "home");
const root = "grid-9.9-darwin-arm64";
const stage = join(scratch, "stage", root, "bin");
mkdirSync(stage, { recursive: true });
writeCommand(stage, "grid", ["printf 'grid 9.9\\n'"]);
const archive = join(scratch, `${root}.tar.gz`);
spawnSync("tar", ["-czf", archive, "-C", join(scratch, "stage"), root]);
const sha = spawnSync("shasum", ["-a", "256", archive], { encoding: "utf8" }).stdout.split(" ")[0];
writeFileSync(join(scratch, "manifest.json"), JSON.stringify({
grid: {
"darwin-arm64": {
version: "9.9",
url: "https://cdn.example/grid.tar.gz",
sha256: tamperSha ? "0".repeat(64) : sha,
size: 1,
archiveRoot: root,
},
},
}, null, 2));
writeCommand(scratch, "curl", [
`printf '%s\\n' "$*" >> '${join(scratch, "curl-invocations")}'`,
`case "$*" in *"-o "*) cp '${archive}' "$4" ;; *) cat '${join(scratch, "manifest.json")}' ;; esac`,
]);
writeCommand(scratch, "uname", [`if [ "$1" = "-m" ]; then printf 'arm64\\n'; else printf 'Darwin\\n'; fi`]);
mkdirSync(home, { recursive: true });
return { home, root };
};
const runGridStep = (scratch: string, home: string, mode: string) =>
spawnSync("/bin/sh", ["-c", gridStepOf(readFileSync(installer, "utf8"))], {
encoding: "utf8",
env: {
...process.env,
HOME: home,
INSTALL_MODE: mode,
PATH: `${scratch}:/usr/bin:/bin`,
HARNESS_GRID_METADATA_URL: "https://cdn.example/manifest.json",
},
});
it("downloads, verifies and records the managed grid read-only, and links nothing", () => {
const scratch = mkdtempSync(join(tmpdir(), "harness-grid-managed-"));
try {
const { home, root } = gridFixture(scratch);
const result = runGridStep(scratch, home, "standalone");
expect(result.status).toBe(0);
expect(result.stdout).toContain("downloading grid 9.9 (darwin-arm64)");
expect(result.stdout).toContain(`installed grid 9.9 → ${join(home, ".harness", "runtime", root)}`);
const binary = join(home, ".harness", "runtime", root, "bin", "grid");
expect(readFileSync(join(home, ".harness", "runtime", "current-grid"), "utf8").trim()).toBe(binary);
// `grid update` is a rename INTO bin/; a bin/ it cannot write to is what makes that fail loudly.
expect(statSync(binary).mode & 0o777).toBe(0o555);
expect(statSync(join(home, ".harness", "runtime", root, "bin")).mode & 0o777).toBe(0o555);
expect(existsSync(join(home, ".local", "bin", "grid"))).toBe(false);
expect(readFileSync(join(scratch, "curl-invocations"), "utf8")).toContain("https://cdn.example/manifest.json");
} finally {
// The read-only bin/ the step lays down cannot be emptied as it is; give it back before the sweep.
try { chmodSync(join(scratch, "home", ".harness", "runtime", "grid-9.9-darwin-arm64", "bin"), 0o755); } catch { /* never laid down */ }
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("survives a grid whose checksum does not match: says so, records nothing, and the install goes on", () => {
const scratch = mkdtempSync(join(tmpdir(), "harness-grid-tampered-"));
try {
const { home } = gridFixture(scratch, true);
const result = runGridStep(scratch, home, "standalone");
expect(result.status).toBe(0);
expect(result.stderr).toContain("checksum verification");
expect(result.stdout).toContain("fetched by the daemon");
expect(existsSync(join(home, ".harness", "runtime", "current-grid"))).toBe(false);
expect(readdirSync(join(home, ".harness", "runtime")).filter((n) => n.startsWith(".grid-staging-"))).toEqual([]);
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("says which platform has no managed grid, fetches nothing, and the install goes on", () => {
const scratch = mkdtempSync(join(tmpdir(), "harness-grid-platform-"));
try {
const { home } = gridFixture(scratch);
writeCommand(scratch, "uname", [`if [ "$1" = "-m" ]; then printf 'riscv64\\n'; else printf 'Linux\\n'; fi`]);
const result = runGridStep(scratch, home, "standalone");
expect(result.status).toBe(0);
expect(result.stderr).toContain("No managed grid is published for Linux/riscv64");
expect(existsSync(join(scratch, "curl-invocations"))).toBe(false);
expect(existsSync(join(home, ".harness", "runtime", "current-grid"))).toBe(false);
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("fetches no grid in host mode, which installs no CLI either", () => {
const scratch = mkdtempSync(join(tmpdir(), "harness-grid-host-"));
try {
const { home } = gridFixture(scratch);
const result = runGridStep(scratch, home, "host");
expect(result.status).toBe(0);
expect(existsSync(join(scratch, "curl-invocations"))).toBe(false);
expect(existsSync(join(home, ".harness", "runtime", "current-grid"))).toBe(false);
} finally {
rmSync(scratch, { recursive: true, force: true });
}
}, 20_000);
it("desktop mode skips all host package and tmux work", () => {
const source = readFileSync(installer, "utf8");
const hostSetup = hostSetupOf(source);
const scratch = mkdtempSync(join(tmpdir(), "harness-desktop-mode-"));
const invoked = join(scratch, "host-command-invoked");
try {
for (const command of ["uname", "sudo", "apt-get", "tmux", "brew"]) {
writeCommand(scratch, command, [`: > '${invoked}'`, "exit 99"]);
}
const result = spawnSync("/bin/sh", ["-c", hostSetup], {
encoding: "utf8",
env: { ...process.env, INSTALL_MODE: "desktop", PATH: scratch },
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("Desktop mode");
expect(() => readFileSync(invoked, "utf8")).toThrow();
} finally {
rmSync(scratch, { recursive: true, force: true });
}
});
});