136 lines
7.3 KiB
PowerShell
136 lines
7.3 KiB
PowerShell
param(
|
|
[ValidateSet('start', 'stop', 'status', 'frontend', 'php', 'composer', 'npm', 'test', 'migrate')]
|
|
[string]$Action = 'status',
|
|
[Parameter(ValueFromRemainingArguments = $true)] [string[]]$ToolArguments
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$sgfProject = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
if ($sgfProject -ne 'C:\laragon\www\sgf-tempo') { throw 'Unexpected project path.' }
|
|
$sgfTools = 'C:\laragon\tools\sgf-tempo'
|
|
$sgfData = 'C:\laragon\data\sgf-tempo'
|
|
$sgfCluster = Join-Path $sgfData 'postgres-17'
|
|
$sgfPg = 'C:\Program Files\PostgreSQL\17\bin'
|
|
$sgfPhp = "$sgfTools\php-8.4.27\php.exe"
|
|
$sgfNode = "$sgfTools\node-v24.22.1-win-arm64\node.exe"
|
|
$sgfComposer = "$sgfTools\composer-2.10.3\composer.phar"
|
|
$sgfNpmCli = "$sgfTools\node-v24.22.1-win-arm64\node_modules\npm\bin\npm-cli.js"
|
|
$sgfRun = Join-Path $sgfData 'run'
|
|
$env:PATH = "$sgfTools\php-8.4.27;$sgfTools\node-v24.22.1-win-arm64;" + $env:PATH
|
|
$env:COMPOSER_HOME = "$sgfTools\composer-home"
|
|
$env:COMPOSER_CACHE_DIR = "$sgfTools\composer-cache"
|
|
$env:npm_config_cache = "$sgfTools\npm-cache"
|
|
$env:npm_config_userconfig = "$sgfTools\npmrc"
|
|
Remove-Item Env:COMPOSER_AUTH -ErrorAction SilentlyContinue
|
|
Set-Location -LiteralPath $sgfProject
|
|
New-Item -ItemType Directory -Path $sgfRun -Force | Out-Null
|
|
|
|
function Assert-FreePort([int]$Port) {
|
|
if (Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue) {
|
|
throw "Port $Port is occupied. No process was stopped."
|
|
}
|
|
}
|
|
function Start-SgfDatabase {
|
|
$listeners = @(Get-NetTCPConnection -LocalPort 55432 -State Listen -ErrorAction SilentlyContinue)
|
|
if ($listeners.Count) {
|
|
$pidFile = Join-Path $sgfCluster 'postmaster.pid'
|
|
if (!(Test-Path -LiteralPath $pidFile)) { throw 'Port 55432 belongs to another process.' }
|
|
$clusterPid = [int](Get-Content -LiteralPath $pidFile -TotalCount 1)
|
|
$process = Get-CimInstance Win32_Process -Filter "ProcessId = $clusterPid"
|
|
$normalizedCommand = if ($process) { $process.CommandLine.Replace('/', '\') } else { '' }
|
|
if (($listeners.OwningProcess -notcontains $clusterPid) -or !$process -or $process.ExecutablePath -ne "$sgfPg\postgres.exe" -or $normalizedCommand -notlike "*$sgfCluster*") {
|
|
throw 'Port 55432 is not owned by the SGF cluster. No process was stopped.'
|
|
}
|
|
return
|
|
}
|
|
Assert-FreePort 55432
|
|
& "$sgfPg\pg_ctl.exe" -D $sgfCluster -l (Join-Path $sgfData 'postgres.log') -w start
|
|
if ($LASTEXITCODE) { throw 'SGF database start failed.' }
|
|
}
|
|
function Start-SgfProcess([string]$Name, [string]$Executable, [string[]]$Arguments, [int]$Port) {
|
|
Assert-FreePort $Port
|
|
$process = Start-Process -FilePath $Executable -ArgumentList $Arguments -WorkingDirectory $sgfProject -WindowStyle Hidden -PassThru -RedirectStandardOutput "$sgfRun\$Name.stdout.log" -RedirectStandardError "$sgfRun\$Name.stderr.log"
|
|
$record = @{pid=$process.Id; executable=$Executable; project=$sgfProject; port=$Port; started=$process.StartTime.ToUniversalTime().ToString('o')}
|
|
$record | ConvertTo-Json | Set-Content -LiteralPath "$sgfRun\$Name.json" -Encoding UTF8
|
|
$ready = $false
|
|
for ($attempt = 0; $attempt -lt 40; $attempt++) {
|
|
$process.Refresh()
|
|
if ($process.HasExited) { throw "$Name exited. See SGF run logs." }
|
|
$listeners = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
|
|
if ($listeners.OwningProcess -contains $process.Id) { $ready = $true; break }
|
|
Start-Sleep -Milliseconds 250
|
|
}
|
|
if (!$ready) { throw "$Name did not become ready. No other process was stopped." }
|
|
Write-Output "$Name ready at http://127.0.0.1:$Port"
|
|
}
|
|
|
|
switch ($Action) {
|
|
'start' {
|
|
Assert-FreePort 8000
|
|
Start-SgfDatabase
|
|
Start-SgfProcess 'web' $sgfPhp @('-S', '127.0.0.1:8000', '-t', 'public', 'scripts/router.php') 8000
|
|
}
|
|
'frontend' {
|
|
Start-SgfProcess 'vite' $sgfNode @('node_modules/vite/bin/vite.js', '--host', '127.0.0.1', '--port', '5173', '--strictPort') 5173
|
|
}
|
|
'status' {
|
|
& "$sgfPg\pg_ctl.exe" -D $sgfCluster status
|
|
Get-NetTCPConnection -LocalPort 55432,8000,5173 -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress,LocalPort,OwningProcess
|
|
}
|
|
'stop' {
|
|
foreach ($name in @('vite', 'web')) {
|
|
$recordFile = "$sgfRun\$name.json"
|
|
if (Test-Path -LiteralPath $recordFile) {
|
|
$record = Get-Content -LiteralPath $recordFile -Raw | ConvertFrom-Json
|
|
$process = Get-Process -Id $record.pid -ErrorAction SilentlyContinue
|
|
if ($process) {
|
|
if ($process.Path -ne $record.executable -or $record.project -ne $sgfProject -or $process.StartTime.ToUniversalTime().ToString('o') -ne $record.started) {
|
|
throw "Process identity mismatch for $name; refusing to stop it."
|
|
}
|
|
Stop-Process -Id $process.Id
|
|
}
|
|
Remove-Item -LiteralPath $recordFile
|
|
if ($name -eq 'vite') {
|
|
$hotFile = Join-Path $sgfProject 'public\hot'
|
|
if ((Test-Path -LiteralPath $hotFile) -and (Get-Content -LiteralPath $hotFile -Raw).Trim() -eq 'http://127.0.0.1:5173') {
|
|
Remove-Item -LiteralPath $hotFile
|
|
}
|
|
}
|
|
}
|
|
}
|
|
& "$sgfPg\pg_ctl.exe" -D $sgfCluster -m fast -w stop
|
|
if ($LASTEXITCODE -ne 0 -and (Test-Path -LiteralPath "$sgfCluster\postmaster.pid")) { throw 'SGF cluster stop failed.' }
|
|
}
|
|
'php' { & $sgfPhp @ToolArguments; if ($LASTEXITCODE) { throw 'PHP command failed.' } }
|
|
'composer' { & $sgfPhp $sgfComposer @ToolArguments; if ($LASTEXITCODE) { throw 'Composer command failed.' } }
|
|
'npm' { & $sgfNode $sgfNpmCli @ToolArguments; if ($LASTEXITCODE) { throw 'npm command failed.' } }
|
|
'test' {
|
|
$demoManifest = Join-Path $sgfData 'secrets\browser-fixture.json'
|
|
if (Test-Path -LiteralPath $demoManifest) {
|
|
$demoFixture = Get-Content -LiteralPath $demoManifest -Raw | ConvertFrom-Json
|
|
if ($demoFixture.product_demo) { throw 'Integrated team demo preserved in sgf_tempo_test. Do not reset it for regression tests; use a separate test environment.' }
|
|
}
|
|
if (Test-Path -LiteralPath "$sgfRun\browser-proof.json") { throw 'Stop the browser proof before tests reset sgf_tempo_test.' }
|
|
Start-SgfDatabase
|
|
& $sgfPhp 'vendor/phpunit/phpunit/phpunit' @ToolArguments
|
|
if ($LASTEXITCODE) { throw 'Tests failed.' }
|
|
}
|
|
'migrate' {
|
|
Start-SgfDatabase
|
|
$credentials = Get-Content -LiteralPath "$sgfData\secrets\database.json" -Raw | ConvertFrom-Json
|
|
$env:APP_ENV = 'local'
|
|
$env:DB_CONNECTION = 'pgsql'
|
|
$env:DB_HOST = '127.0.0.1'
|
|
$env:DB_PORT = '55432'
|
|
$env:DB_DATABASE = 'sgf_tempo_dev'
|
|
$env:DB_USERNAME = 'sgf_dev_owner'
|
|
$env:DB_PASSWORD = $credentials.dev_owner
|
|
$env:DB_URL = ''
|
|
try {
|
|
& $sgfPhp artisan migrate --no-interaction
|
|
if ($LASTEXITCODE) { throw 'Development migration failed.' }
|
|
} finally {
|
|
foreach ($name in @('APP_ENV','DB_CONNECTION','DB_HOST','DB_PORT','DB_DATABASE','DB_USERNAME','DB_PASSWORD','DB_URL')) { Remove-Item "Env:$name" -ErrorAction SilentlyContinue }
|
|
}
|
|
}
|
|
}
|