import { readFile } from "node:fs/promises" ;
import { join, resolve } from "node:path" ;
import { fileURLToPath } from "node:url" ;
import { describe, expect, it } from "vitest" ;
import { BUNDLED_PLUGIN_ROOT_DIR } from "../test/helpers/bundled-plugin-paths.js" ;
const repoRoot = resolve(fileURLToPath(new URL("." , import .meta.url)), ".." );
const dockerfilePath = join(repoRoot, "Dockerfile" );
function collapseDockerContinuations(dockerfile: string): string {
return dockerfile.replace(/\\\r?\n[ \t]*/g, " " );
}
describe("Dockerfile" , () => {
it("uses shared multi-arch base image refs for all root Node stages" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain(
'ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"' ,
);
expect(dockerfile).toContain(
'ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"' ,
);
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps" );
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build" );
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default" );
expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim" );
expect(dockerfile).toContain("current multi-arch manifest list entry" );
expect(dockerfile).not.toContain("current amd64 entry" );
});
it("installs optional browser dependencies after pnpm install" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile" );
const browserArgIndex = dockerfile.indexOf("ARG OPENCLAW_INSTALL_BROWSER" );
expect(installIndex).toBeGreaterThan(-1 );
expect(browserArgIndex).toBeGreaterThan(-1 );
expect(browserArgIndex).toBeGreaterThan(installIndex);
expect(dockerfile).toContain(
"node /app/node_modules/playwright-core/cli.js install --with-deps chromium" ,
);
expect(dockerfile).toContain("apt-get install -y --no-install-recommends xvfb" );
});
it("verifies matrix-sdk-crypto native addons without hardcoded pnpm virtual-store paths" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain("Verifying critical native addons" );
expect(dockerfile).toContain('find /app/node_modules -name "matrix-sdk-crypto*.node"' );
expect(dockerfile).not.toMatch(
/ADDON_DIR=.*node_modules\/\.pnpm\/@matrix-org\+matrix-sdk-crypto-nodejs@/,
);
});
it("prunes runtime dependencies after the build stage" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain("FROM build AS runtime-assets" );
expect(dockerfile).toContain("ARG OPENCLAW_EXTENSIONS" );
expect(dockerfile).toContain("ARG OPENCLAW_BUNDLED_PLUGIN_DIR" );
expect(dockerfile).toContain("pnpm-workspace.runtime.yaml" );
expect(dockerfile).toContain(" - ui\\n" );
expect(dockerfile).toContain("CI=true NPM_CONFIG_FROZEN_LOCKFILE=false pnpm prune --prod" );
expect(dockerfile).toContain("prune must not rediscover unrelated workspaces" );
expect(dockerfile).not.toContain(
`npm install --prefix "${BUNDLED_PLUGIN_ROOT_DIR}/$ext" --omit=dev --silent`,
);
expect(dockerfile).toContain(
"COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules" ,
);
});
it("does not override bundled plugin discovery in runtime images" , async () => {
const dockerfile = collapseDockerContinuations(await readFile(dockerfilePath, "utf8" ));
expect(dockerfile).toContain(`ARG OPENCLAW_BUNDLED_PLUGIN_DIR=${BUNDLED_PLUGIN_ROOT_DIR}`);
expect(dockerfile).not.toMatch(/^\s*ENV\b[^\n]*\bOPENCLAW_BUNDLED_PLUGINS_DIR\b/m);
});
it("normalizes plugin and agent paths permissions in image layers" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain(
"RUN for dir in /app/${OPENCLAW_BUNDLED_PLUGIN_DIR} /app/.agent /app/.agents; do \\" ,
);
expect(dockerfile).toContain('find "$dir" -type d -exec chmod 755 {} +' );
expect(dockerfile).toContain('find "$dir" -type f -exec chmod 644 {} +' );
});
it("Docker GPG fingerprint awk uses correct quoting for OPENCLAW_SANDBOX=1 build" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain('== "fpr" {' );
expect(dockerfile).not.toContain('\\"fpr\\"' );
});
it("keeps runtime pnpm available" , async () => {
const dockerfile = await readFile(dockerfilePath, "utf8" );
expect(dockerfile).toContain("ENV COREPACK_HOME=/usr/local/share/corepack" );
expect(dockerfile).toContain(
'corepack prepare "$(node -p "require(\' ./package .json\').packageManager")" --activate' ,
);
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland