ci: address 4x Opus reviewer findings on frozen-lockfile installs
Two blockers from the parallel Opus review batch: 1. The Tauri CLI install in release-desktop.yml was the last unfrozen install path: `npm install --save-dev --prefix studio @tauri-apps/cli@2.10.1 --no-fund --no-audit` pins the top-level version but leaves the transitive closure floating, defeats the pre-install lockfile audit (no lockfile to scan), and skips integrity verification. Committed a minimal studio/package.json (devDep @tauri-apps/cli@2.10.1) plus the resolved studio/package-lock.json (12 packages: CLI + 11 platform-native binaries, all with integrity hashes, lockfileVersion 3). Switched the step to `npm ci --prefix studio` and added a pre-install lockfile_supply_chain_audit.py step ahead of it so any tarball postinstall is gated by the structural scan. Allowlisted studio/package-lock.json .gitignore and added it to the audit script's default scan set. 2. The bun branch was dead code in build.sh, studio/setup.sh, and studio/setup.ps1: nowhere in the repo is a bun.lock committed, and `bun install --frozen-lockfile` cannot migrate from package-lock.json. With no lockfile, every entry to the bun path either silently regenerates a bun.lock (under permissive install modes -- a fresh attack surface) or fails outright (under frozen-lockfile). Removed `npm install -g bun` bootstrap, the `_try_bun_install` helper + cache-retry, every `if bun.lock && command -v bun` guard, and the now-unreachable "fall back to npm" messaging. All three scripts now have a single `npm ci` path. bun.lock skip entries in lint-ci.yml + wheel-smoke.yml are kept as forward-compat sanity checks -- they assert bun.lock is NOT shipped / scanned, which is stronger after this commit, not weaker. Smoke-tested locally: `npm ci --prefix studio` resolves 3 packages (CLI + 2 linux native binaries), `npx --prefix studio tauri --version` prints `tauri-cli 2.10.1` exactly. `python3 scripts/lockfile_supply_chain_audit.py` scans 3 npm + 1 cargo lockfiles, 0 findings. `bash -n build.sh`, `bash -n studio/setup.sh`, and a pwsh scriptblock parse of studio/setup.ps1 all succeed.
This commit is contained in:
parent
fd5f9be57d
commit
cf39fb6957
8 changed files with 291 additions and 180 deletions
19
.github/workflows/release-desktop.yml
vendored
19
.github/workflows/release-desktop.yml
vendored
|
|
@ -361,14 +361,19 @@ jobs:
|
|||
with:
|
||||
node-version: 24
|
||||
|
||||
# Pre-install lockfile structural audit. Has to fire BEFORE
|
||||
# `npm ci` so any tarball's `prepare` / `postinstall` cannot run
|
||||
# ahead of the scan. Pure-Python read-only; safe everywhere.
|
||||
- name: Lockfile supply-chain audit (pre-install scan)
|
||||
shell: bash
|
||||
run: python3 scripts/lockfile_supply_chain_audit.py
|
||||
|
||||
- name: Install pinned Tauri CLI
|
||||
# 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.
|
||||
run: npm install --save-dev --prefix studio @tauri-apps/cli@2.10.1 --no-fund --no-audit
|
||||
# `npm ci` resolves @tauri-apps/cli and its 11 platform-specific
|
||||
# optional native binaries from studio/package-lock.json --
|
||||
# transitive versions are fully pinned, integrity hashes are
|
||||
# verified, and the install is reproducible across re-runs.
|
||||
run: npm ci --prefix studio --no-fund --no-audit
|
||||
|
||||
- name: Verify pinned Tauri CLI
|
||||
shell: bash
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -233,4 +233,5 @@ server.pid
|
|||
package-lock.json
|
||||
!studio/frontend/package-lock.json
|
||||
!studio/backend/core/data_recipe/oxc-validator/package-lock.json
|
||||
!studio/package-lock.json
|
||||
llama.cpp/
|
||||
|
|
|
|||
25
build.sh
25
build.sh
|
|
@ -33,23 +33,14 @@ _restore_gitignores() {
|
|||
}
|
||||
trap _restore_gitignores EXIT
|
||||
|
||||
# Use bun if a bun.lock is committed (lockfile-strict, fast), else npm ci.
|
||||
# bun install --frozen-lockfile cannot migrate from package-lock.json, so if
|
||||
# only the npm lockfile is present we skip bun and go straight to npm ci.
|
||||
_install_ok=false
|
||||
if [ -f bun.lock ] && command -v bun &>/dev/null; then
|
||||
if bun install --frozen-lockfile; then
|
||||
_install_ok=true
|
||||
else
|
||||
echo "⚠ bun install --frozen-lockfile failed, falling back to npm ci"
|
||||
rm -rf node_modules
|
||||
fi
|
||||
fi
|
||||
if [ "$_install_ok" != "true" ]; then
|
||||
if ! npm ci; then
|
||||
echo "❌ ERROR: package install failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Frontend installs always use npm ci against the committed lockfile.
|
||||
# There is no bun.lock anywhere in the repo, so a bun-first branch
|
||||
# would always miss and silently regenerate (or fail under
|
||||
# --frozen-lockfile). Keep this single path until/unless a real
|
||||
# bun.lock lands.
|
||||
if ! npm ci; then
|
||||
echo "❌ ERROR: npm ci failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
npm run build # outputs to studio/frontend/dist/
|
||||
|
||||
|
|
|
|||
|
|
@ -655,6 +655,7 @@ def audit_cargo_lockfile(path: Path) -> list[Finding]:
|
|||
DEFAULT_NPM_LOCKFILES = (
|
||||
"studio/frontend/package-lock.json",
|
||||
"studio/backend/core/data_recipe/oxc-validator/package-lock.json",
|
||||
"studio/package-lock.json",
|
||||
)
|
||||
DEFAULT_CARGO_LOCKFILES = ("studio/src-tauri/Cargo.lock",)
|
||||
|
||||
|
|
@ -674,8 +675,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
default = None,
|
||||
help = (
|
||||
"Path to a package-lock.json (repeatable). "
|
||||
"Default: studio/frontend/package-lock.json plus "
|
||||
"studio/backend/core/data_recipe/oxc-validator/package-lock.json."
|
||||
"Default: studio/frontend/package-lock.json, "
|
||||
"studio/backend/core/data_recipe/oxc-validator/package-lock.json, "
|
||||
"and studio/package-lock.json (Tauri CLI for desktop release)."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
|
|
|
|||
233
studio/package-lock.json
generated
Normal file
233
studio/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
{
|
||||
"name": "unsloth-studio-tauri-cli",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "unsloth-studio-tauri-cli",
|
||||
"version": "0.0.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz",
|
||||
"integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.10.1",
|
||||
"@tauri-apps/cli-darwin-x64": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.10.1",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.10.1",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.10.1",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.10.1",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.10.1",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz",
|
||||
"integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz",
|
||||
"integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz",
|
||||
"integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz",
|
||||
"integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz",
|
||||
"integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz",
|
||||
"integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz",
|
||||
"integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
studio/package.json
Normal file
10
studio/package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "unsloth-studio-tauri-cli",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "Lockfile holder for @tauri-apps/cli used by the desktop release workflow. Not a real npm package; `npm ci --prefix studio` resolves the pinned Tauri CLI from this directory's package-lock.json.",
|
||||
"license": "AGPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.10.1"
|
||||
}
|
||||
}
|
||||
|
|
@ -1146,24 +1146,6 @@ if ($IsPipInstall) {
|
|||
}
|
||||
|
||||
step "node" "$(node -v) | npm $(npm -v)"
|
||||
|
||||
# ── bun (optional, faster package installs) ──
|
||||
# Installed via npm — Node is already guaranteed above. Works on all platforms.
|
||||
if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
|
||||
substep "installing bun (faster frontend package installs)..."
|
||||
$prevEAP_bun = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
Invoke-SetupCommand { npm install -g bun } | Out-Null
|
||||
$ErrorActionPreference = $prevEAP_bun
|
||||
Refresh-Environment
|
||||
if (Get-Command bun -ErrorAction SilentlyContinue) {
|
||||
substep "bun installed ($(bun --version))"
|
||||
} else {
|
||||
substep "bun install skipped (npm will be used instead)"
|
||||
}
|
||||
} else {
|
||||
substep "bun already installed ($(bun --version))"
|
||||
}
|
||||
}
|
||||
|
||||
# 1g. Python (>= 3.11 and < 3.14). Prefer py.exe so a 3.14 ahead of 3.13 on PATH does not trip the gate.
|
||||
|
|
@ -1286,7 +1268,7 @@ if ($IsPipInstall) {
|
|||
# Also check all top-level files (package.json, vite.config.ts, index.html, etc.)
|
||||
if (-not $NewerFile) {
|
||||
$NewerFile = Get-ChildItem -Path $FrontendDir -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -ne "bun.lock" -and $_.LastWriteTime -gt $DistTime } |
|
||||
Where-Object { $_.LastWriteTime -gt $DistTime } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
if (-not $NewerFile) {
|
||||
|
|
@ -1321,69 +1303,25 @@ 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).
|
||||
# npm ci: install exactly what package-lock.json pins, fail on drift.
|
||||
# There is no committed bun.lock so we don't dispatch to bun; a bun
|
||||
# branch here would always miss and silently regenerate (or fail
|
||||
# under --frozen-lockfile). Keep this single path until/unless a
|
||||
# real bun.lock lands.
|
||||
$prevEAP_npm = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
Push-Location $FrontendDir
|
||||
|
||||
# Only use bun when a committed bun.lock is present. bun install
|
||||
# --frozen-lockfile cannot migrate from package-lock.json, so without
|
||||
# bun.lock the bun path would always fail.
|
||||
$UseBun = ($null -ne (Get-Command bun -ErrorAction SilentlyContinue)) -and (Test-Path "bun.lock")
|
||||
|
||||
# bun's package cache can become corrupt -- packages get stored with only
|
||||
# metadata but no actual content (bin/, lib/). When this happens bun install
|
||||
# exits 0 but leaves binaries missing. We validate after install and clear
|
||||
# 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 --frozen-lockfile }
|
||||
# On Windows, .bin/ entries vary by package manager:
|
||||
# npm → tsc, tsc.cmd, tsc.ps1
|
||||
# bun → tsc.exe, tsc.bunx
|
||||
$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 -eq 0 -and $hasTsc -and $hasVite) {
|
||||
# bun install succeeded and critical binaries are present
|
||||
} elseif ($bunExit -eq 0) {
|
||||
Write-Host " bun install exited 0 but critical binaries are missing, clearing cache and retrying..." -ForegroundColor Yellow
|
||||
if (Test-Path "node_modules") {
|
||||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Invoke-SetupCommand { bun pm cache rm } | Out-Null
|
||||
$bunExit = Invoke-SetupCommand { bun install --frozen-lockfile }
|
||||
$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
|
||||
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"
|
||||
if (Test-Path "node_modules") {
|
||||
Remove-Item "node_modules" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
$UseBun = $false
|
||||
}
|
||||
}
|
||||
if (-not $UseBun) {
|
||||
# npm ci: install exactly what the lockfile pins, fail on drift.
|
||||
$npmExit = Invoke-SetupCommand { npm ci }
|
||||
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 ci failed (exit code $npmExit)" -ForegroundColor Red
|
||||
Write-Host " Try running 'npm ci' manually in frontend/ to see errors" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
$npmExit = Invoke-SetupCommand { npm ci }
|
||||
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 ci failed (exit code $npmExit)" -ForegroundColor Red
|
||||
Write-Host " Try running 'npm ci' manually in frontend/ to see errors" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Always use npm to run the build (Node runtime — avoids bun Windows runtime issues)
|
||||
$buildExit = Invoke-SetupCommand { npm run build }
|
||||
if ($buildExit -ne 0) {
|
||||
Pop-Location
|
||||
|
|
|
|||
|
|
@ -211,7 +211,6 @@ else
|
|||
_NEED_FRONTEND_BUILD=true
|
||||
if [ -d "$SCRIPT_DIR/frontend/dist" ]; then
|
||||
_changed=$(find "$SCRIPT_DIR/frontend" -maxdepth 1 -type f \
|
||||
! -name 'bun.lock' \
|
||||
-newer "$SCRIPT_DIR/frontend/dist" -print -quit 2>/dev/null)
|
||||
if [ -z "$_changed" ]; then
|
||||
_changed=$(find "$SCRIPT_DIR/frontend/src" "$SCRIPT_DIR/frontend/public" \
|
||||
|
|
@ -295,20 +294,6 @@ fi
|
|||
step "node" "$(node -v) | npm $(npm -v)"
|
||||
verbose_substep "node check: NEED_NODE=$NEED_NODE NODE_OK=${NODE_OK:-unknown} NPM_MAJOR=${NPM_MAJOR:-unknown}"
|
||||
|
||||
# ── Install bun (optional, faster package installs) ──
|
||||
# Uses npm to install bun globally -- Node is already guaranteed above,
|
||||
# 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
|
||||
substep "bun installed ($(bun --version))"
|
||||
else
|
||||
substep "bun install skipped (npm will be used instead)"
|
||||
fi
|
||||
else
|
||||
substep "bun already installed ($(bun --version))"
|
||||
fi
|
||||
|
||||
# ── Build frontend ──
|
||||
substep "building frontend..."
|
||||
cd "$SCRIPT_DIR/frontend"
|
||||
|
|
@ -329,69 +314,15 @@ _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.
|
||||
#
|
||||
# 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)
|
||||
# --frozen-lockfile so a fresh caret-range patch can't land via npm registry.
|
||||
bun install --frozen-lockfile >"$_log" 2>&1 || _exit_code=$?
|
||||
|
||||
# bun may create .exe shims on Windows (Git Bash / MSYS2) instead of plain scripts
|
||||
if [ "$_exit_code" -eq 0 ] \
|
||||
&& { [ -x node_modules/.bin/tsc ] || [ -f node_modules/.bin/tsc.exe ] || [ -f node_modules/.bin/tsc.bunx ]; } \
|
||||
&& { [ -x node_modules/.bin/vite ] || [ -f node_modules/.bin/vite.exe ] || [ -f node_modules/.bin/vite.bunx ]; }; then
|
||||
rm -f "$_log"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 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):"
|
||||
else
|
||||
echo " bun install exited 0 but critical binaries are missing:"
|
||||
fi
|
||||
sed 's/^/ | /' "$_log" >&2
|
||||
rm -f "$_log"
|
||||
rm -rf node_modules
|
||||
return 1
|
||||
}
|
||||
|
||||
_bun_install_ok=false
|
||||
# bun install --frozen-lockfile cannot migrate from package-lock.json, so we
|
||||
# only enter the bun path when a committed bun.lock exists. Without it,
|
||||
# bun would fail every time and the corrupt-cache retry would clear the
|
||||
# user's bun cache for nothing.
|
||||
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
|
||||
else
|
||||
# First attempt failed, likely due to corrupt cache entries.
|
||||
# Clear the cache and retry once.
|
||||
echo " Clearing bun cache and retrying..."
|
||||
run_maybe_quiet bun pm cache rm || true
|
||||
if _try_bun_install; then
|
||||
_bun_install_ok=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$_bun_install_ok" = false ]; then
|
||||
# npm ci: install exactly what the lockfile pins, fail on drift.
|
||||
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"
|
||||
fi
|
||||
# npm ci: install exactly what package-lock.json pins, fail on drift.
|
||||
# There is no committed bun.lock so we don't dispatch to bun; a bun
|
||||
# branch here would always miss and silently regenerate (or fail under
|
||||
# --frozen-lockfile). Keep this single path until/unless a real
|
||||
# bun.lock lands.
|
||||
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"
|
||||
fi
|
||||
run_quiet "npm run build" npm run build
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue