feat: implement full-stack TaskPilot scheduler
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$releaseRoot = [System.IO.Path]::GetFullPath((Join-Path $projectRoot 'release\TaskPilot'))
|
||||
$expectedPrefix = [System.IO.Path]::GetFullPath((Join-Path $projectRoot 'release')).TrimEnd('\') + '\'
|
||||
if (-not $releaseRoot.StartsWith($expectedPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Refusing to build outside release directory: $releaseRoot"
|
||||
}
|
||||
|
||||
function Assert-ExitCode {
|
||||
param([string]$Step)
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Step failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
pnpm --dir frontend build
|
||||
Assert-ExitCode 'Frontend build'
|
||||
cargo build --release --bin task-pilot-backend --manifest-path backend\Cargo.toml
|
||||
Assert-ExitCode 'Rust release build'
|
||||
|
||||
if (Test-Path -LiteralPath $releaseRoot) {
|
||||
Remove-Item -LiteralPath $releaseRoot -Recurse -Force
|
||||
}
|
||||
[void](New-Item -ItemType Directory -Path $releaseRoot -Force)
|
||||
[void](New-Item -ItemType Directory -Path (Join-Path $releaseRoot 'web') -Force)
|
||||
[void](New-Item -ItemType Directory -Path (Join-Path $releaseRoot 'data') -Force)
|
||||
[void](New-Item -ItemType Directory -Path (Join-Path $releaseRoot 'logs') -Force)
|
||||
|
||||
Copy-Item -LiteralPath 'backend\target\release\task-pilot-backend.exe' -Destination $releaseRoot
|
||||
Copy-Item -LiteralPath 'scripts\start.ps1' -Destination $releaseRoot
|
||||
Copy-Item -Path 'frontend\dist\*' -Destination (Join-Path $releaseRoot 'web') -Recurse
|
||||
Copy-Item -LiteralPath 'docs\RELEASE.md' -Destination (Join-Path $releaseRoot 'README.md')
|
||||
|
||||
Write-Host "Release package: $releaseRoot"
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
function Assert-ExitCode {
|
||||
param([string]$Step)
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Step failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
pnpm --dir frontend lint
|
||||
Assert-ExitCode 'Frontend lint'
|
||||
pnpm --dir frontend typecheck
|
||||
Assert-ExitCode 'Frontend typecheck'
|
||||
pnpm --dir frontend test
|
||||
Assert-ExitCode 'Frontend tests'
|
||||
pnpm --dir frontend build
|
||||
Assert-ExitCode 'Frontend build'
|
||||
cargo fmt --manifest-path backend\Cargo.toml -- --check
|
||||
Assert-ExitCode 'Rust formatting'
|
||||
cargo clippy --manifest-path backend\Cargo.toml --all-targets -- -D warnings
|
||||
Assert-ExitCode 'Rust clippy'
|
||||
cargo test --manifest-path backend\Cargo.toml
|
||||
Assert-ExitCode 'Rust tests'
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot 'e2e.ps1')
|
||||
Assert-ExitCode 'Playwright end-to-end tests'
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$backendJob = $null
|
||||
$viteJob = $null
|
||||
|
||||
function Assert-ExitCode {
|
||||
param([string]$Step)
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Step failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-HttpReady {
|
||||
param([string]$Url)
|
||||
|
||||
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
||||
try {
|
||||
[void](Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 2)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
}
|
||||
throw "Service did not become ready: $Url"
|
||||
}
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
foreach ($port in 3000, 5173) {
|
||||
if (Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue) {
|
||||
throw "Port $port is already in use"
|
||||
}
|
||||
}
|
||||
|
||||
cargo build --manifest-path backend\Cargo.toml
|
||||
Assert-ExitCode 'Rust build'
|
||||
|
||||
$e2eData = [System.IO.Path]::GetFullPath((Join-Path $projectRoot '.e2e-data'))
|
||||
$expectedE2eData = [System.IO.Path]::GetFullPath($projectRoot).TrimEnd('\') + '\.e2e-data'
|
||||
if ($e2eData -ne $expectedE2eData) {
|
||||
throw "Refusing to clean unexpected end-to-end data path: $e2eData"
|
||||
}
|
||||
if (Test-Path -LiteralPath $e2eData) {
|
||||
Remove-Item -LiteralPath $e2eData -Recurse -Force
|
||||
}
|
||||
[void](New-Item -ItemType Directory -Path $e2eData -Force)
|
||||
$env:TASK_PILOT_DATABASE_URL = 'sqlite://.e2e-data/task_pilot.db'
|
||||
$env:TASK_PILOT_LOG_DIR = '.e2e-data/logs'
|
||||
$env:TASKPILOT_E2E_EXTERNAL = '1'
|
||||
|
||||
$backendBinary = Join-Path $projectRoot 'backend\target\debug\task-pilot-backend.exe'
|
||||
$nodeBinary = (Get-Command node).Source
|
||||
$frontendRoot = Join-Path $projectRoot 'frontend'
|
||||
|
||||
$backendJob = Start-Job -ScriptBlock {
|
||||
param($root, $binary)
|
||||
Set-Location $root
|
||||
& $binary
|
||||
} -ArgumentList $projectRoot, $backendBinary
|
||||
Wait-HttpReady 'http://127.0.0.1:3000/api/v1/health'
|
||||
|
||||
$viteJob = Start-Job -ScriptBlock {
|
||||
param($root, $node)
|
||||
Set-Location $root
|
||||
& $node 'node_modules/vite/bin/vite.js' '--host' '127.0.0.1'
|
||||
} -ArgumentList $frontendRoot, $nodeBinary
|
||||
Wait-HttpReady 'http://127.0.0.1:5173'
|
||||
|
||||
pnpm --dir frontend exec playwright test
|
||||
Assert-ExitCode 'Playwright tests'
|
||||
}
|
||||
finally {
|
||||
foreach ($job in @($viteJob, $backendJob)) {
|
||||
if ($job) {
|
||||
Stop-Job -Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($port in 3000, 5173) {
|
||||
$connection = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
|
||||
if ($connection) {
|
||||
Stop-Process -Id $connection.OwningProcess -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$contractPath = Join-Path $projectRoot 'contracts\openapi.json'
|
||||
|
||||
function Assert-ExitCode {
|
||||
param([string]$Step)
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Step failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
cargo run --quiet --manifest-path backend\Cargo.toml --bin export-openapi -- $contractPath
|
||||
Assert-ExitCode 'OpenAPI export'
|
||||
pnpm --dir frontend exec openapi-typescript ..\contracts\openapi.json -o src\api\generated.ts
|
||||
Assert-ExitCode 'TypeScript API generation'
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
param(
|
||||
[switch]$OpenBrowser
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptRoot = [System.IO.Path]::GetFullPath($PSScriptRoot)
|
||||
$projectRoot = Split-Path -Parent $scriptRoot
|
||||
|
||||
$packagedExecutable = Join-Path $scriptRoot 'task-pilot-backend.exe'
|
||||
if (Test-Path -LiteralPath $packagedExecutable) {
|
||||
$executable = $packagedExecutable
|
||||
$workingDirectory = $scriptRoot
|
||||
$staticDirectory = Join-Path $scriptRoot 'web'
|
||||
}
|
||||
else {
|
||||
$executable = Join-Path $projectRoot 'backend\target\release\task-pilot-backend.exe'
|
||||
$workingDirectory = $projectRoot
|
||||
$staticDirectory = Join-Path $projectRoot 'frontend\dist'
|
||||
if (-not (Test-Path -LiteralPath $executable)) {
|
||||
throw 'Release executable not found. Run .\scripts\build-release.ps1 first.'
|
||||
}
|
||||
}
|
||||
|
||||
$env:TASK_PILOT_STATIC_DIR = $staticDirectory
|
||||
$address = 'http://127.0.0.1:3000'
|
||||
|
||||
Write-Host "TaskPilot: $address"
|
||||
Write-Host "Static assets: $staticDirectory"
|
||||
|
||||
if ($OpenBrowser) {
|
||||
Start-Process $address
|
||||
}
|
||||
|
||||
Push-Location $workingDirectory
|
||||
try {
|
||||
& $executable
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user