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}": [