192 lines
7.7 KiB
PowerShell
192 lines
7.7 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Vignette 로컬 개발 스택을 깔끔하게 (재)기동한다: 엔진 게이트웨이(9099) + API(8000) + 웹(5173).
|
|
|
|
.DESCRIPTION
|
|
- 기존(고아 포함) 프로세스를 커맨드라인 기준으로 정확히 정리한 뒤 새로 띄운다.
|
|
API는 Windows reloader 고아 프로세스/포트 잔류를 피하려고 단일 프로세스로 띄운다.
|
|
게이트웨이는 no-reload 유지(상주 claude -p 세션 보존; Windows에선 reload 시 자식 claude 고아화).
|
|
웹(vite)은 npm run dev 기본 HMR.
|
|
- API는 DB 미가용 시 in-memory degraded로 기동된다(Postgres 불필요). dev-login + seed 페르소나 활성.
|
|
- 로그는 .devlogs/ 에 남긴다. 종료는 scripts/dev-down.ps1.
|
|
|
|
.PARAMETER NoGateway
|
|
엔진 게이트웨이를 띄우지 않는다(AI 턴 생성 불가, UI만 테스트 시).
|
|
.PARAMETER NoWeb
|
|
vite 웹 서버를 띄우지 않는다(API만 필요할 때).
|
|
.EXAMPLE
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1
|
|
#>
|
|
param(
|
|
[int]$ApiPort = 8000,
|
|
[switch]$NoGateway,
|
|
[switch]$NoWeb,
|
|
[switch]$NoDb
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$repo = Split-Path -Parent $PSScriptRoot
|
|
$api = Join-Path $repo 'apps\api'
|
|
$web = Join-Path $repo 'apps\web'
|
|
$logs = Join-Path $repo '.devlogs'
|
|
New-Item -ItemType Directory -Force -Path $logs | Out-Null
|
|
|
|
# uvicorn 이 설치된 python 을 해석한다(시스템에 3.11/3.14 등 복수 python 공존 — 'python' 별칭이
|
|
# uvicorn 없는 인터프리터를 가리킬 수 있다). 후보를 순회해 import uvicorn 성공하는 것을 고른다.
|
|
$pyCandidates = @(
|
|
(Join-Path $env:LOCALAPPDATA 'Programs\Python\Python311\python.exe'),
|
|
(Join-Path $env:LOCALAPPDATA 'Programs\Python\Python312\python.exe'),
|
|
'py',
|
|
'python'
|
|
)
|
|
$Python = $null
|
|
foreach ($c in $pyCandidates) {
|
|
$exe = $c; $pre = @()
|
|
if ($c -eq 'py') { $pre = @('-3') }
|
|
try {
|
|
& $exe @pre '-c' 'import uvicorn' 2>$null
|
|
if ($LASTEXITCODE -eq 0) { $Python = $exe; $PyPre = $pre; break }
|
|
} catch {}
|
|
}
|
|
if (-not $Python) { Write-Host 'ERROR: uvicorn 설치된 python 을 못 찾음 (pip install -r apps/api/requirements.txt)'; exit 1 }
|
|
Write-Host ("python: {0} {1}" -f $Python, ($PyPre -join ' '))
|
|
|
|
function Stop-Stale([string]$pattern, [string]$label) {
|
|
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
Where-Object { ($_.Name -eq 'python.exe' -or $_.Name -eq 'node.exe') -and $_.CommandLine -and $_.CommandLine -match $pattern } |
|
|
ForEach-Object {
|
|
Write-Host (" stop {0,-8} PID {1}" -f $label, $_.ProcessId)
|
|
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Stop-ListenerByPort([int]$port, [string]$label) {
|
|
Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue |
|
|
Select-Object -ExpandProperty OwningProcess -Unique |
|
|
Where-Object { $_ -and $_ -ne 0 } |
|
|
ForEach-Object {
|
|
Write-Host (" stop {0,-8} PID {1} (:${port})" -f $label, $_)
|
|
Stop-Process -Id $_ -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Wait-Health([string]$url, [string]$label, [int]$timeoutSec = 40) {
|
|
$deadline = (Get-Date).AddSeconds($timeoutSec)
|
|
while ((Get-Date) -lt $deadline) {
|
|
try {
|
|
$r = Invoke-WebRequest -Uri $url -TimeoutSec 4 -UseBasicParsing -ErrorAction Stop
|
|
if ($r.StatusCode -ge 200 -and $r.StatusCode -lt 500) { Write-Host " OK $label ($url)"; return $true }
|
|
} catch { Start-Sleep -Milliseconds 600 }
|
|
}
|
|
Write-Host " WARN $label 미응답 ($url) — .devlogs 로그 확인"; return $false
|
|
}
|
|
|
|
function Ensure-DevDb {
|
|
if (!(Get-Command docker -ErrorAction SilentlyContinue)) {
|
|
Write-Host ' WARN docker CLI 없음 - DB 없이 degraded(UI 세션 시작 제한).'
|
|
return
|
|
}
|
|
if (!(Test-Path -LiteralPath '\\.\pipe\dockerDesktopLinuxEngine')) {
|
|
Write-Host ' WARN Docker Desktop Linux engine 미실행 - DB 없이 degraded(UI 세션 시작 제한).'
|
|
return
|
|
}
|
|
docker ps 1>$null 2>$null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host ' WARN Docker 데몬 미응답 - DB 없이 degraded(UI 세션 시작 제한). Docker Desktop 실행 필요.'
|
|
return
|
|
}
|
|
|
|
$running = docker ps --filter name=vignette-dev-db --format '{{.Names}}' 2>$null
|
|
if ($running -match 'vignette-dev-db') {
|
|
Write-Host ' OK db (vignette-dev-db 실행 중)'
|
|
return
|
|
}
|
|
|
|
docker rm -f vignette-dev-db 1>$null 2>$null
|
|
$envLines = Get-Content -LiteralPath (Join-Path $api '.env')
|
|
$dbUrl = (($envLines | Where-Object { $_ -match '^DATABASE_URL=' }) -replace '^DATABASE_URL=', '').Trim()
|
|
if ($dbUrl -notmatch 'postgresql://([^:]+):([^@]+)@[^:]+:([0-9]+)/(\S+)') {
|
|
Write-Host ' WARN DATABASE_URL 파싱 실패 - DB 스킵'
|
|
return
|
|
}
|
|
|
|
$u = $Matches[1]
|
|
$p = $Matches[2]
|
|
$port = $Matches[3]
|
|
$db = $Matches[4]
|
|
$initPath = Join-Path $repo 'infra\db\init'
|
|
$dockerArgs = @(
|
|
'run', '-d',
|
|
'--name', 'vignette-dev-db',
|
|
'-p', ('{0}:5432' -f $port),
|
|
'-e', ('POSTGRES_USER={0}' -f $u),
|
|
'-e', ('POSTGRES_PASSWORD={0}' -f $p),
|
|
'-e', ('POSTGRES_DB={0}' -f $db),
|
|
'-e', 'APP_DB_USER=vignette_app',
|
|
'-e', 'APP_DB_PASSWORD=vignette_app',
|
|
'-v', ('{0}:/docker-entrypoint-initdb.d:ro' -f $initPath),
|
|
'pgvector/pgvector:pg16'
|
|
)
|
|
& docker @dockerArgs 1>$null
|
|
|
|
for ($i = 0; $i -lt 24; $i++) {
|
|
Start-Sleep -Seconds 2
|
|
docker exec vignette-dev-db pg_isready -U $u 1>$null 2>$null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Write-Host ' OK db (Postgres 준비됨, init 스키마 적용)'
|
|
Start-Sleep -Seconds 1
|
|
return
|
|
}
|
|
}
|
|
Write-Host ' WARN db 준비 타임아웃'
|
|
}
|
|
|
|
Write-Host '[1/4] 기존 스택 정리...'
|
|
Stop-Stale 'engine_gateway\.gateway' 'gateway'
|
|
Stop-Stale 'app\.main:app' 'api'
|
|
Stop-Stale 'vite' 'web'
|
|
Stop-ListenerByPort 9099 'gateway'
|
|
Stop-ListenerByPort $ApiPort 'api'
|
|
Stop-ListenerByPort 5173 'web'
|
|
Start-Sleep -Seconds 2
|
|
|
|
if (-not $NoDb) {
|
|
Write-Host '[DB] Postgres(pgvector) 보장...'
|
|
Ensure-DevDb
|
|
}
|
|
|
|
if (-not $NoGateway) {
|
|
Write-Host '[2/4] 엔진 게이트웨이 :9099 (claude_cli)...'
|
|
$gatewayArgs = @($PyPre) + @('-m', 'uvicorn', 'engine_gateway.gateway:app', '--host', '127.0.0.1', '--port', '9099')
|
|
Start-Process -FilePath $Python -ArgumentList $gatewayArgs -WorkingDirectory $api -WindowStyle Hidden -RedirectStandardOutput (Join-Path $logs 'gateway.out.log') -RedirectStandardError (Join-Path $logs 'gateway.err.log')
|
|
} else {
|
|
Write-Host '[2/4] (게이트웨이 건너뜀)'
|
|
}
|
|
|
|
Write-Host ('[3/4] API :{0} (단일 프로세스, degraded in-memory OK, seed 페르소나)...' -f $ApiPort)
|
|
$env:AUTO_SEED_PERSONAS = 'true'
|
|
$env:ALLOW_SEED_PERSONA_FALLBACK = 'true'
|
|
$env:VITE_API_PROXY_TARGET = "http://127.0.0.1:$ApiPort"
|
|
$apiArgs = @($PyPre) + @('-m', 'uvicorn', 'app.main:app', '--host', '127.0.0.1', '--port', "$ApiPort")
|
|
Start-Process -FilePath $Python -ArgumentList $apiArgs -WorkingDirectory $api -WindowStyle Hidden -RedirectStandardOutput (Join-Path $logs 'api.out.log') -RedirectStandardError (Join-Path $logs 'api.err.log')
|
|
|
|
if (-not $NoWeb) {
|
|
Write-Host ('[4/4] 웹 vite :5173... API proxy {0}' -f $env:VITE_API_PROXY_TARGET)
|
|
Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', 'npm run dev -- --host 127.0.0.1 --port 5173') -WorkingDirectory $web -WindowStyle Hidden -RedirectStandardOutput (Join-Path $logs 'web.out.log') -RedirectStandardError (Join-Path $logs 'web.err.log')
|
|
} else {
|
|
Write-Host '[4/4] (웹 건너뜀)'
|
|
}
|
|
|
|
Start-Sleep -Seconds 3
|
|
Write-Host ''
|
|
Write-Host '헬스 체크:'
|
|
if (-not $NoGateway) {
|
|
Wait-Health 'http://127.0.0.1:9099/health' 'gateway' | Out-Null
|
|
}
|
|
Wait-Health "http://127.0.0.1:$ApiPort/health" 'api' | Out-Null
|
|
if (-not $NoWeb) {
|
|
Wait-Health 'http://localhost:5173/' 'web' | Out-Null
|
|
}
|
|
|
|
Write-Host ''
|
|
Write-Host '준비 완료. 진입점: http://localhost:5173 (로그인 페이지에서 dev-login)'
|
|
Write-Host '종료: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-down.ps1'
|