Version: 0.9.79.dev.260506

后端:
1. 本地后端启动体系收口到 `backend/scripts`,移除 `cmd/all` 聚合入口,并将仓库根兼容启动语义收敛为 `StartAPI` 别名;新增 dev-up / dev-down / services-up / services-down / dev-status / dev-logs / service-restart 脚本,统一托管多服务进程、日志、PID 与基础设施启动。
2. 课表服务超时口径统一放宽到 5 分钟,覆盖 gateway / client / rpc server / config example,避免课表导入与图片识别在长耗时场景下被内层提前截断。
3. `today` 课表查询修正为读取真实当前日期,不再使用硬编码测试日期;同时剔除旧缓存与返回结果里的 `empty` 占位事件,后端只返回真实日程,空档改由前端时间轴自行补齐。

前端:
4. 首页路由切回改为复用 `DashboardView` 实例,补 `keep-alive`、`onActivated` 与双帧缩放重算,修复从侧栏返回首页时首帧布局放大与重复加载闪动问题。
5. 首页加载态与今日时间线口径收口:移除额外 800ms `pageLoading` 人为延迟,task / schedule 改为分开驱动;时间线忽略 `empty` 事件,并统一空档文案为“无课”。
6. 收敛助手页与首页若干进场/弹性动画,降低结果卡片、微调弹窗、思考区与面板切换时的抖动感。

仓库:
7. README 补充后端本地快速启动说明,`.gitignore` 忽略 `backend/.dev` 脚本运行态产物。
This commit is contained in:
Losita
2026-05-06 12:59:29 +08:00
parent d4afc6ef74
commit 7d324b77aa
27 changed files with 1329 additions and 135 deletions

View File

@@ -0,0 +1,900 @@
$BackendRoot = Split-Path -Parent $PSScriptRoot
$RepoRoot = Split-Path -Parent $BackendRoot
$ComposeFile = Join-Path $RepoRoot "docker-compose.yml"
$StateRoot = Join-Path $BackendRoot ".dev"
$PidRoot = Join-Path $StateRoot "pids"
$LogRoot = Join-Path $StateRoot "logs"
$BinRoot = Join-Path $StateRoot "bin"
$DockerConfigRoot = Join-Path $StateRoot "docker-config"
$GoCacheRoot = Join-Path $StateRoot "gocache"
function Initialize-DevState {
foreach ($path in @($StateRoot, $PidRoot, $LogRoot, $BinRoot, $DockerConfigRoot, $GoCacheRoot)) {
if (-not (Test-Path -LiteralPath $path)) {
New-Item -ItemType Directory -Path $path -Force | Out-Null
}
}
# 1. 当前桌面环境里可能同时注入 Path / PATH 两个大小写不同的环境变量。
# 2. Windows PowerShell 的 Env: 提供器与 Start-Process 在这种情况下会报“重复键”错误。
# 3. 这里统一只保留一个进程级 Path避免启动脚本因为环境脏数据直接失败。
$effectivePath = ""
try {
$pathValue = [System.Environment]::GetEnvironmentVariable("Path", "Process")
if ($null -ne $pathValue) {
$effectivePath = [string]$pathValue
}
}
catch {
$effectivePath = ""
}
[System.Environment]::SetEnvironmentVariable("PATH", $null, "Process")
[System.Environment]::SetEnvironmentVariable("Path", $null, "Process")
[System.Environment]::SetEnvironmentVariable("Path", $effectivePath, "Process")
$env:DOCKER_CONFIG = $DockerConfigRoot
$env:GOCACHE = $GoCacheRoot
}
function Assert-ToolExists {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Command not found: $Name"
}
}
function Invoke-ExternalCommand {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string[]]$Arguments,
[Parameter(Mandatory = $true)]
[string]$WorkingDirectory
)
Push-Location $WorkingDirectory
try {
& $FilePath @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Command failed: $FilePath $($Arguments -join ' ')"
}
}
finally {
Pop-Location
}
}
function Get-InfrastructureDefinitions {
return @(
[pscustomobject]@{
Name = "mysql"
Container = "smartflow-mysql"
TimeoutSec = 120
},
[pscustomobject]@{
Name = "redis"
Container = "smartflow-redis"
TimeoutSec = 90
},
[pscustomobject]@{
Name = "kafka"
Container = "smartflow-kafka"
TimeoutSec = 180
},
[pscustomobject]@{
Name = "etcd"
Container = "smartflow-etcd"
TimeoutSec = 120
},
[pscustomobject]@{
Name = "minio"
Container = "smartflow-minio"
TimeoutSec = 120
},
[pscustomobject]@{
Name = "milvus-standalone"
Container = "smartflow-milvus"
TimeoutSec = 240
}
)
}
function Get-InfrastructureComposeServices {
return @(
"mysql",
"redis",
"kafka",
"etcd",
"minio",
"milvus-standalone",
"kafka-init"
)
}
function Get-BackendServiceDefinitions {
return @(
[pscustomobject]@{
Name = "userauth"
Package = "./cmd/userauth"
BinaryPath = (Join-Path $BinRoot "userauth.exe")
Port = 9081
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 60
Dependencies = @()
},
[pscustomobject]@{
Name = "task"
Package = "./cmd/task"
BinaryPath = (Join-Path $BinRoot "task.exe")
Port = 9085
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @()
},
[pscustomobject]@{
Name = "schedule"
Package = "./cmd/schedule"
BinaryPath = (Join-Path $BinRoot "schedule.exe")
Port = 9084
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @()
},
[pscustomobject]@{
Name = "task-class"
Package = "./cmd/task-class"
BinaryPath = (Join-Path $BinRoot "task-class.exe")
Port = 9086
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @()
},
[pscustomobject]@{
Name = "course"
Package = "./cmd/course"
BinaryPath = (Join-Path $BinRoot "course.exe")
Port = 9087
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 120
Dependencies = @()
},
[pscustomobject]@{
Name = "tokenstore"
Package = "./cmd/tokenstore"
BinaryPath = (Join-Path $BinRoot "tokenstore.exe")
Port = 9095
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @()
},
[pscustomobject]@{
Name = "notification"
Package = "./cmd/notification"
BinaryPath = (Join-Path $BinRoot "notification.exe")
Port = 9082
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @()
},
[pscustomobject]@{
Name = "memory"
Package = "./cmd/memory"
BinaryPath = (Join-Path $BinRoot "memory.exe")
Port = 9088
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 150
Dependencies = @()
},
[pscustomobject]@{
Name = "taskclassforum"
Package = "./cmd/taskclassforum"
BinaryPath = (Join-Path $BinRoot "taskclassforum.exe")
Port = 9090
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 90
Dependencies = @("task-class")
},
[pscustomobject]@{
Name = "active-scheduler"
Package = "./cmd/active-scheduler"
BinaryPath = (Join-Path $BinRoot "active-scheduler.exe")
Port = 9083
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 120
Dependencies = @("task", "schedule")
},
[pscustomobject]@{
Name = "agent"
Package = "./cmd/agent"
BinaryPath = (Join-Path $BinRoot "agent.exe")
Port = 9089
ProbeType = "tcp"
ProbeTarget = $null
StartTimeoutSec = 180
Dependencies = @("task", "schedule", "task-class", "memory")
},
[pscustomobject]@{
Name = "api"
Package = "./cmd/api"
BinaryPath = (Join-Path $BinRoot "api.exe")
Port = 8080
ProbeType = "http"
ProbeTarget = "http://127.0.0.1:8080/api/v1/health"
StartTimeoutSec = 90
Dependencies = @(
"userauth",
"task",
"schedule",
"task-class",
"course",
"tokenstore",
"notification",
"memory",
"taskclassforum",
"active-scheduler",
"agent"
)
}
)
}
function Get-BackendServiceDefinition {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
foreach ($service in (Get-BackendServiceDefinitions)) {
if ($service.Name -eq $Name) {
return $service
}
}
throw "Service definition not found: $Name"
}
function Get-ServicePidFilePath {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
return (Join-Path $PidRoot "$($Service.Name).json")
}
function Get-ServiceStdoutLogPath {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
return (Join-Path $LogRoot "$($Service.Name).log")
}
function Get-ServiceStderrLogPath {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
return (Join-Path $LogRoot "$($Service.Name).err.log")
}
function New-ServiceLogPaths {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
return [pscustomobject]@{
Stdout = (Join-Path $LogRoot "$($Service.Name)-$stamp.log")
Stderr = (Join-Path $LogRoot "$($Service.Name)-$stamp.err.log")
}
}
function Read-ServicePidInfo {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$path = Get-ServicePidFilePath -Service $Service
if (-not (Test-Path -LiteralPath $path)) {
return $null
}
return (Get-Content -LiteralPath $path -Encoding UTF8 | ConvertFrom-Json)
}
function Get-LatestServiceLogPath {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service,
[Parameter(Mandatory = $true)]
[ValidateSet("stdout", "stderr")]
[string]$Stream
)
$pidInfo = Read-ServicePidInfo -Service $Service
if ($null -ne $pidInfo) {
$managedPath = if ($Stream -eq "stdout") { [string]$pidInfo.StdoutLogPath } else { [string]$pidInfo.StderrLogPath }
if (-not [string]::IsNullOrWhiteSpace($managedPath) -and (Test-Path -LiteralPath $managedPath)) {
return $managedPath
}
}
if ($Stream -eq "stdout") {
$candidates = @(Get-ChildItem -LiteralPath $LogRoot -Filter "$($Service.Name)-*.log" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notlike "*.err.log" } |
Sort-Object LastWriteTime -Descending)
}
else {
$candidates = @(Get-ChildItem -LiteralPath $LogRoot -Filter "$($Service.Name)-*.err.log" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending)
}
if ($null -eq $candidates -or $candidates.Count -eq 0) {
return $null
}
return $candidates[0].FullName
}
function Write-ServicePidInfo {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service,
[Parameter(Mandatory = $true)]
[System.Diagnostics.Process]$Process,
[Parameter(Mandatory = $true)]
[string]$StdoutLogPath,
[Parameter(Mandatory = $true)]
[string]$StderrLogPath
)
$payload = [pscustomobject]@{
Name = $Service.Name
Pid = [int]$Process.Id
Port = [int]$Service.Port
StartedAt = (Get-Date).ToString("o")
BinaryPath = $Service.BinaryPath
StdoutLogPath = $StdoutLogPath
StderrLogPath = $StderrLogPath
}
$path = Get-ServicePidFilePath -Service $Service
$payload | ConvertTo-Json | Set-Content -LiteralPath $path -Encoding UTF8
}
function Remove-ServicePidInfo {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$path = Get-ServicePidFilePath -Service $Service
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Force
}
}
function Test-ProcessAlive {
param(
[Parameter(Mandatory = $true)]
[int]$ProcessId
)
return ($null -ne (Get-Process -Id $ProcessId -ErrorAction SilentlyContinue))
}
function Get-ListeningProcessId {
param(
[Parameter(Mandatory = $true)]
[int]$Port
)
try {
$connection = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop |
Sort-Object -Property OwningProcess |
Select-Object -First 1
if ($null -ne $connection) {
return [int]$connection.OwningProcess
}
}
catch {
}
$pattern = "^\s*TCP\s+\S+:$Port\s+\S+\s+LISTENING\s+(\d+)\s*$"
foreach ($line in (netstat -ano -p tcp)) {
if ($line -match $pattern) {
return [int]$matches[1]
}
}
return $null
}
function Test-TcpPort {
param(
[Parameter(Mandatory = $true)]
[int]$Port,
[string]$HostName = "127.0.0.1",
[int]$TimeoutMs = 500
)
$client = New-Object System.Net.Sockets.TcpClient
try {
$asyncResult = $client.BeginConnect($HostName, $Port, $null, $null)
if (-not $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) {
return $false
}
$client.EndConnect($asyncResult)
return $true
}
catch {
return $false
}
finally {
$client.Close()
}
}
function Test-HttpEndpoint {
param(
[Parameter(Mandatory = $true)]
[string]$Url,
[int]$TimeoutSec = 2
)
try {
$response = Invoke-WebRequest -Uri $Url -Method Get -TimeoutSec $TimeoutSec -UseBasicParsing
return ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300)
}
catch {
return $false
}
}
function Test-ServiceHealthy {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
switch ($Service.ProbeType) {
"http" {
return (Test-HttpEndpoint -Url $Service.ProbeTarget)
}
default {
return (Test-TcpPort -Port $Service.Port)
}
}
}
function Wait-ServiceReady {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service,
[int]$TimeoutSec,
[int]$ProcessId
)
$effectiveTimeout = $TimeoutSec
if ($effectiveTimeout -le 0) {
$effectiveTimeout = [int]$Service.StartTimeoutSec
}
$deadline = (Get-Date).AddSeconds($effectiveTimeout)
while ((Get-Date) -lt $deadline) {
if ($ProcessId -gt 0 -and -not (Test-ProcessAlive -ProcessId $ProcessId)) {
return $false
}
if (Test-ServiceHealthy -Service $Service) {
return $true
}
Start-Sleep -Seconds 1
}
return $false
}
function Get-ServiceStatus {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$pidInfo = Read-ServicePidInfo -Service $Service
$managedPid = $null
$hasStalePid = $false
$stdoutLogPath = Get-ServiceStdoutLogPath -Service $Service
$stderrLogPath = Get-ServiceStderrLogPath -Service $Service
if ($null -ne $pidInfo) {
$managedPid = [int]$pidInfo.Pid
if (-not [string]::IsNullOrWhiteSpace($pidInfo.StdoutLogPath)) {
$stdoutLogPath = [string]$pidInfo.StdoutLogPath
}
if (-not [string]::IsNullOrWhiteSpace($pidInfo.StderrLogPath)) {
$stderrLogPath = [string]$pidInfo.StderrLogPath
}
if (-not (Test-ProcessAlive -ProcessId $managedPid)) {
$hasStalePid = $true
$managedPid = $null
}
}
$healthy = Test-ServiceHealthy -Service $Service
$listenerPid = Get-ListeningProcessId -Port $Service.Port
if ($null -ne $managedPid) {
if ($healthy) {
return [pscustomobject]@{
State = "managed"
Summary = "managed-running"
Pid = $managedPid
Port = $Service.Port
HasStalePid = $false
StdoutLog = $stdoutLogPath
StderrLog = $stderrLogPath
}
}
return [pscustomobject]@{
State = "managed_unhealthy"
Summary = "managed-not-ready"
Pid = $managedPid
Port = $Service.Port
HasStalePid = $false
StdoutLog = $stdoutLogPath
StderrLog = $stderrLogPath
}
}
if ($healthy) {
return [pscustomobject]@{
State = "external"
Summary = "external-running"
Pid = $listenerPid
Port = $Service.Port
HasStalePid = $hasStalePid
StdoutLog = $null
StderrLog = $null
}
}
if ($null -ne $listenerPid) {
return [pscustomobject]@{
State = "conflict"
Summary = "port-conflict"
Pid = $listenerPid
Port = $Service.Port
HasStalePid = $hasStalePid
StdoutLog = $null
StderrLog = $null
}
}
if ($hasStalePid) {
return [pscustomobject]@{
State = "stale"
Summary = "stale-pid-file"
Pid = $null
Port = $Service.Port
HasStalePid = $true
StdoutLog = $stdoutLogPath
StderrLog = $stderrLogPath
}
}
return [pscustomobject]@{
State = "stopped"
Summary = "stopped"
Pid = $null
Port = $Service.Port
HasStalePid = $false
StdoutLog = $stdoutLogPath
StderrLog = $stderrLogPath
}
}
function Build-ServiceBinary {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
Initialize-DevState
Invoke-ExternalCommand -FilePath "go" -Arguments @(
"build",
"-o",
$Service.BinaryPath,
$Service.Package
) -WorkingDirectory $BackendRoot
}
function Start-ServiceProcess {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$logPaths = New-ServiceLogPaths -Service $Service
$stdoutLog = $logPaths.Stdout
$stderrLog = $logPaths.Stderr
$process = Start-Process -FilePath $Service.BinaryPath `
-WorkingDirectory $BackendRoot `
-WindowStyle Hidden `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog `
-PassThru
Write-ServicePidInfo -Service $Service -Process $process -StdoutLogPath $stdoutLog -StderrLogPath $stderrLog
if (-not (Wait-ServiceReady -Service $Service -TimeoutSec $Service.StartTimeoutSec -ProcessId $process.Id)) {
if (Test-ProcessAlive -ProcessId $process.Id) {
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
}
Remove-ServicePidInfo -Service $Service
throw "Service start failed: $($Service.Name). Check logs: $stdoutLog and $stderrLog"
}
return $process
}
function Stop-ServiceProcess {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$pidInfo = Read-ServicePidInfo -Service $Service
if ($null -eq $pidInfo) {
return [pscustomobject]@{
Name = $Service.Name
Action = "skip"
Message = "no managed process"
}
}
$managedProcessId = [int]$pidInfo.Pid
if (Test-ProcessAlive -ProcessId $managedProcessId) {
try {
Stop-Process -Id $managedProcessId -ErrorAction Stop
}
catch {
Stop-Process -Id $managedProcessId -Force -ErrorAction SilentlyContinue
}
$deadline = (Get-Date).AddSeconds(15)
while ((Get-Date) -lt $deadline -and (Test-ProcessAlive -ProcessId $managedProcessId)) {
Start-Sleep -Milliseconds 500
}
if (Test-ProcessAlive -ProcessId $managedProcessId) {
Stop-Process -Id $managedProcessId -Force -ErrorAction SilentlyContinue
}
}
Remove-ServicePidInfo -Service $Service
return [pscustomobject]@{
Name = $Service.Name
Action = "stopped"
Message = "managed process stopped"
}
}
function Ensure-ServiceStarted {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$status = Get-ServiceStatus -Service $Service
if ($status.HasStalePid) {
Remove-ServicePidInfo -Service $Service
$status = Get-ServiceStatus -Service $Service
}
switch ($status.State) {
"managed" {
return [pscustomobject]@{
Name = $Service.Name
Port = $Service.Port
Action = "skip"
Detail = "managed and healthy"
}
}
"external" {
return [pscustomobject]@{
Name = $Service.Name
Port = $Service.Port
Action = "skip"
Detail = "external process already healthy"
}
}
"managed_unhealthy" {
if (Wait-ServiceReady -Service $Service -TimeoutSec 15 -ProcessId $status.Pid) {
return [pscustomobject]@{
Name = $Service.Name
Port = $Service.Port
Action = "skip"
Detail = "existing managed process became healthy"
}
}
throw "Service $($Service.Name) already has a managed process, but it is still not healthy after waiting. Check log: $($status.StdoutLog)"
}
"conflict" {
throw "Service $($Service.Name) port $($Service.Port) is occupied by process $($status.Pid), but the health check failed. Resolve the conflict first."
}
}
Assert-ServiceDependenciesReady -Service $Service
Build-ServiceBinary -Service $Service
$process = Start-ServiceProcess -Service $Service
return [pscustomobject]@{
Name = $Service.Name
Port = $Service.Port
Action = "start"
Detail = "started, PID=$($process.Id)"
}
}
function Restart-BackendService {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
$status = Get-ServiceStatus -Service $Service
if ($status.HasStalePid) {
Remove-ServicePidInfo -Service $Service
$status = Get-ServiceStatus -Service $Service
}
switch ($status.State) {
"external" {
throw "Service $($Service.Name) is managed by an external process. Refuse to restart it automatically."
}
"conflict" {
throw "Service $($Service.Name) port $($Service.Port) is occupied by process $($status.Pid), but the health check failed. Resolve the conflict first."
}
"managed" {
Stop-ServiceProcess -Service $Service | Out-Null
}
"managed_unhealthy" {
Stop-ServiceProcess -Service $Service | Out-Null
}
}
Assert-ServiceDependenciesReady -Service $Service
Build-ServiceBinary -Service $Service
$process = Start-ServiceProcess -Service $Service
return [pscustomobject]@{
Name = $Service.Name
Port = $Service.Port
Action = "restart"
Detail = "restarted, PID=$($process.Id)"
}
}
function Get-ContainerStatus {
param(
[Parameter(Mandatory = $true)]
[string]$ContainerName
)
$status = cmd.exe /d /c "docker inspect --format ""{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}"" $ContainerName 2>nul"
if ($LASTEXITCODE -ne 0) {
return "unavailable"
}
return (($status | Select-Object -First 1).Trim())
}
function Wait-ContainerStatus {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$ContainerDefinition
)
$deadline = (Get-Date).AddSeconds([int]$ContainerDefinition.TimeoutSec)
while ((Get-Date) -lt $deadline) {
$status = Get-ContainerStatus -ContainerName $ContainerDefinition.Container
if ($status -eq "healthy") {
return
}
Start-Sleep -Seconds 2
}
throw "Container did not become healthy in time: $($ContainerDefinition.Name)"
}
function Start-BackendInfrastructure {
Assert-ToolExists -Name "docker"
if (-not (Test-Path -LiteralPath $ComposeFile)) {
throw "docker-compose.yml not found: $ComposeFile"
}
$composeServices = Get-InfrastructureComposeServices
Invoke-ExternalCommand -FilePath "docker" -Arguments (@(
"compose",
"-f",
$ComposeFile,
"up",
"-d"
) + $composeServices) -WorkingDirectory $RepoRoot
foreach ($definition in (Get-InfrastructureDefinitions)) {
Wait-ContainerStatus -ContainerDefinition $definition
}
}
function Stop-BackendInfrastructure {
Assert-ToolExists -Name "docker"
$composeServices = Get-InfrastructureComposeServices
Invoke-ExternalCommand -FilePath "docker" -Arguments (@(
"compose",
"-f",
$ComposeFile,
"stop"
) + $composeServices) -WorkingDirectory $RepoRoot
}
function Get-InfrastructureStatus {
$rows = @()
foreach ($definition in (Get-InfrastructureDefinitions)) {
$rows += [pscustomobject]@{
Name = $definition.Name
Container = $definition.Container
Status = (Get-ContainerStatus -ContainerName $definition.Container)
}
}
return $rows
}
function Assert-ServiceDependenciesReady {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Service
)
foreach ($dependencyName in $Service.Dependencies) {
$dependency = Get-BackendServiceDefinition -Name $dependencyName
$status = Get-ServiceStatus -Service $dependency
if ($status.State -notin @("managed", "external")) {
throw "Dependency not ready for service $($Service.Name): $dependencyName (current state: $($status.Summary))"
}
}
}