Files
fusion/scripts/__tests__/dockerfile-workspace-manifests.test.mjs
gsxdsm a96f0dd932 fix: ship git-lfs in the Docker image
The repo keeps binary assets as Git LFS objects, but the runner stage
installed plain git. Without git-lfs, checkout writes ~130-byte pointer files
in place of the real content and reports a clean tree, so an agent reads a
text stub where an image should be and any `git lfs` subcommand fails. That
is silent corruption of a working checkout, not a visibly missing tool, which
is why it goes in beside git rather than with the optional tooling.

Confirmed in the running container: screenshots/fn-061-medieval-desktop.png
was a `version https://git-lfs.github.com/spec/v1` stub — 129 tracked files
in that state — and became a valid 753KB PNG (signature and IEND intact)
after installing git-lfs and running `git lfs pull`.

The Dockerfile manifest guard now asserts the package so it cannot be dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:42:30 -07:00

167 lines
7.4 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { globSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import YAML from "yaml";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, "../..");
function normalizeDockerSource(source) {
return source.replace(/^\.\//, "").replace(/\/$/, "");
}
function readWorkspacePackageManifestPaths(root = repoRoot) {
const workspacePath = path.join(root, "pnpm-workspace.yaml");
const workspace = YAML.parse(readFileSync(workspacePath, "utf8"));
const entries = Array.isArray(workspace?.packages) ? workspace.packages : [];
const manifestPaths = new Set();
for (const entry of entries) {
if (typeof entry !== "string" || entry.startsWith("!")) {
continue;
}
for (const manifest of globSync(`${entry.replace(/\/$/, "")}/package.json`, {
cwd: root,
nodir: true,
})) {
manifestPaths.add(manifest.split(path.sep).join("/"));
}
}
return manifestPaths;
}
function readBuilderPreInstallCopySources(dockerfile) {
const builderStart = dockerfile.match(/^FROM\s+.*\s+AS\s+builder\s*$/im);
assert.ok(builderStart?.index !== undefined, "Dockerfile must define a builder stage");
const afterBuilder = dockerfile.slice(builderStart.index + builderStart[0].length);
const nextStage = afterBuilder.search(/^FROM\s+/im);
const builderStage = nextStage === -1 ? afterBuilder : afterBuilder.slice(0, nextStage);
const install = builderStage.match(/RUN\s+pnpm\s+install\s+--frozen-lockfile\b/);
assert.ok(install?.index !== undefined, "builder stage must run pnpm install --frozen-lockfile");
const copied = [];
for (const match of builderStage.slice(0, install.index).matchAll(/^COPY\s+(?:--\S+\s+)*(.*?)\s+\S+\s*$/gm)) {
const sources = match[1].trim().split(/\s+/).map(normalizeDockerSource);
copied.push(...sources);
}
return copied;
}
function findMissingWorkspaceManifests(manifests, copySources) {
return [...manifests].filter((manifest) => !copySources.some((source) => (
source === manifest || source === "." || manifest.startsWith(`${source}/`)
))).sort();
}
function readDockerfileCopiedManifestPaths() {
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
const copied = readBuilderPreInstallCopySources(dockerfile);
return { copied, dockerfile };
}
test("Dockerfile builder pre-install copies cover every current workspace manifest", () => {
const expected = readWorkspacePackageManifestPaths();
const { copied } = readDockerfileCopiedManifestPaths();
assert.deepEqual(findMissingWorkspaceManifests(expected, copied), []);
assert.equal(new Set(copied).size, copied.length, "builder pre-install COPY sources must not be duplicated");
});
test("coverage rejects a selected plugin omitted before frozen install", () => {
const expected = readWorkspacePackageManifestPaths();
const omitted = [...expected].sort().find((manifest) => manifest.startsWith("plugins/"));
assert.ok(omitted, "workspace fixture must include a plugin manifest");
const completeSources = [...expected];
const incompleteSources = completeSources.filter((source) => source !== omitted);
assert.deepEqual(findMissingWorkspaceManifests(expected, incompleteSources), [omitted]);
});
test("coverage ignores post-install and runner copies while tolerating removed paths", () => {
const expected = readWorkspacePackageManifestPaths();
const omitted = [...expected].sort().find((manifest) => manifest.startsWith("plugins/"));
assert.ok(omitted, "workspace fixture must include a plugin manifest");
const builderCopies = [...expected]
.filter((manifest) => manifest !== omitted)
.map((manifest) => `COPY ${manifest} ./${manifest}`)
.join("\n");
const dockerfile = `FROM node:22-slim AS builder\n${builderCopies}\nRUN pnpm install --frozen-lockfile\nCOPY ${omitted} ./${omitted}\nFROM node:22-slim AS runner\nCOPY ${omitted} ./${omitted}`;
const copied = readBuilderPreInstallCopySources(dockerfile);
assert.deepEqual(findMissingWorkspaceManifests(expected, copied), [omitted]);
assert.deepEqual(
findMissingWorkspaceManifests(expected, [...expected, "plugins/not-in-workspace/package.json"]),
[],
"removed or nonexistent COPY paths must not affect selected workspace coverage",
);
});
/*
FNXC:DockerRun 2026-08-18-05:35:
The runner stage MUST install ca-certificates. The slim base ships none, and git verifies TLS
against the system store, so without it every HTTPS clone dies with "server certificate
verification failed. CAfile: none CRLfile: none" and project setup is impossible in Docker.
This regressed unnoticed because Node carries its OWN bundled CA store: the dashboard, model APIs
and OAuth token exchanges all worked, so nothing looked wrong until the first clone. Nothing else
in the image exercises the system trust store, which is exactly why it needs a guard rather than
relying on someone noticing.
*/
test("runner stage installs ca-certificates alongside git", () => {
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
const runnerStage = dockerfile.slice(dockerfile.indexOf("FROM node:22-slim AS runner"));
assert.ok(runnerStage.length > 0, "runner stage must exist");
const aptInstall = runnerStage.match(/apt-get install[^\n]*(?:\\\n[^\n]*)*/)?.[0] ?? "";
assert.match(aptInstall, /\bgit\b/, "runner stage must install git");
assert.match(
aptInstall,
/\bca-certificates\b/,
"runner stage must install ca-certificates — git cannot verify HTTPS remotes without a system CA bundle",
);
assert.match(
aptInstall,
/\bripgrep\b/,
"runner stage must install ripgrep — the coding agents Fusion drives use `rg` as their primary search tool",
);
/*
FNXC:DockerRun 2026-08-20-04:30:
Without git-lfs, git checks out 130-byte pointer files in place of LFS-tracked binaries and still
reports a clean tree — silent corruption of a working checkout rather than a visibly missing tool.
*/
assert.match(
aptInstall,
/\bgit-lfs\b/,
"runner stage must install git-lfs — LFS-tracked assets otherwise check out as pointer stubs and git reports the tree clean",
);
});
/*
FNXC:DockerRun 2026-08-18-06:40:
The operator tooling the image promises must actually be in it. `gh` backs the gh-cli GitHub auth
mode, `cloudflared` backs remote access, and `tailscale` is the private-network option; each is
installed from its vendor's signed apt repository. Assert the repo wiring AND the package names, so
dropping either half (a keyring without the install, or an install whose repo line was removed) fails
here instead of at first use inside a container.
*/
test("runner stage installs gh, tailscale and cloudflared from vendor repositories", () => {
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
const runnerStage = dockerfile.slice(dockerfile.indexOf("FROM node:22-slim AS runner"));
for (const [tool, repo] of [
["gh", "https://cli.github.com/packages"],
["tailscale", "https://pkgs.tailscale.com/stable/debian"],
["cloudflared", "https://pkg.cloudflare.com/cloudflared"],
]) {
assert.ok(runnerStage.includes(repo), `runner stage must configure the ${tool} apt repository (${repo})`);
assert.match(runnerStage, new RegExp(`apt-get install[^\n]*(?:\\\n[^\n]*)*\\b${tool}\\b`), `runner stage must install ${tool}`);
}
});