From 62a36a98bc603449fb492961eef5a34112d33d37 Mon Sep 17 00:00:00 2001 From: Joe Thel Date: Tue, 25 Aug 2026 11:58:23 -0700 Subject: [PATCH 1/9] Require npm 12 Most importantly, this makes allowScripts default to off. npm@12 also requires node 22.22.2 or greater, so we just bump the specificity a bit. Technically >=22.22.2 is less strict than npm's real engine requirements, since it has a minimum version for 24 as well, but we assume usage of node 22 in general. This also took a little extra footwork for the flake since no package currently offers npm 12, so we have to install it directly. --- flake.nix | 33 +++++++++++++++++++++++++++++++-- package.json | 9 ++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 9aff164ed..6bdc45a17 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_22; + + # 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..e43f7d58c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,14 @@ "version": "0.0.0", "license": "MPL-2.0", "engines": { - "node": ">=22" + "node": ">=22.22.2" + }, + "devEngines": { + "packageManager": { + "name": "npm", + "version": ">=12", + "onFail": "error" + } }, "scripts": { "start": "API_MODE=msw vite", From a1e7b581e824989647b5ac21f6299c73fbbf2acf Mon Sep 17 00:00:00 2001 From: Joe Thel Date: Tue, 25 Aug 2026 12:20:14 -0700 Subject: [PATCH 2/9] Actually use npm 12 in workflows --- .github/workflows/lintBuildTest.yml | 6 ++++++ .github/workflows/upload-assets.yaml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 20b104eb3..65f76f827 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -19,6 +19,8 @@ jobs: with: node-version: 22 cache: 'npm' + - name: Use npm 12 + run: npm install --global npm@12 - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -38,6 +40,8 @@ jobs: with: node-version: 22 cache: 'npm' + - name: Use npm 12 + run: npm install --global npm@12 - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -75,6 +79,8 @@ jobs: with: node-version: 22 cache: 'npm' + - name: Use npm 12 + run: npm install --global npm@12 - 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..3cd4cfc8b 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -14,6 +14,8 @@ jobs: with: node-version: 22 cache: 'npm' + - name: Use npm 12 + run: npm install --global npm@12 - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: From 0c5626a7d3de97ae128adacdd0fcb53bda5031ac Mon Sep 17 00:00:00 2001 From: Joe Thel Date: Tue, 25 Aug 2026 12:47:26 -0700 Subject: [PATCH 3/9] Add a custom setup-node action Details in the action itself. --- .github/actions/setup-node/action.yml | 40 +++++++++++++++++++++++++++ .github/workflows/lintBuildTest.yml | 21 ++------------ .github/workflows/upload-assets.yaml | 7 +---- 3 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 .github/actions/setup-node/action.yml diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 000000000..0730daae1 --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,40 @@ +name: Set up Node and npm +description: > + Install Node, pin npm to the version we like, and cache the npm download + directory. setup-node@v6 caching uses the bundled-with-node version of npm, + which may not (and does not at time of writing) match our required version. + This action is meant to use our preferred npm version and otherwise behave + the same as setup-node@v6. + +inputs: + node-version: + description: Node version to install + default: '22' + +runs: + using: composite + steps: + - uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node-version }} + # setup-node@v6 caching uses the bundled-with-node version of npm, which + # may not match our required version. + # + # See https://github.com/actions/setup-node/issues/1553 + package-manager-cache: false + - name: Use npm 12 + shell: bash + run: npm install --global npm@12 + + # Caching workaround from https://github.com/actions/setup-node/issues/1553 + - name: Specify npm cache directory + id: npm-cache + shell: bash + run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT" + - name: Use npm cache + uses: actions/cache@v5 + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 65f76f827..164fb494b 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -15,12 +15,7 @@ jobs: runs-on: macos-15-xlarge steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' - - name: Use npm 12 - run: npm install --global npm@12 + - uses: ./.github/actions/setup-node - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -36,12 +31,7 @@ jobs: needs: install steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' - - name: Use npm 12 - run: npm install --global npm@12 + - uses: ./.github/actions/setup-node - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -75,12 +65,7 @@ jobs: vitest: webkit steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' - - name: Use npm 12 - run: npm install --global npm@12 + - uses: ./.github/actions/setup-node - 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 3cd4cfc8b..e2f6ba12f 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -10,12 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: 'npm' - - name: Use npm 12 - run: npm install --global npm@12 + - uses: ./.github/actions/setup-node - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: From 980094c51019ce7944c75c815bc72e27ad00e72b Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 25 Aug 2026 15:20:52 -0500 Subject: [PATCH 4/9] try custom install command for vercel --- vercel.json | 1 + 1 file changed, 1 insertion(+) 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": [ From d0164b55123aeeb7f561296297c03ad22e86e149 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Aug 2026 19:13:38 -0500 Subject: [PATCH 5/9] use shared setup-node action from oxidecomputer/actions --- .github/actions/setup-node/action.yml | 40 --------------------------- .github/workflows/lintBuildTest.yml | 6 ++-- .github/workflows/upload-assets.yaml | 2 +- 3 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 .github/actions/setup-node/action.yml diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml deleted file mode 100644 index 0730daae1..000000000 --- a/.github/actions/setup-node/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Set up Node and npm -description: > - Install Node, pin npm to the version we like, and cache the npm download - directory. setup-node@v6 caching uses the bundled-with-node version of npm, - which may not (and does not at time of writing) match our required version. - This action is meant to use our preferred npm version and otherwise behave - the same as setup-node@v6. - -inputs: - node-version: - description: Node version to install - default: '22' - -runs: - using: composite - steps: - - uses: actions/setup-node@v6 - with: - node-version: ${{ inputs.node-version }} - # setup-node@v6 caching uses the bundled-with-node version of npm, which - # may not match our required version. - # - # See https://github.com/actions/setup-node/issues/1553 - package-manager-cache: false - - name: Use npm 12 - shell: bash - run: npm install --global npm@12 - - # Caching workaround from https://github.com/actions/setup-node/issues/1553 - - name: Specify npm cache directory - id: npm-cache - shell: bash - run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT" - - name: Use npm cache - uses: actions/cache@v5 - with: - path: ${{ steps.npm-cache.outputs.dir }} - key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 164fb494b..76692bb70 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-15-xlarge steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node + - uses: oxidecomputer/actions/setup-node@main - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -31,7 +31,7 @@ jobs: needs: install steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node + - uses: oxidecomputer/actions/setup-node@main - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -65,7 +65,7 @@ jobs: vitest: webkit steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node + - uses: oxidecomputer/actions/setup-node@main - 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 e2f6ba12f..f8ffbb340 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: ./.github/actions/setup-node + - uses: oxidecomputer/actions/setup-node@main - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: From 85b3eb6c849242b809fb0024dcea1f7a82f41161 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Aug 2026 19:17:10 -0500 Subject: [PATCH 6/9] move to Node 24 (flake and engines) --- README.md | 3 ++- flake.nix | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) 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 6bdc45a17..bcf567107 100644 --- a/flake.nix +++ b/flake.nix @@ -19,7 +19,7 @@ npmPlaywrightVersion = (lib.importJSON ./package-lock.json).packages."node_modules/@playwright/test".version; - node = pkgs.nodejs_22; + 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 diff --git a/package.json b/package.json index e43f7d58c..9d19524c9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "license": "MPL-2.0", "engines": { - "node": ">=22.22.2" + "node": ">=24.15.0" }, "devEngines": { "packageManager": { From d60628c22d054049917927c1f081528d348278d7 Mon Sep 17 00:00:00 2001 From: Joe Thel Date: Fri, 28 Aug 2026 17:55:20 -0700 Subject: [PATCH 7/9] Specify a commit Even for our own repos, it's probably best to pin to a tag or commit for actions. And to specify on the defaults, but if we pin a commit that works with our defaults, that's just fine! --- .github/workflows/lintBuildTest.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index 76692bb70..c3786911b 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-15-xlarge steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@main + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -31,7 +31,7 @@ jobs: needs: install steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@main + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -65,7 +65,7 @@ jobs: vitest: webkit steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@main + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: Get node_modules from cache uses: actions/cache@v5 with: From e3ac946eba24b1683270c35e6f4aaa3637cccb9e Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Aug 2026 21:46:52 -0500 Subject: [PATCH 8/9] pin setup-node in upload-assets too --- .github/workflows/upload-assets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-assets.yaml b/.github/workflows/upload-assets.yaml index f8ffbb340..23e273247 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@main + - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: From abb3700c6c651f2f8553ab938bb1becde0defd64 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 28 Aug 2026 22:45:57 -0500 Subject: [PATCH 9/9] tighten engines.node to ^24.15.0 for setup-node node-version-file --- .github/workflows/lintBuildTest.yml | 6 +++--- .github/workflows/upload-assets.yaml | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lintBuildTest.yml b/.github/workflows/lintBuildTest.yml index c3786911b..21646294e 100644 --- a/.github/workflows/lintBuildTest.yml +++ b/.github/workflows/lintBuildTest.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-15-xlarge steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 + - uses: oxidecomputer/actions/setup-node@0f10e5eb0416cb29a99322222a62b268c81d0be9 - name: Cache node_modules uses: actions/cache@v5 id: cache-node-modules @@ -31,7 +31,7 @@ jobs: needs: install steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 + - uses: oxidecomputer/actions/setup-node@0f10e5eb0416cb29a99322222a62b268c81d0be9 - name: Get node_modules from cache uses: actions/cache@v5 id: cache-node-modules @@ -65,7 +65,7 @@ jobs: vitest: webkit steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 + - uses: oxidecomputer/actions/setup-node@0f10e5eb0416cb29a99322222a62b268c81d0be9 - 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 23e273247..e3a3e8e78 100644 --- a/.github/workflows/upload-assets.yaml +++ b/.github/workflows/upload-assets.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: oxidecomputer/actions/setup-node@004e5b42c74f890ccf98e848277e63ae27553f37 + - uses: oxidecomputer/actions/setup-node@0f10e5eb0416cb29a99322222a62b268c81d0be9 - name: 'Authenticate to Google Cloud' uses: 'google-github-actions/auth@v3' with: diff --git a/package.json b/package.json index 9d19524c9..5402e9863 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "license": "MPL-2.0", "engines": { - "node": ">=24.15.0" + "node": "^24.15.0" }, "devEngines": { "packageManager": {