$ErrorActionPreference = "Stop" $requiredFiles = @( "backend/package.json", "backend/package-lock.json", "backend/tsconfig.json", "backend/.env.example", "backend/src/app.ts", "backend/src/auth/auth-repository.ts", "backend/src/auth/jwt.ts", "backend/src/auth/wechat-client.ts", "backend/src/config.ts", "backend/src/db/mysql.ts", "backend/src/db/migration-runner.ts", "backend/src/db/migrate-cli.ts", "backend/src/db/legacy-read-repository.ts", "backend/src/tasks/task-repository.ts", "backend/src/tasks/outbox-repository.ts", "backend/src/tasks/worker.ts", "backend/src/routes/health.ts", "backend/src/routes/platform-bootstrap.ts", "backend/src/routes/auth.ts", "backend/src/tenancy/platform-config-repository.ts", "backend/src/server.ts", "backend/tests/backend-contract.test.mjs", "backend/tests/migration-contract.test.mjs", "backend/tests/mysql-pool-contract.test.mjs", "backend/tests/migration-runner.test.mjs", "backend/tests/mysql-migration-roundtrip.test.mjs", "backend/tests/legacy-read-repository.test.mjs", "backend/tests/task-repository.test.mjs", "backend/tests/platform-config-repository.test.mjs", "backend/tests/auth.test.mjs", "scripts/dev/wsl/mysql-migration-roundtrip.sh", "database/migrations/2026061601_m01b_core_schema.up.sql", "database/migrations/2026061601_m01b_core_schema.down.sql", "database/migrations/2026061601_m01b_core_schema.verify.sql", "database/migrations/2026061802_m01c_async_tasks.up.sql", "database/migrations/2026061802_m01c_async_tasks.down.sql", "database/migrations/2026061802_m01c_async_tasks.verify.sql", "database/migrations/2026061803_m02a_tenant_apps.up.sql", "database/migrations/2026061803_m02a_tenant_apps.down.sql", "database/migrations/2026061803_m02a_tenant_apps.verify.sql", "database/migrations/2026061804_m02b_wechat_auth.up.sql", "database/migrations/2026061804_m02b_wechat_auth.down.sql", "database/migrations/2026061804_m02b_wechat_auth.verify.sql", "database/seeds/2026061601_m01b_minimal_seed.sql", "deploy/pm2/ecosystem.config.cjs" ) foreach ($path in $requiredFiles) { if (-not (Test-Path $path)) { throw "Required backend file missing: $path" } } $packageJson = Get-Content "backend/package.json" -Raw | ConvertFrom-Json if (-not $packageJson.scripts.'test:mysql:migration') { throw "backend/package.json is missing test:mysql:migration" } & npm --prefix backend test if ($LASTEXITCODE -ne 0) { throw "backend contract test failed" } & npm --prefix backend run build if ($LASTEXITCODE -ne 0) { throw "backend build failed" } & npm --prefix backend run db:migrate:plan if ($LASTEXITCODE -ne 0) { throw "backend migration dry-run failed" } $oldHost = $env:QIPAI_API_HOST $oldPort = $env:QIPAI_API_PORT $oldVersion = $env:QIPAI_API_VERSION $env:QIPAI_API_HOST = "127.0.0.1" $env:QIPAI_API_PORT = "3101" $env:QIPAI_API_VERSION = "0.1.0-test" $server = $null try { $server = Start-Process -FilePath node -ArgumentList "dist/server.js" -WorkingDirectory "backend" -PassThru -WindowStyle Hidden $appHealth = $null $adminHealth = $null for ($attempt = 0; $attempt -lt 20; $attempt++) { try { $appHealth = Invoke-RestMethod -Uri "http://127.0.0.1:3101/app-api/health" -Headers @{ "x-trace-id" = "codex-health-app" } $adminHealth = Invoke-RestMethod -Uri "http://127.0.0.1:3101/admin-api/health" -Headers @{ "x-trace-id" = "codex-health-admin" } break } catch { if ($server.HasExited) { throw "backend server exited before health checks completed" } Start-Sleep -Milliseconds 500 } } if (-not $appHealth -or -not $adminHealth) { throw "backend health checks did not respond" } if (-not $appHealth.ok -or $appHealth.service -ne "qipai-api" -or $appHealth.traceId -ne "codex-health-app") { throw "app health response mismatch" } if (-not $adminHealth.ok -or $adminHealth.service -ne "qipai-api" -or $adminHealth.traceId -ne "codex-health-admin") { throw "admin health response mismatch" } } finally { if ($server -and -not $server.HasExited) { Stop-Process -Id $server.Id -Force } $env:QIPAI_API_HOST = $oldHost $env:QIPAI_API_PORT = $oldPort $env:QIPAI_API_VERSION = $oldVersion } Write-Host "PASS: backend M01-A build and HTTP health checks passed."