314 lines
9.3 KiB
PowerShell
314 lines
9.3 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Alias("h")]
|
|
[switch]$Help,
|
|
[string]$ApiHost = "127.0.0.1",
|
|
[int]$ApiPort = 8082,
|
|
[string]$WebHost = "0.0.0.0",
|
|
[int]$WebPort = 3000,
|
|
[string]$SpacetimeHost = "127.0.0.1",
|
|
[int]$SpacetimePort = 3101,
|
|
[string]$SpacetimeRootDir = "",
|
|
[string]$Database = "genarrative-dev",
|
|
[string]$Log = "info,tower_http=info",
|
|
[int]$SpacetimeStartupTimeoutSeconds = 60,
|
|
[switch]$SkipSpacetime,
|
|
[switch]$SkipPublish,
|
|
[switch]$ClearDatabase
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Write-Usage {
|
|
@(
|
|
'Usage:',
|
|
' npm run dev:rust',
|
|
' .\scripts\dev-rust-stack.ps1 -ApiPort 8090 -SpacetimePort 3110',
|
|
' .\scripts\dev-rust-stack.ps1 -SkipSpacetime -SkipPublish',
|
|
' .\scripts\dev-rust-stack.ps1 -ClearDatabase',
|
|
'',
|
|
'Notes:',
|
|
' 1. Start SpacetimeDB standalone, Rust api-server, and Vite web together.',
|
|
' 2. Publish server-rs/crates/spacetime-module by default, without clearing data.',
|
|
' 3. Only -ClearDatabase appends spacetime publish --clear-database.',
|
|
' 4. Web listens on 0.0.0.0:3000 by default; API listens on 127.0.0.1:8082.'
|
|
) -join [Environment]::NewLine
|
|
}
|
|
|
|
function Quote-ProcessArgument {
|
|
param([string]$Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return '""'
|
|
}
|
|
|
|
if ($Value -notmatch '[\s"]') {
|
|
return $Value
|
|
}
|
|
|
|
return '"' + $Value.Replace('"', '\"') + '"'
|
|
}
|
|
|
|
function Join-ProcessArguments {
|
|
param([string[]]$Arguments)
|
|
|
|
return (($Arguments | ForEach-Object { Quote-ProcessArgument $_ }) -join " ")
|
|
}
|
|
|
|
function Resolve-ClientHost {
|
|
param([string]$HostName)
|
|
|
|
if ($HostName -eq "0.0.0.0" -or $HostName -eq "::") {
|
|
return "127.0.0.1"
|
|
}
|
|
|
|
return $HostName
|
|
}
|
|
|
|
function Start-StackProcess {
|
|
param(
|
|
[string]$Name,
|
|
[string]$FilePath,
|
|
[string[]]$Arguments,
|
|
[string]$WorkingDirectory,
|
|
[hashtable]$Environment
|
|
)
|
|
|
|
$argumentLine = Join-ProcessArguments -Arguments $Arguments
|
|
Write-Host "[dev:rust] start ${Name}: $FilePath $argumentLine"
|
|
|
|
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
|
$startInfo.FileName = $FilePath
|
|
$startInfo.Arguments = $argumentLine
|
|
$startInfo.WorkingDirectory = $WorkingDirectory
|
|
$startInfo.UseShellExecute = $false
|
|
$startInfo.RedirectStandardOutput = $false
|
|
$startInfo.RedirectStandardError = $false
|
|
$startInfo.RedirectStandardInput = $false
|
|
|
|
foreach ($entry in $Environment.GetEnumerator()) {
|
|
$startInfo.EnvironmentVariables[$entry.Key] = [string]$entry.Value
|
|
}
|
|
|
|
$process = New-Object System.Diagnostics.Process
|
|
$process.StartInfo = $startInfo
|
|
|
|
if (-not $process.Start()) {
|
|
throw "Failed to start process: $Name"
|
|
}
|
|
|
|
return [PSCustomObject]@{
|
|
Name = $Name
|
|
Process = $process
|
|
}
|
|
}
|
|
|
|
function Stop-StackProcesses {
|
|
param([System.Collections.Generic.List[object]]$Processes)
|
|
|
|
for ($index = $Processes.Count - 1; $index -ge 0; $index--) {
|
|
$item = $Processes[$index]
|
|
$process = $item.Process
|
|
|
|
if ($null -ne $process -and -not $process.HasExited) {
|
|
Write-Host "[dev:rust] stop $($item.Name) (pid=$($process.Id))"
|
|
$taskkillCommand = Get-Command taskkill.exe -ErrorAction SilentlyContinue
|
|
if ($null -ne $taskkillCommand) {
|
|
& $taskkillCommand.Source /PID $process.Id /T /F *> $null
|
|
}
|
|
else {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Wait-ForSpacetimeServer {
|
|
param(
|
|
[string]$CommandPath,
|
|
[string]$Server,
|
|
[int]$TimeoutSeconds,
|
|
$ProcessItem
|
|
)
|
|
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
if ($null -ne $ProcessItem -and $ProcessItem.Process.HasExited) {
|
|
throw "SpacetimeDB exited before readiness, exit code: $($ProcessItem.Process.ExitCode)"
|
|
}
|
|
|
|
& $CommandPath server ping $Server *> $null
|
|
if ($LASTEXITCODE -eq 0) {
|
|
return
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
|
|
throw "Timed out waiting for SpacetimeDB readiness: $Server"
|
|
}
|
|
|
|
if ($Help) {
|
|
Write-Usage
|
|
exit 0
|
|
}
|
|
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$repoRoot = Split-Path -Parent $scriptDir
|
|
$serverRsDir = Join-Path $repoRoot "server-rs"
|
|
$manifestPath = Join-Path $serverRsDir "Cargo.toml"
|
|
$modulePath = Join-Path $serverRsDir "crates\spacetime-module"
|
|
$viteCliPath = Join-Path $repoRoot "scripts\vite-cli.mjs"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($SpacetimeRootDir)) {
|
|
$SpacetimeRootDir = Join-Path $serverRsDir ".spacetimedb\local"
|
|
}
|
|
|
|
if (-not (Test-Path $manifestPath)) {
|
|
throw "Missing server-rs/Cargo.toml, cannot start Rust local stack."
|
|
}
|
|
|
|
if (-not (Test-Path (Join-Path $modulePath "Cargo.toml"))) {
|
|
throw "Missing server-rs/crates/spacetime-module/Cargo.toml, cannot publish SpacetimeDB module."
|
|
}
|
|
|
|
if (-not (Test-Path $viteCliPath)) {
|
|
throw "Missing scripts/vite-cli.mjs, cannot start web frontend."
|
|
}
|
|
|
|
$cargoCommand = Get-Command cargo -ErrorAction SilentlyContinue
|
|
$nodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
|
$spacetimeCommand = Get-Command spacetime -ErrorAction SilentlyContinue
|
|
|
|
if ($null -eq $cargoCommand) {
|
|
throw "Missing cargo. Install Rust toolchain first."
|
|
}
|
|
|
|
if ($null -eq $nodeCommand) {
|
|
throw "Missing node. Install Node.js or use the project bundled runtime first."
|
|
}
|
|
|
|
if (-not $SkipSpacetime -or -not $SkipPublish) {
|
|
if ($null -eq $spacetimeCommand) {
|
|
throw "Missing spacetime CLI. Install guide: https://spacetimedb.com/install"
|
|
}
|
|
}
|
|
|
|
$spacetimeServer = "http://$SpacetimeHost`:$SpacetimePort"
|
|
$apiTargetHost = Resolve-ClientHost -HostName $ApiHost
|
|
$rustServerTarget = "http://$apiTargetHost`:$ApiPort"
|
|
$stackProcesses = New-Object System.Collections.Generic.List[object]
|
|
$exitCode = 0
|
|
|
|
Write-Host "[dev:rust] repo: $repoRoot"
|
|
Write-Host "[dev:rust] web: http://127.0.0.1:$WebPort"
|
|
Write-Host "[dev:rust] rust api: $rustServerTarget"
|
|
Write-Host "[dev:rust] spacetime: $spacetimeServer"
|
|
Write-Host "[dev:rust] database: $Database"
|
|
|
|
try {
|
|
$spacetimeProcessItem = $null
|
|
|
|
if (-not $SkipSpacetime) {
|
|
New-Item -ItemType Directory -Force -Path $SpacetimeRootDir | Out-Null
|
|
$spacetimeProcessItem = Start-StackProcess `
|
|
-Name "spacetimedb" `
|
|
-FilePath $spacetimeCommand.Source `
|
|
-Arguments @(
|
|
"--root-dir", $SpacetimeRootDir,
|
|
"start",
|
|
"--edition", "standalone",
|
|
"--listen-addr", "$SpacetimeHost`:$SpacetimePort"
|
|
) `
|
|
-WorkingDirectory $serverRsDir `
|
|
-Environment @{}
|
|
$stackProcesses.Add($spacetimeProcessItem)
|
|
}
|
|
|
|
if (-not $SkipPublish) {
|
|
Write-Host "[dev:rust] wait for SpacetimeDB readiness"
|
|
Wait-ForSpacetimeServer `
|
|
-CommandPath $spacetimeCommand.Source `
|
|
-Server $spacetimeServer `
|
|
-TimeoutSeconds $SpacetimeStartupTimeoutSeconds `
|
|
-ProcessItem $spacetimeProcessItem
|
|
|
|
$publishArgs = @(
|
|
"publish",
|
|
$Database,
|
|
"--server", $spacetimeServer,
|
|
"--module-path", $modulePath
|
|
)
|
|
|
|
if ($ClearDatabase) {
|
|
$publishArgs += "--clear-database"
|
|
}
|
|
|
|
$publishArgs += "--yes"
|
|
|
|
Write-Host "[dev:rust] publish SpacetimeDB module: $Database"
|
|
& $spacetimeCommand.Source @publishArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "spacetime publish failed, exit code: $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
$apiEnvironment = @{
|
|
GENARRATIVE_API_HOST = $ApiHost
|
|
GENARRATIVE_API_PORT = "$ApiPort"
|
|
GENARRATIVE_API_LOG = $Log
|
|
GENARRATIVE_SPACETIME_SERVER_URL = $spacetimeServer
|
|
GENARRATIVE_SPACETIME_DATABASE = $Database
|
|
}
|
|
|
|
$apiProcessItem = Start-StackProcess `
|
|
-Name "api-server" `
|
|
-FilePath $cargoCommand.Source `
|
|
-Arguments @("run", "-p", "api-server", "--manifest-path", $manifestPath) `
|
|
-WorkingDirectory $repoRoot `
|
|
-Environment $apiEnvironment
|
|
$stackProcesses.Add($apiProcessItem)
|
|
|
|
$webEnvironment = @{
|
|
GENARRATIVE_BACKEND_STACK = "rust"
|
|
RUST_SERVER_TARGET = $rustServerTarget
|
|
GENARRATIVE_RUNTIME_SERVER_TARGET = $rustServerTarget
|
|
VITE_DEV_HOST = $WebHost
|
|
}
|
|
|
|
$webProcessItem = Start-StackProcess `
|
|
-Name "vite" `
|
|
-FilePath $nodeCommand.Source `
|
|
-Arguments @($viteCliPath, "--port=$WebPort", "--host=$WebHost") `
|
|
-WorkingDirectory $repoRoot `
|
|
-Environment $webEnvironment
|
|
$stackProcesses.Add($webProcessItem)
|
|
|
|
Write-Host "[dev:rust] local Rust stack is running. Press Ctrl+C to stop all child processes."
|
|
|
|
while ($true) {
|
|
foreach ($item in $stackProcesses) {
|
|
if ($item.Process.HasExited) {
|
|
$exitCode = $item.Process.ExitCode
|
|
Write-Host "[dev:rust] $($item.Name) exited, code: $exitCode"
|
|
throw "Child process exited, shutting down Rust local stack."
|
|
}
|
|
}
|
|
|
|
Start-Sleep -Seconds 1
|
|
}
|
|
}
|
|
catch {
|
|
if ($exitCode -eq 0) {
|
|
$exitCode = 1
|
|
}
|
|
|
|
Write-Host "[dev:rust] $($_.Exception.Message)"
|
|
}
|
|
finally {
|
|
Stop-StackProcesses -Processes $stackProcesses
|
|
}
|
|
|
|
exit $exitCode
|