diff --git a/.github/workflows/studio-frontend-ci.yml b/.github/workflows/studio-frontend-ci.yml index 1270a57ef6..b5a7efb29d 100644 --- a/.github/workflows/studio-frontend-ci.yml +++ b/.github/workflows/studio-frontend-ci.yml @@ -69,12 +69,11 @@ jobs: run: python3 scripts/lockfile_supply_chain_audit.py - name: Lockfile must agree with package.json (npm ci is strict) - # Lifecycle scripts (esbuild native-binary postinstall, etc.) are - # required for `vite build`. The pre-install lockfile structural - # audit (lockfile_supply_chain_audit.py) is the practical defence - # against the npm postinstall-dropper class -- it fires BEFORE any - # tarball runs, on the injection pattern itself rather than an - # advisory-DB lookup. + # The vite 8 chain (rolldown, lightningcss, tailwind oxide) ships napi + # binaries with no install scripts. The only script-bearing deps are + # covered by `allowScripts` in package.json (npm >=11.16, default in + # npm 12). The pre-install lockfile audit above stays the first line + # of defence -- it fires before any tarball can run code. run: npm ci --no-fund --no-audit - name: npm ci must not have modified the working tree diff --git a/studio/frontend/.npmrc b/studio/frontend/.npmrc index 8e21abe7a2..f2d15a4f15 100644 --- a/studio/frontend/.npmrc +++ b/studio/frontend/.npmrc @@ -6,11 +6,8 @@ # upstream removal. npm interprets the bare integer as DAYS; do not # append `d`, npm 11.x will parse `7d` as a Date string and abort. min-release-age=7 -# Defensive alias: `minimum-release-age` takes minutes (10080 = 7 days). -# Some npm versions / wrappers consult one key but not the other; setting -# both means a single setting-name parse change upstream cannot silently -# disable the cooldown. The two keys MUST agree; do not let them drift. -minimum-release-age=10080 +# Do not re-add the old `minimum-release-age` alias: npm >=11.16 warns on +# unknown project configs and npm 12 stops accepting them. # Belt-and-braces: refuse to write back loose `^x.y.z` ranges into # package.json when a maintainer runs `npm install ` locally. This # does NOT rewrite already-present ranges (those need an explicit diff --git a/studio/frontend/package.json b/studio/frontend/package.json index 1e527813f9..2217dd080b 100644 --- a/studio/frontend/package.json +++ b/studio/frontend/package.json @@ -108,5 +108,10 @@ "typescript": "~5.9.3", "typescript-eslint": "^8.55.0", "vite": "^8.0.1" + }, + "allowScripts": { + "@biomejs/biome@1.9.4": true, + "msw@2.14.3": true, + "fsevents": true } } diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 920d1af13a..9a919e31e3 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -1405,7 +1405,9 @@ if ($IsPipInstall) { substep "installing bun (faster frontend package installs)..." $prevEAP_bun = $ErrorActionPreference $ErrorActionPreference = "Continue" - Invoke-SetupCommand { npm install -g bun } | Out-Null + # --allow-scripts=bun: npm >=11.16 gates install scripts and bun's + # postinstall fetches its binary; without it the install is a broken stub. + Invoke-SetupCommand { npm install -g bun --allow-scripts=bun } | Out-Null $ErrorActionPreference = $prevEAP_bun Refresh-Environment if (Get-Command bun -ErrorAction SilentlyContinue) { diff --git a/studio/setup.sh b/studio/setup.sh index 0db9a612c8..15a20a572f 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -435,7 +435,9 @@ verbose_substep "node check: NEED_NODE=$NEED_NODE NODE_OK=${NODE_OK:-unknown} NP # avoids platform-specific installers, PATH issues, and admin requirements. if ! command -v bun &>/dev/null; then substep "installing bun..." - if run_maybe_quiet npm install -g bun && command -v bun &>/dev/null; then + # --allow-scripts=bun: npm >=11.16 gates install scripts and bun's + # postinstall fetches its binary; without it the install is a broken stub. + if run_maybe_quiet npm install -g bun --allow-scripts=bun && command -v bun &>/dev/null; then substep "bun installed ($(bun --version))" else substep "bun install skipped (npm will be used instead)"