


























@@ -22,7 +22,7 @@ describe("Dockerfile", () => {
2222expect(dockerfile).toContain(
2323'ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"',
2424);
25-expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps");
25+expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS workspace-deps");
2626expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build");
2727expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-runtime");
2828expect(dockerfile).toContain("FROM base-runtime");
@@ -38,7 +38,7 @@ describe("Dockerfile", () => {
3838"FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-runtime",
3939);
4040const caInstallIndex = collapsed.indexOf(
41-"ca-certificates procps hostname curl git lsof openssl python3",
41+"ca-certificates curl git hostname lsof openssl procps python3",
4242);
43434444expect(runtimeIndex).toBeGreaterThan(-1);
@@ -54,14 +54,14 @@ describe("Dockerfile", () => {
5454"FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-runtime",
5555);
5656const pythonInstallIndex = dockerfile.indexOf(
57-"ca-certificates procps hostname curl git lsof openssl python3",
57+"ca-certificates curl git hostname lsof openssl procps python3",
5858);
59596060expect(runtimeIndex).toBeGreaterThan(-1);
6161expect(pythonInstallIndex).toBeGreaterThan(runtimeIndex);
6262expect(pythonInstallIndex).toBeLessThan(dockerfile.indexOf("RUN chown node:node /app"));
6363expect(dockerfile).toContain(
64-"ca-certificates procps hostname curl git lsof openssl python3 tini",
64+"ca-certificates curl git hostname lsof openssl procps python3 tini",
6565);
6666expect(dockerfile).toContain('ENTRYPOINT ["tini", "-s", "--"]');
6767});
@@ -80,6 +80,20 @@ describe("Dockerfile", () => {
8080expect(dockerfile).toContain("apt-get install -y --no-install-recommends xvfb");
8181});
828283+it("uses the Docker target platform for pnpm install and prune", async () => {
84+const dockerfile = await readFile(dockerfilePath, "utf8");
85+86+expect(dockerfile).toContain("pnpm install --frozen-lockfile \\");
87+expect(dockerfile).toContain("CI=true pnpm prune --prod \\");
88+expect(dockerfile).toContain("--config.offline=true");
89+expect(dockerfile.split("--config.supportedArchitectures.os=linux").length - 1).toBe(2);
90+expect(
91+dockerfile.split("--config.supportedArchitectures.cpu=\"$(node -p 'process.arch')\"").length -
92+1,
93+).toBe(2);
94+expect(dockerfile.split("--config.supportedArchitectures.libc=glibc").length - 1).toBe(2);
95+});
96+8397it("verifies matrix-sdk-crypto native addons without hardcoded pnpm virtual-store paths", async () => {
8498const dockerfile = await readFile(dockerfilePath, "utf8");
8599expect(dockerfile).toContain("Verifying critical native addons");
@@ -93,24 +107,49 @@ describe("Dockerfile", () => {
93107);
94108});
9510996-it("copies postinstall helper imports before pnpm install", async () => {
110+it("copies install workspace manifests before pnpm install", async () => {
97111const dockerfile = await readFile(dockerfilePath, "utf8");
98112const installIndex = dockerfile.indexOf("pnpm install --frozen-lockfile");
99113const postinstallIndex = dockerfile.indexOf("COPY scripts/postinstall-bundled-plugins.mjs");
100114const distImportHelperIndex = dockerfile.indexOf(
101115"COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs",
102116);
117+const packageManifestIndex = dockerfile.indexOf(
118+"COPY --from=workspace-deps /out/packages/ ./packages/",
119+);
120+const extensionManifestIndex = dockerfile.indexOf(
121+"COPY --from=workspace-deps /out/${OPENCLAW_BUNDLED_PLUGIN_DIR}/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/",
122+);
103123104124expect(postinstallIndex).toBeGreaterThan(-1);
105125expect(distImportHelperIndex).toBeGreaterThan(-1);
126+expect(packageManifestIndex).toBeGreaterThan(-1);
127+expect(extensionManifestIndex).toBeGreaterThan(-1);
128+expect(dockerfile).toContain("for manifest in /tmp/packages/*/package.json");
129+expect(dockerfile).toContain(
130+`if [ -f "/tmp/\${OPENCLAW_BUNDLED_PLUGIN_DIR}/$ext/package.json" ]; then`,
131+);
106132expect(postinstallIndex).toBeLessThan(installIndex);
107133expect(distImportHelperIndex).toBeLessThan(installIndex);
134+expect(packageManifestIndex).toBeLessThan(installIndex);
135+expect(extensionManifestIndex).toBeLessThan(installIndex);
136+});
137+138+it("does not let pnpm resync the full source workspace during Docker build scripts", async () => {
139+const dockerfile = await readFile(dockerfilePath, "utf8");
140+141+expect(dockerfile).toContain(
142+"NODE_OPTIONS=--max-old-space-size=8192 pnpm_config_verify_deps_before_run=false pnpm build:docker",
143+);
144+expect(dockerfile).toContain(
145+"pnpm_config_verify_deps_before_run=false pnpm canvas:a2ui:bundle",
146+);
147+expect(dockerfile).toContain("pnpm_config_verify_deps_before_run=false pnpm ui:build");
148+expect(dockerfile).toContain("pnpm_config_verify_deps_before_run=false pnpm qa:lab:build");
108149});
109150110151it("prunes runtime dependencies after the build stage", async () => {
111152const dockerfile = await readFile(dockerfilePath, "utf8");
112-const normalizedExtensionLoop =
113-"for ext in $(printf '%s\\n' \"$OPENCLAW_EXTENSIONS\" | tr ',' ' '); do \\";
114153expect(dockerfile).toContain("FROM build AS runtime-assets");
115154expect(dockerfile).toContain("ARG OPENCLAW_EXTENSIONS");
116155expect(dockerfile).toContain("ARG OPENCLAW_BUNDLED_PLUGIN_DIR");
@@ -120,14 +159,25 @@ describe("Dockerfile", () => {
120159expect(dockerfile).toContain(
121160'Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,matrix" .',
122161);
123-expect(dockerfile.split(normalizedExtensionLoop).length - 1).toBe(2);
124-expect(dockerfile).toContain("pnpm-workspace.runtime.yaml");
125-expect(dockerfile).toContain(" - ui\\n");
126-expect(dockerfile).toContain("CI=true pnpm_config_frozen_lockfile=false pnpm prune --prod");
162+expect(dockerfile).toContain(
163+"RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \\",
164+);
165+expect(dockerfile).toContain("COPY --from=workspace-deps /out/packages/ ./packages/");
166+expect(dockerfile).toContain(
167+"COPY --from=workspace-deps /out/${OPENCLAW_BUNDLED_PLUGIN_DIR}/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/",
168+);
169+expect(dockerfile).toContain("CI=true pnpm prune --prod \\");
170+expect(dockerfile).toContain("--config.offline=true");
171+expect(dockerfile).toContain("--config.supportedArchitectures.os=linux");
172+expect(dockerfile).toContain(
173+"--config.supportedArchitectures.cpu=\"$(node -p 'process.arch')\"",
174+);
175+expect(dockerfile).toContain("--config.supportedArchitectures.libc=glibc");
127176expect(dockerfile).toContain(
128177'OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" node scripts/prune-docker-plugin-dist.mjs',
129178);
130-expect(dockerfile).toContain("prune must not rediscover unrelated workspaces");
179+expect(dockerfile).not.toContain("pnpm-workspace.runtime.yaml");
180+expect(dockerfile).not.toContain("write-runtime-pnpm-workspace");
131181expect(dockerfile).not.toContain(
132182`npm install --prefix "${BUNDLED_PLUGIN_ROOT_DIR}/$ext" --omit=dev --silent`,
133183);
@@ -147,26 +197,16 @@ describe("Dockerfile", () => {
147197const pnpmWorkspace = YAML.parse(await readFile(pnpmWorkspacePath, "utf8")) as {
148198patchedDependencies?: Record<string, string>;
149199};
150-const saveSourceWorkspace = "cp pnpm-workspace.yaml /tmp/pnpm-workspace.source.yaml";
151-const usePruneWorkspace = "cp /tmp/pnpm-workspace.runtime.yaml pnpm-workspace.yaml";
152-const pruneProd = "CI=true pnpm_config_frozen_lockfile=false pnpm prune --prod";
153-const restoreSourceWorkspace = "cp /tmp/pnpm-workspace.source.yaml pnpm-workspace.yaml";
200+const pruneProd = "CI=true pnpm prune --prod";
154201const finalWorkspaceCopy =
155202"COPY --from=runtime-assets --chown=node:node /app/pnpm-workspace.yaml .";
156203157204expect(Object.keys(pnpmWorkspace.patchedDependencies ?? {})).not.toHaveLength(0);
158-expect(dockerfile).toContain(saveSourceWorkspace);
159-expect(dockerfile).toContain(usePruneWorkspace);
160-expect(dockerfile).toContain(restoreSourceWorkspace);
205+expect(dockerfile).not.toContain("pnpm-workspace.runtime.yaml");
206+expect(dockerfile).not.toContain("write-runtime-pnpm-workspace");
207+expect(dockerfile).not.toContain("pnpm_config_frozen_lockfile=false");
161208expect(dockerfile).toContain(finalWorkspaceCopy);
162-expect(dockerfile.indexOf(saveSourceWorkspace)).toBeLessThan(
163-dockerfile.indexOf(usePruneWorkspace),
164-);
165-expect(dockerfile.indexOf(usePruneWorkspace)).toBeLessThan(dockerfile.indexOf(pruneProd));
166-expect(dockerfile.indexOf(pruneProd)).toBeLessThan(dockerfile.indexOf(restoreSourceWorkspace));
167-expect(dockerfile.indexOf(restoreSourceWorkspace)).toBeLessThan(
168-dockerfile.indexOf(finalWorkspaceCopy),
169-);
209+expect(dockerfile.indexOf(pruneProd)).toBeLessThan(dockerfile.indexOf(finalWorkspaceCopy));
170210expect(dockerfile).toContain(
171211"COPY --from=runtime-assets --chown=node:node /app/patches ./patches",
172212);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。