ci: use lockfile installs for Studio npm paths
PR #5604 committed three npm lockfiles (studio/, studio/frontend/, studio/backend/core/data_recipe/oxc-validator/) but the install paths still ran `npm install` -- mutates the lockfile, ignores integrity hashes. Flip them to `npm ci`, which is non-mutating and refuses to run unless the committed package-lock.json matches the dependency tree byte-for-byte. This closes the gap between "the lockfile passes the audit" and "the install respected that lockfile". For bun: gate every `bun install` on `[ -f bun.lock ]` (Test-Path on Windows) plus `command -v bun` (Get-Command), and add `--frozen-lockfile --no-progress` to the bun invocation. No bun.lock is committed in this PR, so the gate fails closed today and execution falls through to `npm ci`. When the eventual bun.lock PR lands, the gate auto-activates with no further script edits. Existing bun infrastructure (cache-corruption retry + critical-binary verification + npm fallback) is kept intact so the auto-upgrade is clean. Deliberately NOT introducing _BUN_PIN_VERSION or the 3-attempt cache-corruption recovery ladder; both require committed bun.lock to be useful. Touches three files only: - build.sh - studio/setup.sh - studio/setup.ps1
This commit is contained in:
parent
07c03777b8
commit
8dce77f80d
3 changed files with 89 additions and 39 deletions
16
build.sh
16
build.sh
|
|
@ -33,18 +33,24 @@ _restore_gitignores() {
|
|||
}
|
||||
trap _restore_gitignores EXIT
|
||||
|
||||
# Use bun for install if available (faster), fall back to npm.
|
||||
# Lockfile-pinned install. Prefer bun when a bun.lock is checked in AND
|
||||
# bun is on PATH; otherwise use `npm ci`, which is non-mutating and
|
||||
# refuses to run unless the committed package-lock.json matches the
|
||||
# dependency tree byte-for-byte. The `[ -f bun.lock ]` gate keeps bun
|
||||
# off the path until a bun.lock is also committed -- without it,
|
||||
# `bun install --frozen-lockfile` would fail and the script would
|
||||
# still fall through to npm ci, so the gate just skips the noise.
|
||||
_install_ok=false
|
||||
if command -v bun &>/dev/null; then
|
||||
if bun install; then
|
||||
if [ -f bun.lock ] && command -v bun &>/dev/null; then
|
||||
if bun install --frozen-lockfile --no-progress; then
|
||||
_install_ok=true
|
||||
else
|
||||
echo "⚠ bun install failed, falling back to npm"
|
||||
echo "⚠ bun install --frozen-lockfile failed, falling back to npm ci"
|
||||
rm -rf node_modules
|
||||
fi
|
||||
fi
|
||||
if [ "$_install_ok" != "true" ]; then
|
||||
if ! npm install; then
|
||||
if ! npm ci --no-fund --no-audit; then
|
||||
echo "❌ ERROR: package install failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1321,13 +1321,19 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
$WalkDir = Split-Path $WalkDir -Parent
|
||||
}
|
||||
|
||||
# Use bun if available (faster install), fall back to npm.
|
||||
# Bun is used only as package manager; Node runs the actual build (Vite 8).
|
||||
# Lockfile-pinned install. Prefer bun ONLY when a bun.lock is checked
|
||||
# in AND bun is on PATH; otherwise use `npm ci`, which is non-mutating
|
||||
# and refuses to run unless the committed package-lock.json matches
|
||||
# the dependency tree byte-for-byte. The `Test-Path "bun.lock"` gate
|
||||
# keeps bun off the path until a bun.lock is also committed -- the
|
||||
# bun-fallback wiring (cache-clear retry + critical-binary
|
||||
# verification) is kept intact so this script auto-upgrades when
|
||||
# bun.lock eventually lands.
|
||||
$prevEAP_npm = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
Push-Location $FrontendDir
|
||||
|
||||
$UseBun = $null -ne (Get-Command bun -ErrorAction SilentlyContinue)
|
||||
$UseBun = (Test-Path "bun.lock") -and ($null -ne (Get-Command bun -ErrorAction SilentlyContinue))
|
||||
|
||||
# bun's package cache can become corrupt -- packages get stored with only
|
||||
# metadata but no actual content (bin/, lib/). When this happens bun install
|
||||
|
|
@ -1335,7 +1341,7 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
# the cache + retry once before falling back to npm.
|
||||
if ($UseBun) {
|
||||
Write-Host " Using bun for package install (faster)" -ForegroundColor DarkGray
|
||||
$bunExit = Invoke-SetupCommand { bun install }
|
||||
$bunExit = Invoke-SetupCommand { bun install --frozen-lockfile --no-progress }
|
||||
# On Windows, .bin/ entries vary by package manager:
|
||||
# npm → tsc, tsc.cmd, tsc.ps1
|
||||
# bun → tsc.exe, tsc.bunx
|
||||
|
|
@ -1349,18 +1355,18 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Invoke-SetupCommand { bun pm cache rm } | Out-Null
|
||||
$bunExit = Invoke-SetupCommand { bun install }
|
||||
$bunExit = Invoke-SetupCommand { bun install --frozen-lockfile --no-progress }
|
||||
$hasTsc = (Test-Path "node_modules\.bin\tsc") -or (Test-Path "node_modules\.bin\tsc.cmd") -or (Test-Path "node_modules\.bin\tsc.exe") -or (Test-Path "node_modules\.bin\tsc.bunx")
|
||||
$hasVite = (Test-Path "node_modules\.bin\vite") -or (Test-Path "node_modules\.bin\vite.cmd") -or (Test-Path "node_modules\.bin\vite.exe") -or (Test-Path "node_modules\.bin\vite.bunx")
|
||||
if ($bunExit -ne 0 -or -not $hasTsc -or -not $hasVite) {
|
||||
Write-Host " bun retry failed, falling back to npm" -ForegroundColor Yellow
|
||||
Write-Host " bun retry failed, falling back to npm ci" -ForegroundColor Yellow
|
||||
if (Test-Path "node_modules") {
|
||||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$UseBun = $false
|
||||
}
|
||||
} else {
|
||||
substep "bun install failed (exit $bunExit), falling back to npm" "Yellow"
|
||||
substep "bun install --frozen-lockfile failed (exit $bunExit), falling back to npm ci" "Yellow"
|
||||
if (Test-Path "node_modules") {
|
||||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
|
@ -1368,13 +1374,13 @@ if ($NeedFrontendBuild -and -not $IsPipInstall) {
|
|||
}
|
||||
}
|
||||
if (-not $UseBun) {
|
||||
$npmExit = Invoke-SetupCommand { npm install }
|
||||
$npmExit = Invoke-SetupCommand { npm ci --no-fund --no-audit }
|
||||
if ($npmExit -ne 0) {
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_npm
|
||||
foreach ($gi in $HiddenGitignores) { Rename-Item -Path "$gi._twbuild" -NewName (Split-Path $gi -Leaf) -Force -ErrorAction SilentlyContinue }
|
||||
Write-Host "[ERROR] npm install failed (exit code $npmExit)" -ForegroundColor Red
|
||||
Write-Host " Try running 'npm install' manually in frontend/ to see errors" -ForegroundColor Yellow
|
||||
Write-Host "[ERROR] npm ci failed (exit code $npmExit)" -ForegroundColor Red
|
||||
Write-Host " Try running 'npm ci' manually in frontend/ to see errors" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1411,12 +1417,31 @@ if (Test-Path $OxcValidatorDir) {
|
|||
$prevEAP_oxc = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
Push-Location $OxcValidatorDir
|
||||
$oxcInstallExit = Invoke-SetupCommand { npm install }
|
||||
if ($oxcInstallExit -ne 0) {
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_oxc
|
||||
Write-Host "[ERROR] OXC validator npm install failed (exit code $oxcInstallExit)" -ForegroundColor Red
|
||||
exit 1
|
||||
|
||||
# Same lockfile-pinned shape as the frontend block above: bun only
|
||||
# when a bun.lock is checked in next to package.json (currently not
|
||||
# the case), otherwise `npm ci` against the committed
|
||||
# package-lock.json (shipped in #5604).
|
||||
$UseOxcBun = (Test-Path "bun.lock") -and ($null -ne (Get-Command bun -ErrorAction SilentlyContinue))
|
||||
$oxcInstallOk = $false
|
||||
if ($UseOxcBun) {
|
||||
$oxcBunExit = Invoke-SetupCommand { bun install --frozen-lockfile --no-progress }
|
||||
if ($oxcBunExit -eq 0) {
|
||||
$oxcInstallOk = $true
|
||||
} else {
|
||||
if (Test-Path "node_modules") {
|
||||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $oxcInstallOk) {
|
||||
$oxcInstallExit = Invoke-SetupCommand { npm ci --no-fund --no-audit }
|
||||
if ($oxcInstallExit -ne 0) {
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_oxc
|
||||
Write-Host "[ERROR] OXC validator npm ci failed (exit code $oxcInstallExit)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
Pop-Location
|
||||
$ErrorActionPreference = $prevEAP_oxc
|
||||
|
|
|
|||
|
|
@ -329,21 +329,23 @@ _restore_gitignores() {
|
|||
}
|
||||
trap _restore_gitignores EXIT
|
||||
|
||||
# Use bun for install if available (faster), fall back to npm.
|
||||
# Build always uses npm (Node runtime -- avoids bun runtime issues on some platforms).
|
||||
# NOTE: We intentionally avoid run_quiet for the bun install attempt because
|
||||
# run_quiet calls exit on failure, which would kill the script before the npm
|
||||
# fallback can run. Instead we capture output manually and only show it on failure.
|
||||
# Lockfile-pinned install. Prefer bun ONLY when a bun.lock is checked in
|
||||
# AND bun is on PATH; otherwise use `npm ci`, which is non-mutating and
|
||||
# refuses to run unless the committed package-lock.json matches the
|
||||
# dependency tree byte-for-byte. The `[ -f bun.lock ]` gate keeps bun
|
||||
# off the path until a bun.lock is also committed -- the bun-fallback
|
||||
# wiring (cache-clear retry + critical-binary verification) is kept
|
||||
# intact so this script auto-upgrades when bun.lock eventually lands.
|
||||
#
|
||||
# IMPORTANT: bun's package cache can become corrupt -- packages get stored
|
||||
# with only metadata (package.json, README) but no actual content (bin/,
|
||||
# lib/). When this happens bun install exits 0 but leaves binaries missing.
|
||||
# We verify critical binaries after install. If missing, we clear the cache
|
||||
# and retry once before falling back to npm.
|
||||
# IMPORTANT: bun's package cache can become corrupt -- packages get
|
||||
# stored with only metadata (package.json, README) but no actual content
|
||||
# (bin/, lib/). When this happens bun install exits 0 but leaves
|
||||
# binaries missing. We verify critical binaries after install. If
|
||||
# missing, we clear the cache and retry once before falling back to npm.
|
||||
_try_bun_install() {
|
||||
local _log _exit_code=0
|
||||
_log=$(mktemp)
|
||||
bun install >"$_log" 2>&1 || _exit_code=$?
|
||||
bun install --frozen-lockfile --no-progress >"$_log" 2>&1 || _exit_code=$?
|
||||
|
||||
# bun may create .exe shims on Windows (Git Bash / MSYS2) instead of plain scripts
|
||||
if [ "$_exit_code" -eq 0 ] \
|
||||
|
|
@ -355,7 +357,7 @@ _try_bun_install() {
|
|||
|
||||
# Either bun install failed or it exited 0 but left packages missing
|
||||
if [ "$_exit_code" -ne 0 ]; then
|
||||
echo " bun install failed (exit code $_exit_code):"
|
||||
echo " bun install --frozen-lockfile failed (exit code $_exit_code):"
|
||||
else
|
||||
echo " bun install exited 0 but critical binaries are missing:"
|
||||
fi
|
||||
|
|
@ -366,7 +368,7 @@ _try_bun_install() {
|
|||
}
|
||||
|
||||
_bun_install_ok=false
|
||||
if command -v bun &>/dev/null; then
|
||||
if [ -f bun.lock ] && command -v bun &>/dev/null; then
|
||||
substep "using bun for package install (faster)"
|
||||
if _try_bun_install; then
|
||||
_bun_install_ok=true
|
||||
|
|
@ -381,7 +383,10 @@ if command -v bun &>/dev/null; then
|
|||
fi
|
||||
fi
|
||||
if [ "$_bun_install_ok" = false ]; then
|
||||
run_quiet_no_exit "npm install" npm install --no-fund --no-audit --loglevel=error
|
||||
# npm ci enforces the committed package-lock.json (refuses to install
|
||||
# otherwise); --no-fund / --no-audit / --loglevel=error keep the
|
||||
# output quiet during normal installs.
|
||||
run_quiet_no_exit "npm ci" npm ci --no-fund --no-audit --loglevel=error
|
||||
_npm_install_rc=$?
|
||||
if [ "$_npm_install_rc" -ne 0 ]; then
|
||||
exit "$_npm_install_rc"
|
||||
|
|
@ -406,12 +411,26 @@ cd "$SCRIPT_DIR"
|
|||
fi # end frontend build check
|
||||
|
||||
# ── oxc-validator runtime ──
|
||||
# Same lockfile-pinned shape as the frontend block above: bun only when
|
||||
# a bun.lock is checked in next to package.json (currently not the
|
||||
# case), otherwise `npm ci` against the committed package-lock.json
|
||||
# (shipped in #5604).
|
||||
if [ -d "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" ] && command -v npm &>/dev/null; then
|
||||
cd "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator"
|
||||
run_quiet_no_exit "npm install (oxc validator runtime)" npm install --no-fund --no-audit --loglevel=error
|
||||
_oxc_install_rc=$?
|
||||
if [ "$_oxc_install_rc" -ne 0 ]; then
|
||||
exit "$_oxc_install_rc"
|
||||
_oxc_install_ok=false
|
||||
if [ -f bun.lock ] && command -v bun &>/dev/null; then
|
||||
if run_quiet_no_exit "bun install (oxc validator runtime)" bun install --frozen-lockfile --no-progress; then
|
||||
_oxc_install_ok=true
|
||||
else
|
||||
rm -rf node_modules
|
||||
fi
|
||||
fi
|
||||
if [ "$_oxc_install_ok" = false ]; then
|
||||
run_quiet_no_exit "npm ci (oxc validator runtime)" npm ci --no-fund --no-audit --loglevel=error
|
||||
_oxc_install_rc=$?
|
||||
if [ "$_oxc_install_rc" -ne 0 ]; then
|
||||
exit "$_oxc_install_rc"
|
||||
fi
|
||||
fi
|
||||
cd "$SCRIPT_DIR"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue