From 27d5061d973426f49a044df924b4226af828a0f1 Mon Sep 17 00:00:00 2001 From: Sundaram Kumar Jha Date: Sun, 22 Mar 2026 13:51:02 +0530 Subject: [PATCH] micro-fix: quickstart dashboard auto-launch for PowerShell (#6655) * Fix quickstart dashboard auto-launch on Windows * chore: refresh locks * fix: gate quickstart hive shim to Git Bash * chore: revert unrelated frontend lockfile churn --- core/framework/runner/cli.py | 37 ++++++++++++++-- core/tests/test_runner_cli_frontend.py | 60 ++++++++++++++++++++++++++ quickstart.ps1 | 56 ++++++++++++++++++++---- quickstart.sh | 22 +++++++--- 4 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 core/tests/test_runner_cli_frontend.py diff --git a/core/framework/runner/cli.py b/core/framework/runner/cli.py index fdbec06f..22d2f221 100644 --- a/core/framework/runner/cli.py +++ b/core/framework/runner/cli.py @@ -1561,6 +1561,22 @@ def _open_browser(url: str) -> None: pass # Best-effort — don't crash if browser can't open +def _format_subprocess_output(output: str | bytes | None, limit: int = 2000) -> str: + """Return subprocess output as trimmed text safe for console logging.""" + if not output: + return "" + + if isinstance(output, bytes): + text = output.decode(errors="replace") + else: + text = output + + text = text.strip() + if len(text) <= limit: + return text + return text[-limit:] + + def _build_frontend() -> bool: """Build the frontend if source is newer than dist. Returns True if dist exists.""" import subprocess @@ -1596,18 +1612,25 @@ def _build_frontend() -> bool: # Need to build print("Building frontend...") + npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm" try: + # Incremental tsc caches can drift across branch changes and block builds. + for cache_file in frontend_dir.glob("tsconfig*.tsbuildinfo"): + cache_file.unlink(missing_ok=True) + # Ensure deps are installed subprocess.run( - ["npm", "install", "--no-fund", "--no-audit"], + [npm_cmd, "install", "--no-fund", "--no-audit"], encoding="utf-8", + errors="replace", cwd=frontend_dir, check=True, capture_output=True, ) subprocess.run( - ["npm", "run", "build"], + [npm_cmd, "run", "build"], encoding="utf-8", + errors="replace", cwd=frontend_dir, check=True, capture_output=True, @@ -1618,8 +1641,14 @@ def _build_frontend() -> bool: print("Node.js not found — skipping frontend build.") return dist_dir.is_dir() except subprocess.CalledProcessError as exc: - stderr = exc.stderr.decode(errors="replace") if exc.stderr else "" - print(f"Frontend build failed: {stderr[:500]}") + stdout = _format_subprocess_output(exc.stdout) + stderr = _format_subprocess_output(exc.stderr) + cmd = " ".join(exc.cmd) if isinstance(exc.cmd, (list, tuple)) else str(exc.cmd) + details = "\n".join(part for part in [stdout, stderr] if part).strip() + if details: + print(f"Frontend build failed while running {cmd}:\n{details}") + else: + print(f"Frontend build failed while running {cmd} (exit {exc.returncode}).") return dist_dir.is_dir() diff --git a/core/tests/test_runner_cli_frontend.py b/core/tests/test_runner_cli_frontend.py new file mode 100644 index 00000000..0f67b3ca --- /dev/null +++ b/core/tests/test_runner_cli_frontend.py @@ -0,0 +1,60 @@ +"""Tests for frontend build fallback in the runner CLI.""" + +import subprocess + +from framework.runner import cli as runner_cli + + +def _write_frontend_tree(tmp_path, *, with_dist: bool = False): + frontend_dir = tmp_path / "core" / "frontend" + (frontend_dir / "src").mkdir(parents=True) + (frontend_dir / "package.json").write_text("{}", encoding="utf-8") + (frontend_dir / "src" / "main.tsx").write_text("console.log('hi')", encoding="utf-8") + if with_dist: + (frontend_dir / "dist").mkdir() + (frontend_dir / "dist" / "index.html").write_text("", encoding="utf-8") + return frontend_dir + + +def test_build_frontend_handles_text_calledprocesserror(monkeypatch, tmp_path, capsys): + _write_frontend_tree(tmp_path) + monkeypatch.chdir(tmp_path) + + def fake_run(cmd, **kwargs): + raise subprocess.CalledProcessError( + 1, + cmd, + output="npm output", + stderr="vite config failed", + ) + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert runner_cli._build_frontend() is False + + output = capsys.readouterr().out + assert "Frontend build failed while running" in output + assert "vite config failed" in output + + +def test_build_frontend_cleans_cache_and_uses_windows_npm_cmd(monkeypatch, tmp_path): + frontend_dir = _write_frontend_tree(tmp_path) + cache_file = frontend_dir / "tsconfig.app.tsbuildinfo" + cache_file.write_text("stale", encoding="utf-8") + monkeypatch.chdir(tmp_path) + monkeypatch.setattr(runner_cli.sys, "platform", "win32") + + commands = [] + + def fake_run(cmd, **kwargs): + commands.append(cmd) + return subprocess.CompletedProcess(cmd, 0, stdout="", stderr="") + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert runner_cli._build_frontend() is True + assert not cache_file.exists() + assert commands == [ + ["npm.cmd", "install", "--no-fund", "--no-audit"], + ["npm.cmd", "run", "build"], + ] diff --git a/quickstart.ps1 b/quickstart.ps1 index 211806ef..858d2e89 100644 --- a/quickstart.ps1 +++ b/quickstart.ps1 @@ -66,6 +66,32 @@ function Write-Fail { Write-Color -Text " X $Text" -Color Red } +function Write-CommandFailureDetails { + param( + [object[]]$Output, + [int]$Tail = 40 + ) + + $lines = @($Output | Where-Object { $_ -ne $null } | ForEach-Object { "$_" }) + if ($lines.Count -eq 0) { + return + } + + $start = [Math]::Max(0, $lines.Count - $Tail) + if ($start -gt 0) { + Write-Host " ... showing last $($lines.Count - $start) lines ..." -ForegroundColor DarkGray + } + + for ($i = $start; $i -lt $lines.Count; $i++) { + Write-Host " $($lines[$i])" -ForegroundColor DarkGray + } +} + +function Test-FrontendDistReady { + param([string]$RootDir) + return (Test-Path (Join-Path $RootDir "core\frontend\dist\index.html")) +} + function Prompt-YesNo { param( [string]$Prompt, @@ -540,6 +566,7 @@ Write-Host "" # Build frontend (if Node.js is available) $FrontendBuilt = $false +$FrontendDistReady = Test-FrontendDistReady -RootDir $ScriptDir if ($NodeAvailable) { Write-Step -Number "" -Text "Building frontend dashboard..." Write-Host "" @@ -548,30 +575,33 @@ if ($NodeAvailable) { Write-Host " Installing npm packages... " -NoNewline Push-Location $frontendDir try { - $null = & npm install --no-fund --no-audit 2>&1 + $installOutput = & npm install --no-fund --no-audit 2>&1 if ($LASTEXITCODE -eq 0) { Write-Ok "ok" # Clean stale tsbuildinfo cache — tsc -b incremental builds fail # silently when these are out of sync with source files Get-ChildItem -Path $frontendDir -Filter "tsconfig*.tsbuildinfo" -ErrorAction SilentlyContinue | Remove-Item -Force Write-Host " Building frontend... " -NoNewline - $null = & npm run build 2>&1 + $buildOutput = & npm run build 2>&1 if ($LASTEXITCODE -eq 0) { Write-Ok "ok" Write-Ok "Frontend built -> core/frontend/dist/" $FrontendBuilt = $true + $FrontendDistReady = $true } else { Write-Warn "build failed" - Write-Host " Run 'cd core\frontend && npm run build' manually to debug." -ForegroundColor DarkGray + Write-CommandFailureDetails -Output $buildOutput -Tail 60 + Write-Host " Quickstart will still try '.\hive.ps1 open' if Hive can rebuild the dashboard." -ForegroundColor DarkGray } } else { Write-Warn "npm install failed" - $NodeAvailable = $false + Write-CommandFailureDetails -Output $installOutput -Tail 60 } } finally { Pop-Location } } + $FrontendDistReady = Test-FrontendDistReady -RootDir $ScriptDir Write-Host "" } @@ -2016,13 +2046,21 @@ Write-Color -Text "hive open" -Color Cyan -NoNewline Write-Host ". Run .\quickstart.ps1 again to reconfigure." -ForegroundColor DarkGray Write-Host "" -if ($FrontendBuilt) { - Write-Color -Text "Launching dashboard..." -Color White +if ($FrontendDistReady -or $NodeAvailable) { + if ($FrontendDistReady) { + Write-Color -Text "Launching dashboard..." -Color White + } else { + Write-Color -Text "Launching dashboard (retrying frontend build via hive open)..." -Color White + } Write-Host "" - & hive open + & $hivePs1Path open + if ($LASTEXITCODE -ne 0) { + Write-Warn "Dashboard launch failed" + Write-Host " Run '.\hive.ps1 open' manually to inspect the error." -ForegroundColor DarkGray + } } else { - Write-Color -Text "Frontend build was skipped or failed." -Color Yellow -NoNewline + Write-Color -Text "Frontend build was skipped or failed, and no built dashboard is available." -Color Yellow -NoNewline Write-Host " Launch manually when ready:" - Write-Color -Text " hive open" -Color Cyan + Write-Color -Text " .\hive.ps1 open" -Color Cyan Write-Host "" } diff --git a/quickstart.sh b/quickstart.sh index a5dbd0bb..e59cf760 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -1792,15 +1792,27 @@ echo "" # Ensure ~/.local/bin exists and is in PATH mkdir -p "$HOME/.local/bin" -# Create/update symlink +# Git Bash on Windows may materialize `ln -s` as a plain file copy. +# Use a launcher shim there, but prefer a real symlink on Linux/macOS. HIVE_SCRIPT="$SCRIPT_DIR/hive" HIVE_LINK="$HOME/.local/bin/hive" +HIVE_SCRIPT_ESCAPED=$(printf '%q' "$HIVE_SCRIPT") if [ -L "$HIVE_LINK" ] || [ -e "$HIVE_LINK" ]; then rm -f "$HIVE_LINK" fi -ln -s "$HIVE_SCRIPT" "$HIVE_LINK" +if [ -n "$MSYSTEM" ] || [ -n "$MINGW_PREFIX" ]; then + cat > "$HIVE_LINK" <