diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 2420448deb..1046304c46 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -748,9 +748,39 @@ Write-Host "" # ========================================================================== # PHASE 2: Frontend build (skip if pip-installed -- already bundled) # ========================================================================== +$DistDir = Join-Path $FrontendDir "dist" +# Skip build if dist/ exists and no tracked input is newer than dist/. +# Checks src/, public/, package.json, config files -- not just src/. +$NeedFrontendBuild = $true if ($IsPipInstall) { + $NeedFrontendBuild = $false Write-Host "[OK] Running from pip install - frontend already bundled, skipping build" -ForegroundColor Green -} else { +} elseif (Test-Path $DistDir) { + $DistTime = (Get-Item $DistDir).LastWriteTime + # Check src/ and public/ recursively using explicit paths + $TrackedDirs = @("src", "public") | + ForEach-Object { Join-Path $FrontendDir $_ } | + Where-Object { Test-Path $_ } + $NewerFile = $null + if ($TrackedDirs.Count -gt 0) { + $NewerFile = $TrackedDirs | + ForEach-Object { Get-ChildItem -Path $_ -Recurse -File -ErrorAction SilentlyContinue } | + Where-Object { $_.LastWriteTime -gt $DistTime } | Select-Object -First 1 + } + # Also check top-level config and entry files (package.json, vite.config.ts, index.html, etc.) + if (-not $NewerFile) { + $NewerFile = Get-ChildItem -Path $FrontendDir -File -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match '\.(json|ts|js|mjs|html)$' -and $_.LastWriteTime -gt $DistTime } | + Select-Object -First 1 + } + if (-not $NewerFile) { + $NeedFrontendBuild = $false + Write-Host "[OK] Frontend already built and up to date -- skipping build" -ForegroundColor Green + } else { + Write-Host "[INFO] Frontend source changed since last build -- rebuilding..." -ForegroundColor Yellow + } +} +if ($NeedFrontendBuild -and -not $IsPipInstall) { Write-Host "" Write-Host "Building frontend..." -ForegroundColor Cyan # npm writes warnings to stderr; lower ErrorActionPreference so PS doesn't @@ -758,9 +788,6 @@ if ($IsPipInstall) { $prevEAP_npm = $ErrorActionPreference $ErrorActionPreference = "Continue" Push-Location $FrontendDir - # Remove stale node_modules and package-lock.json to avoid version conflicts - if (Test-Path "node_modules") { Remove-Item -Recurse -Force "node_modules" } - if (Test-Path "package-lock.json") { Remove-Item -Force "package-lock.json" } npm install 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Pop-Location diff --git a/studio/setup.sh b/studio/setup.sh index 4c8a6c7dde..98d6d4b8f4 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -41,13 +41,27 @@ if [[ "$keynames" == *$'\nCOLAB_'* ]]; then fi # ── Detect whether frontend needs building ── -# Only skip when BOTH conditions are true: -# 1. We're inside site-packages (PyPI / pip install, not editable) -# 2. dist/ already exists (pre-built in the wheel) -# Otherwise always (re)build — handles upgrades, editable installs, and -# pip-from-source where dist/ was never built. -if [[ "$SCRIPT_DIR" == */site-packages/* ]] && [ -d "$SCRIPT_DIR/frontend/dist" ]; then - echo "✅ Frontend pre-built (PyPI) — skipping Node/npm check." +# Skip if dist/ exists AND no tracked input is newer than dist/. +# Checks top-level config/entry files and src/, public/ recursively. +# This handles: PyPI installs (dist/ bundled), repeat runs (no changes), +# and upgrades/pulls (source newer than dist/ triggers rebuild). +_NEED_FRONTEND_BUILD=true +if [ -d "$SCRIPT_DIR/frontend/dist" ]; then + # Check top-level config and entry files (package.json, vite.config.ts, index.html, etc.) + _changed=$(find "$SCRIPT_DIR/frontend" -maxdepth 1 \ + \( -name "*.json" -o -name "*.ts" -o -name "*.js" -o -name "*.mjs" -o -name "*.html" \) \ + -newer "$SCRIPT_DIR/frontend/dist" -print -quit 2>/dev/null) + # Check src/ and public/ recursively + if [ -z "$_changed" ]; then + _changed=$(find "$SCRIPT_DIR/frontend/src" "$SCRIPT_DIR/frontend/public" \ + -type f -newer "$SCRIPT_DIR/frontend/dist" -print -quit 2>/dev/null) + fi + if [ -z "$_changed" ]; then + _NEED_FRONTEND_BUILD=false + fi +fi +if [ "$_NEED_FRONTEND_BUILD" = false ]; then + echo "✅ Frontend already built and up to date -- skipping Node/npm check." else NEED_NODE=true if command -v node &>/dev/null && command -v npm &>/dev/null; then @@ -146,12 +160,17 @@ run_quiet "npm run build" npm run build _restore_gitignores trap - EXIT -cd "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" -run_quiet "npm install (oxc validator runtime)" npm install cd "$SCRIPT_DIR" echo "✅ Frontend built to frontend/dist" -fi # end frontend dist check +fi # end frontend build check + +# ── oxc-validator runtime (always install, independent of frontend build) ── +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 "npm install (oxc validator runtime)" npm install + cd "$SCRIPT_DIR" +fi # ── 6. Python venv + deps ──