$ErrorActionPreference = 'Stop' function Stop-TrackedProcess { param( [string]$PidFile ) if (-not (Test-Path $PidFile)) { return } $pidText = (Get-Content $PidFile -Raw).Trim() if (-not $pidText) { Remove-Item $PidFile -Force -ErrorAction SilentlyContinue return } $existing = Get-Process -Id ([int]$pidText) -ErrorAction SilentlyContinue if ($null -ne $existing) { Stop-Process -Id $existing.Id -Force } Remove-Item $PidFile -Force -ErrorAction SilentlyContinue } function Wait-HttpReady { param( [string]$Url, [int]$MaxAttempts = 60, [int]$SleepSeconds = 1 ) for ($i = 0; $i -lt $MaxAttempts; $i++) { try { $response = Invoke-WebRequest -UseBasicParsing -Uri $Url -TimeoutSec 3 return $response } catch { Start-Sleep -Seconds $SleepSeconds } } return $null } function Show-LogTail { param( [string]$Path ) if (Test-Path $Path) { Write-Host "" Write-Host "Last log lines: $Path" Get-Content $Path -Tail 40 } } $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path $frontendRoot = Join-Path $repoRoot 'frontend\admin' $runtimeRoot = Join-Path $repoRoot 'runtime' $logsRoot = Join-Path $repoRoot 'logs' $binRoot = Join-Path $repoRoot 'bin' $cacheRoot = Join-Path $repoRoot '.cache\go' $buildCache = Join-Path $repoRoot '.cache\go-build' $modCache = Join-Path $cacheRoot 'pkg\mod' New-Item -ItemType Directory -Force $runtimeRoot, $logsRoot, $binRoot, $cacheRoot, $buildCache, $modCache | Out-Null $backendPidFile = Join-Path $runtimeRoot 'backend.pid' $frontendPidFile = Join-Path $runtimeRoot 'frontend.pid' $backendOut = Join-Path $logsRoot 'backend-dev.out.log' $backendErr = Join-Path $logsRoot 'backend-dev.err.log' $frontendOut = Join-Path $logsRoot 'frontend-dev.out.log' $frontendErr = Join-Path $logsRoot 'frontend-dev.err.log' $backendExe = Join-Path $binRoot 'server.exe' Stop-TrackedProcess -PidFile $backendPidFile Stop-TrackedProcess -PidFile $frontendPidFile Remove-Item $backendOut, $backendErr, $frontendOut, $frontendErr -Force -ErrorAction SilentlyContinue $env:GOPATH = $cacheRoot $env:GOCACHE = $buildCache $env:GOMODCACHE = $modCache Push-Location $repoRoot try { & go build -o $backendExe .\cmd\server } finally { Pop-Location } $node = (Get-Command node).Source $backendProcess = Start-Process -FilePath $backendExe ` -WorkingDirectory $repoRoot ` -RedirectStandardOutput $backendOut ` -RedirectStandardError $backendErr ` -PassThru Set-Content -Path $backendPidFile -Value $backendProcess.Id $frontendProcess = Start-Process -FilePath $node ` -ArgumentList '.\node_modules\vite\bin\vite.js', '--configLoader', 'native', '--host', '0.0.0.0', '--port', '3000' ` -WorkingDirectory $frontendRoot ` -RedirectStandardOutput $frontendOut ` -RedirectStandardError $frontendErr ` -PassThru Set-Content -Path $frontendPidFile -Value $frontendProcess.Id $backendReady = Wait-HttpReady -Url 'http://127.0.0.1:8080/health' $frontendReady = Wait-HttpReady -Url 'http://127.0.0.1:3000' if ($null -eq $backendReady) { Write-Host 'Backend failed to become ready on http://127.0.0.1:8080/health' Show-LogTail -Path $backendErr Show-LogTail -Path $backendOut exit 1 } if ($null -eq $frontendReady) { Write-Host 'Frontend failed to become ready on http://127.0.0.1:3000' Show-LogTail -Path $frontendErr Show-LogTail -Path $frontendOut exit 1 } Write-Host '' Write-Host "Backend ready: http://127.0.0.1:8080" Write-Host "Frontend ready: http://127.0.0.1:3000" Write-Host "Backend PID: $($backendProcess.Id)" Write-Host "Frontend PID: $($frontendProcess.Id)" Write-Host "Stop command: powershell -ExecutionPolicy Bypass -File scripts/dev/stop-preview-local.ps1" Write-Host "" Write-Host "Note: this repository does not ship a default admin account." Write-Host "If login fails on first run, initialize one explicitly:" Write-Host " powershell -ExecutionPolicy Bypass -File scripts/dev/init-admin-local.ps1 -Username admin -Password '' -Email 'admin@example.com'"