Files
juliusbrussee__caveman/scripts/test-windows-compat.mjs
T
Julius Brussee 7a03e6ffbc test(cli): run the pi round trips on Windows, and Go on macOS
pi-enable and pi-wrap could not join the Windows gate: their fixtures built
PATH with `:` and wrote extensionless `#!/bin/sh` stubs, which Windows cannot
execute at all. tests/harness/stub-bin.mjs gives them two stub shapes, one
per way the CLI launches things — a .cmd Node shim for commands routed
through portableInvocation (`pi`), and, for the Go binaries the CLI
execFile's directly, a real .exe: node.exe hardlinked under the binary's
name, answering from a --require preload that runs before Node resolves
argv[1] as a script. A .cmd cannot serve there, since Node refuses to spawn
one without a shell (CVE-2024-27980). Both fixtures also set USERPROFILE
beside HOME — os.homedir() reads it on Windows, and the Pi extension
directory hangs off it — and retry cleanup for handles Windows still holds.

update.runtime.mjs stays out, and not for a harness reason: its committed
checksum manifest carries only darwin and linux artifacts, so on win32
`update` looks up caveman-proxy_win32_amd64 and fails before downloading
anything. Adding those rows needs the release signing key; the regeneration
recipe is recorded at the fixture read.

The macOS job also runs `go test ./engine/... ./proxy/... ./shared/...` —
without it the runner touched no Go at all, so a BSD-only path, spawn or
timer regression shipped green on the platform this job exists to cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
2026-08-20 04:14:49 +02:00

77 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
import { mkdirSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const require = createRequire(import.meta.url);
const { portableInvocation } = require(join(root, "bin", "lib", "portable-process.js"));
const goCache = join(tmpdir(), "caveman-windows-go-cache");
mkdirSync(goCache, { recursive: true });
function run(label, command, args, options = {}) {
process.stderr.write(`\n[windows-compat] ${label}\n`);
const result = spawnSync(command, args, {
cwd: root,
stdio: "inherit",
...options,
});
if (result.error) throw result.error;
if (result.status !== 0) process.exit(result.status ?? 1);
}
function runPnpm(label, args) {
const invocation = portableInvocation("pnpm", args);
run(label, invocation.command, invocation.args);
}
runPnpm("build CLI", ["--dir", "packages/cli", "build"]);
runPnpm("build agent", ["--dir", "packages/agent", "build"]);
run("Node Windows contracts", process.execPath, [
"--test",
"--test-force-exit",
"packages/cli/tests/windows-platform.runtime.mjs",
"packages/cli/tests/portable-command.runtime.mjs",
"packages/cli/tests/delegate-windows.runtime.mjs",
// Full CLI round trips, not unit contracts: both spawn the built CLI against
// stub binaries (tests/harness/stub-bin.mjs) that are real .exe/.cmd on
// Windows, so `enable pi` and `wrap pi` are exercised end to end there.
"packages/cli/tests/pi-enable.runtime.mjs",
"packages/cli/tests/pi-wrap.runtime.mjs",
"tests/installer/binary-installer-platform.test.mjs",
"tests/installer/release-binaries.test.mjs",
"tests/installer/windows-source-install.test.mjs",
"tests/installer/portable-process.test.mjs",
"tests/installer/mcp-shrink-windows.test.mjs",
"packages/subagent-tax/tests/process-tree.test.mjs",
"packages/agent/tests/windows-process.runtime.mjs",
"packages/agent/tests/windows-shell-smoke.runtime.mjs",
]);
const baseGoEnv = {
...process.env,
CGO_ENABLED: "0",
GOOS: "windows",
GOCACHE: goCache,
GOTMPDIR: tmpdir(),
};
const hostGoArch = { x64: "amd64", arm64: "arm64" }[process.arch];
for (const arch of ["amd64", "arm64"]) {
const args = ["test", "-count=1", "-run", "^$"];
// Compiled test binaries only execute when host OS and arch both match;
// everywhere else a no-op -exec keeps this a pure compile check (a Windows
// amd64 runner cannot execute the arm64 binaries).
if (process.platform !== "win32") {
args.push("-exec", process.platform === "darwin" ? "/usr/bin/true" : "/bin/true");
} else if (arch !== hostGoArch) {
args.push("-exec", "cmd /c exit 0");
}
args.push("./...");
run(`Go Windows/${arch} compile`, "go", args, { env: { ...baseGoEnv, GOARCH: arch } });
}
process.stdout.write("windows compatibility gate passed\n");