-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstackflow.ps1
More file actions
360 lines (319 loc) · 13.6 KB
/
Copy pathstackflow.ps1
File metadata and controls
360 lines (319 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#Requires -Version 5.1
<#
.SYNOPSIS
StackFlow CLI - hybrid mode: Docker for infra, local for API + editor.
.DESCRIPTION
Windows PowerShell equivalent of the Unix stackflow CLI.
Commands: start, stop, restart, docker, logs, pm, install
.EXAMPLE
.\stackflow.ps1 start
.\stackflow.ps1 stop all
.\stackflow.ps1 logs api -f
#>
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Ensure UTF-8 output so log files with Unicode render correctly
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
$SCRIPT_DIR = Split-Path -Parent $PSCommandPath
Set-Location $SCRIPT_DIR
# ── Ensure PATH includes common install locations (needed with -NoProfile) ─
$extraPaths = @(
"${env:ProgramFiles}\Docker\Docker\resources\bin"
"${env:ProgramFiles}\Docker\Docker"
"${env:LOCALAPPDATA}\Programs\Python\Python313"
"${env:LOCALAPPDATA}\Programs\Python\Python313\Scripts"
"${env:ProgramFiles}\nodejs"
)
foreach ($p in $extraPaths) {
if ((Test-Path $p) -and ($env:Path -notlike "*$p*")) {
$env:Path = "$p;$env:Path"
}
}
# Also pull in the full user+machine PATH in case -NoProfile dropped it
$machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
foreach ($segment in ($machinePath + ';' + $userPath).Split(';')) {
if ($segment -and ($env:Path -notlike "*$segment*")) {
$env:Path = "$env:Path;$segment"
}
}
# ── Resolve compose command ───────────────────────────────────────────
$DC = "docker compose"
$savedEAP = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
$null = & docker compose version 2>$null
if ($LASTEXITCODE -ne 0) { $DC = "docker-compose" }
$ErrorActionPreference = $savedEAP
# ── Colours ────────────────────────────────────────────────────────────
function Write-Info { param([string]$Msg) Write-Host " -> " -ForegroundColor Cyan -NoNewline; Write-Host $Msg }
function Write-Ok { param([string]$Msg) Write-Host " [ok] " -ForegroundColor Green -NoNewline; Write-Host $Msg }
function Write-Warn { param([string]$Msg) Write-Host " [!] " -ForegroundColor Yellow -NoNewline; Write-Host $Msg }
$PIDFILE = Join-Path $SCRIPT_DIR ".stackflow.pids"
$LOGDIR = Join-Path $SCRIPT_DIR "logs"
$VENV_PYTHON = Join-Path $SCRIPT_DIR "venv\Scripts\python.exe"
function Show-Usage {
Write-Host ""
Write-Host " +-++-++-++-++-++-++-++-++-+" -ForegroundColor Cyan
Write-Host " |S||t||a||c||k||F||l||o||w|" -ForegroundColor Cyan
Write-Host " +-++-++-++-++-++-++-++-++-+" -ForegroundColor Cyan
Write-Host ""
Write-Host " Usage: " -ForegroundColor White -NoNewline; Write-Host "stackflow <command>"
Write-Host ""
Write-Host " Commands:" -ForegroundColor White
Write-Host " start " -ForegroundColor Cyan -NoNewline; Write-Host "Start infra + API + editor (default)"
Write-Host " stop " -ForegroundColor Cyan -NoNewline; Write-Host "Stop API + editor (infra keeps running)"
Write-Host " stop all " -ForegroundColor Cyan -NoNewline; Write-Host "Stop everything including Docker infra"
Write-Host " restart [ui] " -ForegroundColor Cyan -NoNewline; Write-Host "Restart the API server (or UI with 'ui')"
Write-Host " docker " -ForegroundColor Cyan -NoNewline; Write-Host "Show infra container status"
Write-Host " logs [api|ui] [-f]" -ForegroundColor Cyan -NoNewline; Write-Host " Show logs"
Write-Host " pm <command> " -ForegroundColor Cyan -NoNewline; Write-Host "Package manager (list, install, uninstall, info)"
Write-Host " install " -ForegroundColor Cyan -NoNewline; Write-Host "Re-run the setup script"
Write-Host ""
}
function Test-DockerReady {
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { return $false }
$savedPref = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
$null = & docker info 2>$null
$result = $LASTEXITCODE -eq 0
$ErrorActionPreference = $savedPref
return $result
}
function Start-DockerDesktop {
$dockerDesktop = "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe"
if (Test-Path $dockerDesktop) {
Start-Process $dockerDesktop
Write-Info "Waiting for Docker Desktop to start..."
$maxWait = 60; $waited = 0
while ($waited -lt $maxWait) {
Start-Sleep -Seconds 3; $waited += 3
if (Test-DockerReady) {
Write-Ok "Docker Desktop is running"
return
}
Write-Host "." -NoNewline
}
Write-Host ""
Write-Warn "Docker Desktop did not start in time. Please start it manually."
} else {
Write-Warn "Docker Desktop not found. Please install and start Docker Desktop."
}
}
function Ensure-Infra {
if (-not (Test-DockerReady)) {
Start-DockerDesktop
if (-not (Test-DockerReady)) { return }
}
# Start Docker infra if not already running
$savedPref = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
$running = & docker compose ps 2>$null | Select-String "running"
if (-not $running) {
Write-Info "Starting infrastructure services..."
& docker compose up -d --quiet-pull 2>$null
if ($LASTEXITCODE -ne 0) {
& docker compose up -d 2>$null
}
}
$ErrorActionPreference = $savedPref
}
function Wait-ForLangfuse {
$url = "http://localhost:3000/api/public/health"
$maxAttempts = 30; $attempt = 0
Write-Host " -> Waiting for Langfuse..." -ForegroundColor Cyan -NoNewline
while ($attempt -lt $maxAttempts) {
try {
$null = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 2 -ErrorAction SilentlyContinue
Write-Host " ready" -ForegroundColor Green
return
} catch {}
Write-Host "." -NoNewline
Start-Sleep -Seconds 2
$attempt++
}
Write-Host " timed out (API will retry on its own)" -ForegroundColor Yellow
}
function Stop-ByPort {
param([int]$Port)
try {
$connections = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue |
Where-Object { $_.State -eq 'Listen' }
foreach ($conn in $connections) {
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
}
} catch {}
}
function Start-ApiServer {
if (-not (Test-Path $LOGDIR)) { New-Item -ItemType Directory -Path $LOGDIR -Force | Out-Null }
$apiLog = Join-Path $LOGDIR "api.log"
$env:PYTHONPATH = $SCRIPT_DIR
$env:PYTHONUNBUFFERED = "1"
$env:PYTHONIOENCODING = "utf-8"
$env:PYTHONUTF8 = "1"
$proc = Start-Process -FilePath $VENV_PYTHON `
-ArgumentList "src/api_server.py" `
-WorkingDirectory $SCRIPT_DIR `
-RedirectStandardOutput $apiLog `
-RedirectStandardError (Join-Path $LOGDIR "api.err.log") `
-WindowStyle Hidden `
-PassThru
$proc.Id | Out-File -Append -FilePath $PIDFILE
}
function Start-EditorServer {
if (-not (Test-Path $LOGDIR)) { New-Item -ItemType Directory -Path $LOGDIR -Force | Out-Null }
$editorLog = Join-Path $LOGDIR "editor.log"
$npmCmd = (Get-Command npm.cmd -ErrorAction SilentlyContinue).Source
if (-not $npmCmd) {
Write-Warn "npm not found. Cannot start editor."
return
}
$editorDir = Join-Path $SCRIPT_DIR "litegraph-editor"
$proc = Start-Process -FilePath $npmCmd `
-ArgumentList "run dev" `
-WorkingDirectory $editorDir `
-RedirectStandardOutput $editorLog `
-RedirectStandardError (Join-Path $LOGDIR "editor.err.log") `
-WindowStyle Hidden `
-PassThru
$proc.Id | Out-File -Append -FilePath $PIDFILE
}
function Stop-LocalProcesses {
if (Test-Path $PIDFILE) {
Get-Content $PIDFILE | ForEach-Object {
$pid_ = $_.Trim()
if ($pid_) {
# Kill the process and its children
try {
$proc = Get-Process -Id $pid_ -ErrorAction SilentlyContinue
if ($proc) {
# Kill child processes first
Get-CimInstance Win32_Process |
Where-Object { $_.ParentProcessId -eq $pid_ } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
Stop-Process -Id $pid_ -Force -ErrorAction SilentlyContinue
}
} catch {}
}
}
Remove-Item $PIDFILE -Force -ErrorAction SilentlyContinue
}
# Belt and suspenders: kill anything on our ports
Stop-ByPort 8000
Stop-ByPort 5173
}
# ── Parse command ──────────────────────────────────────────────────────
$cmd = if ($args.Count -ge 1) { $args[0] } else { "" }
[System.Collections.ArrayList]$rest = @()
if ($args.Count -ge 2) { $args[1..($args.Count - 1)] | ForEach-Object { $null = $rest.Add($_) } }
switch ($cmd) {
{ $_ -eq "start" -or $_ -eq "" } {
Write-Host ""
Write-Host " +-++-++-++-++-++-++-++-++-+" -ForegroundColor Cyan
Write-Host " |S||t||a||c||k||F||l||o||w|" -ForegroundColor Cyan
Write-Host " +-++-++-++-++-++-++-++-++-+" -ForegroundColor Cyan
Write-Host ""
if (-not (Test-Path $VENV_PYTHON)) {
Write-Host " venv not found. Run: stackflow install" -ForegroundColor Red
exit 1
}
# Stop any existing local processes
Stop-LocalProcesses
# Prepare log directory and truncate old logs
if (-not (Test-Path $LOGDIR)) { New-Item -ItemType Directory -Path $LOGDIR -Force | Out-Null }
"" | Set-Content (Join-Path $LOGDIR "api.log")
"" | Set-Content (Join-Path $LOGDIR "editor.log")
# Ensure Docker infra is up
Ensure-Infra
# Wait for Langfuse before starting API
Wait-ForLangfuse
# Start API + editor
Start-ApiServer
Start-EditorServer
Write-Host ""
Write-Host " Services running:" -ForegroundColor Green
Write-Host " API " -ForegroundColor Cyan -NoNewline; Write-Host "http://localhost:8000"
Write-Host " Editor " -ForegroundColor Cyan -NoNewline; Write-Host "http://localhost:5173"
Write-Host " Langfuse " -ForegroundColor Cyan -NoNewline; Write-Host "http://localhost:3000 (admin@stackflow.local / adminadmin)"
Write-Host ""
Write-Host " Use " -ForegroundColor DarkGray -NoNewline
Write-Host "stackflow logs -f" -ForegroundColor Cyan -NoNewline
Write-Host " to follow logs" -ForegroundColor DarkGray
Write-Host " Use " -ForegroundColor DarkGray -NoNewline
Write-Host "stackflow stop" -ForegroundColor Cyan -NoNewline
Write-Host " to stop services" -ForegroundColor DarkGray
Write-Host ""
}
"stop" {
Stop-LocalProcesses
if ($rest -contains "all") {
Write-Info "Stopping Docker infrastructure..."
$savedPref = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
& docker compose down 2>$null
$ErrorActionPreference = $savedPref
Write-Ok "Everything stopped"
} else {
Write-Ok "API + editor stopped"
}
}
"restart" {
$target = if ($rest.Count -ge 1) { $rest[0] } else { "api" }
switch ($target) {
{ $_ -eq "ui" -or $_ -eq "editor" } {
Stop-ByPort 5173
Start-Sleep -Seconds 2
try { "" | Set-Content (Join-Path $LOGDIR "editor.log") } catch {}
Start-EditorServer
Write-Ok "Editor restarted"
}
default {
Stop-ByPort 8000
Start-Sleep -Seconds 2
try { "" | Set-Content (Join-Path $LOGDIR "api.log") } catch {}
Start-ApiServer
Write-Ok "API restarted"
}
}
}
"docker" {
$savedPref = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
& docker compose ps
$ErrorActionPreference = $savedPref
}
"logs" {
$follow = $false
$target = ""
foreach ($arg in $rest) {
switch ($arg) {
{ $_ -eq "--follow" -or $_ -eq "-f" } { $follow = $true }
"api" { $target = "api" }
{ $_ -eq "editor" -or $_ -eq "ui" } { $target = "editor" }
}
}
$logFile = switch ($target) {
"editor" { Join-Path $LOGDIR "editor.log" }
default { Join-Path $LOGDIR "api.log" }
}
if (-not (Test-Path $logFile)) {
Write-Warn "No log files found. Run 'stackflow start' first."
} elseif ($follow) {
Get-Content -Path $logFile -Tail 50 -Wait -Encoding UTF8
} else {
Get-Content -Path $logFile -Tail 50 -Encoding UTF8
}
}
"pm" {
$env:PYTHONPATH = $SCRIPT_DIR
& $VENV_PYTHON -m src.pm @rest
}
"install" {
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $SCRIPT_DIR "install.ps1")
}
{ $_ -eq "help" -or $_ -eq "--help" -or $_ -eq "-h" } {
Show-Usage
}
default {
Write-Host " Unknown command: $cmd" -ForegroundColor Yellow
Show-Usage
exit 1
}
}