From 49dd7fb16a95aadca0bb5c5a42a8bbe11291a14a Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Wed, 5 Aug 2026 11:35:34 +0200 Subject: [PATCH] chore: upgrade to NPM v12 Require npm >=12.0.2 <13, enforced via check-node-version on npm install and npm ci. CI installs the version from engines.npm via the new .github/actions/install-npm composite action. The motivation is that npm v12 turns three code-execution paths off by default, most notably the unauthorized execution of install scripts: - allowScripts now defaults to off, so npm install no longer executes preinstall, install or postinstall scripts from dependencies unless they are explicitly allowed in package.json. This also covers prepare scripts from git, file and link dependencies. - --allow-git now defaults to none, which closes a code-execution path where a git dependency's .npmrc could override the git executable, even with --ignore-scripts. - --allow-remote now defaults to none, blocking dependencies from remote URLs such as HTTPS tarballs. See https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/ The check is skipped during npm publish and npm pack, because semantic-release bundles its own npm (@semantic-release/npm depends on npm@^11.6.2) and runs the publish with that version rather than the one installed in CI. The Node.js requirement is left unchanged. --- .github/actions/install-npm/action.yml | 14 +++ .github/workflows/deploy.yaml | 3 + .github/workflows/test-and-release.yaml | 9 ++ README.md | 10 +++ package-lock.json | 111 +++++++++++++++++++++++- package.json | 5 +- 6 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 .github/actions/install-npm/action.yml diff --git a/.github/actions/install-npm/action.yml b/.github/actions/install-npm/action.yml new file mode 100644 index 00000000..e2631ad6 --- /dev/null +++ b/.github/actions/install-npm/action.yml @@ -0,0 +1,14 @@ +name: Install NPM +description: > + Installs the NPM version required by the `engines.npm` directive in + package.json, which is required for the project to build correctly. + +runs: + using: composite + steps: + - name: Install NPM + shell: bash + run: | + npmVersion="$(node -p 'require(`${process.env.GITHUB_WORKSPACE}/package.json`).engines.npm')" + echo "Installing npm@${npmVersion}" + npm install -g "npm@${npmVersion}" diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 0b5e59bf..d46147ad 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -29,6 +29,9 @@ jobs: node-version: "22.x" cache: "npm" + - name: Install NPM version specified in package.json + uses: ./.github/actions/install-npm + - name: Install dependencies run: npm ci --no-audit diff --git a/.github/workflows/test-and-release.yaml b/.github/workflows/test-and-release.yaml index 565ae1f2..408d44fa 100644 --- a/.github/workflows/test-and-release.yaml +++ b/.github/workflows/test-and-release.yaml @@ -18,6 +18,9 @@ jobs: node-version: "22.x" cache: "npm" + - name: Install NPM version specified in package.json + uses: ./.github/actions/install-npm + - name: Install dependencies run: npm ci --no-audit @@ -75,6 +78,9 @@ jobs: node-version: "22.x" cache: "npm" + - name: Install NPM version specified in package.json + uses: ./.github/actions/install-npm + - name: Install dependencies run: npm ci --no-audit --force @@ -113,6 +119,9 @@ jobs: node-version: "22.x" cache: "npm" + - name: Install NPM version specified in package.json + uses: ./.github/actions/install-npm + - name: Semantic release run: npx semantic-release env: diff --git a/README.md b/README.md index a1ac4cf9..a0ba585b 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,13 @@ Run the interactive code generator: ```bash npx playwright codegen ``` + +## Node & NPM + +This project requires Node.js `>=20.0.0` and npm `>=12.0.2 <13` (enforced via +`check-node-version` on `npm install` and `npm ci`). + +The check is skipped during `npm publish` and `npm pack`, because +`semantic-release` bundles its own npm (`@semantic-release/npm` depends on +`npm@^11.6.2`) and runs the publish with that version rather than the one +installed in CI. diff --git a/package-lock.json b/package-lock.json index 6bbfab92..64e66ba8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "@types/jsonwebtoken": "9.0.10", "@types/node": "24.9.1", "@types/semver": "7.7.1", + "check-node-version": "4.2.1", "commitlint": "19.8.1", "glob": "11.1.0", "globstar": "1.0.0", @@ -49,7 +50,7 @@ }, "engines": { "node": ">=20.0.0", - "npm": ">=9.0.0" + "npm": ">=12.0.2 <13" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3400,6 +3401,100 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-node-version": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/check-node-version/-/check-node-version-4.2.1.tgz", + "integrity": "sha512-YYmFYHV/X7kSJhuN/QYHUu998n/TRuDe8UenM3+m5NrkiH670lb9ILqHIvBencvJc4SDh+XcbXMR4b+TtubJiw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "chalk": "^3.0.0", + "map-values": "^1.0.1", + "minimist": "^1.2.0", + "object-filter": "^1.0.2", + "run-parallel": "^1.1.4", + "semver": "^6.3.0" + }, + "bin": { + "check-node-version": "bin.js" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/check-node-version/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/check-node-version/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-node-version/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/check-node-version/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/check-node-version/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/check-node-version/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ci-info": { "version": "4.0.0", "dev": true, @@ -6449,6 +6544,13 @@ "integrity": "sha512-2rfORNh9fzhCUKu0ciE48ogR1Zg7hleABLO5MBF06vOb4esTtE8FbfQJiC+wveiNFoeojWKz6zXheUToRR5uIg==", "license": "ISC" }, + "node_modules/map-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-values/-/map-values-1.0.1.tgz", + "integrity": "sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==", + "dev": true, + "license": "Public Domain" + }, "node_modules/maplibre-gl": { "version": "5.24.0", "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.24.0.tgz", @@ -7231,6 +7333,13 @@ "node": ">=0.10.0" } }, + "node_modules/object-filter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", + "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", + "dev": true, + "license": "MIT" + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/package.json b/package.json index 3ddb8147..b60b3f5b 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "start": "vite", "start:e2e": "vite -c e2e-tests/vite.config.e2e.ts", "build": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --emptyOutDir", - "prepare": "husky", + "prepare": "husky && case \"$npm_command\" in install|ci) check-node-version --package ;; esac", "test": "npx globstar -- npx tsx --test --test-reporter spec \"!(node_modules|e2e-tests)/**/*.spec.ts\"", "test:e2e": "npx playwright test --project chromium" }, @@ -39,6 +39,7 @@ "@types/jsonwebtoken": "9.0.10", "@types/node": "24.9.1", "@types/semver": "7.7.1", + "check-node-version": "4.2.1", "commitlint": "19.8.1", "glob": "11.1.0", "globstar": "1.0.0", @@ -67,7 +68,7 @@ }, "engines": { "node": ">=20.0.0", - "npm": ">=9.0.0" + "npm": ">=12.0.2 <13" }, "lint-staged": { "*.{ts,tsx}": [