The startup output was dominated by two upstream behaviours. Migrate printed a bare count for every already-applied migration — a lone "1" with no version, no explanation — through the standard logger, hence stderr, which the launcher's `2>&1 |` renders as a red PowerShell error block that looks like a failed migration. And the version-existence check echoed one SELECT count(*) per version. Both replaced: a Warn-level session for the check, and one summary line on stdout. Console encoding needed more than [Console]::OutputEncoding: PowerShell 5.1 decodes child-process output by the console code page, so chcp 65001 goes with it. The regression test capture was wrong twice and mutation testing caught both. It captured only stdout while the bug wrote to stderr, and it compared lines to "1" when the logger prefixes a timestamp, so the assertion could never fail. It now captures the standard logger too and matches with a pattern that allows the prefix — verified by reinstating the bug and watching the test fail. Not verified: the PowerShell edits, which need a Windows run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
3.9 KiB
PowerShell
118 lines
3.9 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$ConfigPath,
|
|
[switch]$ValidateConfigOnly
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# Go 进程输出 UTF-8。不显式设定的话,中文日志会按控制台的 ANSI 代码页(简中为
|
|
# GBK)解码,显示成乱码——见 #48 启动日志。
|
|
#
|
|
# 只设 [Console]::OutputEncoding 不够:PowerShell 5.1 解码子进程输出时还要看
|
|
# 控制台代码页,所以两个都得改。chcp 的输出丢弃掉,避免多打一行。
|
|
try {
|
|
$null = & chcp.com 65001
|
|
[Console]::OutputEncoding = [Text.Encoding]::UTF8
|
|
$OutputEncoding = [Text.Encoding]::UTF8
|
|
} catch { }
|
|
$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
|
|
}
|