From 575099926df8b189760283c3f3bb0a5dcc1de30c Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 23 Sep 2026 11:08:45 -0700 Subject: [PATCH 1/5] node consumers: depend on the node binary only at runtime (#751) pkgs#670 fixed pnpm by depending on `subsetOf node ["node"]`. The other 17 packages that run under node still pulled in the whole `node` package at runtime: npm, npx and /usr/lib/node_modules/npm included. In any session that also contains node-lts, two npm trees get hardlinked first-writer-wins per file. Since node-lts 24.19.0 moved its bundled npm ahead of node's, the blend no longer loads: every npm/npx dies with "Class extends value undefined". webapp's activation hook (`npx astro dev`) hits this, so webapp is pinned to an older pkgs. None of these packages invoke npm at runtime. Build deps keep the full package, since builds need npm. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/agent-browser/build.ncl | 4 ++-- packages/bash-language-server/build.ncl | 4 ++-- packages/cf/build.ncl | 4 ++-- packages/emscripten/build.ncl | 2 +- packages/eslint/build.ncl | 4 ++-- packages/json-server/build.ncl | 4 ++-- packages/lerna/build.ncl | 4 ++-- packages/mermaid-cli/build.ncl | 4 ++-- packages/next/build.ncl | 4 ++-- packages/pi/build.ncl | 4 ++-- packages/prettier/build.ncl | 4 ++-- packages/pyright/build.ncl | 4 ++-- packages/serve/build.ncl | 4 ++-- packages/typescript-language-server/build.ncl | 4 ++-- packages/typescript/build.ncl | 4 ++-- packages/wrangler/build.ncl | 4 ++-- packages/yarn/build.ncl | 4 ++-- 17 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/agent-browser/build.ncl b/packages/agent-browser/build.ncl index 20b03d88e..01841d159 100644 --- a/packages/agent-browser/build.ncl +++ b/packages/agent-browser/build.ncl @@ -1,4 +1,4 @@ -let { standaloneTest, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let toolchain = import "../toolchain/build.ncl" in let make = import "../make/build.ncl" in @@ -71,7 +71,7 @@ let version = "0.15.1" in ], runtime_deps = [ bash, - node, + subsetOf node ["node"], glibc, chromium-headless-shell-bin, ], diff --git a/packages/bash-language-server/build.ncl b/packages/bash-language-server/build.ncl index 158835457..29f6cee4f 100644 --- a/packages/bash-language-server/build.ncl +++ b/packages/bash-language-server/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -13,7 +13,7 @@ let version = "5.8.0" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/cf/build.ncl b/packages/cf/build.ncl index fe2b4a5e0..1c405a04e 100644 --- a/packages/cf/build.ncl +++ b/packages/cf/build.ncl @@ -1,4 +1,4 @@ -let { standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in +let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -24,7 +24,7 @@ let version = "0.1.0" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/emscripten/build.ncl b/packages/emscripten/build.ncl index 26d032cb0..265d4a7d6 100644 --- a/packages/emscripten/build.ncl +++ b/packages/emscripten/build.ncl @@ -42,7 +42,7 @@ let version = "4.0.11" in runtime_deps = [ binaryen, glibc, - node, + subsetOf node ["node"], python, subsetOf llvm ["bins", "libllvm", "libs_clang", "liblld", "libclang"], ], diff --git a/packages/eslint/build.ncl b/packages/eslint/build.ncl index 024c0d6fe..06001872e 100644 --- a/packages/eslint/build.ncl +++ b/packages/eslint/build.ncl @@ -1,6 +1,6 @@ # Imported from Wolfi `eslint` by `pkgmgr import-wolfi` (node build), # with outputs refined from a verified `minimal package` build. -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -12,7 +12,7 @@ let version = "10.9.1" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/json-server/build.ncl b/packages/json-server/build.ncl index 505460a2a..c0bdb61eb 100644 --- a/packages/json-server/build.ncl +++ b/packages/json-server/build.ncl @@ -1,6 +1,6 @@ # Imported from Wolfi `json-server` by `pkgmgr import-wolfi` (node build), # with outputs refined from a verified `minimal package` build. -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -12,7 +12,7 @@ let version = "0.17.4" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/lerna/build.ncl b/packages/lerna/build.ncl index 7089fec8b..2ae43d09b 100644 --- a/packages/lerna/build.ncl +++ b/packages/lerna/build.ncl @@ -1,6 +1,6 @@ # Imported from Wolfi `lerna` by `pkgmgr import-wolfi` (node build), # with outputs refined from a verified `minimal package` build. -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -12,7 +12,7 @@ let version = "10.0.1" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/mermaid-cli/build.ncl b/packages/mermaid-cli/build.ncl index abfe0b1d0..7fd661cd9 100644 --- a/packages/mermaid-cli/build.ncl +++ b/packages/mermaid-cli/build.ncl @@ -1,4 +1,4 @@ -let { standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let curl = import "../curl/build.ncl" in let gh = import "../gh/build.ncl" in @@ -29,7 +29,7 @@ let version = "11.17.0" in runtime_deps = [ base, chromium-headless-shell-bin, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/next/build.ncl b/packages/next/build.ncl index af9d8f862..2e77c14da 100644 --- a/packages/next/build.ncl +++ b/packages/next/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let glib = import "../glib/build.ncl" in @@ -34,7 +34,7 @@ let version = "16.3.5" in coreutils, glib, libvips, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/pi/build.ncl b/packages/pi/build.ncl index da05d85d6..47427ff38 100644 --- a/packages/pi/build.ncl +++ b/packages/pi/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -25,7 +25,7 @@ let version = "0.84.4" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/prettier/build.ncl b/packages/prettier/build.ncl index 3212e44ac..3dc8462d9 100644 --- a/packages/prettier/build.ncl +++ b/packages/prettier/build.ncl @@ -1,7 +1,7 @@ # Imported from npm `prettier` by `pkgmgr import npm` (node build). # TODO: REVIEWER — installs `prettier` from npm; add any peer packages the # tool needs (cf. typescript-language-server pulling `typescript`). -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -15,7 +15,7 @@ let version = "3.9.8" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/pyright/build.ncl b/packages/pyright/build.ncl index d5af2b34b..9fa9a399a 100644 --- a/packages/pyright/build.ncl +++ b/packages/pyright/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -13,7 +13,7 @@ let version = "1.1.413" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/serve/build.ncl b/packages/serve/build.ncl index 9b07fc6b5..1a6f6f1a9 100644 --- a/packages/serve/build.ncl +++ b/packages/serve/build.ncl @@ -1,6 +1,6 @@ # Imported from Wolfi `serve` by `pkgmgr import-wolfi` (node build), # with outputs refined from a verified `minimal package` build. -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -12,7 +12,7 @@ let version = "14.2.6" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/typescript-language-server/build.ncl b/packages/typescript-language-server/build.ncl index 66b5e4ddd..235c34156 100644 --- a/packages/typescript-language-server/build.ncl +++ b/packages/typescript-language-server/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -13,7 +13,7 @@ let version = "5.3.0" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/typescript/build.ncl b/packages/typescript/build.ncl index 540619a84..c54608d41 100644 --- a/packages/typescript/build.ncl +++ b/packages/typescript/build.ncl @@ -1,7 +1,7 @@ # Imported from npm `typescript` by `pkgmgr import npm` (node build). # TODO: REVIEWER — installs `typescript` from npm; add any peer packages the # tool needs (cf. typescript-language-server pulling `typescript`). -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -15,7 +15,7 @@ let version = "7.0.2" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, diff --git a/packages/wrangler/build.ncl b/packages/wrangler/build.ncl index dac04242e..5323b6b33 100644 --- a/packages/wrangler/build.ncl +++ b/packages/wrangler/build.ncl @@ -1,4 +1,4 @@ -let { standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node = import "../node/build.ncl" in @@ -13,7 +13,7 @@ let version = "4.125.0" in ], runtime_deps = [ coreutils, - node, + subsetOf node ["node"], ], needs = { diff --git a/packages/yarn/build.ncl b/packages/yarn/build.ncl index 630f19c33..c8a785cab 100644 --- a/packages/yarn/build.ncl +++ b/packages/yarn/build.ncl @@ -1,7 +1,7 @@ # Imported from npm `yarn` by `pkgmgr import npm` (node build). # TODO: REVIEWER — installs `yarn` from npm; add any peer packages the # tool needs (cf. typescript-language-server pulling `typescript`). -let { Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node = import "../node/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -15,7 +15,7 @@ let version = "1.22.22" in base, node, ], - runtime_deps = [coreutils, node], + runtime_deps = [coreutils, subsetOf node ["node"]], needs = { dns = {}, From 5adbad9777098953951c3910009a098b4ee57218 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 23 Sep 2026 11:14:24 -0700 Subject: [PATCH 2/5] capy, vlt: depend on node-lts's binary only at runtime (#751) Mirror image of the previous commit: capy and vlt pulled in the whole node-lts package at runtime, so a session with `node` plus either of them got the same two-npm blend. Interpreter only. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/capy/build.ncl | 4 ++-- packages/vlt/build.ncl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/capy/build.ncl b/packages/capy/build.ncl index ab87affc5..e30c20be2 100644 --- a/packages/capy/build.ncl +++ b/packages/capy/build.ncl @@ -1,4 +1,4 @@ -let { Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { subsetOf, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let glibc = import "../glibc/build.ncl" in @@ -28,7 +28,7 @@ let version = "0.9.1" in coreutils, # `capy` shebang resolves node via /usr/bin/env ca-certificates, # HTTPS to the Capy API (sync, decryption) git, # capy shells out to git for repo/branch detection - node-lts, # capy is a Node CLI (#!/usr/bin/env node) + subsetOf node-lts ["node"], # capy is a Node CLI (#!/usr/bin/env node); interpreter only, no npm (#751) ], cmd = "./build.sh", diff --git a/packages/vlt/build.ncl b/packages/vlt/build.ncl index ba31fa702..b032ad095 100644 --- a/packages/vlt/build.ncl +++ b/packages/vlt/build.ncl @@ -47,7 +47,7 @@ # take it OFF that arm onto the repo-purl arm, where GHSA's npm advisories — # which are keyed by package name, not repo — would no longer match. Absent is # correct here, not an oversight. -let { standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Test, .. } = import "minimal.ncl" in +let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in @@ -61,7 +61,7 @@ let version = "1.0.5" in base, node-lts, ], - runtime_deps = [coreutils, node-lts], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, From b74b0cb7e8841bbc445b89cfd72fd910a30ba6e2 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 23 Sep 2026 11:17:47 -0700 Subject: [PATCH 3/5] node consumers: build and run on node-lts, not node (#751 review) Per Karl's review: `node` is 25.8.2, an odd line that never becomes LTS and is past end-of-life, so every tool here ran on a Node without security fixes. No package needs 25 (strictest `engines` is agent-browser's >=24). One runtime flavor across the catalog also removes the remaining two-/usr/bin/node race: every package now ships the same interpreter file, so the only sessions with two are ones that explicitly add `node`. Build deps move too. That is required, not just tidier: pnpm's runtime interpreter enters the build closure of everything built with pnpm (next, agent-browser), so leaving builds on `node` would put two /usr/bin/node binaries in those sandboxes. `node` stays in the catalog as an explicit user opt-in. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/agent-browser/build.ncl | 6 ++-- packages/bash-language-server/build.ncl | 6 ++-- packages/cf/build.ncl | 6 ++-- packages/emscripten/build.ncl | 4 +-- packages/eslint/build.ncl | 6 ++-- packages/json-server/build.ncl | 6 ++-- packages/lerna/build.ncl | 6 ++-- packages/mermaid-cli/build.ncl | 6 ++-- packages/next/build.ncl | 6 ++-- packages/pi/build.ncl | 6 ++-- packages/pnpm/build.ncl | 36 ++++++++----------- packages/prettier/build.ncl | 6 ++-- packages/pyright/build.ncl | 6 ++-- packages/serve/build.ncl | 6 ++-- packages/typescript-language-server/build.ncl | 6 ++-- packages/typescript/build.ncl | 6 ++-- packages/wrangler/build.ncl | 6 ++-- packages/yarn/build.ncl | 6 ++-- 18 files changed, 65 insertions(+), 71 deletions(-) diff --git a/packages/agent-browser/build.ncl b/packages/agent-browser/build.ncl index 01841d159..0d5d3348e 100644 --- a/packages/agent-browser/build.ncl +++ b/packages/agent-browser/build.ncl @@ -4,7 +4,7 @@ let toolchain = import "../toolchain/build.ncl" in let make = import "../make/build.ncl" in let pkgconf = import "../pkgconf/build.ncl" in let rust = import "../rust/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let pnpm = import "../pnpm/build.ncl" in let bash = import "../bash/build.ncl" in @@ -66,12 +66,12 @@ let version = "0.15.1" in make, pkgconf, rust, - node, + node-lts, pnpm, ], runtime_deps = [ bash, - subsetOf node ["node"], + subsetOf node-lts ["node"], glibc, chromium-headless-shell-bin, ], diff --git a/packages/bash-language-server/build.ncl b/packages/bash-language-server/build.ncl index 29f6cee4f..1e550103e 100644 --- a/packages/bash-language-server/build.ncl +++ b/packages/bash-language-server/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let version = "5.8.0" in { @@ -9,11 +9,11 @@ let version = "5.8.0" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/cf/build.ncl b/packages/cf/build.ncl index 1c405a04e..c9d44c721 100644 --- a/packages/cf/build.ncl +++ b/packages/cf/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in # cf is Cloudflare's unified CLI (technical preview). It is closed-source and # distributed only as a pre-bundled npm package — there is no public source @@ -20,11 +20,11 @@ let version = "0.1.0" in sha256 = "758b65757a10cf6ef918c3ec02da45374d6b4ec402ebe39b6907e46b8ac25c1b", } | Source, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/emscripten/build.ncl b/packages/emscripten/build.ncl index 265d4a7d6..6741356db 100644 --- a/packages/emscripten/build.ncl +++ b/packages/emscripten/build.ncl @@ -2,7 +2,7 @@ let { subsetOf, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. let base = import "../base/build.ncl" in let binaryen = import "../binaryen/build.ncl" in let llvm = import "../llvm/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let python = import "../python/build.ncl" in let glibc = import "../glibc/build.ncl" in @@ -42,7 +42,7 @@ let version = "4.0.11" in runtime_deps = [ binaryen, glibc, - subsetOf node ["node"], + subsetOf node-lts ["node"], python, subsetOf llvm ["bins", "libllvm", "libs_clang", "liblld", "libclang"], ], diff --git a/packages/eslint/build.ncl b/packages/eslint/build.ncl index 06001872e..02fcd8150 100644 --- a/packages/eslint/build.ncl +++ b/packages/eslint/build.ncl @@ -2,7 +2,7 @@ # with outputs refined from a verified `minimal package` build. let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "10.9.1" in { @@ -10,9 +10,9 @@ let version = "10.9.1" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/json-server/build.ncl b/packages/json-server/build.ncl index c0bdb61eb..191127474 100644 --- a/packages/json-server/build.ncl +++ b/packages/json-server/build.ncl @@ -2,7 +2,7 @@ # with outputs refined from a verified `minimal package` build. let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "0.17.4" in { @@ -10,9 +10,9 @@ let version = "0.17.4" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/lerna/build.ncl b/packages/lerna/build.ncl index 2ae43d09b..291bec069 100644 --- a/packages/lerna/build.ncl +++ b/packages/lerna/build.ncl @@ -2,7 +2,7 @@ # with outputs refined from a verified `minimal package` build. let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "10.0.1" in { @@ -10,9 +10,9 @@ let version = "10.0.1" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/mermaid-cli/build.ncl b/packages/mermaid-cli/build.ncl index 7fd661cd9..5d065b283 100644 --- a/packages/mermaid-cli/build.ncl +++ b/packages/mermaid-cli/build.ncl @@ -2,7 +2,7 @@ let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, Outpu let base = import "../base/build.ncl" in let curl = import "../curl/build.ncl" in let gh = import "../gh/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in # chromium-headless-shell-bin provides the headless-shell binary (which # Puppeteer drives in mermaid's "headless: shell" mode) and its full @@ -24,12 +24,12 @@ let version = "11.17.0" in base, curl, gh, - node, + node-lts, ], runtime_deps = [ base, chromium-headless-shell-bin, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/next/build.ncl b/packages/next/build.ncl index 2e77c14da..c1bb4f31d 100644 --- a/packages/next/build.ncl +++ b/packages/next/build.ncl @@ -4,7 +4,7 @@ let coreutils = import "../coreutils/build.ncl" in let glib = import "../glib/build.ncl" in let libvips = import "../libvips/build.ncl" in let make = import "../make/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let pkgconf = import "../pkgconf/build.ncl" in let pnpm = import "../pnpm/build.ncl" in let python = import "../python/build.ncl" in @@ -24,7 +24,7 @@ let version = "16.3.5" in base, glib, make, - node, + node-lts, pkgconf, pnpm, python, @@ -34,7 +34,7 @@ let version = "16.3.5" in coreutils, glib, libvips, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/pi/build.ncl b/packages/pi/build.ncl index 47427ff38..756815389 100644 --- a/packages/pi/build.ncl +++ b/packages/pi/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in # pi is the Pi coding agent (pi.dev), published to npm as # @earendil-works/pi-coding-agent. It is installed from the sha256-pinned @@ -21,11 +21,11 @@ let version = "0.84.4" in sha256 = "5bce766d19c3ceba18f3fbaad91c449c9f9d73981f9e3400ecef932006f06968", } | Source, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/pnpm/build.ncl b/packages/pnpm/build.ncl index 6bd7ee0fd..d9284ed8f 100644 --- a/packages/pnpm/build.ncl +++ b/packages/pnpm/build.ncl @@ -3,7 +3,7 @@ let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let tar = import "../tar/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let version = "11.21.0" in { @@ -18,28 +18,22 @@ let version = "11.21.0" in tar, ], runtime_deps = [ - # `node`, NOT `node-lts`. pnpm only needs *a* node for its - # `#!/usr/bin/env node` shebang, but whichever one it names lands in the - # closure of every package that builds with pnpm. `next` and - # `agent-browser` both declare `node` themselves, so naming `node-lts` here - # put TWO npm installs in one sandbox — and since the sandbox hardlinks - # closure members first-writer-wins PER FILE, the resulting - # /usr/lib/node_modules/npm was a blend of both versions: - # minipass-flush/index.js from npm 11.17.0 (named `{ Minipass }` import) - # beside a nested minipass@3.3.6 from npm 11.11.1 (default export only). - # That tree has never existed upstream and cannot load — every - # `npm install` died, which broke `next` for two days. See pkgs#665/#670. - # ONLY the `node` binary — deliberately NOT npm/npx/node_modules. + # ONLY the `node` binary from `node-lts` — deliberately NOT npm/npx/node_modules. # # pnpm needs a node to interpret its `#!/usr/bin/env node` shebang and - # nothing else. Depending on the whole package dragged a SECOND npm into - # the closure of everything built with pnpm, and since the sandbox - # hardlinks closure members first-writer-wins PER FILE, the resulting - # /usr/lib/node_modules/npm was a blend of two npm versions that cannot - # load (see the commit message and pkgs#665). Taking the subset means pnpm - # can never contribute an npm tree at all, so that class is closed even if - # a consumer later picks a different node. - subsetOf node ["node"], + # nothing else, but whatever it names lands in the closure of every package + # built with pnpm. Depending on a whole node package dragged a SECOND npm + # into that closure; the sandbox (and a user session) hardlinks closure + # members first-writer-wins PER FILE, so /usr/lib/node_modules/npm became a + # blend of two npm versions that cannot load ("Class extends value + # undefined"). That broke `next` for two days (#665/#670) and webapp's + # sessions (#751). The subset means pnpm can never contribute an npm tree. + # + # `node-lts`, like every other node consumer: one runtime flavor across the + # catalog means every package ships the same /usr/bin/node, so there is no + # first-writer race on the interpreter either (#751 review). `node` (Current) + # stays available as an explicit opt-in for users. + subsetOf node-lts ["node"], coreutils, ], needs = diff --git a/packages/prettier/build.ncl b/packages/prettier/build.ncl index 3dc8462d9..fe74c6b54 100644 --- a/packages/prettier/build.ncl +++ b/packages/prettier/build.ncl @@ -3,7 +3,7 @@ # tool needs (cf. typescript-language-server pulling `typescript`). let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "3.9.8" in { @@ -13,9 +13,9 @@ let version = "3.9.8" in { file = "package.json" } | Local, { file = "package-lock.json" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/pyright/build.ncl b/packages/pyright/build.ncl index 9fa9a399a..7736c346e 100644 --- a/packages/pyright/build.ncl +++ b/packages/pyright/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let version = "1.1.413" in { @@ -9,11 +9,11 @@ let version = "1.1.413" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/serve/build.ncl b/packages/serve/build.ncl index 1a6f6f1a9..ce9a9d890 100644 --- a/packages/serve/build.ncl +++ b/packages/serve/build.ncl @@ -2,7 +2,7 @@ # with outputs refined from a verified `minimal package` build. let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "14.2.6" in { @@ -10,9 +10,9 @@ let version = "14.2.6" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/typescript-language-server/build.ncl b/packages/typescript-language-server/build.ncl index 235c34156..85e1ff3db 100644 --- a/packages/typescript-language-server/build.ncl +++ b/packages/typescript-language-server/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let version = "5.3.0" in { @@ -9,11 +9,11 @@ let version = "5.3.0" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/typescript/build.ncl b/packages/typescript/build.ncl index c54608d41..d4f698198 100644 --- a/packages/typescript/build.ncl +++ b/packages/typescript/build.ncl @@ -3,7 +3,7 @@ # tool needs (cf. typescript-language-server pulling `typescript`). let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "7.0.2" in { @@ -13,9 +13,9 @@ let version = "7.0.2" in { file = "package.json" } | Local, { file = "package-lock.json" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, diff --git a/packages/wrangler/build.ncl b/packages/wrangler/build.ncl index 5323b6b33..63bc11b9a 100644 --- a/packages/wrangler/build.ncl +++ b/packages/wrangler/build.ncl @@ -1,7 +1,7 @@ let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let version = "4.125.0" in { @@ -9,11 +9,11 @@ let version = "4.125.0" in build_deps = [ { file = "build.sh" } | Local, base, - node, + node-lts, ], runtime_deps = [ coreutils, - subsetOf node ["node"], + subsetOf node-lts ["node"], ], needs = { diff --git a/packages/yarn/build.ncl b/packages/yarn/build.ncl index c8a785cab..2a961648f 100644 --- a/packages/yarn/build.ncl +++ b/packages/yarn/build.ncl @@ -3,7 +3,7 @@ # tool needs (cf. typescript-language-server pulling `typescript`). let { subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in -let node = import "../node/build.ncl" in +let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let version = "1.22.22" in { @@ -13,9 +13,9 @@ let version = "1.22.22" in { file = "package.json" } | Local, { file = "package-lock.json" } | Local, base, - node, + node-lts, ], - runtime_deps = [coreutils, subsetOf node ["node"]], + runtime_deps = [coreutils, subsetOf node-lts ["node"]], needs = { dns = {}, From 468f31d9f58e088abcf53e0e2fc1f747394f0da7 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 23 Sep 2026 11:36:46 -0700 Subject: [PATCH 4/5] import lines: standaloneTest before subsetOf (canonical order) Five import lines had `subsetOf` prepended ahead of `standaloneTest`. Lowercase identifiers sort alphabetically, so the import-line checker failed them. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/agent-browser/build.ncl | 2 +- packages/cf/build.ncl | 2 +- packages/mermaid-cli/build.ncl | 2 +- packages/vlt/build.ncl | 2 +- packages/wrangler/build.ncl | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/agent-browser/build.ncl b/packages/agent-browser/build.ncl index 0d5d3348e..b29cc2408 100644 --- a/packages/agent-browser/build.ncl +++ b/packages/agent-browser/build.ncl @@ -1,4 +1,4 @@ -let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { standaloneTest, subsetOf, Attrs, BuildSpec, Local, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let toolchain = import "../toolchain/build.ncl" in let make = import "../make/build.ncl" in diff --git a/packages/cf/build.ncl b/packages/cf/build.ncl index c9d44c721..f78f45f2d 100644 --- a/packages/cf/build.ncl +++ b/packages/cf/build.ncl @@ -1,4 +1,4 @@ -let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in +let { standaloneTest, subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node-lts = import "../node-lts/build.ncl" in diff --git a/packages/mermaid-cli/build.ncl b/packages/mermaid-cli/build.ncl index 5d065b283..3cd646b5f 100644 --- a/packages/mermaid-cli/build.ncl +++ b/packages/mermaid-cli/build.ncl @@ -1,4 +1,4 @@ -let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in +let { standaloneTest, subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Source, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let curl = import "../curl/build.ncl" in let gh = import "../gh/build.ncl" in diff --git a/packages/vlt/build.ncl b/packages/vlt/build.ncl index b032ad095..84922c4a4 100644 --- a/packages/vlt/build.ncl +++ b/packages/vlt/build.ncl @@ -47,7 +47,7 @@ # take it OFF that arm onto the repo-purl arm, where GHSA's npm advisories — # which are keyed by package name, not repo — would no longer match. Absent is # correct here, not an oversight. -let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Test, .. } = import "minimal.ncl" in +let { standaloneTest, subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, Test, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let node-lts = import "../node-lts/build.ncl" in let coreutils = import "../coreutils/build.ncl" in diff --git a/packages/wrangler/build.ncl b/packages/wrangler/build.ncl index 63bc11b9a..10ff5066e 100644 --- a/packages/wrangler/build.ncl +++ b/packages/wrangler/build.ncl @@ -1,4 +1,4 @@ -let { subsetOf, standaloneTest, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in +let { standaloneTest, subsetOf, Attrs, BuildSpec, Local, Needs, OutputBin, OutputData, .. } = import "minimal.ncl" in let base = import "../base/build.ncl" in let coreutils = import "../coreutils/build.ncl" in let node-lts = import "../node-lts/build.ncl" in From c283d1e3213ac8f92c1bd3558951746138e84454 Mon Sep 17 00:00:00 2001 From: bryan Date: Wed, 23 Sep 2026 12:11:32 -0700 Subject: [PATCH 5/5] next: replace (not nest) npm's sharp with the source build; drop prebuilts on every arch sharp is one of next's optionalDependencies, so `npm install -g next.tgz` already creates node_modules/sharp, and `cp -r` of the source-built sharp nested it at sharp/sharp/ where nothing loads it. The npm copy won: - amd64: its prebuilt @img/sharp-linux-x64 had been deleted, so sharp failed to load at all. This is the sharp_uses_system_libvips failure on #752. - arm64: @img/sharp-linux-arm64 was never deleted, so sharp quietly loaded the prebuilt addon with bundled libvips, and the test passed without testing its name. Pre-existing, not caused by the node-lts move. Main's CI Skips next's standalone tests unless next is rebuilt, and #752 forces the rebuild. The fix: - rm the npm sharp before copying the source build in; - remove @img/sharp-* for every arch; - guard the @img/colour copy the same way; - check at build time that sharp loads its OWN src/build/Release addon with a libvips version. Verified in the Linux sandbox (arm64): - fixed: `sharp 0.35.4 vips 8.18.6 addon sharp-linux-arm64-0.35.4.node` - negative control, without the rm: the build FAILS with `Could not load the "sharp" module using the linux-arm64 runtime` Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/next/build.sh | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/next/build.sh b/packages/next/build.sh index d3e9b66c0..ad49c8dae 100755 --- a/packages/next/build.sh +++ b/packages/next/build.sh @@ -99,8 +99,14 @@ if [ -d src/build ]; then fi fi -# Copy the source-built sharp into next's node_modules +# Copy the source-built sharp into next's node_modules, REPLACING the sharp npm +# already installed there: sharp is one of next's optionalDependencies. With +# the target present, `cp -r` nests the build at sharp/sharp/, which nothing +# loads. The npm copy then wins with its prebuilt @img binaries (deleted +# below): no binary at all on amd64, and on arm64 a silent fallback to the +# prebuilt addon plus bundled libvips. cd "$SHARP_STAGING" +rm -rf "$NEXT_DIR/node_modules/sharp" cp -r node_modules/sharp "$NEXT_DIR/node_modules/sharp" # Copy sharp's runtime dependencies @@ -110,14 +116,30 @@ for dep in detect-libc semver; do fi done mkdir -p "$NEXT_DIR/node_modules/@img" -if [ -d "node_modules/@img/colour" ]; then +if [ -d "node_modules/@img/colour" ] && [ ! -d "$NEXT_DIR/node_modules/@img/colour" ]; then cp -r node_modules/@img/colour "$NEXT_DIR/node_modules/@img/colour" fi -# Remove any prebuilt platform binaries (we use source-built sharp + system libvips) -rm -rf "$NEXT_DIR/node_modules/@img/sharp-linux-x64" -rm -rf "$NEXT_DIR/node_modules/@img/sharp-libvips-linux-x64" -rm -rf "$NEXT_DIR/node_modules/sharp/node_modules/@img/sharp-linux-x64" -rm -rf "$NEXT_DIR/node_modules/sharp/node_modules/@img/sharp-libvips-linux-x64" -rm -rf "$NEXT_DIR/node_modules/sharp/node_modules/@img/sharp-linuxmusl-x64" -rm -rf "$NEXT_DIR/node_modules/sharp/node_modules/@img/sharp-libvips-linuxmusl-x64" +# Remove every prebuilt platform binary, for EVERY arch (we use source-built +# sharp + system libvips). Naming only the x64 ones left arm64's +# @img/sharp-linux-arm64 in place, so arm64 quietly loaded the prebuilt addon. +# @img/colour is a JS dependency, not a binary; it stays. +for img in "$NEXT_DIR/node_modules/@img" "$NEXT_DIR/node_modules/sharp/node_modules/@img"; do + [ -d "$img" ] || continue + find "$img" -mindepth 1 -maxdepth 1 -name 'sharp-*' -exec rm -rf {} + +done + +# Prove it at build time, not in a later test: sharp must load from the +# installed tree, from its source-built addon, with a libvips version. +node -e ' +const path = require("path"); +const dir = process.argv[1]; +const s = require(dir); +const addon = Object.keys(require.cache).find((f) => f.endsWith(".node")); +const want = path.join(dir, "src", "build", "Release") + path.sep; +if (!addon || !addon.startsWith(want)) { + throw new Error("sharp did not load its source-built addon (loaded: " + addon + ")"); +} +if (!s.versions.vips) throw new Error("sharp loaded without libvips"); +console.log("sharp", s.versions.sharp, "vips", s.versions.vips, "addon", path.basename(addon)); +' "$NEXT_DIR/node_modules/sharp"