From aab9a958e282f871e04c5ebf9300689e91546ac8 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 13 Sep 2026 07:53:45 +0000 Subject: [PATCH] fix: use tested npm for compatible CLI installs --- apps/logicsrc-web/public/install.sh | 16 +++++++++++++++- packages/cli/src/install-npm.test.ts | 25 +++++++++++++++++++++++++ scripts/install-npm.cjs | 17 +++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/install-npm.test.ts create mode 100644 scripts/install-npm.cjs diff --git a/apps/logicsrc-web/public/install.sh b/apps/logicsrc-web/public/install.sh index 939513d..34cec55 100755 --- a/apps/logicsrc-web/public/install.sh +++ b/apps/logicsrc-web/public/install.sh @@ -134,7 +134,21 @@ do_install() { ok "downloaded${short_sha:+ ($short_sha)}" info "installing dependencies (this can take a minute)…" - ( cd "$STAGE" && npm install --no-audit --no-fund --ignore-scripts ) >"$BUILD_LOG" 2>&1 \ + # Git dependency preparation can break under a newer system npm even with + # --ignore-scripts. Use the repo's tested npm where its Node engine permits + # it, while retaining the documented older-Node CLI installation path. + ( cd "$STAGE" && + npm_spec="" && + # Older tags predate the selector; preserve their install behavior too. + if [ -f scripts/install-npm.cjs ]; then + npm_spec="$(node scripts/install-npm.cjs)" || exit 1 + fi + if [ -n "$npm_spec" ]; then + npm exec --yes --package="$npm_spec" -- npm install --no-audit --no-fund --ignore-scripts + else + npm install --no-audit --no-fund --ignore-scripts + fi + ) >"$BUILD_LOG" 2>&1 \ || step_fail "npm install failed" info "building the CLI…" ( cd "$STAGE" && npm run build:cli ) >>"$BUILD_LOG" 2>&1 \ diff --git a/packages/cli/src/install-npm.test.ts b/packages/cli/src/install-npm.test.ts new file mode 100644 index 0000000..3446f87 --- /dev/null +++ b/packages/cli/src/install-npm.test.ts @@ -0,0 +1,25 @@ +import { createRequire } from "node:module"; +import { describe, expect, it } from "vitest"; + +const require = createRequire(import.meta.url); +const { selectNpm } = require("../../../scripts/install-npm.cjs") as { + selectNpm: (nodeVersion: string, packageManager: string) => string | null; +}; + +describe("installer npm compatibility", () => { + it.each(["18.20.8", "20.16.0", "21.7.3", "22.8.0"])("preserves the host npm on Node %s", (version) => { + expect(selectNpm(version, "npm@11.11.0")).toBeNull(); + }); + + it.each(["20.17.0", "20.19.0", "22.9.0", "23.0.0", "24.18.1", "25.0.0"])( + "selects the tested npm on Node %s", (version) => { + expect(selectNpm(version, "npm@11.11.0")).toBe("npm@11.11.0"); + } + ); + + it("does not assume compatibility for a different package manager or npm major", () => { + expect(selectNpm("24.18.1", "pnpm@11.11.0")).toBeNull(); + expect(selectNpm("24.18.1", "npm@12.0.0")).toBeNull(); + expect(selectNpm("unknown", "npm@11.11.0")).toBeNull(); + }); +}); diff --git a/scripts/install-npm.cjs b/scripts/install-npm.cjs new file mode 100644 index 0000000..c12b696 --- /dev/null +++ b/scripts/install-npm.cjs @@ -0,0 +1,17 @@ +// npm 11 supports Node ^20.17.0 || >=22.9.0. Older CLI runtimes keep +// their existing npm; modern runtimes use the repository's tested version. +function selectNpm(nodeVersion, packageManager) { + const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(nodeVersion); + if (!match || !/^npm@11\.\d+\.\d+$/.test(packageManager)) return null; + const major = Number(match[1]); + const minor = Number(match[2]); + const supported = (major === 20 && minor >= 17) || major > 22 || (major === 22 && minor >= 9); + return supported ? packageManager : null; +} + +module.exports = { selectNpm }; + +if (require.main === module) { + const { packageManager } = require("../package.json"); + process.stdout.write(selectNpm(process.versions.node, packageManager) || ""); +}