diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 20b104eb3..c3786911b 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -15,10 +15,7 @@ jobs: runs-on: macos-15-xlarge steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -34,10 +31,7 @@ jobs: needs: install steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -71,10 +65,7 @@ jobs: vitest: webkit steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Get node_modules from cache uses: actions/cache@v5 with: diff --git a/.github/workflows/upload-assets.yaml b/.github/workflows/upload-assets.yaml index 52ea8629f..f8ffbb340 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -10,10 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' + - uses: oxidecomputer/actions/setup-node@main - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: diff --git a/README.md b/README.md index 42f3ffa47..e08761aaf 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ The app is in [`app`](app). You can see the route structure in [`app/routes.tsx` ### Node.js version -Use Node.js v18+. +Use Node.js v24+ and npm v12+. Node bundles an older npm, so upgrade it with +`npm install --global npm@12`. ### Install dependencies diff --git a/flake.nix b/flake.nix index 9aff164ed..bcf567107 100644 --- a/flake.nix +++ b/flake.nix @@ -18,6 +18,33 @@ # as long as the major.minor version matches, we'll have compatible browsers. npmPlaywrightVersion = (lib.importJSON ./package-lock.json).packages."node_modules/@playwright/test".version; + + node = pkgs.nodejs_24; + + # nodejs_x doesn't always bundle the npm/npx you want. but npm and npx + # are just scripts bundled with node, so they're easy to shadow + npm = pkgs.stdenvNoCC.mkDerivation rec { + pname = "npm"; + version = "12.0.2"; + src = pkgs.fetchurl { + url = "https://registry.npmjs.org/npm/-/npm-${version}.tgz"; + hash = "sha256-XbuGxx0HoZV/LpBzQJLdali9zZ68LY1ByhxuaiHTZOE="; + }; + nativeBuildInputs = [ pkgs.makeWrapper ]; + dontBuild = true; + installPhase = '' + mkdir -p $out/lib/node_modules/npm + cp -r . $out/lib/node_modules/npm + for cli in npm npx; do + entry=$out/lib/node_modules/npm/bin/$cli-cli.js + if [ ! -f "$entry" ]; then + echo "npm/x issue: expected entry point $entry wasn't found. The npm tarball layout may have changed." >&2 + exit 1 + fi + makeWrapper ${node}/bin/node $out/bin/$cli --add-flags "$entry" + done + ''; + }; in { devShells.default = @@ -30,7 +57,9 @@ ''; pkgs.mkShell { packages = [ - pkgs.nodejs_22 + # npm needs to come first to shadow the npm/npx commands + npm + node ]; env = { PLAYWRIGHT_BROWSERS_PATH = "${playwrightDriver.browsers}"; @@ -38,7 +67,7 @@ # PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = "true"; }; shellHook = '' - echo "Node $(node --version)" + echo "Node $(node --version), npm $(npm --version)" ''; }; } diff --git a/package.json b/package.json index 76681eeb3..9d19524c9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,14 @@ "version": "0.0.0", "license": "MPL-2.0", "engines": { - "node": ">=22" + "node": ">=24.15.0" + }, + "devEngines": { + "packageManager": { + "name": "npm", + "version": ">=12", + "onFail": "error" + } }, "scripts": { "start": "API_MODE=msw vite", diff --git a/vercel.json b/vercel.json index 8a6ed0f9b..bdee2f340 100644 --- a/vercel.json +++ b/vercel.json @@ -1,4 +1,5 @@ { + "installCommand": "npm install --global npm@12 && npm install", "buildCommand": "API_MODE=msw npm run build && cp mockServiceWorker.js dist/ && npx patch-package --reverse", "outputDirectory": "dist", "headers": [