feat: implement full-stack TaskPilot scheduler

This commit is contained in:
2026-10-18 22:05:45 +08:00
parent d88c21753d
commit 8ce02af13a
72 changed files with 17812 additions and 0 deletions
+42
View File
@@ -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
}
+34
View File
@@ -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
}
+91
View File
@@ -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
}
+23
View File
@@ -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
}
+40
View File
@@ -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
}