#!/usr/bin/env node import { spawnSync } from "node:child_process"; import { mkdirSync, mkdtempSync, readdirSync, statSync } from "node:fs"; import os from "node:os"; import path from "node:path"; const repoRoot = process.cwd(); const serverTestsDir = path.join(repoRoot, "server", "src", "__tests__"); const nonServerProjects = [ "@paperclipai/shared", "@paperclipai/db", "@paperclipai/adapter-utils", "@paperclipai/adapter-codex-local", "@paperclipai/adapter-opencode-local", "@paperclipai/ui", "paperclipai", ]; const routeTestPattern = /[^/]*(?:route|routes|authz)[^/]*\.test\.ts$/; const additionalSerializedServerTests = new Set([ "server/src/__tests__/approval-routes-idempotency.test.ts", "server/src/__tests__/assets.test.ts", "server/src/__tests__/authz-company-access.test.ts", "server/src/__tests__/companies-route-path-guard.test.ts", "server/src/__tests__/company-portability.test.ts", "server/src/__tests__/costs-service.test.ts", "server/src/__tests__/express5-auth-wildcard.test.ts", "server/src/__tests__/health-dev-server-token.test.ts", "server/src/__tests__/health.test.ts", "server/src/__tests__/heartbeat-dependency-scheduling.test.ts", "server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts", "server/src/__tests__/heartbeat-process-recovery.test.ts", "server/src/__tests__/invite-accept-existing-member.test.ts", "server/src/__tests__/invite-accept-gateway-defaults.test.ts", "server/src/__tests__/invite-accept-replay.test.ts", "server/src/__tests__/invite-expiry.test.ts", "server/src/__tests__/invite-join-manager.test.ts", "server/src/__tests__/invite-onboarding-text.test.ts", "server/src/__tests__/issues-checkout-wakeup.test.ts", "server/src/__tests__/issues-service.test.ts", "server/src/__tests__/opencode-local-adapter-environment.test.ts", "server/src/__tests__/project-routes-env.test.ts", "server/src/__tests__/redaction.test.ts", "server/src/__tests__/routines-e2e.test.ts", ]); let invocationIndex = 0; function walk(dir) { const entries = readdirSync(dir); const files = []; for (const entry of entries) { const absolute = path.join(dir, entry); const stats = statSync(absolute); if (stats.isDirectory()) { files.push(...walk(absolute)); } else if (stats.isFile()) { files.push(absolute); } } return files; } function toRepoPath(file) { return path.relative(repoRoot, file).split(path.sep).join("/"); } function isRouteOrAuthzTest(file) { if (routeTestPattern.test(file)) { return true; } return additionalSerializedServerTests.has(file); } function runVitest(args, label) { console.log(`\n[test:run] ${label}`); invocationIndex += 1; const testRoot = mkdtempSync(path.join(os.tmpdir(), `paperclip-vitest-${process.pid}-${invocationIndex}-`)); const env = { ...process.env, PAPERCLIP_HOME: path.join(testRoot, "home"), PAPERCLIP_INSTANCE_ID: `vitest-${process.pid}-${invocationIndex}`, TMPDIR: path.join(testRoot, "tmp"), }; mkdirSync(env.PAPERCLIP_HOME, { recursive: true }); mkdirSync(env.TMPDIR, { recursive: true }); const result = spawnSync("pnpm", ["exec", "vitest", "run", ...args], { cwd: repoRoot, env, stdio: "inherit", }); if (result.error) { console.error(`[test:run] Failed to start Vitest: ${result.error.message}`); process.exit(1); } if (result.status !== 0) { process.exit(result.status ?? 1); } } const routeTests = walk(serverTestsDir) .filter((file) => isRouteOrAuthzTest(toRepoPath(file))) .map((file) => ({ repoPath: toRepoPath(file) })) .sort((a, b) => a.repoPath.localeCompare(b.repoPath)); const excludeRouteArgs = routeTests.flatMap((file) => ["--exclude", file.repoPath]); for (const project of nonServerProjects) { runVitest(["--project", project], `non-server project ${project}`); } runVitest( ["--project", "@paperclipai/server", ...excludeRouteArgs], `server suites excluding ${routeTests.length} serialized suites`, ); for (const routeTest of routeTests) { runVitest( [ "--project", "@paperclipai/server", routeTest.repoPath, "--pool=forks", "--poolOptions.forks.isolate=true", ], routeTest.repoPath, ); }