fix(tools-pack): replace corepack with npx in linux container build (#558)

This commit is contained in:
lefarcen
2026-05-06 00:05:22 +08:00
committed by GitHub
parent 0953ab58a2
commit 74f1a18b71
2 changed files with 42 additions and 13 deletions
+16 -8
View File
@@ -96,14 +96,22 @@ export function buildDockerArgs(
// None of these values can contain shell metacharacters, so direct
// interpolation into the inner command string is safe.
//
// We invoke pnpm via `corepack pnpm` instead of `corepack enable && pnpm ...`
// because the container runs as the host's non-root uid (--user above) and
// `corepack enable` writes shims next to the system Node binary, which is
// owned by root in electronuserland/builder:base and not writable by the
// unprivileged container user. `corepack pnpm` resolves and executes the
// pinned packageManager from package.json directly, no shims required.
// We can't rely on `corepack pnpm` here: although Node 16.10+ ships corepack,
// the `electronuserland/builder:base` image strips the corepack binary, so
// the inner `bash -lc` fails with `corepack: command not found`. We also
// can't `corepack enable` ourselves — the container runs as the host's
// non-root uid (--user above) and corepack would try to write shims next
// to the system Node binary, which is owned by root in this image.
//
// Use `npx --yes pnpm@<version>` instead: `npx` ships with npm (always
// present in the image), `--yes` skips the install confirmation, and the
// package gets cached under `$HOME/.npm/_npx`, which is writable by the
// unprivileged user. The pinned version matches the `packageManager`
// field in the root package.json so reproducibility is preserved.
const PNPM_VERSION = "10.33.2";
const pnpmCmd = `npx --yes pnpm@${PNPM_VERSION}`;
const innerArgs = [
"corepack pnpm tools-pack linux build",
`${pnpmCmd} tools-pack linux build`,
`--to ${config.to}`,
`--namespace ${config.namespace}`,
"--dir /tools-pack",
@@ -111,7 +119,7 @@ export function buildDockerArgs(
if (config.portable) {
innerArgs.push("--portable");
}
const innerCommand = "corepack pnpm install --frozen-lockfile && " + innerArgs.join(" ");
const innerCommand = `${pnpmCmd} install --frozen-lockfile && ` + innerArgs.join(" ");
return [
"run",
+26 -5
View File
@@ -1,3 +1,7 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import type { ToolPackConfig } from "../src/config.js";
@@ -83,16 +87,33 @@ describe("buildDockerArgs", () => {
it("re-invokes pnpm tools-pack linux build inside the container without --containerized", () => {
const args = buildDockerArgs(makeConfig(), { uid: 1000, gid: 1000 });
const last = args[args.length - 1];
expect(last).toMatch(/corepack pnpm install --frozen-lockfile/);
expect(last).toMatch(/corepack pnpm tools-pack linux build --to all --namespace default/);
expect(last).toMatch(/npx --yes pnpm@\d+\.\d+\.\d+ install --frozen-lockfile/);
expect(last).toMatch(/npx --yes pnpm@\d+\.\d+\.\d+ tools-pack linux build --to all --namespace default/);
expect(last).not.toMatch(/--containerized/);
});
it("invokes pnpm via `corepack pnpm` rather than `corepack enable` (non-root container can't write Node shim dir)", () => {
it("invokes pnpm via `npx --yes pnpm@<version>` (electronuserland/builder:base strips corepack, and the non-root container can't write Node shim dir)", () => {
const args = buildDockerArgs(makeConfig(), { uid: 1000, gid: 1000 });
const last = args[args.length - 1];
expect(last).not.toMatch(/corepack enable/);
expect(last).toMatch(/corepack pnpm/);
expect(last).not.toMatch(/corepack/);
expect(last).toMatch(/npx --yes pnpm@/);
});
it("hardcoded pnpm version stays in lockstep with root package.json `packageManager`", () => {
// Guard against silent drift: if someone bumps packageManager in the
// root package.json but forgets to update PNPM_VERSION in linux.ts,
// the Linux container build would silently keep using the old pnpm.
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
const rootPkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf-8")) as {
packageManager?: string;
};
const match = String(rootPkg.packageManager ?? "").match(/^pnpm@(\d+\.\d+\.\d+)$/);
expect(match, `expected root packageManager "pnpm@x.y.z", got ${rootPkg.packageManager}`).not.toBeNull();
const expectedVersion = match![1];
const args = buildDockerArgs(makeConfig(), { uid: 1000, gid: 1000 });
const last = args[args.length - 1];
expect(last).toContain(`npx --yes pnpm@${expectedVersion}`);
});
it("forwards --dir /tools-pack so inner build output lands under the mounted host dir", () => {