[CmdletBinding()] param( [string]$ConfigPath, [switch]$ValidateConfigOnly ) $ErrorActionPreference = "Stop" $workspaceRoot = Split-Path -Parent $PSScriptRoot $webDirectory = Join-Path $workspaceRoot "web" function ConvertFrom-YamlScalar { param([string]$Value) $value = $Value.Trim() if ($value.Length -ge 2) { if ($value.StartsWith('"') -and $value.EndsWith('"')) { return $value.Substring(1, $value.Length - 2).Replace('\"', '"').Replace('\\', '\') } if ($value.StartsWith("'") -and $value.EndsWith("'")) { return $value.Substring(1, $value.Length - 2).Replace("''", "'") } } return $value } function Read-PortConfig { param([string]$Path) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Local configuration file was not found: $Path. Copy config.example.yaml to config.yaml." } $values = @{} $insidePorts = $false foreach ($line in Get-Content -LiteralPath $Path) { if ($line -match '^\s*(#.*)?$') { continue } if ($line -match '^ports\s*:\s*$') { $insidePorts = $true continue } if ($insidePorts -and $line -match '^\S') { break } if ($insidePorts -and $line -match '^\s+(server|web)\s*:\s*(.*?)\s*$') { $values[$Matches[1]] = ConvertFrom-YamlScalar $Matches[2] } } foreach ($key in 'server', 'web') { $parsedPort = 0 if (-not $values.ContainsKey($key) -or -not [int]::TryParse([string]$values[$key], [ref]$parsedPort) -or $parsedPort -lt 1 -or $parsedPort -gt 65535) { throw "ports.$key must be an integer between 1 and 65535 in: $Path" } $values[$key] = $parsedPort } if ($values.server -eq $values.web) { throw "ports.server and ports.web must be different in: $Path" } return @{ Server = [int]$values.server; Web = [int]$values.web } } if ([string]::IsNullOrWhiteSpace($ConfigPath)) { $ConfigPath = Join-Path $workspaceRoot "config.yaml" } elseif (-not [IO.Path]::IsPathRooted($ConfigPath)) { $ConfigPath = Join-Path $workspaceRoot $ConfigPath } $ConfigPath = [IO.Path]::GetFullPath($ConfigPath) $ports = Read-PortConfig $ConfigPath Write-Host "GoAuto web UI" -ForegroundColor Cyan Write-Host "Config: $ConfigPath" Write-Host "Ports: server=$($ports.Server), web=$($ports.Web)" if ($ValidateConfigOnly) { Write-Host "Local port configuration is valid." -ForegroundColor Green return } $pnpm = Get-Command pnpm.cmd -ErrorAction SilentlyContinue if (-not $pnpm) { throw "pnpm was not found. Install pnpm and try again." } Push-Location $webDirectory try { if (-not (Test-Path -LiteralPath "node_modules" -PathType Container)) { Write-Host "Installing frontend dependencies..." -ForegroundColor Cyan & $pnpm.Source install --frozen-lockfile if ($LASTEXITCODE -ne 0) { throw "Frontend dependency installation failed." } } $env:VUE_APP_BASE_API = "http://127.0.0.1:$($ports.Server)" Write-Host "Starting GoAuto web UI at http://127.0.0.1:$($ports.Web)" -ForegroundColor Green & $pnpm.Source exec vite --host 127.0.0.1 --port $ports.Web --strictPort if ($LASTEXITCODE -ne 0) { throw "Frontend exited with code $LASTEXITCODE." } } finally { Remove-Item Env:VUE_APP_BASE_API -ErrorAction SilentlyContinue Pop-Location }