@jterrazz/test 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1215,6 +1215,7 @@ var SpecificationBuilder = class {
1215
1215
  return this.runCliAction(workDir);
1216
1216
  }
1217
1217
  prepareWorkDir() {
1218
+ if (!this.projectName && this.fixtures.length === 0) return this.config.fixturesRoot ?? process.cwd();
1218
1219
  const tempDir = (0, node_fs.mkdtempSync)((0, node_path.resolve)((0, node_os.tmpdir)(), "spec-cli-"));
1219
1220
  if (this.projectName && this.config.fixturesRoot) {
1220
1221
  const projectDir = (0, node_path.resolve)(this.config.fixturesRoot, this.projectName);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["MockDatePackage","mockDeep","Client"],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts","../src/infrastructure/adapters/compose.adapter.ts","../src/infrastructure/adapters/testcontainers.adapter.ts","../src/infrastructure/compose-parser.ts","../src/infrastructure/reporter.ts","../src/infrastructure/services/postgres.ts","../src/infrastructure/services/redis.ts","../src/infrastructure/orchestrator.ts","../src/specification/adapters/exec.adapter.ts","../src/specification/adapters/fetch.adapter.ts","../src/specification/adapters/hono.adapter.ts","../src/specification/assertions/base.ts","../src/specification/assertions/file.ts","../src/specification/assertions/response.ts","../src/specification/assertions/string.ts","../src/specification/assertions/table.ts","../src/specification/assertions/value.ts","../src/specification/specification.ts","../src/specification/index.ts"],"sourcesContent":["import MockDatePackage from \"mockdate\";\n\nexport interface MockDatePort {\n reset: () => void;\n set: (date: Date | number | string) => void;\n}\n\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from \"vitest-mock-extended\";\n\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\nexport const mockOf: MockPort = mockDeep;\n","import { execSync } from \"node:child_process\";\nimport { dirname } from \"node:path\";\n\nimport type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using docker compose — runs full compose stack.\n * Used by e2e() to start all services including the app.\n */\nexport class ComposeAdapter implements ContainerPort {\n private composeFile: string;\n private serviceName: string;\n private started = false;\n\n constructor(composeFile: string, serviceName: string) {\n this.composeFile = composeFile;\n this.serviceName = serviceName;\n }\n\n private exec(command: string): string {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim();\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} up -d --wait ${this.serviceName}`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} rm -fsv ${this.serviceName}`);\n this.started = false;\n }\n\n getMappedPort(containerPort: number): number {\n const output = this.exec(\n `docker compose -f ${this.composeFile} port ${this.serviceName} ${containerPort}`,\n );\n // Output: 0.0.0.0:54321\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(0)}`;\n }\n\n async getLogs(): Promise<string> {\n try {\n return this.exec(`docker compose -f ${this.composeFile} logs ${this.serviceName} --tail=50`);\n } catch {\n return \"\";\n }\n }\n}\n\n/**\n * Start the full compose stack and stop it all on cleanup.\n */\nexport class ComposeStackAdapter {\n private composeFile: string;\n private started = false;\n\n constructor(composeFile: string) {\n this.composeFile = composeFile;\n }\n\n private run(command: string): string {\n try {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n timeout: 120_000,\n }).trim();\n } catch (error: any) {\n const stderr = error.stderr?.toString().trim() ?? error.message;\n throw new Error(`docker compose failed: ${stderr}`, { cause: error });\n }\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} up -d --wait`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} down -v`);\n this.started = false;\n }\n\n getMappedPort(serviceName: string, containerPort: number): number {\n const output = this.run(\n `docker compose -f ${this.composeFile} port ${serviceName} ${containerPort}`,\n );\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n}\n","import type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using testcontainers.\n * Wraps a GenericContainer for programmatic container lifecycle.\n */\nexport class TestcontainersAdapter implements ContainerPort {\n private image: string;\n private containerPort: number;\n private env: Record<string, string>;\n private reuse: boolean;\n private container: any = null;\n\n constructor(options: {\n image: string;\n port: number;\n env?: Record<string, string>;\n reuse?: boolean;\n }) {\n this.image = options.image;\n this.containerPort = options.port;\n this.env = options.env ?? {};\n this.reuse = options.reuse ?? false;\n }\n\n async start(): Promise<void> {\n const { GenericContainer, Wait } = await import(\"testcontainers\");\n\n let builder = new GenericContainer(this.image).withExposedPorts(this.containerPort);\n\n for (const [key, value] of Object.entries(this.env)) {\n builder = builder.withEnvironment({ [key]: value });\n }\n\n if (this.image.startsWith(\"postgres\")) {\n builder = builder.withWaitStrategy(\n Wait.forLogMessage(/database system is ready to accept connections/, 2),\n );\n }\n\n if (this.reuse) {\n builder = builder.withReuse();\n }\n\n this.container = await builder.start();\n }\n\n async stop(): Promise<void> {\n if (this.container && !this.reuse) {\n await this.container.stop();\n this.container = null;\n }\n }\n\n getMappedPort(containerPort: number): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(containerPort);\n }\n\n getHost(): string {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getHost();\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(this.containerPort)}`;\n }\n\n async getLogs(): Promise<string> {\n if (!this.container) {\n return \"\";\n }\n\n const stream = await this.container.logs();\n return new Promise((resolve) => {\n let output = \"\";\n stream.on(\"data\", (chunk: Buffer) => {\n output += chunk.toString();\n });\n stream.on(\"end\", () => {\n resolve(output);\n });\n // Timeout after 1s if stream doesn't end\n setTimeout(() => {\n resolve(output);\n }, 1000);\n });\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\n\n/**\n * A parsed service from docker-compose.test.yaml.\n */\nexport interface ComposeService {\n name: string;\n image?: string;\n build?: string;\n ports: { container: number; host?: number }[];\n environment: Record<string, string>;\n volumes: string[];\n dependsOn: string[];\n}\n\n/**\n * Result of parsing a compose file.\n */\nexport interface ComposeConfig {\n services: ComposeService[];\n appService: ComposeService | null;\n infraServices: ComposeService[];\n}\n\n/**\n * Detect the service type from the image name.\n */\nexport function detectServiceType(\n image: string | undefined,\n): \"app\" | \"postgres\" | \"redis\" | \"unknown\" {\n if (!image) {\n return \"app\";\n }\n\n const lower = image.toLowerCase();\n\n if (lower.startsWith(\"postgres\")) {\n return \"postgres\";\n }\n if (lower.startsWith(\"redis\")) {\n return \"redis\";\n }\n\n return \"unknown\";\n}\n\n/**\n * Find the compose file in the project.\n * Looks for docker/compose.test.yaml or docker-compose.test.yaml.\n */\nexport function findComposeFile(projectRoot: string): null | string {\n const candidates = [\n resolve(projectRoot, \"docker/compose.test.yaml\"),\n resolve(projectRoot, \"docker/compose.test.yml\"),\n resolve(projectRoot, \"docker-compose.test.yaml\"),\n resolve(projectRoot, \"docker-compose.test.yml\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * Parse a docker-compose file and extract service definitions.\n */\nexport function parseComposeFile(filePath: string): ComposeConfig {\n const content = readFileSync(filePath, \"utf8\");\n const doc = parseYaml(content);\n\n if (!doc?.services) {\n return { services: [], appService: null, infraServices: [] };\n }\n\n const services: ComposeService[] = Object.entries(doc.services).map(\n ([name, def]: [string, any]) => {\n const ports: { container: number; host?: number }[] = [];\n if (def.ports) {\n for (const port of def.ports) {\n const str = String(port);\n if (str.includes(\":\")) {\n const [host, container] = str.split(\":\");\n ports.push({ container: Number(container), host: Number(host) });\n } else {\n ports.push({ container: Number(str) });\n }\n }\n }\n\n const environment: Record<string, string> = {};\n if (def.environment) {\n if (Array.isArray(def.environment)) {\n for (const env of def.environment) {\n const [key, ...rest] = String(env).split(\"=\");\n environment[key] = rest.join(\"=\");\n }\n } else {\n Object.assign(environment, def.environment);\n }\n }\n\n const volumes: string[] = def.volumes ? def.volumes.map((v: string) => String(v)) : [];\n\n let dependsOn: string[] = [];\n if (def.depends_on) {\n dependsOn = Array.isArray(def.depends_on) ? def.depends_on : Object.keys(def.depends_on);\n }\n\n return {\n name,\n image: def.image,\n build: def.build,\n ports,\n environment,\n volumes,\n dependsOn,\n };\n },\n );\n\n const appService = services.find((s) => s.build !== undefined) ?? null;\n const infraServices = services.filter((s) => s.build === undefined);\n\n return { services, appService, infraServices };\n}\n","// ── Colors ──\n\nconst GREEN = \"\\x1b[32m\";\nconst RED = \"\\x1b[31m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\nconst RESET = \"\\x1b[0m\";\nconst BG_CYAN = \"\\x1b[46m\";\nconst BLACK = \"\\x1b[30m\";\n\n// ── Symbols (vitest-native) ──\n\nconst CHECK = \"✓\";\nconst CROSS = \"×\";\nconst ARROW = \"→\";\nconst DASH = \"⎯\";\n\n// ── Types ──\n\nexport interface ServiceReport {\n name: string;\n type: string;\n connectionString?: string;\n durationMs: number;\n error?: string;\n logs?: string;\n}\n\nexport interface AppInfo {\n type: \"http\" | \"in-process\";\n url?: string;\n}\n\n// ── Startup report ──\n\nexport function formatStartupReport(\n mode: \"e2e\" | \"integration\",\n services: ServiceReport[],\n app?: AppInfo,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(`${BG_CYAN}${BLACK}${BOLD} INFRA ${RESET} Starting infrastructure...`);\n lines.push(\"\");\n\n for (const service of services) {\n if (service.error) {\n lines.push(\n ` ${RED}${CROSS}${RESET} ${service.type} (${service.name}) ${RED}${service.error}${RESET} ${DIM}${service.durationMs}ms${RESET}`,\n );\n if (service.logs) {\n const logLines = service.logs.trim().split(\"\\n\").slice(-10);\n for (const logLine of logLines) {\n lines.push(` ${DIM}${logLine}${RESET}`);\n }\n }\n } else {\n const conn = service.connectionString ? `${DIM}${service.connectionString}${RESET}` : \"\";\n lines.push(\n ` ${GREEN}${CHECK}${RESET} ${service.type} (${service.name}) ${conn} ${DIM}${service.durationMs}ms${RESET}`,\n );\n }\n }\n\n if (app) {\n lines.push(\"\");\n if (app.type === \"in-process\") {\n lines.push(` ${DIM}${ARROW} app: in-process (Hono)${RESET}`);\n } else {\n lines.push(` ${DIM}${ARROW} app: ${app.url}${RESET}`);\n }\n }\n\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ── Error divider ──\n\nexport function formatErrorDivider(label: string): string {\n const dashes = DASH.repeat(30);\n return `\\n${RED}${dashes} ${label} ${dashes}${RESET}\\n`;\n}\n\n// ── Status error ──\n\nexport function formatStatusError(\n expectedStatus: number,\n receivedStatus: number,\n request: { method: string; path: string; body?: unknown },\n responseBody: unknown,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected status: ${GREEN}${expectedStatus}${RESET}`);\n lines.push(`Received status: ${RED}${receivedStatus}${RESET}`);\n lines.push(\"\");\n lines.push(`${DIM}${request.method} ${request.path}${RESET}`);\n\n if (request.body) {\n lines.push(formatJson(request.body, DIM));\n }\n\n if (responseBody) {\n lines.push(\"\");\n lines.push(`${DIM}Response:${RESET}`);\n lines.push(formatJson(responseBody, RED));\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Table diff ──\n\nexport function formatTableDiff(\n table: string,\n columns: string[],\n expected: unknown[][],\n actual: unknown[][],\n): string {\n const lines: string[] = [];\n\n lines.push(`Table \"${table}\" mismatch`);\n lines.push(`${DIM} query: ${columns.join(\", \")}${RESET}`);\n lines.push(`${DIM} expected: ${rowLabel(expected.length)}${RESET}`);\n lines.push(`${DIM} received: ${rowLabel(actual.length)}${RESET}`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const header = columns.join(\" | \");\n lines.push(`${DIM} ${header}${RESET}`);\n\n const maxRows = Math.max(expected.length, actual.length);\n\n for (let i = 0; i < maxRows; i++) {\n const exp = expected[i];\n const act = actual[i];\n\n if (exp && !act) {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n } else if (!exp && act) {\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n } else if (exp && act) {\n const same = JSON.stringify(exp) === JSON.stringify(act);\n if (same) {\n lines.push(` ${formatRow(act)}`);\n } else {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n }\n }\n }\n\n if (expected.length === 0 && actual.length === 0) {\n lines.push(` (empty)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Response diff ──\n\nexport function formatResponseDiff(file: string, expected: unknown, actual: unknown): string {\n const lines: string[] = [];\n\n lines.push(`Response mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = JSON.stringify(expected, null, 2).split(\"\\n\");\n const actualLines = JSON.stringify(actual, null, 2).split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Exit code error ──\n\nexport function formatExitCodeError(\n expected: number,\n received: number,\n stdout: string,\n stderr: string,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected exit code: ${GREEN}${expected}${RESET}`);\n lines.push(`Received exit code: ${RED}${received}${RESET}`);\n\n if (stdout.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stdout:${RESET}`);\n for (const line of stdout.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n if (stderr.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stderr:${RESET}`);\n for (const line of stderr.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${RED}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Stdout/stderr diff ──\n\nexport function formatStdoutDiff(file: string, expected: string, actual: string): string {\n const lines: string[] = [];\n\n lines.push(`Output mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = expected.split(\"\\n\");\n const actualLines = actual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── File assertions ──\n\nexport function formatFileMissing(path: string): string {\n return `Expected file to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileUnexpected(path: string): string {\n return `Expected file NOT to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileContentMismatch(path: string, expected: string, actual: string): string {\n const lines: string[] = [];\n lines.push(`File \"${path}\" does not contain expected content`);\n lines.push(\"\");\n lines.push(`${GREEN}Expected to contain:${RESET}`);\n lines.push(` ${GREEN}${expected}${RESET}`);\n lines.push(\"\");\n lines.push(`${RED}Actual content (first 20 lines):${RESET}`);\n for (const line of actual.split(\"\\n\").slice(0, 20)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ── Service logs section ──\n\nexport function formatServiceLogs(services: { name: string; logs: string }[]): string {\n const lines: string[] = [];\n\n for (const { name, logs } of services) {\n if (!logs.trim()) {\n continue;\n }\n\n lines.push(\"\");\n lines.push(`${DIM}${name} logs (last 10 lines):${RESET}`);\n\n const logLines = logs.trim().split(\"\\n\").slice(-10);\n for (const line of logLines) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Helpers ──\n\nfunction rowLabel(n: number): string {\n return n === 1 ? \"1 row\" : `${n} rows`;\n}\n\nfunction formatJson(value: unknown, color: string): string {\n return JSON.stringify(value, null, 2)\n .split(\"\\n\")\n .map((line) => `${color}${line}${RESET}`)\n .join(\"\\n\");\n}\n\nfunction formatRow(row: unknown[]): string {\n return row.map((v) => String(v ?? \"null\")).join(\" | \");\n}\n\n// ── Test utilities ──\n\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function normalizeOutput(str: string): string {\n return stripAnsi(str)\n .replace(/localhost:\\d+/g, \"localhost:PORT\")\n .replace(/\\d+ms/g, \"Xms\")\n .replace(/\\d+\\.\\d+s/g, \"X.Xs\")\n .trim();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { Client } from \"pg\";\n\nimport type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface PostgresOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n /** Override environment variables. */\n env?: Record<string, string>;\n}\n\nclass PostgresHandle implements DatabasePort, ServiceHandle {\n readonly type = \"postgres\";\n readonly composeName: null | string;\n readonly defaultPort = 5432;\n readonly defaultImage: string;\n readonly environment: Record<string, string>;\n\n connectionString = \"\";\n started = false;\n\n private client: Client | null = null;\n\n constructor(options: PostgresOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"postgres:17\";\n this.environment = {\n POSTGRES_DB: \"test\",\n POSTGRES_PASSWORD: \"test\",\n POSTGRES_USER: \"test\",\n ...options.env,\n };\n }\n\n buildConnectionString(host: string, port: number): string {\n const user = this.environment.POSTGRES_USER ?? \"test\";\n const password = this.environment.POSTGRES_PASSWORD ?? \"test\";\n const db = this.environment.POSTGRES_DB ?? \"test\";\n return `postgresql://${user}:${password}@${host}:${port}/${db}`;\n }\n\n createDatabaseAdapter(): DatabasePort {\n return this;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"postgres: cannot healthcheck — no connection string\");\n }\n\n // Healthcheck uses a throwaway client (connection might not be established yet)\n try {\n const client = new Client({ connectionString: this.connectionString });\n await client.connect();\n await client.query(\"SELECT 1\");\n await client.end();\n } catch (error: any) {\n throw new Error(\n `postgres healthcheck failed: ${error.message || error.code || String(error)}`,\n { cause: error },\n );\n }\n }\n\n async initialize(composeDir: string): Promise<void> {\n if (!this.composeName) {\n return;\n }\n\n const initPaths = [\n resolve(composeDir, `${this.composeName}/init.sql`),\n resolve(composeDir, \"postgres/init.sql\"),\n ];\n\n for (const initPath of initPaths) {\n if (existsSync(initPath)) {\n const sql = readFileSync(initPath, \"utf8\");\n try {\n await this.seed(sql);\n } catch (error: any) {\n throw new Error(`postgres init script failed (${initPath}):\\n${error.message}`, {\n cause: error,\n });\n }\n return;\n }\n }\n }\n\n private async getClient(): Promise<Client> {\n if (this.client) {\n return this.client;\n }\n const client = new Client({ connectionString: this.connectionString });\n client.on(\"error\", () => {\n // Connection dropped (container stopped) — reset so next call reconnects\n this.client = null;\n });\n await client.connect();\n this.client = client;\n return client;\n }\n\n async seed(sql: string): Promise<void> {\n const client = await this.getClient();\n await client.query(sql);\n }\n\n async query(table: string, columns: string[]): Promise<unknown[][]> {\n const client = await this.getClient();\n const columnList = columns.join(\", \");\n const result = await client.query(`SELECT ${columnList} FROM \"${table}\" ORDER BY 1`);\n return result.rows.map((row: Record<string, unknown>) => columns.map((col) => row[col]));\n }\n\n async reset(): Promise<void> {\n const client = await this.getClient();\n const result = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of result.rows) {\n await client.query(`TRUNCATE \"${row.tablename}\" CASCADE`);\n }\n }\n}\n\n/**\n * Create a PostgreSQL service handle.\n *\n * @example\n * const db = postgres({ compose: \"db\" });\n * // After start: db.connectionString is populated\n */\nexport function postgres(options: PostgresOptions = {}): PostgresHandle {\n return new PostgresHandle(options);\n}\n","import type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nclass RedisHandle implements ServiceHandle {\n readonly type = \"redis\";\n readonly composeName: null | string;\n readonly defaultPort = 6379;\n readonly defaultImage: string;\n readonly environment: Record<string, string> = {};\n\n connectionString = \"\";\n started = false;\n\n constructor(options: RedisOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"redis:7\";\n }\n\n buildConnectionString(host: string, port: number): string {\n return `redis://${host}:${port}`;\n }\n\n createDatabaseAdapter(): DatabasePort | null {\n return null;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"redis: cannot healthcheck — no connection string\");\n }\n\n try {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n await client.ping();\n await client.disconnect();\n } catch (error: any) {\n throw new Error(`redis healthcheck failed: ${error.message || error.code || String(error)}`, {\n cause: error,\n });\n }\n }\n\n async initialize(): Promise<void> {\n // Redis doesn't need initialization scripts\n }\n\n async reset(): Promise<void> {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n try {\n await client.flushAll();\n } finally {\n await client.disconnect();\n }\n }\n}\n\n/**\n * Create a Redis service handle.\n *\n * @example\n * const cache = redis({ compose: \"cache\" });\n * // After start: cache.connectionString is populated\n */\nexport function redis(options: RedisOptions = {}): RedisHandle {\n return new RedisHandle(options);\n}\n","import { dirname } from \"node:path\";\n\nimport type { DatabasePort } from \"../specification/ports/database.port.js\";\nimport { ComposeStackAdapter } from \"./adapters/compose.adapter.js\";\nimport { TestcontainersAdapter } from \"./adapters/testcontainers.adapter.js\";\nimport { detectServiceType, findComposeFile, parseComposeFile } from \"./compose-parser.js\";\nimport type { ContainerPort } from \"./ports/container.port.js\";\nimport { type AppInfo, formatStartupReport, type ServiceReport } from \"./reporter.js\";\nimport { postgres } from \"./services/postgres.js\";\nimport { redis } from \"./services/redis.js\";\nimport type { ServiceHandle } from \"./services/service.port.js\";\n\ninterface RunningService {\n handle: ServiceHandle;\n container: ContainerPort | null;\n}\n\ninterface OrchestratorOptions {\n services: ServiceHandle[];\n mode: \"e2e\" | \"integration\";\n root?: string;\n}\n\n/**\n * Orchestrator for test infrastructure.\n * Integration: starts services via testcontainers.\n * E2E: runs full docker compose up.\n */\nexport class Orchestrator {\n private services: ServiceHandle[];\n private mode: \"e2e\" | \"integration\";\n private root: string;\n private running: RunningService[] = [];\n private composeStack: ComposeStackAdapter | null = null;\n private composeHandles: ServiceHandle[] = [];\n private started = false;\n\n constructor(options: OrchestratorOptions) {\n this.services = options.services;\n this.mode = options.mode;\n this.root = options.root ?? process.cwd();\n }\n\n /**\n * Start declared services via testcontainers (integration mode).\n * Phase 1: start all containers in parallel (the slow part).\n * Phase 2: wire connections, healthcheck, and init sequentially (fast).\n */\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n const composePath = findComposeFile(this.root);\n const composeDir = composePath ? dirname(composePath) : this.root;\n const composeConfig = composePath ? parseComposeFile(composePath) : null;\n\n // Phase 1: resolve config and start all containers in parallel\n const containerTasks = this.services.map((handle) => {\n let image = handle.defaultImage;\n let env = { ...handle.environment };\n\n if (handle.composeName && composeConfig) {\n const composeService = composeConfig.services.find((s) => s.name === handle.composeName);\n if (composeService) {\n image = composeService.image ?? image;\n env = { ...env, ...composeService.environment };\n Object.assign(handle.environment, composeService.environment);\n }\n }\n\n const container = new TestcontainersAdapter({ image, port: handle.defaultPort, env });\n return { container, handle };\n });\n\n // Start all containers concurrently\n await Promise.all(containerTasks.map(({ container }) => container.start()));\n\n // Phase 2: wire connections, healthcheck, init (fast — containers already running)\n const reports: ServiceReport[] = [];\n\n for (const { container, handle } of containerTasks) {\n const serviceStartTime = Date.now();\n\n try {\n const host = container.getHost();\n const port = container.getMappedPort(handle.defaultPort);\n handle.connectionString = handle.buildConnectionString(host, port);\n\n await handle.healthcheck();\n await handle.initialize(composeDir);\n handle.started = true;\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n connectionString: handle.connectionString,\n durationMs: Date.now() - serviceStartTime,\n });\n this.running.push({ handle, container });\n } catch (error: any) {\n let logs = \"\";\n try {\n logs = await container.getLogs();\n } catch {\n /* Ignore log fetch errors */\n }\n try {\n await container.stop();\n } catch {\n /* Ignore stop errors */\n }\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n durationMs: Date.now() - serviceStartTime,\n error: error.message,\n logs,\n });\n\n const output = formatStartupReport(\"integration\", reports, { type: \"in-process\" });\n console.error(output);\n throw error;\n }\n }\n\n this.started = true;\n\n const appInfo: AppInfo = { type: \"in-process\" };\n const output = formatStartupReport(\"integration\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop testcontainers (integration mode).\n */\n async stop(): Promise<void> {\n for (const { container } of this.running) {\n if (container) {\n await container.stop();\n }\n }\n this.running = [];\n this.started = false;\n }\n\n /**\n * Start full docker compose stack (e2e mode).\n * Auto-detects infra services and creates handles for them.\n */\n async startCompose(): Promise<void> {\n const composePath = findComposeFile(this.root);\n if (!composePath) {\n throw new Error(`E2E: no compose file found in ${this.root}`);\n }\n\n const startTime = Date.now();\n const composeDir = dirname(composePath);\n const composeConfig = parseComposeFile(composePath);\n\n this.composeStack = new ComposeStackAdapter(composePath);\n await this.composeStack.start();\n\n // Create handles for detected infra services\n for (const service of composeConfig.infraServices) {\n const type = detectServiceType(service.image);\n\n if (type === \"postgres\") {\n const handle = postgres({ compose: service.name, env: service.environment });\n const port = this.composeStack.getMappedPort(service.name, 5432);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n\n await handle.initialize(composeDir);\n handle.started = true;\n\n this.composeHandles.push(handle);\n } else if (type === \"redis\") {\n const handle = redis({ compose: service.name });\n const port = this.composeStack.getMappedPort(service.name, 6379);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n handle.started = true;\n\n this.composeHandles.push(handle);\n }\n }\n\n const durationMs = Date.now() - startTime;\n const reports: ServiceReport[] = this.composeHandles.map((h) => ({\n name: h.composeName ?? h.type,\n type: h.type,\n connectionString: h.connectionString,\n durationMs,\n }));\n\n const appUrl = this.getAppUrl();\n const appInfo: AppInfo = { type: \"http\", url: appUrl ?? undefined };\n const output = formatStartupReport(\"e2e\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop docker compose stack (e2e mode).\n */\n async stopCompose(): Promise<void> {\n if (this.composeStack) {\n await this.composeStack.stop();\n this.composeStack = null;\n }\n this.composeHandles = [];\n }\n\n /**\n * Get a database service by compose name, or the first one if no name given.\n */\n getDatabase(serviceName?: string): DatabasePort | null {\n for (const handle of [...this.services, ...this.composeHandles]) {\n if (serviceName && handle.composeName !== serviceName) {\n continue;\n }\n const adapter = handle.createDatabaseAdapter();\n if (adapter) {\n return adapter;\n }\n }\n return null;\n }\n\n /**\n * Get all database services keyed by compose name.\n */\n getDatabases(): Map<string, DatabasePort> {\n const map = new Map<string, DatabasePort>();\n for (const handle of [...this.services, ...this.composeHandles]) {\n const adapter = handle.createDatabaseAdapter();\n if (adapter && handle.composeName) {\n map.set(handle.composeName, adapter);\n }\n }\n return map;\n }\n\n /**\n * Get app URL from compose (e2e mode).\n */\n getAppUrl(): null | string {\n const composePath = findComposeFile(this.root);\n if (!composePath || !this.composeStack) {\n return null;\n }\n\n const config = parseComposeFile(composePath);\n const appService = config.appService;\n\n if (!appService || appService.ports.length === 0) {\n return null;\n }\n\n const port = this.composeStack.getMappedPort(appService.name, appService.ports[0].container);\n return `http://localhost:${port}`;\n }\n}\n","import { execSync, spawn } from \"node:child_process\";\n\nimport type { CommandPort, CommandResult, SpawnOptions } from \"../ports/command.port.js\";\n\n/**\n * Executes CLI commands via execSync (blocking) or spawn (long-running).\n * Used by cli() for local command execution.\n */\nexport class ExecAdapter implements CommandPort {\n private command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n\n async exec(args: string, cwd: string): Promise<CommandResult> {\n // Clear INIT_CWD so CLI tools use the actual cwd, not npm's caller directory\n const env = { ...process.env, INIT_CWD: undefined };\n\n try {\n const stdout = execSync(`${this.command} ${args}`, {\n cwd,\n encoding: \"utf8\",\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { exitCode: 0, stdout, stderr: \"\" };\n } catch (error: any) {\n return {\n exitCode: error.status ?? 1,\n stdout: error.stdout?.toString() ?? \"\",\n stderr: error.stderr?.toString() ?? \"\",\n };\n }\n }\n\n async spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult> {\n const env = { ...process.env, INIT_CWD: undefined };\n\n return new Promise((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let resolved = false;\n\n const child = spawn(this.command, args.split(/\\s+/).filter(Boolean), {\n cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const finish = (exitCode: number) => {\n if (resolved) {\n return;\n }\n resolved = true;\n child.kill(\"SIGTERM\");\n resolve({ exitCode, stdout, stderr });\n };\n\n let patternMatched = false;\n\n const checkPattern = () => {\n if (\n !patternMatched &&\n (stdout.includes(options.waitFor) || stderr.includes(options.waitFor))\n ) {\n patternMatched = true;\n finish(0);\n }\n };\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n checkPattern();\n });\n\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n checkPattern();\n });\n\n // Process exited before pattern matched\n child.on(\"exit\", (code) => {\n if (!patternMatched) {\n finish(code === 0 ? 1 : (code ?? 1));\n }\n });\n\n setTimeout(() => finish(124), options.timeout);\n });\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for real HTTP — sends actual fetch requests.\n * Used by e2e() specification runner.\n */\nexport class FetchAdapter implements ServerPort {\n private baseUrl: string;\n\n constructor(url: string) {\n this.baseUrl = url.replace(/\\/$/, \"\");\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(`${this.baseUrl}${path}`, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for Hono — in-process requests, no real HTTP.\n * Used by integration() specification runner.\n */\nexport class HonoAdapter implements ServerPort {\n private app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n };\n\n constructor(app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n }) {\n this.app = app;\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await this.app.request(path, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","/**\n * Base assertion that handles .not negation.\n * Subclasses call this.assert(condition, message, negatedMessage) for each predicate.\n */\nexport class BaseAssertion {\n protected negated = false;\n\n get not(): this {\n const clone = Object.create(Object.getPrototypeOf(this));\n Object.assign(clone, this);\n clone.negated = !this.negated;\n return clone;\n }\n\n protected assert(condition: boolean, message: string, negatedMessage: string): void {\n if (this.negated) {\n if (condition) {\n throw new Error(negatedMessage);\n }\n } else {\n if (!condition) {\n throw new Error(message);\n }\n }\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport {\n formatFileContentMismatch,\n formatFileMissing,\n formatFileUnexpected,\n} from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a file in the working directory.\n * Usage: result.file(\"dist/index.js\").toExist()\n */\nexport class FileAssertion extends BaseAssertion {\n private filePath: string;\n private resolvedPath: string;\n\n constructor(filePath: string, workDir: string) {\n super();\n this.filePath = filePath;\n this.resolvedPath = resolve(workDir, filePath);\n }\n\n toExist(): void {\n const exists = existsSync(this.resolvedPath);\n this.assert(exists, formatFileMissing(this.filePath), formatFileUnexpected(this.filePath));\n }\n\n toContain(expected: string): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return; // File doesn't exist, so it certainly doesn't contain the string\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = content.includes(expected);\n this.assert(\n found,\n formatFileContentMismatch(this.filePath, expected, content),\n `Expected file \"${this.filePath}\" NOT to contain \"${expected}\"`,\n );\n }\n\n toMatch(pattern: RegExp): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return;\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = pattern.test(content);\n this.assert(\n found,\n `Expected file \"${this.filePath}\" to match: ${pattern}\\n\\nActual content:\\n${content.slice(0, 500)}`,\n `Expected file \"${this.filePath}\" NOT to match: ${pattern}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatResponseDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on an HTTP response body.\n * Usage: result.response.toMatchFile(\"expected.json\")\n */\nexport class ResponseAssertion extends BaseAssertion {\n private body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\n super();\n this.body = body;\n this.testDir = testDir;\n }\n\n toMatchFile(file: string): void {\n const expected = JSON.parse(readFileSync(resolve(this.testDir, \"responses\", file), \"utf8\"));\n const match = JSON.stringify(this.body) === JSON.stringify(expected);\n this.assert(\n match,\n formatResponseDiff(file, expected, this.body),\n `Expected response NOT to match file \"${file}\", but it did`,\n );\n }\n\n toContain(subset: Record<string, unknown>): void {\n const bodyStr = JSON.stringify(this.body);\n const subsetStr = JSON.stringify(subset);\n // Check if all keys in subset exist with same values in body\n const bodyObj = typeof this.body === \"object\" && this.body !== null ? this.body : {};\n const match = Object.entries(subset).every(\n ([key, value]) =>\n JSON.stringify((bodyObj as Record<string, unknown>)[key]) === JSON.stringify(value),\n );\n this.assert(\n match,\n `Expected response to contain: ${subsetStr}\\n\\nActual response:\\n${bodyStr}`,\n `Expected response NOT to contain: ${subsetStr}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatStdoutDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a string (stdout, stderr, response body).\n * Usage: result.stdout.toContain(\"hello\")\n */\nexport class StringAssertion extends BaseAssertion {\n private actual: string;\n private label: string;\n private testDir?: string;\n\n constructor(actual: string, label: string, testDir?: string) {\n super();\n this.actual = actual;\n this.label = label;\n this.testDir = testDir;\n }\n\n toContain(expected: string, options?: { near?: string }): void {\n if (options?.near) {\n const found = this.containsNear(expected, options.near);\n this.assert(\n found,\n `Expected ${this.label} to contain \"${expected}\" near \"${options.near}\"\\n\\n${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain \"${expected}\" near \"${options.near}\", but it was found`,\n );\n } else {\n const found = this.actual.includes(expected);\n this.assert(\n found,\n `Expected ${this.label} to contain: \"${expected}\"\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain: \"${expected}\"`,\n );\n }\n }\n\n toMatch(pattern: RegExp): void {\n const found = pattern.test(this.actual);\n this.assert(\n found,\n `Expected ${this.label} to match: ${pattern}\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to match: ${pattern}`,\n );\n }\n\n toMatchFile(file: string): void {\n if (!this.testDir) {\n throw new Error(\"toMatchFile requires a test directory context\");\n }\n const expected = readFileSync(resolve(this.testDir, \"expected\", file), \"utf8\").trim();\n const actual = this.actual.trim();\n const match = actual === expected;\n this.assert(\n match,\n formatStdoutDiff(file, expected, actual),\n `Expected ${this.label} NOT to match file \"${file}\", but it did`,\n );\n }\n\n toBeEmpty(): void {\n const empty = this.actual.trim() === \"\";\n this.assert(\n empty,\n `Expected ${this.label} to be empty\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to be empty`,\n );\n }\n\n // ── Private ──\n\n private containsNear(target: string, near: string, proximity = 500): boolean {\n const clean = this.stripAnsi(this.actual);\n const nearLower = near.toLowerCase();\n const targetLower = target.toLowerCase();\n\n // Find all occurrences of `near` and check if `target` appears within proximity\n let searchFrom = 0;\n while (true) {\n const idx = clean.toLowerCase().indexOf(nearLower, searchFrom);\n if (idx === -1) {\n break;\n }\n const windowStart = Math.max(0, idx - proximity);\n const windowEnd = Math.min(clean.length, idx + nearLower.length + proximity);\n const window = clean.substring(windowStart, windowEnd).toLowerCase();\n if (window.includes(targetLower)) {\n return true;\n }\n searchFrom = idx + 1;\n }\n return false;\n }\n\n private stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n }\n\n private truncate(str: string, maxLines = 20): string {\n const lines = str.split(\"\\n\");\n if (lines.length <= maxLines) {\n return str;\n }\n return `${lines.slice(0, maxLines).join(\"\\n\")}\\n... (${lines.length - maxLines} more lines)`;\n }\n}\n","import { formatTableDiff } from \"../../infrastructure/reporter.js\";\nimport type { DatabasePort } from \"../ports/database.port.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a database table.\n * Usage: await result.table(\"users\").toMatch({ columns: [\"name\"], rows: [[\"Alice\"]] })\n */\nexport class TableAssertion extends BaseAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\n super();\n this.tableName = tableName;\n this.db = db;\n }\n\n async toMatch(expected: { columns: string[]; rows: unknown[][] }): Promise<void> {\n const actual = await this.db.query(this.tableName, expected.columns);\n const match = JSON.stringify(actual) === JSON.stringify(expected.rows);\n this.assert(\n match,\n formatTableDiff(this.tableName, expected.columns, expected.rows, actual),\n `Expected table \"${this.tableName}\" NOT to match, but it did`,\n );\n }\n\n async toBeEmpty(): Promise<void> {\n // Query a single column to check if table has rows — use a lightweight approach\n const actual = await this.db.query(this.tableName, [\"*\"]);\n const empty = actual.length === 0;\n this.assert(\n empty,\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n `Expected table \"${this.tableName}\" NOT to be empty, but it is`,\n );\n }\n}\n","import { formatExitCodeError, formatStatusError } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a single value (exit code, status code).\n * Usage: result.exitCode.toBe(0)\n */\nexport class ValueAssertion extends BaseAssertion {\n private actual: number;\n private label: string;\n private context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string };\n\n constructor(\n actual: number,\n label: string,\n context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string },\n ) {\n super();\n this.actual = actual;\n this.label = label;\n this.context = context;\n }\n\n toBe(expected: number): void {\n const match = this.actual === expected;\n\n let message: string;\n if (this.label === \"exit code\" && this.context?.stdout !== undefined) {\n message = formatExitCodeError(\n expected,\n this.actual,\n this.context.stdout ?? \"\",\n this.context.stderr ?? \"\",\n );\n } else if (this.label === \"status\" && this.context?.request) {\n message = formatStatusError(\n expected,\n this.actual,\n this.context.request,\n this.context.responseBody,\n );\n } else {\n message = `Expected ${this.label}: ${expected}\\nReceived ${this.label}: ${this.actual}`;\n }\n\n this.assert(match, message, `Expected ${this.label} NOT to be ${expected}, but it was`);\n }\n}\n","import { cpSync, existsSync, mkdtempSync, readFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\nimport { FileAssertion } from \"./assertions/file.js\";\nimport { ResponseAssertion } from \"./assertions/response.js\";\nimport { StringAssertion } from \"./assertions/string.js\";\nimport { TableAssertion } from \"./assertions/table.js\";\nimport { ValueAssertion } from \"./assertions/value.js\";\nimport type { CommandPort, CommandResult, SpawnOptions } from \"./ports/command.port.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// ── Types ──\n\nexport interface SpecificationConfig {\n command?: CommandPort;\n database?: DatabasePort;\n databases?: Map<string, DatabasePort>;\n fixturesRoot?: string;\n server?: ServerPort;\n}\n\nexport interface SeedEntry {\n file: string;\n service?: string;\n}\n\nexport interface FixtureEntry {\n file: string;\n}\n\nexport interface MockEntry {\n file: string;\n}\n\nexport interface RequestEntry {\n bodyFile?: string;\n method: string;\n path: string;\n}\n\n// ── Result (after .run()) ──\n\ninterface RequestInfo {\n body?: unknown;\n method: string;\n path: string;\n}\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: RequestInfo;\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: RequestInfo;\n response?: ServerResponse;\n testDir: string;\n workDir?: string;\n }) {\n this.responseData = options.response;\n this.commandResult = options.commandResult;\n this.config = options.config;\n this.testDir = options.testDir;\n this.requestInfo = options.requestInfo;\n this.workDir = options.workDir;\n }\n\n // ── Scoped assertion accessors ──\n\n get exitCode(): ValueAssertion {\n if (!this.commandResult) {\n throw new Error(\".exitCode requires a CLI action (.exec())\");\n }\n return new ValueAssertion(this.commandResult.exitCode, \"exit code\", {\n stderr: this.commandResult.stderr,\n stdout: this.commandResult.stdout,\n });\n }\n\n get status(): ValueAssertion {\n if (!this.responseData || !this.requestInfo) {\n throw new Error(\".status requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ValueAssertion(this.responseData.status, \"status\", {\n request: this.requestInfo,\n responseBody: this.responseData.body,\n });\n }\n\n get response(): ResponseAssertion {\n if (!this.responseData) {\n throw new Error(\".response requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ResponseAssertion(this.responseData.body, this.testDir);\n }\n\n get stdout(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stdout requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stdout, \"stdout\", this.testDir);\n }\n\n get stderr(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stderr requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stderr, \"stderr\", this.testDir);\n }\n\n file(path: string): FileAssertion {\n const baseDir = this.workDir ?? this.testDir;\n return new FileAssertion(path, baseDir);\n }\n\n table(tableName: string, options?: { service?: string }): TableAssertion {\n const db = this.resolveDatabase(options?.service);\n if (!db) {\n throw new Error(\n options?.service\n ? `table(\"${tableName}\") requires database \"${options.service}\" but it was not found`\n : `table(\"${tableName}\") requires a database adapter`,\n );\n }\n return new TableAssertion(tableName, db);\n }\n\n // ── Private ──\n\n private resolveDatabase(serviceName?: string): DatabasePort | undefined {\n if (serviceName && this.config.databases) {\n return this.config.databases.get(serviceName);\n }\n return this.config.database;\n }\n}\n\n// ── Builder (before .run()) ──\n\nexport class SpecificationBuilder {\n private commandArgs: null | string | string[] = null;\n private config: SpecificationConfig;\n private fixtures: FixtureEntry[] = [];\n private label: string;\n private mocks: MockEntry[] = [];\n private projectName: null | string = null;\n private request: null | RequestEntry = null;\n private seeds: SeedEntry[] = [];\n private spawnConfig: null | { args: string; options: SpawnOptions } = null;\n private testDir: string;\n\n constructor(config: SpecificationConfig, testDir: string, label: string) {\n this.config = config;\n this.testDir = testDir;\n this.label = label;\n }\n\n // ── Setup ──\n\n seed(file: string, options?: { service?: string }): this {\n this.seeds.push({ file, service: options?.service });\n return this;\n }\n\n fixture(file: string): this {\n this.fixtures.push({ file });\n return this;\n }\n\n project(name: string): this {\n this.projectName = name;\n return this;\n }\n\n mock(file: string): this {\n this.mocks.push({ file });\n return this;\n }\n\n // ── HTTP actions ──\n\n get(path: string): this {\n this.request = { method: \"GET\", path };\n return this;\n }\n\n post(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"POST\", path };\n return this;\n }\n\n put(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"PUT\", path };\n return this;\n }\n\n delete(path: string): this {\n this.request = { method: \"DELETE\", path };\n return this;\n }\n\n // ── CLI actions ──\n\n exec(args: string | string[]): this {\n this.commandArgs = args;\n return this;\n }\n\n spawn(args: string, options: SpawnOptions): this {\n this.spawnConfig = { args, options };\n return this;\n }\n\n // ── Run ──\n\n async run(): Promise<SpecificationResult> {\n const hasHttpAction = this.request !== null;\n const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;\n\n if (!hasHttpAction && !hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": no action defined. Call .get(), .post(), .exec(), etc. before .run()`,\n );\n }\n\n if (hasHttpAction && hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`,\n );\n }\n\n // Resolve working directory for CLI mode\n let workDir: null | string = null;\n if (hasCliAction) {\n workDir = this.prepareWorkDir();\n }\n\n // Reset all databases\n if (this.config.databases) {\n for (const db of this.config.databases.values()) {\n await db.reset();\n }\n } else if (this.config.database) {\n await this.config.database.reset();\n }\n\n // Execute seeds\n for (const entry of this.seeds) {\n let db: DatabasePort | undefined;\n if (entry.service && this.config.databases) {\n db = this.config.databases.get(entry.service);\n if (!db) {\n throw new Error(\n `seed() targets database \"${entry.service}\" but it was not found. Available: ${[...this.config.databases.keys()].join(\", \")}`,\n );\n }\n } else {\n db = this.config.database;\n }\n\n if (!db) {\n throw new Error(\"seed() requires a database adapter\");\n }\n\n const sql = readFileSync(resolve(this.testDir, \"seeds\", entry.file), \"utf8\");\n await db.seed(sql);\n }\n\n // Copy fixture files into working directory\n if (this.fixtures.length > 0 && workDir) {\n for (const entry of this.fixtures) {\n const src = resolve(this.testDir, \"fixtures\", entry.file);\n const dest = resolve(workDir, entry.file);\n cpSync(src, dest, { recursive: true });\n }\n }\n\n // Register MSW mocks\n for (const entry of this.mocks) {\n const _mockData = JSON.parse(readFileSync(resolve(this.testDir, \"mock\", entry.file), \"utf8\"));\n // TODO: Register MSW handler from mock data\n }\n\n // Execute action\n if (hasHttpAction) {\n return this.runHttpAction();\n }\n return this.runCliAction(workDir!);\n }\n\n // ── Private ──\n\n private prepareWorkDir(): string {\n const tempDir = mkdtempSync(resolve(tmpdir(), \"spec-cli-\"));\n\n if (this.projectName && this.config.fixturesRoot) {\n const projectDir = resolve(this.config.fixturesRoot, this.projectName);\n if (!existsSync(projectDir)) {\n throw new Error(\n `project(\"${this.projectName}\"): fixture project not found at ${projectDir}`,\n );\n }\n cpSync(projectDir, tempDir, { recursive: true });\n }\n\n return tempDir;\n }\n\n private async runHttpAction(): Promise<SpecificationResult> {\n if (!this.config.server) {\n throw new Error(\"HTTP actions require a server adapter (use integration() or e2e())\");\n }\n\n let body: unknown;\n if (this.request!.bodyFile) {\n body = JSON.parse(\n readFileSync(resolve(this.testDir, \"requests\", this.request!.bodyFile), \"utf8\"),\n );\n }\n\n const response = await this.config.server.request(\n this.request!.method,\n this.request!.path,\n body,\n );\n\n return new SpecificationResult({\n config: this.config,\n requestInfo: { body, method: this.request!.method, path: this.request!.path },\n response,\n testDir: this.testDir,\n });\n }\n\n private async runCliAction(workDir: string): Promise<SpecificationResult> {\n if (!this.config.command) {\n throw new Error(\"CLI actions require a command adapter (use cli())\");\n }\n\n let commandResult: CommandResult;\n\n if (this.spawnConfig) {\n commandResult = await this.config.command.spawn(\n this.spawnConfig.args,\n workDir,\n this.spawnConfig.options,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stdout: \"\", stderr: \"\" };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir);\n }\n\n return new SpecificationResult({\n commandResult,\n config: this.config,\n testDir: this.testDir,\n workDir,\n });\n }\n}\n\n// ── Caller detection ──\n\nfunction getCallerDir(): string {\n const stack = new Error(\"caller detection\").stack;\n if (!stack) {\n throw new Error(\"Cannot detect caller directory: no stack trace\");\n }\n\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n\n if (filePath.includes(\"node_modules\")) {\n continue;\n }\n if (filePath.includes(\"/src/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\");\n }\n\n throw new Error(\"Cannot detect caller directory from stack trace\");\n}\n\n// ── Factory functions ──\n\nexport type SpecificationRunner = (label: string) => SpecificationBuilder;\n\n/**\n * Create a specification runner.\n * Automatically detects the test directory from the call site.\n */\nexport function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner {\n return (label: string) => {\n const testDir = getCallerDir();\n return new SpecificationBuilder(config, testDir, label);\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { Orchestrator } from \"../infrastructure/orchestrator.js\";\nimport type { ServiceHandle } from \"../infrastructure/services/service.port.js\";\nimport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nimport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nimport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport { createSpecificationRunner, type SpecificationRunner } from \"./specification.js\";\n\n/**\n * Resolve root — if relative, resolves from the caller's directory.\n */\nfunction resolveProjectRoot(root: string | undefined): string {\n if (!root) {\n return process.cwd();\n }\n\n if (isAbsolute(root)) {\n return root;\n }\n\n const stack = new Error(\"resolve root\").stack;\n if (stack) {\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n if (filePath.includes(\"node_modules\") || filePath.includes(\"/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\", root);\n }\n }\n\n return resolve(process.cwd(), root);\n}\n\n/**\n * Resolve a CLI command — checks node_modules/.bin, then treats as absolute/PATH.\n */\nfunction resolveCommand(command: string, root: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n // Check node_modules/.bin in fixture root\n const binPath = resolve(root, \"node_modules/.bin\", command);\n if (existsSync(binPath)) {\n return binPath;\n }\n\n // Check project root node_modules/.bin\n const cwdBinPath = resolve(process.cwd(), \"node_modules/.bin\", command);\n if (existsSync(cwdBinPath)) {\n return cwdBinPath;\n }\n\n // Treat as PATH command or absolute\n return command;\n}\n\ntype HonoApp = {\n fetch: (...args: any[]) => any;\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n};\n\ninterface IntegrationOptions {\n /** Factory that returns a Hono app — called after services start. */\n app: () => HonoApp;\n /** Project root for compose detection (relative paths supported). */\n root?: string;\n /** Declared services — started via testcontainers. */\n services: ServiceHandle[];\n}\n\ninterface E2eOptions {\n /** Project root — must contain docker/compose.test.yaml. */\n root?: string;\n}\n\ninterface CliOptions {\n /** CLI command to run (resolved from node_modules/.bin or PATH). */\n command: string;\n /** Project root — base dir for .project() fixture lookup (relative paths supported). */\n root?: string;\n /** Optional infrastructure services (started via testcontainers). */\n services?: ServiceHandle[];\n}\n\ninterface SpecificationRunnerWithCleanup extends SpecificationRunner {\n cleanup: () => Promise<void>;\n orchestrator: Orchestrator;\n}\n\n/**\n * Create an integration specification runner.\n * Starts infra containers via testcontainers, app runs in-process.\n */\nasync function integration(options: IntegrationOptions): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"integration\",\n root: resolveProjectRoot(options.root),\n services: options.services,\n });\n\n await orchestrator.start();\n\n const app = options.app();\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new HonoAdapter(app),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stop();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create an E2E specification runner.\n * Starts full docker compose stack. App URL and database auto-detected.\n */\nasync function e2e(options: E2eOptions = {}): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"e2e\",\n root: resolveProjectRoot(options.root),\n services: [],\n });\n\n await orchestrator.startCompose();\n\n const appUrl = orchestrator.getAppUrl();\n if (!appUrl) {\n throw new Error(\n \"E2E: could not detect app URL from compose. Ensure an app service with ports is defined.\",\n );\n }\n\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new FetchAdapter(appUrl),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stopCompose();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create a CLI specification runner.\n * Runs CLI commands against fixture projects. Optionally starts infrastructure.\n *\n * @example\n * export const spec = await cli({\n * command: resolve(import.meta.dirname, \"../../bin/my-cli.sh\"),\n * root: \"../fixtures\",\n * });\n */\nasync function cli(options: CliOptions): Promise<SpecificationRunnerWithCleanup> {\n const root = resolveProjectRoot(options.root);\n const command = resolveCommand(options.command, root);\n\n let orchestrator: null | Orchestrator = null;\n let database: DatabasePort | undefined;\n let databases: Map<string, DatabasePort> | undefined;\n\n if (options.services?.length) {\n orchestrator = new Orchestrator({\n mode: \"integration\",\n root,\n services: options.services,\n });\n await orchestrator.start();\n database = orchestrator.getDatabase() ?? undefined;\n const dbMap = orchestrator.getDatabases();\n databases = dbMap.size > 0 ? dbMap : undefined;\n }\n\n const runner = createSpecificationRunner({\n command: new ExecAdapter(command),\n database,\n databases,\n fixturesRoot: root,\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = async () => {\n if (orchestrator) {\n await orchestrator.stop();\n }\n };\n runner.orchestrator = orchestrator!;\n\n return runner;\n}\n\n// Service factories\nexport { postgres } from \"../infrastructure/services/postgres.js\";\nexport { redis } from \"../infrastructure/services/redis.js\";\n\n// Types\nexport type { CommandPort, CommandResult } from \"./ports/command.port.js\";\nexport type { DatabasePort } from \"./ports/database.port.js\";\nexport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// Adapters (for advanced usage)\nexport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nexport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nexport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nexport { Orchestrator } from \"../infrastructure/orchestrator.js\";\n\n// Reporter (for testing output)\nexport { normalizeOutput, stripAnsi } from \"../infrastructure/reporter.js\";\n\n// Runners\nexport { cli, e2e, integration };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["MockDatePackage","mockDeep","Client"],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts","../src/infrastructure/adapters/compose.adapter.ts","../src/infrastructure/adapters/testcontainers.adapter.ts","../src/infrastructure/compose-parser.ts","../src/infrastructure/reporter.ts","../src/infrastructure/services/postgres.ts","../src/infrastructure/services/redis.ts","../src/infrastructure/orchestrator.ts","../src/specification/adapters/exec.adapter.ts","../src/specification/adapters/fetch.adapter.ts","../src/specification/adapters/hono.adapter.ts","../src/specification/assertions/base.ts","../src/specification/assertions/file.ts","../src/specification/assertions/response.ts","../src/specification/assertions/string.ts","../src/specification/assertions/table.ts","../src/specification/assertions/value.ts","../src/specification/specification.ts","../src/specification/index.ts"],"sourcesContent":["import MockDatePackage from \"mockdate\";\n\nexport interface MockDatePort {\n reset: () => void;\n set: (date: Date | number | string) => void;\n}\n\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from \"vitest-mock-extended\";\n\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\nexport const mockOf: MockPort = mockDeep;\n","import { execSync } from \"node:child_process\";\nimport { dirname } from \"node:path\";\n\nimport type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using docker compose — runs full compose stack.\n * Used by e2e() to start all services including the app.\n */\nexport class ComposeAdapter implements ContainerPort {\n private composeFile: string;\n private serviceName: string;\n private started = false;\n\n constructor(composeFile: string, serviceName: string) {\n this.composeFile = composeFile;\n this.serviceName = serviceName;\n }\n\n private exec(command: string): string {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim();\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} up -d --wait ${this.serviceName}`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} rm -fsv ${this.serviceName}`);\n this.started = false;\n }\n\n getMappedPort(containerPort: number): number {\n const output = this.exec(\n `docker compose -f ${this.composeFile} port ${this.serviceName} ${containerPort}`,\n );\n // Output: 0.0.0.0:54321\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(0)}`;\n }\n\n async getLogs(): Promise<string> {\n try {\n return this.exec(`docker compose -f ${this.composeFile} logs ${this.serviceName} --tail=50`);\n } catch {\n return \"\";\n }\n }\n}\n\n/**\n * Start the full compose stack and stop it all on cleanup.\n */\nexport class ComposeStackAdapter {\n private composeFile: string;\n private started = false;\n\n constructor(composeFile: string) {\n this.composeFile = composeFile;\n }\n\n private run(command: string): string {\n try {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n timeout: 120_000,\n }).trim();\n } catch (error: any) {\n const stderr = error.stderr?.toString().trim() ?? error.message;\n throw new Error(`docker compose failed: ${stderr}`, { cause: error });\n }\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} up -d --wait`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} down -v`);\n this.started = false;\n }\n\n getMappedPort(serviceName: string, containerPort: number): number {\n const output = this.run(\n `docker compose -f ${this.composeFile} port ${serviceName} ${containerPort}`,\n );\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n}\n","import type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using testcontainers.\n * Wraps a GenericContainer for programmatic container lifecycle.\n */\nexport class TestcontainersAdapter implements ContainerPort {\n private image: string;\n private containerPort: number;\n private env: Record<string, string>;\n private reuse: boolean;\n private container: any = null;\n\n constructor(options: {\n image: string;\n port: number;\n env?: Record<string, string>;\n reuse?: boolean;\n }) {\n this.image = options.image;\n this.containerPort = options.port;\n this.env = options.env ?? {};\n this.reuse = options.reuse ?? false;\n }\n\n async start(): Promise<void> {\n const { GenericContainer, Wait } = await import(\"testcontainers\");\n\n let builder = new GenericContainer(this.image).withExposedPorts(this.containerPort);\n\n for (const [key, value] of Object.entries(this.env)) {\n builder = builder.withEnvironment({ [key]: value });\n }\n\n if (this.image.startsWith(\"postgres\")) {\n builder = builder.withWaitStrategy(\n Wait.forLogMessage(/database system is ready to accept connections/, 2),\n );\n }\n\n if (this.reuse) {\n builder = builder.withReuse();\n }\n\n this.container = await builder.start();\n }\n\n async stop(): Promise<void> {\n if (this.container && !this.reuse) {\n await this.container.stop();\n this.container = null;\n }\n }\n\n getMappedPort(containerPort: number): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(containerPort);\n }\n\n getHost(): string {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getHost();\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(this.containerPort)}`;\n }\n\n async getLogs(): Promise<string> {\n if (!this.container) {\n return \"\";\n }\n\n const stream = await this.container.logs();\n return new Promise((resolve) => {\n let output = \"\";\n stream.on(\"data\", (chunk: Buffer) => {\n output += chunk.toString();\n });\n stream.on(\"end\", () => {\n resolve(output);\n });\n // Timeout after 1s if stream doesn't end\n setTimeout(() => {\n resolve(output);\n }, 1000);\n });\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\n\n/**\n * A parsed service from docker-compose.test.yaml.\n */\nexport interface ComposeService {\n name: string;\n image?: string;\n build?: string;\n ports: { container: number; host?: number }[];\n environment: Record<string, string>;\n volumes: string[];\n dependsOn: string[];\n}\n\n/**\n * Result of parsing a compose file.\n */\nexport interface ComposeConfig {\n services: ComposeService[];\n appService: ComposeService | null;\n infraServices: ComposeService[];\n}\n\n/**\n * Detect the service type from the image name.\n */\nexport function detectServiceType(\n image: string | undefined,\n): \"app\" | \"postgres\" | \"redis\" | \"unknown\" {\n if (!image) {\n return \"app\";\n }\n\n const lower = image.toLowerCase();\n\n if (lower.startsWith(\"postgres\")) {\n return \"postgres\";\n }\n if (lower.startsWith(\"redis\")) {\n return \"redis\";\n }\n\n return \"unknown\";\n}\n\n/**\n * Find the compose file in the project.\n * Looks for docker/compose.test.yaml or docker-compose.test.yaml.\n */\nexport function findComposeFile(projectRoot: string): null | string {\n const candidates = [\n resolve(projectRoot, \"docker/compose.test.yaml\"),\n resolve(projectRoot, \"docker/compose.test.yml\"),\n resolve(projectRoot, \"docker-compose.test.yaml\"),\n resolve(projectRoot, \"docker-compose.test.yml\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * Parse a docker-compose file and extract service definitions.\n */\nexport function parseComposeFile(filePath: string): ComposeConfig {\n const content = readFileSync(filePath, \"utf8\");\n const doc = parseYaml(content);\n\n if (!doc?.services) {\n return { services: [], appService: null, infraServices: [] };\n }\n\n const services: ComposeService[] = Object.entries(doc.services).map(\n ([name, def]: [string, any]) => {\n const ports: { container: number; host?: number }[] = [];\n if (def.ports) {\n for (const port of def.ports) {\n const str = String(port);\n if (str.includes(\":\")) {\n const [host, container] = str.split(\":\");\n ports.push({ container: Number(container), host: Number(host) });\n } else {\n ports.push({ container: Number(str) });\n }\n }\n }\n\n const environment: Record<string, string> = {};\n if (def.environment) {\n if (Array.isArray(def.environment)) {\n for (const env of def.environment) {\n const [key, ...rest] = String(env).split(\"=\");\n environment[key] = rest.join(\"=\");\n }\n } else {\n Object.assign(environment, def.environment);\n }\n }\n\n const volumes: string[] = def.volumes ? def.volumes.map((v: string) => String(v)) : [];\n\n let dependsOn: string[] = [];\n if (def.depends_on) {\n dependsOn = Array.isArray(def.depends_on) ? def.depends_on : Object.keys(def.depends_on);\n }\n\n return {\n name,\n image: def.image,\n build: def.build,\n ports,\n environment,\n volumes,\n dependsOn,\n };\n },\n );\n\n const appService = services.find((s) => s.build !== undefined) ?? null;\n const infraServices = services.filter((s) => s.build === undefined);\n\n return { services, appService, infraServices };\n}\n","// ── Colors ──\n\nconst GREEN = \"\\x1b[32m\";\nconst RED = \"\\x1b[31m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\nconst RESET = \"\\x1b[0m\";\nconst BG_CYAN = \"\\x1b[46m\";\nconst BLACK = \"\\x1b[30m\";\n\n// ── Symbols (vitest-native) ──\n\nconst CHECK = \"✓\";\nconst CROSS = \"×\";\nconst ARROW = \"→\";\nconst DASH = \"⎯\";\n\n// ── Types ──\n\nexport interface ServiceReport {\n name: string;\n type: string;\n connectionString?: string;\n durationMs: number;\n error?: string;\n logs?: string;\n}\n\nexport interface AppInfo {\n type: \"http\" | \"in-process\";\n url?: string;\n}\n\n// ── Startup report ──\n\nexport function formatStartupReport(\n mode: \"e2e\" | \"integration\",\n services: ServiceReport[],\n app?: AppInfo,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(`${BG_CYAN}${BLACK}${BOLD} INFRA ${RESET} Starting infrastructure...`);\n lines.push(\"\");\n\n for (const service of services) {\n if (service.error) {\n lines.push(\n ` ${RED}${CROSS}${RESET} ${service.type} (${service.name}) ${RED}${service.error}${RESET} ${DIM}${service.durationMs}ms${RESET}`,\n );\n if (service.logs) {\n const logLines = service.logs.trim().split(\"\\n\").slice(-10);\n for (const logLine of logLines) {\n lines.push(` ${DIM}${logLine}${RESET}`);\n }\n }\n } else {\n const conn = service.connectionString ? `${DIM}${service.connectionString}${RESET}` : \"\";\n lines.push(\n ` ${GREEN}${CHECK}${RESET} ${service.type} (${service.name}) ${conn} ${DIM}${service.durationMs}ms${RESET}`,\n );\n }\n }\n\n if (app) {\n lines.push(\"\");\n if (app.type === \"in-process\") {\n lines.push(` ${DIM}${ARROW} app: in-process (Hono)${RESET}`);\n } else {\n lines.push(` ${DIM}${ARROW} app: ${app.url}${RESET}`);\n }\n }\n\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ── Error divider ──\n\nexport function formatErrorDivider(label: string): string {\n const dashes = DASH.repeat(30);\n return `\\n${RED}${dashes} ${label} ${dashes}${RESET}\\n`;\n}\n\n// ── Status error ──\n\nexport function formatStatusError(\n expectedStatus: number,\n receivedStatus: number,\n request: { method: string; path: string; body?: unknown },\n responseBody: unknown,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected status: ${GREEN}${expectedStatus}${RESET}`);\n lines.push(`Received status: ${RED}${receivedStatus}${RESET}`);\n lines.push(\"\");\n lines.push(`${DIM}${request.method} ${request.path}${RESET}`);\n\n if (request.body) {\n lines.push(formatJson(request.body, DIM));\n }\n\n if (responseBody) {\n lines.push(\"\");\n lines.push(`${DIM}Response:${RESET}`);\n lines.push(formatJson(responseBody, RED));\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Table diff ──\n\nexport function formatTableDiff(\n table: string,\n columns: string[],\n expected: unknown[][],\n actual: unknown[][],\n): string {\n const lines: string[] = [];\n\n lines.push(`Table \"${table}\" mismatch`);\n lines.push(`${DIM} query: ${columns.join(\", \")}${RESET}`);\n lines.push(`${DIM} expected: ${rowLabel(expected.length)}${RESET}`);\n lines.push(`${DIM} received: ${rowLabel(actual.length)}${RESET}`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const header = columns.join(\" | \");\n lines.push(`${DIM} ${header}${RESET}`);\n\n const maxRows = Math.max(expected.length, actual.length);\n\n for (let i = 0; i < maxRows; i++) {\n const exp = expected[i];\n const act = actual[i];\n\n if (exp && !act) {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n } else if (!exp && act) {\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n } else if (exp && act) {\n const same = JSON.stringify(exp) === JSON.stringify(act);\n if (same) {\n lines.push(` ${formatRow(act)}`);\n } else {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n }\n }\n }\n\n if (expected.length === 0 && actual.length === 0) {\n lines.push(` (empty)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Response diff ──\n\nexport function formatResponseDiff(file: string, expected: unknown, actual: unknown): string {\n const lines: string[] = [];\n\n lines.push(`Response mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = JSON.stringify(expected, null, 2).split(\"\\n\");\n const actualLines = JSON.stringify(actual, null, 2).split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Exit code error ──\n\nexport function formatExitCodeError(\n expected: number,\n received: number,\n stdout: string,\n stderr: string,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected exit code: ${GREEN}${expected}${RESET}`);\n lines.push(`Received exit code: ${RED}${received}${RESET}`);\n\n if (stdout.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stdout:${RESET}`);\n for (const line of stdout.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n if (stderr.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stderr:${RESET}`);\n for (const line of stderr.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${RED}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Stdout/stderr diff ──\n\nexport function formatStdoutDiff(file: string, expected: string, actual: string): string {\n const lines: string[] = [];\n\n lines.push(`Output mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = expected.split(\"\\n\");\n const actualLines = actual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── File assertions ──\n\nexport function formatFileMissing(path: string): string {\n return `Expected file to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileUnexpected(path: string): string {\n return `Expected file NOT to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileContentMismatch(path: string, expected: string, actual: string): string {\n const lines: string[] = [];\n lines.push(`File \"${path}\" does not contain expected content`);\n lines.push(\"\");\n lines.push(`${GREEN}Expected to contain:${RESET}`);\n lines.push(` ${GREEN}${expected}${RESET}`);\n lines.push(\"\");\n lines.push(`${RED}Actual content (first 20 lines):${RESET}`);\n for (const line of actual.split(\"\\n\").slice(0, 20)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ── Service logs section ──\n\nexport function formatServiceLogs(services: { name: string; logs: string }[]): string {\n const lines: string[] = [];\n\n for (const { name, logs } of services) {\n if (!logs.trim()) {\n continue;\n }\n\n lines.push(\"\");\n lines.push(`${DIM}${name} logs (last 10 lines):${RESET}`);\n\n const logLines = logs.trim().split(\"\\n\").slice(-10);\n for (const line of logLines) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Helpers ──\n\nfunction rowLabel(n: number): string {\n return n === 1 ? \"1 row\" : `${n} rows`;\n}\n\nfunction formatJson(value: unknown, color: string): string {\n return JSON.stringify(value, null, 2)\n .split(\"\\n\")\n .map((line) => `${color}${line}${RESET}`)\n .join(\"\\n\");\n}\n\nfunction formatRow(row: unknown[]): string {\n return row.map((v) => String(v ?? \"null\")).join(\" | \");\n}\n\n// ── Test utilities ──\n\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function normalizeOutput(str: string): string {\n return stripAnsi(str)\n .replace(/localhost:\\d+/g, \"localhost:PORT\")\n .replace(/\\d+ms/g, \"Xms\")\n .replace(/\\d+\\.\\d+s/g, \"X.Xs\")\n .trim();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { Client } from \"pg\";\n\nimport type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface PostgresOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n /** Override environment variables. */\n env?: Record<string, string>;\n}\n\nclass PostgresHandle implements DatabasePort, ServiceHandle {\n readonly type = \"postgres\";\n readonly composeName: null | string;\n readonly defaultPort = 5432;\n readonly defaultImage: string;\n readonly environment: Record<string, string>;\n\n connectionString = \"\";\n started = false;\n\n private client: Client | null = null;\n\n constructor(options: PostgresOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"postgres:17\";\n this.environment = {\n POSTGRES_DB: \"test\",\n POSTGRES_PASSWORD: \"test\",\n POSTGRES_USER: \"test\",\n ...options.env,\n };\n }\n\n buildConnectionString(host: string, port: number): string {\n const user = this.environment.POSTGRES_USER ?? \"test\";\n const password = this.environment.POSTGRES_PASSWORD ?? \"test\";\n const db = this.environment.POSTGRES_DB ?? \"test\";\n return `postgresql://${user}:${password}@${host}:${port}/${db}`;\n }\n\n createDatabaseAdapter(): DatabasePort {\n return this;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"postgres: cannot healthcheck — no connection string\");\n }\n\n // Healthcheck uses a throwaway client (connection might not be established yet)\n try {\n const client = new Client({ connectionString: this.connectionString });\n await client.connect();\n await client.query(\"SELECT 1\");\n await client.end();\n } catch (error: any) {\n throw new Error(\n `postgres healthcheck failed: ${error.message || error.code || String(error)}`,\n { cause: error },\n );\n }\n }\n\n async initialize(composeDir: string): Promise<void> {\n if (!this.composeName) {\n return;\n }\n\n const initPaths = [\n resolve(composeDir, `${this.composeName}/init.sql`),\n resolve(composeDir, \"postgres/init.sql\"),\n ];\n\n for (const initPath of initPaths) {\n if (existsSync(initPath)) {\n const sql = readFileSync(initPath, \"utf8\");\n try {\n await this.seed(sql);\n } catch (error: any) {\n throw new Error(`postgres init script failed (${initPath}):\\n${error.message}`, {\n cause: error,\n });\n }\n return;\n }\n }\n }\n\n private async getClient(): Promise<Client> {\n if (this.client) {\n return this.client;\n }\n const client = new Client({ connectionString: this.connectionString });\n client.on(\"error\", () => {\n // Connection dropped (container stopped) — reset so next call reconnects\n this.client = null;\n });\n await client.connect();\n this.client = client;\n return client;\n }\n\n async seed(sql: string): Promise<void> {\n const client = await this.getClient();\n await client.query(sql);\n }\n\n async query(table: string, columns: string[]): Promise<unknown[][]> {\n const client = await this.getClient();\n const columnList = columns.join(\", \");\n const result = await client.query(`SELECT ${columnList} FROM \"${table}\" ORDER BY 1`);\n return result.rows.map((row: Record<string, unknown>) => columns.map((col) => row[col]));\n }\n\n async reset(): Promise<void> {\n const client = await this.getClient();\n const result = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of result.rows) {\n await client.query(`TRUNCATE \"${row.tablename}\" CASCADE`);\n }\n }\n}\n\n/**\n * Create a PostgreSQL service handle.\n *\n * @example\n * const db = postgres({ compose: \"db\" });\n * // After start: db.connectionString is populated\n */\nexport function postgres(options: PostgresOptions = {}): PostgresHandle {\n return new PostgresHandle(options);\n}\n","import type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nclass RedisHandle implements ServiceHandle {\n readonly type = \"redis\";\n readonly composeName: null | string;\n readonly defaultPort = 6379;\n readonly defaultImage: string;\n readonly environment: Record<string, string> = {};\n\n connectionString = \"\";\n started = false;\n\n constructor(options: RedisOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"redis:7\";\n }\n\n buildConnectionString(host: string, port: number): string {\n return `redis://${host}:${port}`;\n }\n\n createDatabaseAdapter(): DatabasePort | null {\n return null;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"redis: cannot healthcheck — no connection string\");\n }\n\n try {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n await client.ping();\n await client.disconnect();\n } catch (error: any) {\n throw new Error(`redis healthcheck failed: ${error.message || error.code || String(error)}`, {\n cause: error,\n });\n }\n }\n\n async initialize(): Promise<void> {\n // Redis doesn't need initialization scripts\n }\n\n async reset(): Promise<void> {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n try {\n await client.flushAll();\n } finally {\n await client.disconnect();\n }\n }\n}\n\n/**\n * Create a Redis service handle.\n *\n * @example\n * const cache = redis({ compose: \"cache\" });\n * // After start: cache.connectionString is populated\n */\nexport function redis(options: RedisOptions = {}): RedisHandle {\n return new RedisHandle(options);\n}\n","import { dirname } from \"node:path\";\n\nimport type { DatabasePort } from \"../specification/ports/database.port.js\";\nimport { ComposeStackAdapter } from \"./adapters/compose.adapter.js\";\nimport { TestcontainersAdapter } from \"./adapters/testcontainers.adapter.js\";\nimport { detectServiceType, findComposeFile, parseComposeFile } from \"./compose-parser.js\";\nimport type { ContainerPort } from \"./ports/container.port.js\";\nimport { type AppInfo, formatStartupReport, type ServiceReport } from \"./reporter.js\";\nimport { postgres } from \"./services/postgres.js\";\nimport { redis } from \"./services/redis.js\";\nimport type { ServiceHandle } from \"./services/service.port.js\";\n\ninterface RunningService {\n handle: ServiceHandle;\n container: ContainerPort | null;\n}\n\ninterface OrchestratorOptions {\n services: ServiceHandle[];\n mode: \"e2e\" | \"integration\";\n root?: string;\n}\n\n/**\n * Orchestrator for test infrastructure.\n * Integration: starts services via testcontainers.\n * E2E: runs full docker compose up.\n */\nexport class Orchestrator {\n private services: ServiceHandle[];\n private mode: \"e2e\" | \"integration\";\n private root: string;\n private running: RunningService[] = [];\n private composeStack: ComposeStackAdapter | null = null;\n private composeHandles: ServiceHandle[] = [];\n private started = false;\n\n constructor(options: OrchestratorOptions) {\n this.services = options.services;\n this.mode = options.mode;\n this.root = options.root ?? process.cwd();\n }\n\n /**\n * Start declared services via testcontainers (integration mode).\n * Phase 1: start all containers in parallel (the slow part).\n * Phase 2: wire connections, healthcheck, and init sequentially (fast).\n */\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n const composePath = findComposeFile(this.root);\n const composeDir = composePath ? dirname(composePath) : this.root;\n const composeConfig = composePath ? parseComposeFile(composePath) : null;\n\n // Phase 1: resolve config and start all containers in parallel\n const containerTasks = this.services.map((handle) => {\n let image = handle.defaultImage;\n let env = { ...handle.environment };\n\n if (handle.composeName && composeConfig) {\n const composeService = composeConfig.services.find((s) => s.name === handle.composeName);\n if (composeService) {\n image = composeService.image ?? image;\n env = { ...env, ...composeService.environment };\n Object.assign(handle.environment, composeService.environment);\n }\n }\n\n const container = new TestcontainersAdapter({ image, port: handle.defaultPort, env });\n return { container, handle };\n });\n\n // Start all containers concurrently\n await Promise.all(containerTasks.map(({ container }) => container.start()));\n\n // Phase 2: wire connections, healthcheck, init (fast — containers already running)\n const reports: ServiceReport[] = [];\n\n for (const { container, handle } of containerTasks) {\n const serviceStartTime = Date.now();\n\n try {\n const host = container.getHost();\n const port = container.getMappedPort(handle.defaultPort);\n handle.connectionString = handle.buildConnectionString(host, port);\n\n await handle.healthcheck();\n await handle.initialize(composeDir);\n handle.started = true;\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n connectionString: handle.connectionString,\n durationMs: Date.now() - serviceStartTime,\n });\n this.running.push({ handle, container });\n } catch (error: any) {\n let logs = \"\";\n try {\n logs = await container.getLogs();\n } catch {\n /* Ignore log fetch errors */\n }\n try {\n await container.stop();\n } catch {\n /* Ignore stop errors */\n }\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n durationMs: Date.now() - serviceStartTime,\n error: error.message,\n logs,\n });\n\n const output = formatStartupReport(\"integration\", reports, { type: \"in-process\" });\n console.error(output);\n throw error;\n }\n }\n\n this.started = true;\n\n const appInfo: AppInfo = { type: \"in-process\" };\n const output = formatStartupReport(\"integration\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop testcontainers (integration mode).\n */\n async stop(): Promise<void> {\n for (const { container } of this.running) {\n if (container) {\n await container.stop();\n }\n }\n this.running = [];\n this.started = false;\n }\n\n /**\n * Start full docker compose stack (e2e mode).\n * Auto-detects infra services and creates handles for them.\n */\n async startCompose(): Promise<void> {\n const composePath = findComposeFile(this.root);\n if (!composePath) {\n throw new Error(`E2E: no compose file found in ${this.root}`);\n }\n\n const startTime = Date.now();\n const composeDir = dirname(composePath);\n const composeConfig = parseComposeFile(composePath);\n\n this.composeStack = new ComposeStackAdapter(composePath);\n await this.composeStack.start();\n\n // Create handles for detected infra services\n for (const service of composeConfig.infraServices) {\n const type = detectServiceType(service.image);\n\n if (type === \"postgres\") {\n const handle = postgres({ compose: service.name, env: service.environment });\n const port = this.composeStack.getMappedPort(service.name, 5432);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n\n await handle.initialize(composeDir);\n handle.started = true;\n\n this.composeHandles.push(handle);\n } else if (type === \"redis\") {\n const handle = redis({ compose: service.name });\n const port = this.composeStack.getMappedPort(service.name, 6379);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n handle.started = true;\n\n this.composeHandles.push(handle);\n }\n }\n\n const durationMs = Date.now() - startTime;\n const reports: ServiceReport[] = this.composeHandles.map((h) => ({\n name: h.composeName ?? h.type,\n type: h.type,\n connectionString: h.connectionString,\n durationMs,\n }));\n\n const appUrl = this.getAppUrl();\n const appInfo: AppInfo = { type: \"http\", url: appUrl ?? undefined };\n const output = formatStartupReport(\"e2e\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop docker compose stack (e2e mode).\n */\n async stopCompose(): Promise<void> {\n if (this.composeStack) {\n await this.composeStack.stop();\n this.composeStack = null;\n }\n this.composeHandles = [];\n }\n\n /**\n * Get a database service by compose name, or the first one if no name given.\n */\n getDatabase(serviceName?: string): DatabasePort | null {\n for (const handle of [...this.services, ...this.composeHandles]) {\n if (serviceName && handle.composeName !== serviceName) {\n continue;\n }\n const adapter = handle.createDatabaseAdapter();\n if (adapter) {\n return adapter;\n }\n }\n return null;\n }\n\n /**\n * Get all database services keyed by compose name.\n */\n getDatabases(): Map<string, DatabasePort> {\n const map = new Map<string, DatabasePort>();\n for (const handle of [...this.services, ...this.composeHandles]) {\n const adapter = handle.createDatabaseAdapter();\n if (adapter && handle.composeName) {\n map.set(handle.composeName, adapter);\n }\n }\n return map;\n }\n\n /**\n * Get app URL from compose (e2e mode).\n */\n getAppUrl(): null | string {\n const composePath = findComposeFile(this.root);\n if (!composePath || !this.composeStack) {\n return null;\n }\n\n const config = parseComposeFile(composePath);\n const appService = config.appService;\n\n if (!appService || appService.ports.length === 0) {\n return null;\n }\n\n const port = this.composeStack.getMappedPort(appService.name, appService.ports[0].container);\n return `http://localhost:${port}`;\n }\n}\n","import { execSync, spawn } from \"node:child_process\";\n\nimport type { CommandPort, CommandResult, SpawnOptions } from \"../ports/command.port.js\";\n\n/**\n * Executes CLI commands via execSync (blocking) or spawn (long-running).\n * Used by cli() for local command execution.\n */\nexport class ExecAdapter implements CommandPort {\n private command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n\n async exec(args: string, cwd: string): Promise<CommandResult> {\n // Clear INIT_CWD so CLI tools use the actual cwd, not npm's caller directory\n const env = { ...process.env, INIT_CWD: undefined };\n\n try {\n const stdout = execSync(`${this.command} ${args}`, {\n cwd,\n encoding: \"utf8\",\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { exitCode: 0, stdout, stderr: \"\" };\n } catch (error: any) {\n return {\n exitCode: error.status ?? 1,\n stdout: error.stdout?.toString() ?? \"\",\n stderr: error.stderr?.toString() ?? \"\",\n };\n }\n }\n\n async spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult> {\n const env = { ...process.env, INIT_CWD: undefined };\n\n return new Promise((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let resolved = false;\n\n const child = spawn(this.command, args.split(/\\s+/).filter(Boolean), {\n cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const finish = (exitCode: number) => {\n if (resolved) {\n return;\n }\n resolved = true;\n child.kill(\"SIGTERM\");\n resolve({ exitCode, stdout, stderr });\n };\n\n let patternMatched = false;\n\n const checkPattern = () => {\n if (\n !patternMatched &&\n (stdout.includes(options.waitFor) || stderr.includes(options.waitFor))\n ) {\n patternMatched = true;\n finish(0);\n }\n };\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n checkPattern();\n });\n\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n checkPattern();\n });\n\n // Process exited before pattern matched\n child.on(\"exit\", (code) => {\n if (!patternMatched) {\n finish(code === 0 ? 1 : (code ?? 1));\n }\n });\n\n setTimeout(() => finish(124), options.timeout);\n });\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for real HTTP — sends actual fetch requests.\n * Used by e2e() specification runner.\n */\nexport class FetchAdapter implements ServerPort {\n private baseUrl: string;\n\n constructor(url: string) {\n this.baseUrl = url.replace(/\\/$/, \"\");\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(`${this.baseUrl}${path}`, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for Hono — in-process requests, no real HTTP.\n * Used by integration() specification runner.\n */\nexport class HonoAdapter implements ServerPort {\n private app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n };\n\n constructor(app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n }) {\n this.app = app;\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await this.app.request(path, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","/**\n * Base assertion that handles .not negation.\n * Subclasses call this.assert(condition, message, negatedMessage) for each predicate.\n */\nexport class BaseAssertion {\n protected negated = false;\n\n get not(): this {\n const clone = Object.create(Object.getPrototypeOf(this));\n Object.assign(clone, this);\n clone.negated = !this.negated;\n return clone;\n }\n\n protected assert(condition: boolean, message: string, negatedMessage: string): void {\n if (this.negated) {\n if (condition) {\n throw new Error(negatedMessage);\n }\n } else {\n if (!condition) {\n throw new Error(message);\n }\n }\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport {\n formatFileContentMismatch,\n formatFileMissing,\n formatFileUnexpected,\n} from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a file in the working directory.\n * Usage: result.file(\"dist/index.js\").toExist()\n */\nexport class FileAssertion extends BaseAssertion {\n private filePath: string;\n private resolvedPath: string;\n\n constructor(filePath: string, workDir: string) {\n super();\n this.filePath = filePath;\n this.resolvedPath = resolve(workDir, filePath);\n }\n\n toExist(): void {\n const exists = existsSync(this.resolvedPath);\n this.assert(exists, formatFileMissing(this.filePath), formatFileUnexpected(this.filePath));\n }\n\n toContain(expected: string): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return; // File doesn't exist, so it certainly doesn't contain the string\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = content.includes(expected);\n this.assert(\n found,\n formatFileContentMismatch(this.filePath, expected, content),\n `Expected file \"${this.filePath}\" NOT to contain \"${expected}\"`,\n );\n }\n\n toMatch(pattern: RegExp): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return;\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = pattern.test(content);\n this.assert(\n found,\n `Expected file \"${this.filePath}\" to match: ${pattern}\\n\\nActual content:\\n${content.slice(0, 500)}`,\n `Expected file \"${this.filePath}\" NOT to match: ${pattern}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatResponseDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on an HTTP response body.\n * Usage: result.response.toMatchFile(\"expected.json\")\n */\nexport class ResponseAssertion extends BaseAssertion {\n private body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\n super();\n this.body = body;\n this.testDir = testDir;\n }\n\n toMatchFile(file: string): void {\n const expected = JSON.parse(readFileSync(resolve(this.testDir, \"responses\", file), \"utf8\"));\n const match = JSON.stringify(this.body) === JSON.stringify(expected);\n this.assert(\n match,\n formatResponseDiff(file, expected, this.body),\n `Expected response NOT to match file \"${file}\", but it did`,\n );\n }\n\n toContain(subset: Record<string, unknown>): void {\n const bodyStr = JSON.stringify(this.body);\n const subsetStr = JSON.stringify(subset);\n // Check if all keys in subset exist with same values in body\n const bodyObj = typeof this.body === \"object\" && this.body !== null ? this.body : {};\n const match = Object.entries(subset).every(\n ([key, value]) =>\n JSON.stringify((bodyObj as Record<string, unknown>)[key]) === JSON.stringify(value),\n );\n this.assert(\n match,\n `Expected response to contain: ${subsetStr}\\n\\nActual response:\\n${bodyStr}`,\n `Expected response NOT to contain: ${subsetStr}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatStdoutDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a string (stdout, stderr, response body).\n * Usage: result.stdout.toContain(\"hello\")\n */\nexport class StringAssertion extends BaseAssertion {\n private actual: string;\n private label: string;\n private testDir?: string;\n\n constructor(actual: string, label: string, testDir?: string) {\n super();\n this.actual = actual;\n this.label = label;\n this.testDir = testDir;\n }\n\n toContain(expected: string, options?: { near?: string }): void {\n if (options?.near) {\n const found = this.containsNear(expected, options.near);\n this.assert(\n found,\n `Expected ${this.label} to contain \"${expected}\" near \"${options.near}\"\\n\\n${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain \"${expected}\" near \"${options.near}\", but it was found`,\n );\n } else {\n const found = this.actual.includes(expected);\n this.assert(\n found,\n `Expected ${this.label} to contain: \"${expected}\"\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain: \"${expected}\"`,\n );\n }\n }\n\n toMatch(pattern: RegExp): void {\n const found = pattern.test(this.actual);\n this.assert(\n found,\n `Expected ${this.label} to match: ${pattern}\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to match: ${pattern}`,\n );\n }\n\n toMatchFile(file: string): void {\n if (!this.testDir) {\n throw new Error(\"toMatchFile requires a test directory context\");\n }\n const expected = readFileSync(resolve(this.testDir, \"expected\", file), \"utf8\").trim();\n const actual = this.actual.trim();\n const match = actual === expected;\n this.assert(\n match,\n formatStdoutDiff(file, expected, actual),\n `Expected ${this.label} NOT to match file \"${file}\", but it did`,\n );\n }\n\n toBeEmpty(): void {\n const empty = this.actual.trim() === \"\";\n this.assert(\n empty,\n `Expected ${this.label} to be empty\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to be empty`,\n );\n }\n\n // ── Private ──\n\n private containsNear(target: string, near: string, proximity = 500): boolean {\n const clean = this.stripAnsi(this.actual);\n const nearLower = near.toLowerCase();\n const targetLower = target.toLowerCase();\n\n // Find all occurrences of `near` and check if `target` appears within proximity\n let searchFrom = 0;\n while (true) {\n const idx = clean.toLowerCase().indexOf(nearLower, searchFrom);\n if (idx === -1) {\n break;\n }\n const windowStart = Math.max(0, idx - proximity);\n const windowEnd = Math.min(clean.length, idx + nearLower.length + proximity);\n const window = clean.substring(windowStart, windowEnd).toLowerCase();\n if (window.includes(targetLower)) {\n return true;\n }\n searchFrom = idx + 1;\n }\n return false;\n }\n\n private stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n }\n\n private truncate(str: string, maxLines = 20): string {\n const lines = str.split(\"\\n\");\n if (lines.length <= maxLines) {\n return str;\n }\n return `${lines.slice(0, maxLines).join(\"\\n\")}\\n... (${lines.length - maxLines} more lines)`;\n }\n}\n","import { formatTableDiff } from \"../../infrastructure/reporter.js\";\nimport type { DatabasePort } from \"../ports/database.port.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a database table.\n * Usage: await result.table(\"users\").toMatch({ columns: [\"name\"], rows: [[\"Alice\"]] })\n */\nexport class TableAssertion extends BaseAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\n super();\n this.tableName = tableName;\n this.db = db;\n }\n\n async toMatch(expected: { columns: string[]; rows: unknown[][] }): Promise<void> {\n const actual = await this.db.query(this.tableName, expected.columns);\n const match = JSON.stringify(actual) === JSON.stringify(expected.rows);\n this.assert(\n match,\n formatTableDiff(this.tableName, expected.columns, expected.rows, actual),\n `Expected table \"${this.tableName}\" NOT to match, but it did`,\n );\n }\n\n async toBeEmpty(): Promise<void> {\n // Query a single column to check if table has rows — use a lightweight approach\n const actual = await this.db.query(this.tableName, [\"*\"]);\n const empty = actual.length === 0;\n this.assert(\n empty,\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n `Expected table \"${this.tableName}\" NOT to be empty, but it is`,\n );\n }\n}\n","import { formatExitCodeError, formatStatusError } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a single value (exit code, status code).\n * Usage: result.exitCode.toBe(0)\n */\nexport class ValueAssertion extends BaseAssertion {\n private actual: number;\n private label: string;\n private context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string };\n\n constructor(\n actual: number,\n label: string,\n context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string },\n ) {\n super();\n this.actual = actual;\n this.label = label;\n this.context = context;\n }\n\n toBe(expected: number): void {\n const match = this.actual === expected;\n\n let message: string;\n if (this.label === \"exit code\" && this.context?.stdout !== undefined) {\n message = formatExitCodeError(\n expected,\n this.actual,\n this.context.stdout ?? \"\",\n this.context.stderr ?? \"\",\n );\n } else if (this.label === \"status\" && this.context?.request) {\n message = formatStatusError(\n expected,\n this.actual,\n this.context.request,\n this.context.responseBody,\n );\n } else {\n message = `Expected ${this.label}: ${expected}\\nReceived ${this.label}: ${this.actual}`;\n }\n\n this.assert(match, message, `Expected ${this.label} NOT to be ${expected}, but it was`);\n }\n}\n","import { cpSync, existsSync, mkdtempSync, readFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\nimport { FileAssertion } from \"./assertions/file.js\";\nimport { ResponseAssertion } from \"./assertions/response.js\";\nimport { StringAssertion } from \"./assertions/string.js\";\nimport { TableAssertion } from \"./assertions/table.js\";\nimport { ValueAssertion } from \"./assertions/value.js\";\nimport type { CommandPort, CommandResult, SpawnOptions } from \"./ports/command.port.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// ── Types ──\n\nexport interface SpecificationConfig {\n command?: CommandPort;\n database?: DatabasePort;\n databases?: Map<string, DatabasePort>;\n fixturesRoot?: string;\n server?: ServerPort;\n}\n\nexport interface SeedEntry {\n file: string;\n service?: string;\n}\n\nexport interface FixtureEntry {\n file: string;\n}\n\nexport interface MockEntry {\n file: string;\n}\n\nexport interface RequestEntry {\n bodyFile?: string;\n method: string;\n path: string;\n}\n\n// ── Result (after .run()) ──\n\ninterface RequestInfo {\n body?: unknown;\n method: string;\n path: string;\n}\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: RequestInfo;\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: RequestInfo;\n response?: ServerResponse;\n testDir: string;\n workDir?: string;\n }) {\n this.responseData = options.response;\n this.commandResult = options.commandResult;\n this.config = options.config;\n this.testDir = options.testDir;\n this.requestInfo = options.requestInfo;\n this.workDir = options.workDir;\n }\n\n // ── Scoped assertion accessors ──\n\n get exitCode(): ValueAssertion {\n if (!this.commandResult) {\n throw new Error(\".exitCode requires a CLI action (.exec())\");\n }\n return new ValueAssertion(this.commandResult.exitCode, \"exit code\", {\n stderr: this.commandResult.stderr,\n stdout: this.commandResult.stdout,\n });\n }\n\n get status(): ValueAssertion {\n if (!this.responseData || !this.requestInfo) {\n throw new Error(\".status requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ValueAssertion(this.responseData.status, \"status\", {\n request: this.requestInfo,\n responseBody: this.responseData.body,\n });\n }\n\n get response(): ResponseAssertion {\n if (!this.responseData) {\n throw new Error(\".response requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ResponseAssertion(this.responseData.body, this.testDir);\n }\n\n get stdout(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stdout requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stdout, \"stdout\", this.testDir);\n }\n\n get stderr(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stderr requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stderr, \"stderr\", this.testDir);\n }\n\n file(path: string): FileAssertion {\n const baseDir = this.workDir ?? this.testDir;\n return new FileAssertion(path, baseDir);\n }\n\n table(tableName: string, options?: { service?: string }): TableAssertion {\n const db = this.resolveDatabase(options?.service);\n if (!db) {\n throw new Error(\n options?.service\n ? `table(\"${tableName}\") requires database \"${options.service}\" but it was not found`\n : `table(\"${tableName}\") requires a database adapter`,\n );\n }\n return new TableAssertion(tableName, db);\n }\n\n // ── Private ──\n\n private resolveDatabase(serviceName?: string): DatabasePort | undefined {\n if (serviceName && this.config.databases) {\n return this.config.databases.get(serviceName);\n }\n return this.config.database;\n }\n}\n\n// ── Builder (before .run()) ──\n\nexport class SpecificationBuilder {\n private commandArgs: null | string | string[] = null;\n private config: SpecificationConfig;\n private fixtures: FixtureEntry[] = [];\n private label: string;\n private mocks: MockEntry[] = [];\n private projectName: null | string = null;\n private request: null | RequestEntry = null;\n private seeds: SeedEntry[] = [];\n private spawnConfig: null | { args: string; options: SpawnOptions } = null;\n private testDir: string;\n\n constructor(config: SpecificationConfig, testDir: string, label: string) {\n this.config = config;\n this.testDir = testDir;\n this.label = label;\n }\n\n // ── Setup ──\n\n seed(file: string, options?: { service?: string }): this {\n this.seeds.push({ file, service: options?.service });\n return this;\n }\n\n fixture(file: string): this {\n this.fixtures.push({ file });\n return this;\n }\n\n project(name: string): this {\n this.projectName = name;\n return this;\n }\n\n mock(file: string): this {\n this.mocks.push({ file });\n return this;\n }\n\n // ── HTTP actions ──\n\n get(path: string): this {\n this.request = { method: \"GET\", path };\n return this;\n }\n\n post(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"POST\", path };\n return this;\n }\n\n put(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"PUT\", path };\n return this;\n }\n\n delete(path: string): this {\n this.request = { method: \"DELETE\", path };\n return this;\n }\n\n // ── CLI actions ──\n\n exec(args: string | string[]): this {\n this.commandArgs = args;\n return this;\n }\n\n spawn(args: string, options: SpawnOptions): this {\n this.spawnConfig = { args, options };\n return this;\n }\n\n // ── Run ──\n\n async run(): Promise<SpecificationResult> {\n const hasHttpAction = this.request !== null;\n const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;\n\n if (!hasHttpAction && !hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": no action defined. Call .get(), .post(), .exec(), etc. before .run()`,\n );\n }\n\n if (hasHttpAction && hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`,\n );\n }\n\n // Resolve working directory for CLI mode\n let workDir: null | string = null;\n if (hasCliAction) {\n workDir = this.prepareWorkDir();\n }\n\n // Reset all databases\n if (this.config.databases) {\n for (const db of this.config.databases.values()) {\n await db.reset();\n }\n } else if (this.config.database) {\n await this.config.database.reset();\n }\n\n // Execute seeds\n for (const entry of this.seeds) {\n let db: DatabasePort | undefined;\n if (entry.service && this.config.databases) {\n db = this.config.databases.get(entry.service);\n if (!db) {\n throw new Error(\n `seed() targets database \"${entry.service}\" but it was not found. Available: ${[...this.config.databases.keys()].join(\", \")}`,\n );\n }\n } else {\n db = this.config.database;\n }\n\n if (!db) {\n throw new Error(\"seed() requires a database adapter\");\n }\n\n const sql = readFileSync(resolve(this.testDir, \"seeds\", entry.file), \"utf8\");\n await db.seed(sql);\n }\n\n // Copy fixture files into working directory\n if (this.fixtures.length > 0 && workDir) {\n for (const entry of this.fixtures) {\n const src = resolve(this.testDir, \"fixtures\", entry.file);\n const dest = resolve(workDir, entry.file);\n cpSync(src, dest, { recursive: true });\n }\n }\n\n // Register MSW mocks\n for (const entry of this.mocks) {\n const _mockData = JSON.parse(readFileSync(resolve(this.testDir, \"mock\", entry.file), \"utf8\"));\n // TODO: Register MSW handler from mock data\n }\n\n // Execute action\n if (hasHttpAction) {\n return this.runHttpAction();\n }\n return this.runCliAction(workDir!);\n }\n\n // ── Private ──\n\n private prepareWorkDir(): string {\n // No project or fixtures — run from fixturesRoot or cwd (no temp dir needed)\n if (!this.projectName && this.fixtures.length === 0) {\n return this.config.fixturesRoot ?? process.cwd();\n }\n\n const tempDir = mkdtempSync(resolve(tmpdir(), \"spec-cli-\"));\n\n if (this.projectName && this.config.fixturesRoot) {\n const projectDir = resolve(this.config.fixturesRoot, this.projectName);\n if (!existsSync(projectDir)) {\n throw new Error(\n `project(\"${this.projectName}\"): fixture project not found at ${projectDir}`,\n );\n }\n cpSync(projectDir, tempDir, { recursive: true });\n }\n\n return tempDir;\n }\n\n private async runHttpAction(): Promise<SpecificationResult> {\n if (!this.config.server) {\n throw new Error(\"HTTP actions require a server adapter (use integration() or e2e())\");\n }\n\n let body: unknown;\n if (this.request!.bodyFile) {\n body = JSON.parse(\n readFileSync(resolve(this.testDir, \"requests\", this.request!.bodyFile), \"utf8\"),\n );\n }\n\n const response = await this.config.server.request(\n this.request!.method,\n this.request!.path,\n body,\n );\n\n return new SpecificationResult({\n config: this.config,\n requestInfo: { body, method: this.request!.method, path: this.request!.path },\n response,\n testDir: this.testDir,\n });\n }\n\n private async runCliAction(workDir: string): Promise<SpecificationResult> {\n if (!this.config.command) {\n throw new Error(\"CLI actions require a command adapter (use cli())\");\n }\n\n let commandResult: CommandResult;\n\n if (this.spawnConfig) {\n commandResult = await this.config.command.spawn(\n this.spawnConfig.args,\n workDir,\n this.spawnConfig.options,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stdout: \"\", stderr: \"\" };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir);\n }\n\n return new SpecificationResult({\n commandResult,\n config: this.config,\n testDir: this.testDir,\n workDir,\n });\n }\n}\n\n// ── Caller detection ──\n\nfunction getCallerDir(): string {\n const stack = new Error(\"caller detection\").stack;\n if (!stack) {\n throw new Error(\"Cannot detect caller directory: no stack trace\");\n }\n\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n\n if (filePath.includes(\"node_modules\")) {\n continue;\n }\n if (filePath.includes(\"/src/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\");\n }\n\n throw new Error(\"Cannot detect caller directory from stack trace\");\n}\n\n// ── Factory functions ──\n\nexport type SpecificationRunner = (label: string) => SpecificationBuilder;\n\n/**\n * Create a specification runner.\n * Automatically detects the test directory from the call site.\n */\nexport function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner {\n return (label: string) => {\n const testDir = getCallerDir();\n return new SpecificationBuilder(config, testDir, label);\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { Orchestrator } from \"../infrastructure/orchestrator.js\";\nimport type { ServiceHandle } from \"../infrastructure/services/service.port.js\";\nimport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nimport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nimport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport { createSpecificationRunner, type SpecificationRunner } from \"./specification.js\";\n\n/**\n * Resolve root — if relative, resolves from the caller's directory.\n */\nfunction resolveProjectRoot(root: string | undefined): string {\n if (!root) {\n return process.cwd();\n }\n\n if (isAbsolute(root)) {\n return root;\n }\n\n const stack = new Error(\"resolve root\").stack;\n if (stack) {\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n if (filePath.includes(\"node_modules\") || filePath.includes(\"/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\", root);\n }\n }\n\n return resolve(process.cwd(), root);\n}\n\n/**\n * Resolve a CLI command — checks node_modules/.bin, then treats as absolute/PATH.\n */\nfunction resolveCommand(command: string, root: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n // Check node_modules/.bin in fixture root\n const binPath = resolve(root, \"node_modules/.bin\", command);\n if (existsSync(binPath)) {\n return binPath;\n }\n\n // Check project root node_modules/.bin\n const cwdBinPath = resolve(process.cwd(), \"node_modules/.bin\", command);\n if (existsSync(cwdBinPath)) {\n return cwdBinPath;\n }\n\n // Treat as PATH command or absolute\n return command;\n}\n\ntype HonoApp = {\n fetch: (...args: any[]) => any;\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n};\n\ninterface IntegrationOptions {\n /** Factory that returns a Hono app — called after services start. */\n app: () => HonoApp;\n /** Project root for compose detection (relative paths supported). */\n root?: string;\n /** Declared services — started via testcontainers. */\n services: ServiceHandle[];\n}\n\ninterface E2eOptions {\n /** Project root — must contain docker/compose.test.yaml. */\n root?: string;\n}\n\ninterface CliOptions {\n /** CLI command to run (resolved from node_modules/.bin or PATH). */\n command: string;\n /** Project root — base dir for .project() fixture lookup (relative paths supported). */\n root?: string;\n /** Optional infrastructure services (started via testcontainers). */\n services?: ServiceHandle[];\n}\n\ninterface SpecificationRunnerWithCleanup extends SpecificationRunner {\n cleanup: () => Promise<void>;\n orchestrator: Orchestrator;\n}\n\n/**\n * Create an integration specification runner.\n * Starts infra containers via testcontainers, app runs in-process.\n */\nasync function integration(options: IntegrationOptions): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"integration\",\n root: resolveProjectRoot(options.root),\n services: options.services,\n });\n\n await orchestrator.start();\n\n const app = options.app();\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new HonoAdapter(app),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stop();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create an E2E specification runner.\n * Starts full docker compose stack. App URL and database auto-detected.\n */\nasync function e2e(options: E2eOptions = {}): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"e2e\",\n root: resolveProjectRoot(options.root),\n services: [],\n });\n\n await orchestrator.startCompose();\n\n const appUrl = orchestrator.getAppUrl();\n if (!appUrl) {\n throw new Error(\n \"E2E: could not detect app URL from compose. Ensure an app service with ports is defined.\",\n );\n }\n\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new FetchAdapter(appUrl),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stopCompose();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create a CLI specification runner.\n * Runs CLI commands against fixture projects. Optionally starts infrastructure.\n *\n * @example\n * export const spec = await cli({\n * command: resolve(import.meta.dirname, \"../../bin/my-cli.sh\"),\n * root: \"../fixtures\",\n * });\n */\nasync function cli(options: CliOptions): Promise<SpecificationRunnerWithCleanup> {\n const root = resolveProjectRoot(options.root);\n const command = resolveCommand(options.command, root);\n\n let orchestrator: null | Orchestrator = null;\n let database: DatabasePort | undefined;\n let databases: Map<string, DatabasePort> | undefined;\n\n if (options.services?.length) {\n orchestrator = new Orchestrator({\n mode: \"integration\",\n root,\n services: options.services,\n });\n await orchestrator.start();\n database = orchestrator.getDatabase() ?? undefined;\n const dbMap = orchestrator.getDatabases();\n databases = dbMap.size > 0 ? dbMap : undefined;\n }\n\n const runner = createSpecificationRunner({\n command: new ExecAdapter(command),\n database,\n databases,\n fixturesRoot: root,\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = async () => {\n if (orchestrator) {\n await orchestrator.stop();\n }\n };\n runner.orchestrator = orchestrator!;\n\n return runner;\n}\n\n// Service factories\nexport { postgres } from \"../infrastructure/services/postgres.js\";\nexport { redis } from \"../infrastructure/services/redis.js\";\n\n// Types\nexport type { CommandPort, CommandResult } from \"./ports/command.port.js\";\nexport type { DatabasePort } from \"./ports/database.port.js\";\nexport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// Adapters (for advanced usage)\nexport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nexport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nexport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nexport { Orchestrator } from \"../infrastructure/orchestrator.js\";\n\n// Reporter (for testing output)\nexport { normalizeOutput, stripAnsi } from \"../infrastructure/reporter.js\";\n\n// Runners\nexport { cli, e2e, integration };\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1191,6 +1191,7 @@ var SpecificationBuilder = class {
1191
1191
  return this.runCliAction(workDir);
1192
1192
  }
1193
1193
  prepareWorkDir() {
1194
+ if (!this.projectName && this.fixtures.length === 0) return this.config.fixturesRoot ?? process.cwd();
1194
1195
  const tempDir = mkdtempSync(resolve(tmpdir(), "spec-cli-"));
1195
1196
  if (this.projectName && this.config.fixturesRoot) {
1196
1197
  const projectDir = resolve(this.config.fixturesRoot, this.projectName);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["parseYaml"],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts","../src/infrastructure/adapters/compose.adapter.ts","../src/infrastructure/adapters/testcontainers.adapter.ts","../src/infrastructure/compose-parser.ts","../src/infrastructure/reporter.ts","../src/infrastructure/services/postgres.ts","../src/infrastructure/services/redis.ts","../src/infrastructure/orchestrator.ts","../src/specification/adapters/exec.adapter.ts","../src/specification/adapters/fetch.adapter.ts","../src/specification/adapters/hono.adapter.ts","../src/specification/assertions/base.ts","../src/specification/assertions/file.ts","../src/specification/assertions/response.ts","../src/specification/assertions/string.ts","../src/specification/assertions/table.ts","../src/specification/assertions/value.ts","../src/specification/specification.ts","../src/specification/index.ts"],"sourcesContent":["import MockDatePackage from \"mockdate\";\n\nexport interface MockDatePort {\n reset: () => void;\n set: (date: Date | number | string) => void;\n}\n\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from \"vitest-mock-extended\";\n\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\nexport const mockOf: MockPort = mockDeep;\n","import { execSync } from \"node:child_process\";\nimport { dirname } from \"node:path\";\n\nimport type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using docker compose — runs full compose stack.\n * Used by e2e() to start all services including the app.\n */\nexport class ComposeAdapter implements ContainerPort {\n private composeFile: string;\n private serviceName: string;\n private started = false;\n\n constructor(composeFile: string, serviceName: string) {\n this.composeFile = composeFile;\n this.serviceName = serviceName;\n }\n\n private exec(command: string): string {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim();\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} up -d --wait ${this.serviceName}`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} rm -fsv ${this.serviceName}`);\n this.started = false;\n }\n\n getMappedPort(containerPort: number): number {\n const output = this.exec(\n `docker compose -f ${this.composeFile} port ${this.serviceName} ${containerPort}`,\n );\n // Output: 0.0.0.0:54321\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(0)}`;\n }\n\n async getLogs(): Promise<string> {\n try {\n return this.exec(`docker compose -f ${this.composeFile} logs ${this.serviceName} --tail=50`);\n } catch {\n return \"\";\n }\n }\n}\n\n/**\n * Start the full compose stack and stop it all on cleanup.\n */\nexport class ComposeStackAdapter {\n private composeFile: string;\n private started = false;\n\n constructor(composeFile: string) {\n this.composeFile = composeFile;\n }\n\n private run(command: string): string {\n try {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n timeout: 120_000,\n }).trim();\n } catch (error: any) {\n const stderr = error.stderr?.toString().trim() ?? error.message;\n throw new Error(`docker compose failed: ${stderr}`, { cause: error });\n }\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} up -d --wait`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} down -v`);\n this.started = false;\n }\n\n getMappedPort(serviceName: string, containerPort: number): number {\n const output = this.run(\n `docker compose -f ${this.composeFile} port ${serviceName} ${containerPort}`,\n );\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n}\n","import type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using testcontainers.\n * Wraps a GenericContainer for programmatic container lifecycle.\n */\nexport class TestcontainersAdapter implements ContainerPort {\n private image: string;\n private containerPort: number;\n private env: Record<string, string>;\n private reuse: boolean;\n private container: any = null;\n\n constructor(options: {\n image: string;\n port: number;\n env?: Record<string, string>;\n reuse?: boolean;\n }) {\n this.image = options.image;\n this.containerPort = options.port;\n this.env = options.env ?? {};\n this.reuse = options.reuse ?? false;\n }\n\n async start(): Promise<void> {\n const { GenericContainer, Wait } = await import(\"testcontainers\");\n\n let builder = new GenericContainer(this.image).withExposedPorts(this.containerPort);\n\n for (const [key, value] of Object.entries(this.env)) {\n builder = builder.withEnvironment({ [key]: value });\n }\n\n if (this.image.startsWith(\"postgres\")) {\n builder = builder.withWaitStrategy(\n Wait.forLogMessage(/database system is ready to accept connections/, 2),\n );\n }\n\n if (this.reuse) {\n builder = builder.withReuse();\n }\n\n this.container = await builder.start();\n }\n\n async stop(): Promise<void> {\n if (this.container && !this.reuse) {\n await this.container.stop();\n this.container = null;\n }\n }\n\n getMappedPort(containerPort: number): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(containerPort);\n }\n\n getHost(): string {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getHost();\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(this.containerPort)}`;\n }\n\n async getLogs(): Promise<string> {\n if (!this.container) {\n return \"\";\n }\n\n const stream = await this.container.logs();\n return new Promise((resolve) => {\n let output = \"\";\n stream.on(\"data\", (chunk: Buffer) => {\n output += chunk.toString();\n });\n stream.on(\"end\", () => {\n resolve(output);\n });\n // Timeout after 1s if stream doesn't end\n setTimeout(() => {\n resolve(output);\n }, 1000);\n });\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\n\n/**\n * A parsed service from docker-compose.test.yaml.\n */\nexport interface ComposeService {\n name: string;\n image?: string;\n build?: string;\n ports: { container: number; host?: number }[];\n environment: Record<string, string>;\n volumes: string[];\n dependsOn: string[];\n}\n\n/**\n * Result of parsing a compose file.\n */\nexport interface ComposeConfig {\n services: ComposeService[];\n appService: ComposeService | null;\n infraServices: ComposeService[];\n}\n\n/**\n * Detect the service type from the image name.\n */\nexport function detectServiceType(\n image: string | undefined,\n): \"app\" | \"postgres\" | \"redis\" | \"unknown\" {\n if (!image) {\n return \"app\";\n }\n\n const lower = image.toLowerCase();\n\n if (lower.startsWith(\"postgres\")) {\n return \"postgres\";\n }\n if (lower.startsWith(\"redis\")) {\n return \"redis\";\n }\n\n return \"unknown\";\n}\n\n/**\n * Find the compose file in the project.\n * Looks for docker/compose.test.yaml or docker-compose.test.yaml.\n */\nexport function findComposeFile(projectRoot: string): null | string {\n const candidates = [\n resolve(projectRoot, \"docker/compose.test.yaml\"),\n resolve(projectRoot, \"docker/compose.test.yml\"),\n resolve(projectRoot, \"docker-compose.test.yaml\"),\n resolve(projectRoot, \"docker-compose.test.yml\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * Parse a docker-compose file and extract service definitions.\n */\nexport function parseComposeFile(filePath: string): ComposeConfig {\n const content = readFileSync(filePath, \"utf8\");\n const doc = parseYaml(content);\n\n if (!doc?.services) {\n return { services: [], appService: null, infraServices: [] };\n }\n\n const services: ComposeService[] = Object.entries(doc.services).map(\n ([name, def]: [string, any]) => {\n const ports: { container: number; host?: number }[] = [];\n if (def.ports) {\n for (const port of def.ports) {\n const str = String(port);\n if (str.includes(\":\")) {\n const [host, container] = str.split(\":\");\n ports.push({ container: Number(container), host: Number(host) });\n } else {\n ports.push({ container: Number(str) });\n }\n }\n }\n\n const environment: Record<string, string> = {};\n if (def.environment) {\n if (Array.isArray(def.environment)) {\n for (const env of def.environment) {\n const [key, ...rest] = String(env).split(\"=\");\n environment[key] = rest.join(\"=\");\n }\n } else {\n Object.assign(environment, def.environment);\n }\n }\n\n const volumes: string[] = def.volumes ? def.volumes.map((v: string) => String(v)) : [];\n\n let dependsOn: string[] = [];\n if (def.depends_on) {\n dependsOn = Array.isArray(def.depends_on) ? def.depends_on : Object.keys(def.depends_on);\n }\n\n return {\n name,\n image: def.image,\n build: def.build,\n ports,\n environment,\n volumes,\n dependsOn,\n };\n },\n );\n\n const appService = services.find((s) => s.build !== undefined) ?? null;\n const infraServices = services.filter((s) => s.build === undefined);\n\n return { services, appService, infraServices };\n}\n","// ── Colors ──\n\nconst GREEN = \"\\x1b[32m\";\nconst RED = \"\\x1b[31m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\nconst RESET = \"\\x1b[0m\";\nconst BG_CYAN = \"\\x1b[46m\";\nconst BLACK = \"\\x1b[30m\";\n\n// ── Symbols (vitest-native) ──\n\nconst CHECK = \"✓\";\nconst CROSS = \"×\";\nconst ARROW = \"→\";\nconst DASH = \"⎯\";\n\n// ── Types ──\n\nexport interface ServiceReport {\n name: string;\n type: string;\n connectionString?: string;\n durationMs: number;\n error?: string;\n logs?: string;\n}\n\nexport interface AppInfo {\n type: \"http\" | \"in-process\";\n url?: string;\n}\n\n// ── Startup report ──\n\nexport function formatStartupReport(\n mode: \"e2e\" | \"integration\",\n services: ServiceReport[],\n app?: AppInfo,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(`${BG_CYAN}${BLACK}${BOLD} INFRA ${RESET} Starting infrastructure...`);\n lines.push(\"\");\n\n for (const service of services) {\n if (service.error) {\n lines.push(\n ` ${RED}${CROSS}${RESET} ${service.type} (${service.name}) ${RED}${service.error}${RESET} ${DIM}${service.durationMs}ms${RESET}`,\n );\n if (service.logs) {\n const logLines = service.logs.trim().split(\"\\n\").slice(-10);\n for (const logLine of logLines) {\n lines.push(` ${DIM}${logLine}${RESET}`);\n }\n }\n } else {\n const conn = service.connectionString ? `${DIM}${service.connectionString}${RESET}` : \"\";\n lines.push(\n ` ${GREEN}${CHECK}${RESET} ${service.type} (${service.name}) ${conn} ${DIM}${service.durationMs}ms${RESET}`,\n );\n }\n }\n\n if (app) {\n lines.push(\"\");\n if (app.type === \"in-process\") {\n lines.push(` ${DIM}${ARROW} app: in-process (Hono)${RESET}`);\n } else {\n lines.push(` ${DIM}${ARROW} app: ${app.url}${RESET}`);\n }\n }\n\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ── Error divider ──\n\nexport function formatErrorDivider(label: string): string {\n const dashes = DASH.repeat(30);\n return `\\n${RED}${dashes} ${label} ${dashes}${RESET}\\n`;\n}\n\n// ── Status error ──\n\nexport function formatStatusError(\n expectedStatus: number,\n receivedStatus: number,\n request: { method: string; path: string; body?: unknown },\n responseBody: unknown,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected status: ${GREEN}${expectedStatus}${RESET}`);\n lines.push(`Received status: ${RED}${receivedStatus}${RESET}`);\n lines.push(\"\");\n lines.push(`${DIM}${request.method} ${request.path}${RESET}`);\n\n if (request.body) {\n lines.push(formatJson(request.body, DIM));\n }\n\n if (responseBody) {\n lines.push(\"\");\n lines.push(`${DIM}Response:${RESET}`);\n lines.push(formatJson(responseBody, RED));\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Table diff ──\n\nexport function formatTableDiff(\n table: string,\n columns: string[],\n expected: unknown[][],\n actual: unknown[][],\n): string {\n const lines: string[] = [];\n\n lines.push(`Table \"${table}\" mismatch`);\n lines.push(`${DIM} query: ${columns.join(\", \")}${RESET}`);\n lines.push(`${DIM} expected: ${rowLabel(expected.length)}${RESET}`);\n lines.push(`${DIM} received: ${rowLabel(actual.length)}${RESET}`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const header = columns.join(\" | \");\n lines.push(`${DIM} ${header}${RESET}`);\n\n const maxRows = Math.max(expected.length, actual.length);\n\n for (let i = 0; i < maxRows; i++) {\n const exp = expected[i];\n const act = actual[i];\n\n if (exp && !act) {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n } else if (!exp && act) {\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n } else if (exp && act) {\n const same = JSON.stringify(exp) === JSON.stringify(act);\n if (same) {\n lines.push(` ${formatRow(act)}`);\n } else {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n }\n }\n }\n\n if (expected.length === 0 && actual.length === 0) {\n lines.push(` (empty)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Response diff ──\n\nexport function formatResponseDiff(file: string, expected: unknown, actual: unknown): string {\n const lines: string[] = [];\n\n lines.push(`Response mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = JSON.stringify(expected, null, 2).split(\"\\n\");\n const actualLines = JSON.stringify(actual, null, 2).split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Exit code error ──\n\nexport function formatExitCodeError(\n expected: number,\n received: number,\n stdout: string,\n stderr: string,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected exit code: ${GREEN}${expected}${RESET}`);\n lines.push(`Received exit code: ${RED}${received}${RESET}`);\n\n if (stdout.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stdout:${RESET}`);\n for (const line of stdout.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n if (stderr.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stderr:${RESET}`);\n for (const line of stderr.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${RED}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Stdout/stderr diff ──\n\nexport function formatStdoutDiff(file: string, expected: string, actual: string): string {\n const lines: string[] = [];\n\n lines.push(`Output mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = expected.split(\"\\n\");\n const actualLines = actual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── File assertions ──\n\nexport function formatFileMissing(path: string): string {\n return `Expected file to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileUnexpected(path: string): string {\n return `Expected file NOT to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileContentMismatch(path: string, expected: string, actual: string): string {\n const lines: string[] = [];\n lines.push(`File \"${path}\" does not contain expected content`);\n lines.push(\"\");\n lines.push(`${GREEN}Expected to contain:${RESET}`);\n lines.push(` ${GREEN}${expected}${RESET}`);\n lines.push(\"\");\n lines.push(`${RED}Actual content (first 20 lines):${RESET}`);\n for (const line of actual.split(\"\\n\").slice(0, 20)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ── Service logs section ──\n\nexport function formatServiceLogs(services: { name: string; logs: string }[]): string {\n const lines: string[] = [];\n\n for (const { name, logs } of services) {\n if (!logs.trim()) {\n continue;\n }\n\n lines.push(\"\");\n lines.push(`${DIM}${name} logs (last 10 lines):${RESET}`);\n\n const logLines = logs.trim().split(\"\\n\").slice(-10);\n for (const line of logLines) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Helpers ──\n\nfunction rowLabel(n: number): string {\n return n === 1 ? \"1 row\" : `${n} rows`;\n}\n\nfunction formatJson(value: unknown, color: string): string {\n return JSON.stringify(value, null, 2)\n .split(\"\\n\")\n .map((line) => `${color}${line}${RESET}`)\n .join(\"\\n\");\n}\n\nfunction formatRow(row: unknown[]): string {\n return row.map((v) => String(v ?? \"null\")).join(\" | \");\n}\n\n// ── Test utilities ──\n\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function normalizeOutput(str: string): string {\n return stripAnsi(str)\n .replace(/localhost:\\d+/g, \"localhost:PORT\")\n .replace(/\\d+ms/g, \"Xms\")\n .replace(/\\d+\\.\\d+s/g, \"X.Xs\")\n .trim();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { Client } from \"pg\";\n\nimport type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface PostgresOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n /** Override environment variables. */\n env?: Record<string, string>;\n}\n\nclass PostgresHandle implements DatabasePort, ServiceHandle {\n readonly type = \"postgres\";\n readonly composeName: null | string;\n readonly defaultPort = 5432;\n readonly defaultImage: string;\n readonly environment: Record<string, string>;\n\n connectionString = \"\";\n started = false;\n\n private client: Client | null = null;\n\n constructor(options: PostgresOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"postgres:17\";\n this.environment = {\n POSTGRES_DB: \"test\",\n POSTGRES_PASSWORD: \"test\",\n POSTGRES_USER: \"test\",\n ...options.env,\n };\n }\n\n buildConnectionString(host: string, port: number): string {\n const user = this.environment.POSTGRES_USER ?? \"test\";\n const password = this.environment.POSTGRES_PASSWORD ?? \"test\";\n const db = this.environment.POSTGRES_DB ?? \"test\";\n return `postgresql://${user}:${password}@${host}:${port}/${db}`;\n }\n\n createDatabaseAdapter(): DatabasePort {\n return this;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"postgres: cannot healthcheck — no connection string\");\n }\n\n // Healthcheck uses a throwaway client (connection might not be established yet)\n try {\n const client = new Client({ connectionString: this.connectionString });\n await client.connect();\n await client.query(\"SELECT 1\");\n await client.end();\n } catch (error: any) {\n throw new Error(\n `postgres healthcheck failed: ${error.message || error.code || String(error)}`,\n { cause: error },\n );\n }\n }\n\n async initialize(composeDir: string): Promise<void> {\n if (!this.composeName) {\n return;\n }\n\n const initPaths = [\n resolve(composeDir, `${this.composeName}/init.sql`),\n resolve(composeDir, \"postgres/init.sql\"),\n ];\n\n for (const initPath of initPaths) {\n if (existsSync(initPath)) {\n const sql = readFileSync(initPath, \"utf8\");\n try {\n await this.seed(sql);\n } catch (error: any) {\n throw new Error(`postgres init script failed (${initPath}):\\n${error.message}`, {\n cause: error,\n });\n }\n return;\n }\n }\n }\n\n private async getClient(): Promise<Client> {\n if (this.client) {\n return this.client;\n }\n const client = new Client({ connectionString: this.connectionString });\n client.on(\"error\", () => {\n // Connection dropped (container stopped) — reset so next call reconnects\n this.client = null;\n });\n await client.connect();\n this.client = client;\n return client;\n }\n\n async seed(sql: string): Promise<void> {\n const client = await this.getClient();\n await client.query(sql);\n }\n\n async query(table: string, columns: string[]): Promise<unknown[][]> {\n const client = await this.getClient();\n const columnList = columns.join(\", \");\n const result = await client.query(`SELECT ${columnList} FROM \"${table}\" ORDER BY 1`);\n return result.rows.map((row: Record<string, unknown>) => columns.map((col) => row[col]));\n }\n\n async reset(): Promise<void> {\n const client = await this.getClient();\n const result = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of result.rows) {\n await client.query(`TRUNCATE \"${row.tablename}\" CASCADE`);\n }\n }\n}\n\n/**\n * Create a PostgreSQL service handle.\n *\n * @example\n * const db = postgres({ compose: \"db\" });\n * // After start: db.connectionString is populated\n */\nexport function postgres(options: PostgresOptions = {}): PostgresHandle {\n return new PostgresHandle(options);\n}\n","import type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nclass RedisHandle implements ServiceHandle {\n readonly type = \"redis\";\n readonly composeName: null | string;\n readonly defaultPort = 6379;\n readonly defaultImage: string;\n readonly environment: Record<string, string> = {};\n\n connectionString = \"\";\n started = false;\n\n constructor(options: RedisOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"redis:7\";\n }\n\n buildConnectionString(host: string, port: number): string {\n return `redis://${host}:${port}`;\n }\n\n createDatabaseAdapter(): DatabasePort | null {\n return null;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"redis: cannot healthcheck — no connection string\");\n }\n\n try {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n await client.ping();\n await client.disconnect();\n } catch (error: any) {\n throw new Error(`redis healthcheck failed: ${error.message || error.code || String(error)}`, {\n cause: error,\n });\n }\n }\n\n async initialize(): Promise<void> {\n // Redis doesn't need initialization scripts\n }\n\n async reset(): Promise<void> {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n try {\n await client.flushAll();\n } finally {\n await client.disconnect();\n }\n }\n}\n\n/**\n * Create a Redis service handle.\n *\n * @example\n * const cache = redis({ compose: \"cache\" });\n * // After start: cache.connectionString is populated\n */\nexport function redis(options: RedisOptions = {}): RedisHandle {\n return new RedisHandle(options);\n}\n","import { dirname } from \"node:path\";\n\nimport type { DatabasePort } from \"../specification/ports/database.port.js\";\nimport { ComposeStackAdapter } from \"./adapters/compose.adapter.js\";\nimport { TestcontainersAdapter } from \"./adapters/testcontainers.adapter.js\";\nimport { detectServiceType, findComposeFile, parseComposeFile } from \"./compose-parser.js\";\nimport type { ContainerPort } from \"./ports/container.port.js\";\nimport { type AppInfo, formatStartupReport, type ServiceReport } from \"./reporter.js\";\nimport { postgres } from \"./services/postgres.js\";\nimport { redis } from \"./services/redis.js\";\nimport type { ServiceHandle } from \"./services/service.port.js\";\n\ninterface RunningService {\n handle: ServiceHandle;\n container: ContainerPort | null;\n}\n\ninterface OrchestratorOptions {\n services: ServiceHandle[];\n mode: \"e2e\" | \"integration\";\n root?: string;\n}\n\n/**\n * Orchestrator for test infrastructure.\n * Integration: starts services via testcontainers.\n * E2E: runs full docker compose up.\n */\nexport class Orchestrator {\n private services: ServiceHandle[];\n private mode: \"e2e\" | \"integration\";\n private root: string;\n private running: RunningService[] = [];\n private composeStack: ComposeStackAdapter | null = null;\n private composeHandles: ServiceHandle[] = [];\n private started = false;\n\n constructor(options: OrchestratorOptions) {\n this.services = options.services;\n this.mode = options.mode;\n this.root = options.root ?? process.cwd();\n }\n\n /**\n * Start declared services via testcontainers (integration mode).\n * Phase 1: start all containers in parallel (the slow part).\n * Phase 2: wire connections, healthcheck, and init sequentially (fast).\n */\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n const composePath = findComposeFile(this.root);\n const composeDir = composePath ? dirname(composePath) : this.root;\n const composeConfig = composePath ? parseComposeFile(composePath) : null;\n\n // Phase 1: resolve config and start all containers in parallel\n const containerTasks = this.services.map((handle) => {\n let image = handle.defaultImage;\n let env = { ...handle.environment };\n\n if (handle.composeName && composeConfig) {\n const composeService = composeConfig.services.find((s) => s.name === handle.composeName);\n if (composeService) {\n image = composeService.image ?? image;\n env = { ...env, ...composeService.environment };\n Object.assign(handle.environment, composeService.environment);\n }\n }\n\n const container = new TestcontainersAdapter({ image, port: handle.defaultPort, env });\n return { container, handle };\n });\n\n // Start all containers concurrently\n await Promise.all(containerTasks.map(({ container }) => container.start()));\n\n // Phase 2: wire connections, healthcheck, init (fast — containers already running)\n const reports: ServiceReport[] = [];\n\n for (const { container, handle } of containerTasks) {\n const serviceStartTime = Date.now();\n\n try {\n const host = container.getHost();\n const port = container.getMappedPort(handle.defaultPort);\n handle.connectionString = handle.buildConnectionString(host, port);\n\n await handle.healthcheck();\n await handle.initialize(composeDir);\n handle.started = true;\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n connectionString: handle.connectionString,\n durationMs: Date.now() - serviceStartTime,\n });\n this.running.push({ handle, container });\n } catch (error: any) {\n let logs = \"\";\n try {\n logs = await container.getLogs();\n } catch {\n /* Ignore log fetch errors */\n }\n try {\n await container.stop();\n } catch {\n /* Ignore stop errors */\n }\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n durationMs: Date.now() - serviceStartTime,\n error: error.message,\n logs,\n });\n\n const output = formatStartupReport(\"integration\", reports, { type: \"in-process\" });\n console.error(output);\n throw error;\n }\n }\n\n this.started = true;\n\n const appInfo: AppInfo = { type: \"in-process\" };\n const output = formatStartupReport(\"integration\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop testcontainers (integration mode).\n */\n async stop(): Promise<void> {\n for (const { container } of this.running) {\n if (container) {\n await container.stop();\n }\n }\n this.running = [];\n this.started = false;\n }\n\n /**\n * Start full docker compose stack (e2e mode).\n * Auto-detects infra services and creates handles for them.\n */\n async startCompose(): Promise<void> {\n const composePath = findComposeFile(this.root);\n if (!composePath) {\n throw new Error(`E2E: no compose file found in ${this.root}`);\n }\n\n const startTime = Date.now();\n const composeDir = dirname(composePath);\n const composeConfig = parseComposeFile(composePath);\n\n this.composeStack = new ComposeStackAdapter(composePath);\n await this.composeStack.start();\n\n // Create handles for detected infra services\n for (const service of composeConfig.infraServices) {\n const type = detectServiceType(service.image);\n\n if (type === \"postgres\") {\n const handle = postgres({ compose: service.name, env: service.environment });\n const port = this.composeStack.getMappedPort(service.name, 5432);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n\n await handle.initialize(composeDir);\n handle.started = true;\n\n this.composeHandles.push(handle);\n } else if (type === \"redis\") {\n const handle = redis({ compose: service.name });\n const port = this.composeStack.getMappedPort(service.name, 6379);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n handle.started = true;\n\n this.composeHandles.push(handle);\n }\n }\n\n const durationMs = Date.now() - startTime;\n const reports: ServiceReport[] = this.composeHandles.map((h) => ({\n name: h.composeName ?? h.type,\n type: h.type,\n connectionString: h.connectionString,\n durationMs,\n }));\n\n const appUrl = this.getAppUrl();\n const appInfo: AppInfo = { type: \"http\", url: appUrl ?? undefined };\n const output = formatStartupReport(\"e2e\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop docker compose stack (e2e mode).\n */\n async stopCompose(): Promise<void> {\n if (this.composeStack) {\n await this.composeStack.stop();\n this.composeStack = null;\n }\n this.composeHandles = [];\n }\n\n /**\n * Get a database service by compose name, or the first one if no name given.\n */\n getDatabase(serviceName?: string): DatabasePort | null {\n for (const handle of [...this.services, ...this.composeHandles]) {\n if (serviceName && handle.composeName !== serviceName) {\n continue;\n }\n const adapter = handle.createDatabaseAdapter();\n if (adapter) {\n return adapter;\n }\n }\n return null;\n }\n\n /**\n * Get all database services keyed by compose name.\n */\n getDatabases(): Map<string, DatabasePort> {\n const map = new Map<string, DatabasePort>();\n for (const handle of [...this.services, ...this.composeHandles]) {\n const adapter = handle.createDatabaseAdapter();\n if (adapter && handle.composeName) {\n map.set(handle.composeName, adapter);\n }\n }\n return map;\n }\n\n /**\n * Get app URL from compose (e2e mode).\n */\n getAppUrl(): null | string {\n const composePath = findComposeFile(this.root);\n if (!composePath || !this.composeStack) {\n return null;\n }\n\n const config = parseComposeFile(composePath);\n const appService = config.appService;\n\n if (!appService || appService.ports.length === 0) {\n return null;\n }\n\n const port = this.composeStack.getMappedPort(appService.name, appService.ports[0].container);\n return `http://localhost:${port}`;\n }\n}\n","import { execSync, spawn } from \"node:child_process\";\n\nimport type { CommandPort, CommandResult, SpawnOptions } from \"../ports/command.port.js\";\n\n/**\n * Executes CLI commands via execSync (blocking) or spawn (long-running).\n * Used by cli() for local command execution.\n */\nexport class ExecAdapter implements CommandPort {\n private command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n\n async exec(args: string, cwd: string): Promise<CommandResult> {\n // Clear INIT_CWD so CLI tools use the actual cwd, not npm's caller directory\n const env = { ...process.env, INIT_CWD: undefined };\n\n try {\n const stdout = execSync(`${this.command} ${args}`, {\n cwd,\n encoding: \"utf8\",\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { exitCode: 0, stdout, stderr: \"\" };\n } catch (error: any) {\n return {\n exitCode: error.status ?? 1,\n stdout: error.stdout?.toString() ?? \"\",\n stderr: error.stderr?.toString() ?? \"\",\n };\n }\n }\n\n async spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult> {\n const env = { ...process.env, INIT_CWD: undefined };\n\n return new Promise((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let resolved = false;\n\n const child = spawn(this.command, args.split(/\\s+/).filter(Boolean), {\n cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const finish = (exitCode: number) => {\n if (resolved) {\n return;\n }\n resolved = true;\n child.kill(\"SIGTERM\");\n resolve({ exitCode, stdout, stderr });\n };\n\n let patternMatched = false;\n\n const checkPattern = () => {\n if (\n !patternMatched &&\n (stdout.includes(options.waitFor) || stderr.includes(options.waitFor))\n ) {\n patternMatched = true;\n finish(0);\n }\n };\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n checkPattern();\n });\n\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n checkPattern();\n });\n\n // Process exited before pattern matched\n child.on(\"exit\", (code) => {\n if (!patternMatched) {\n finish(code === 0 ? 1 : (code ?? 1));\n }\n });\n\n setTimeout(() => finish(124), options.timeout);\n });\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for real HTTP — sends actual fetch requests.\n * Used by e2e() specification runner.\n */\nexport class FetchAdapter implements ServerPort {\n private baseUrl: string;\n\n constructor(url: string) {\n this.baseUrl = url.replace(/\\/$/, \"\");\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(`${this.baseUrl}${path}`, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for Hono — in-process requests, no real HTTP.\n * Used by integration() specification runner.\n */\nexport class HonoAdapter implements ServerPort {\n private app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n };\n\n constructor(app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n }) {\n this.app = app;\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await this.app.request(path, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","/**\n * Base assertion that handles .not negation.\n * Subclasses call this.assert(condition, message, negatedMessage) for each predicate.\n */\nexport class BaseAssertion {\n protected negated = false;\n\n get not(): this {\n const clone = Object.create(Object.getPrototypeOf(this));\n Object.assign(clone, this);\n clone.negated = !this.negated;\n return clone;\n }\n\n protected assert(condition: boolean, message: string, negatedMessage: string): void {\n if (this.negated) {\n if (condition) {\n throw new Error(negatedMessage);\n }\n } else {\n if (!condition) {\n throw new Error(message);\n }\n }\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport {\n formatFileContentMismatch,\n formatFileMissing,\n formatFileUnexpected,\n} from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a file in the working directory.\n * Usage: result.file(\"dist/index.js\").toExist()\n */\nexport class FileAssertion extends BaseAssertion {\n private filePath: string;\n private resolvedPath: string;\n\n constructor(filePath: string, workDir: string) {\n super();\n this.filePath = filePath;\n this.resolvedPath = resolve(workDir, filePath);\n }\n\n toExist(): void {\n const exists = existsSync(this.resolvedPath);\n this.assert(exists, formatFileMissing(this.filePath), formatFileUnexpected(this.filePath));\n }\n\n toContain(expected: string): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return; // File doesn't exist, so it certainly doesn't contain the string\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = content.includes(expected);\n this.assert(\n found,\n formatFileContentMismatch(this.filePath, expected, content),\n `Expected file \"${this.filePath}\" NOT to contain \"${expected}\"`,\n );\n }\n\n toMatch(pattern: RegExp): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return;\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = pattern.test(content);\n this.assert(\n found,\n `Expected file \"${this.filePath}\" to match: ${pattern}\\n\\nActual content:\\n${content.slice(0, 500)}`,\n `Expected file \"${this.filePath}\" NOT to match: ${pattern}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatResponseDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on an HTTP response body.\n * Usage: result.response.toMatchFile(\"expected.json\")\n */\nexport class ResponseAssertion extends BaseAssertion {\n private body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\n super();\n this.body = body;\n this.testDir = testDir;\n }\n\n toMatchFile(file: string): void {\n const expected = JSON.parse(readFileSync(resolve(this.testDir, \"responses\", file), \"utf8\"));\n const match = JSON.stringify(this.body) === JSON.stringify(expected);\n this.assert(\n match,\n formatResponseDiff(file, expected, this.body),\n `Expected response NOT to match file \"${file}\", but it did`,\n );\n }\n\n toContain(subset: Record<string, unknown>): void {\n const bodyStr = JSON.stringify(this.body);\n const subsetStr = JSON.stringify(subset);\n // Check if all keys in subset exist with same values in body\n const bodyObj = typeof this.body === \"object\" && this.body !== null ? this.body : {};\n const match = Object.entries(subset).every(\n ([key, value]) =>\n JSON.stringify((bodyObj as Record<string, unknown>)[key]) === JSON.stringify(value),\n );\n this.assert(\n match,\n `Expected response to contain: ${subsetStr}\\n\\nActual response:\\n${bodyStr}`,\n `Expected response NOT to contain: ${subsetStr}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatStdoutDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a string (stdout, stderr, response body).\n * Usage: result.stdout.toContain(\"hello\")\n */\nexport class StringAssertion extends BaseAssertion {\n private actual: string;\n private label: string;\n private testDir?: string;\n\n constructor(actual: string, label: string, testDir?: string) {\n super();\n this.actual = actual;\n this.label = label;\n this.testDir = testDir;\n }\n\n toContain(expected: string, options?: { near?: string }): void {\n if (options?.near) {\n const found = this.containsNear(expected, options.near);\n this.assert(\n found,\n `Expected ${this.label} to contain \"${expected}\" near \"${options.near}\"\\n\\n${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain \"${expected}\" near \"${options.near}\", but it was found`,\n );\n } else {\n const found = this.actual.includes(expected);\n this.assert(\n found,\n `Expected ${this.label} to contain: \"${expected}\"\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain: \"${expected}\"`,\n );\n }\n }\n\n toMatch(pattern: RegExp): void {\n const found = pattern.test(this.actual);\n this.assert(\n found,\n `Expected ${this.label} to match: ${pattern}\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to match: ${pattern}`,\n );\n }\n\n toMatchFile(file: string): void {\n if (!this.testDir) {\n throw new Error(\"toMatchFile requires a test directory context\");\n }\n const expected = readFileSync(resolve(this.testDir, \"expected\", file), \"utf8\").trim();\n const actual = this.actual.trim();\n const match = actual === expected;\n this.assert(\n match,\n formatStdoutDiff(file, expected, actual),\n `Expected ${this.label} NOT to match file \"${file}\", but it did`,\n );\n }\n\n toBeEmpty(): void {\n const empty = this.actual.trim() === \"\";\n this.assert(\n empty,\n `Expected ${this.label} to be empty\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to be empty`,\n );\n }\n\n // ── Private ──\n\n private containsNear(target: string, near: string, proximity = 500): boolean {\n const clean = this.stripAnsi(this.actual);\n const nearLower = near.toLowerCase();\n const targetLower = target.toLowerCase();\n\n // Find all occurrences of `near` and check if `target` appears within proximity\n let searchFrom = 0;\n while (true) {\n const idx = clean.toLowerCase().indexOf(nearLower, searchFrom);\n if (idx === -1) {\n break;\n }\n const windowStart = Math.max(0, idx - proximity);\n const windowEnd = Math.min(clean.length, idx + nearLower.length + proximity);\n const window = clean.substring(windowStart, windowEnd).toLowerCase();\n if (window.includes(targetLower)) {\n return true;\n }\n searchFrom = idx + 1;\n }\n return false;\n }\n\n private stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n }\n\n private truncate(str: string, maxLines = 20): string {\n const lines = str.split(\"\\n\");\n if (lines.length <= maxLines) {\n return str;\n }\n return `${lines.slice(0, maxLines).join(\"\\n\")}\\n... (${lines.length - maxLines} more lines)`;\n }\n}\n","import { formatTableDiff } from \"../../infrastructure/reporter.js\";\nimport type { DatabasePort } from \"../ports/database.port.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a database table.\n * Usage: await result.table(\"users\").toMatch({ columns: [\"name\"], rows: [[\"Alice\"]] })\n */\nexport class TableAssertion extends BaseAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\n super();\n this.tableName = tableName;\n this.db = db;\n }\n\n async toMatch(expected: { columns: string[]; rows: unknown[][] }): Promise<void> {\n const actual = await this.db.query(this.tableName, expected.columns);\n const match = JSON.stringify(actual) === JSON.stringify(expected.rows);\n this.assert(\n match,\n formatTableDiff(this.tableName, expected.columns, expected.rows, actual),\n `Expected table \"${this.tableName}\" NOT to match, but it did`,\n );\n }\n\n async toBeEmpty(): Promise<void> {\n // Query a single column to check if table has rows — use a lightweight approach\n const actual = await this.db.query(this.tableName, [\"*\"]);\n const empty = actual.length === 0;\n this.assert(\n empty,\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n `Expected table \"${this.tableName}\" NOT to be empty, but it is`,\n );\n }\n}\n","import { formatExitCodeError, formatStatusError } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a single value (exit code, status code).\n * Usage: result.exitCode.toBe(0)\n */\nexport class ValueAssertion extends BaseAssertion {\n private actual: number;\n private label: string;\n private context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string };\n\n constructor(\n actual: number,\n label: string,\n context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string },\n ) {\n super();\n this.actual = actual;\n this.label = label;\n this.context = context;\n }\n\n toBe(expected: number): void {\n const match = this.actual === expected;\n\n let message: string;\n if (this.label === \"exit code\" && this.context?.stdout !== undefined) {\n message = formatExitCodeError(\n expected,\n this.actual,\n this.context.stdout ?? \"\",\n this.context.stderr ?? \"\",\n );\n } else if (this.label === \"status\" && this.context?.request) {\n message = formatStatusError(\n expected,\n this.actual,\n this.context.request,\n this.context.responseBody,\n );\n } else {\n message = `Expected ${this.label}: ${expected}\\nReceived ${this.label}: ${this.actual}`;\n }\n\n this.assert(match, message, `Expected ${this.label} NOT to be ${expected}, but it was`);\n }\n}\n","import { cpSync, existsSync, mkdtempSync, readFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\nimport { FileAssertion } from \"./assertions/file.js\";\nimport { ResponseAssertion } from \"./assertions/response.js\";\nimport { StringAssertion } from \"./assertions/string.js\";\nimport { TableAssertion } from \"./assertions/table.js\";\nimport { ValueAssertion } from \"./assertions/value.js\";\nimport type { CommandPort, CommandResult, SpawnOptions } from \"./ports/command.port.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// ── Types ──\n\nexport interface SpecificationConfig {\n command?: CommandPort;\n database?: DatabasePort;\n databases?: Map<string, DatabasePort>;\n fixturesRoot?: string;\n server?: ServerPort;\n}\n\nexport interface SeedEntry {\n file: string;\n service?: string;\n}\n\nexport interface FixtureEntry {\n file: string;\n}\n\nexport interface MockEntry {\n file: string;\n}\n\nexport interface RequestEntry {\n bodyFile?: string;\n method: string;\n path: string;\n}\n\n// ── Result (after .run()) ──\n\ninterface RequestInfo {\n body?: unknown;\n method: string;\n path: string;\n}\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: RequestInfo;\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: RequestInfo;\n response?: ServerResponse;\n testDir: string;\n workDir?: string;\n }) {\n this.responseData = options.response;\n this.commandResult = options.commandResult;\n this.config = options.config;\n this.testDir = options.testDir;\n this.requestInfo = options.requestInfo;\n this.workDir = options.workDir;\n }\n\n // ── Scoped assertion accessors ──\n\n get exitCode(): ValueAssertion {\n if (!this.commandResult) {\n throw new Error(\".exitCode requires a CLI action (.exec())\");\n }\n return new ValueAssertion(this.commandResult.exitCode, \"exit code\", {\n stderr: this.commandResult.stderr,\n stdout: this.commandResult.stdout,\n });\n }\n\n get status(): ValueAssertion {\n if (!this.responseData || !this.requestInfo) {\n throw new Error(\".status requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ValueAssertion(this.responseData.status, \"status\", {\n request: this.requestInfo,\n responseBody: this.responseData.body,\n });\n }\n\n get response(): ResponseAssertion {\n if (!this.responseData) {\n throw new Error(\".response requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ResponseAssertion(this.responseData.body, this.testDir);\n }\n\n get stdout(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stdout requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stdout, \"stdout\", this.testDir);\n }\n\n get stderr(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stderr requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stderr, \"stderr\", this.testDir);\n }\n\n file(path: string): FileAssertion {\n const baseDir = this.workDir ?? this.testDir;\n return new FileAssertion(path, baseDir);\n }\n\n table(tableName: string, options?: { service?: string }): TableAssertion {\n const db = this.resolveDatabase(options?.service);\n if (!db) {\n throw new Error(\n options?.service\n ? `table(\"${tableName}\") requires database \"${options.service}\" but it was not found`\n : `table(\"${tableName}\") requires a database adapter`,\n );\n }\n return new TableAssertion(tableName, db);\n }\n\n // ── Private ──\n\n private resolveDatabase(serviceName?: string): DatabasePort | undefined {\n if (serviceName && this.config.databases) {\n return this.config.databases.get(serviceName);\n }\n return this.config.database;\n }\n}\n\n// ── Builder (before .run()) ──\n\nexport class SpecificationBuilder {\n private commandArgs: null | string | string[] = null;\n private config: SpecificationConfig;\n private fixtures: FixtureEntry[] = [];\n private label: string;\n private mocks: MockEntry[] = [];\n private projectName: null | string = null;\n private request: null | RequestEntry = null;\n private seeds: SeedEntry[] = [];\n private spawnConfig: null | { args: string; options: SpawnOptions } = null;\n private testDir: string;\n\n constructor(config: SpecificationConfig, testDir: string, label: string) {\n this.config = config;\n this.testDir = testDir;\n this.label = label;\n }\n\n // ── Setup ──\n\n seed(file: string, options?: { service?: string }): this {\n this.seeds.push({ file, service: options?.service });\n return this;\n }\n\n fixture(file: string): this {\n this.fixtures.push({ file });\n return this;\n }\n\n project(name: string): this {\n this.projectName = name;\n return this;\n }\n\n mock(file: string): this {\n this.mocks.push({ file });\n return this;\n }\n\n // ── HTTP actions ──\n\n get(path: string): this {\n this.request = { method: \"GET\", path };\n return this;\n }\n\n post(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"POST\", path };\n return this;\n }\n\n put(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"PUT\", path };\n return this;\n }\n\n delete(path: string): this {\n this.request = { method: \"DELETE\", path };\n return this;\n }\n\n // ── CLI actions ──\n\n exec(args: string | string[]): this {\n this.commandArgs = args;\n return this;\n }\n\n spawn(args: string, options: SpawnOptions): this {\n this.spawnConfig = { args, options };\n return this;\n }\n\n // ── Run ──\n\n async run(): Promise<SpecificationResult> {\n const hasHttpAction = this.request !== null;\n const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;\n\n if (!hasHttpAction && !hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": no action defined. Call .get(), .post(), .exec(), etc. before .run()`,\n );\n }\n\n if (hasHttpAction && hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`,\n );\n }\n\n // Resolve working directory for CLI mode\n let workDir: null | string = null;\n if (hasCliAction) {\n workDir = this.prepareWorkDir();\n }\n\n // Reset all databases\n if (this.config.databases) {\n for (const db of this.config.databases.values()) {\n await db.reset();\n }\n } else if (this.config.database) {\n await this.config.database.reset();\n }\n\n // Execute seeds\n for (const entry of this.seeds) {\n let db: DatabasePort | undefined;\n if (entry.service && this.config.databases) {\n db = this.config.databases.get(entry.service);\n if (!db) {\n throw new Error(\n `seed() targets database \"${entry.service}\" but it was not found. Available: ${[...this.config.databases.keys()].join(\", \")}`,\n );\n }\n } else {\n db = this.config.database;\n }\n\n if (!db) {\n throw new Error(\"seed() requires a database adapter\");\n }\n\n const sql = readFileSync(resolve(this.testDir, \"seeds\", entry.file), \"utf8\");\n await db.seed(sql);\n }\n\n // Copy fixture files into working directory\n if (this.fixtures.length > 0 && workDir) {\n for (const entry of this.fixtures) {\n const src = resolve(this.testDir, \"fixtures\", entry.file);\n const dest = resolve(workDir, entry.file);\n cpSync(src, dest, { recursive: true });\n }\n }\n\n // Register MSW mocks\n for (const entry of this.mocks) {\n const _mockData = JSON.parse(readFileSync(resolve(this.testDir, \"mock\", entry.file), \"utf8\"));\n // TODO: Register MSW handler from mock data\n }\n\n // Execute action\n if (hasHttpAction) {\n return this.runHttpAction();\n }\n return this.runCliAction(workDir!);\n }\n\n // ── Private ──\n\n private prepareWorkDir(): string {\n const tempDir = mkdtempSync(resolve(tmpdir(), \"spec-cli-\"));\n\n if (this.projectName && this.config.fixturesRoot) {\n const projectDir = resolve(this.config.fixturesRoot, this.projectName);\n if (!existsSync(projectDir)) {\n throw new Error(\n `project(\"${this.projectName}\"): fixture project not found at ${projectDir}`,\n );\n }\n cpSync(projectDir, tempDir, { recursive: true });\n }\n\n return tempDir;\n }\n\n private async runHttpAction(): Promise<SpecificationResult> {\n if (!this.config.server) {\n throw new Error(\"HTTP actions require a server adapter (use integration() or e2e())\");\n }\n\n let body: unknown;\n if (this.request!.bodyFile) {\n body = JSON.parse(\n readFileSync(resolve(this.testDir, \"requests\", this.request!.bodyFile), \"utf8\"),\n );\n }\n\n const response = await this.config.server.request(\n this.request!.method,\n this.request!.path,\n body,\n );\n\n return new SpecificationResult({\n config: this.config,\n requestInfo: { body, method: this.request!.method, path: this.request!.path },\n response,\n testDir: this.testDir,\n });\n }\n\n private async runCliAction(workDir: string): Promise<SpecificationResult> {\n if (!this.config.command) {\n throw new Error(\"CLI actions require a command adapter (use cli())\");\n }\n\n let commandResult: CommandResult;\n\n if (this.spawnConfig) {\n commandResult = await this.config.command.spawn(\n this.spawnConfig.args,\n workDir,\n this.spawnConfig.options,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stdout: \"\", stderr: \"\" };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir);\n }\n\n return new SpecificationResult({\n commandResult,\n config: this.config,\n testDir: this.testDir,\n workDir,\n });\n }\n}\n\n// ── Caller detection ──\n\nfunction getCallerDir(): string {\n const stack = new Error(\"caller detection\").stack;\n if (!stack) {\n throw new Error(\"Cannot detect caller directory: no stack trace\");\n }\n\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n\n if (filePath.includes(\"node_modules\")) {\n continue;\n }\n if (filePath.includes(\"/src/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\");\n }\n\n throw new Error(\"Cannot detect caller directory from stack trace\");\n}\n\n// ── Factory functions ──\n\nexport type SpecificationRunner = (label: string) => SpecificationBuilder;\n\n/**\n * Create a specification runner.\n * Automatically detects the test directory from the call site.\n */\nexport function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner {\n return (label: string) => {\n const testDir = getCallerDir();\n return new SpecificationBuilder(config, testDir, label);\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { Orchestrator } from \"../infrastructure/orchestrator.js\";\nimport type { ServiceHandle } from \"../infrastructure/services/service.port.js\";\nimport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nimport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nimport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport { createSpecificationRunner, type SpecificationRunner } from \"./specification.js\";\n\n/**\n * Resolve root — if relative, resolves from the caller's directory.\n */\nfunction resolveProjectRoot(root: string | undefined): string {\n if (!root) {\n return process.cwd();\n }\n\n if (isAbsolute(root)) {\n return root;\n }\n\n const stack = new Error(\"resolve root\").stack;\n if (stack) {\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n if (filePath.includes(\"node_modules\") || filePath.includes(\"/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\", root);\n }\n }\n\n return resolve(process.cwd(), root);\n}\n\n/**\n * Resolve a CLI command — checks node_modules/.bin, then treats as absolute/PATH.\n */\nfunction resolveCommand(command: string, root: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n // Check node_modules/.bin in fixture root\n const binPath = resolve(root, \"node_modules/.bin\", command);\n if (existsSync(binPath)) {\n return binPath;\n }\n\n // Check project root node_modules/.bin\n const cwdBinPath = resolve(process.cwd(), \"node_modules/.bin\", command);\n if (existsSync(cwdBinPath)) {\n return cwdBinPath;\n }\n\n // Treat as PATH command or absolute\n return command;\n}\n\ntype HonoApp = {\n fetch: (...args: any[]) => any;\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n};\n\ninterface IntegrationOptions {\n /** Factory that returns a Hono app — called after services start. */\n app: () => HonoApp;\n /** Project root for compose detection (relative paths supported). */\n root?: string;\n /** Declared services — started via testcontainers. */\n services: ServiceHandle[];\n}\n\ninterface E2eOptions {\n /** Project root — must contain docker/compose.test.yaml. */\n root?: string;\n}\n\ninterface CliOptions {\n /** CLI command to run (resolved from node_modules/.bin or PATH). */\n command: string;\n /** Project root — base dir for .project() fixture lookup (relative paths supported). */\n root?: string;\n /** Optional infrastructure services (started via testcontainers). */\n services?: ServiceHandle[];\n}\n\ninterface SpecificationRunnerWithCleanup extends SpecificationRunner {\n cleanup: () => Promise<void>;\n orchestrator: Orchestrator;\n}\n\n/**\n * Create an integration specification runner.\n * Starts infra containers via testcontainers, app runs in-process.\n */\nasync function integration(options: IntegrationOptions): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"integration\",\n root: resolveProjectRoot(options.root),\n services: options.services,\n });\n\n await orchestrator.start();\n\n const app = options.app();\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new HonoAdapter(app),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stop();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create an E2E specification runner.\n * Starts full docker compose stack. App URL and database auto-detected.\n */\nasync function e2e(options: E2eOptions = {}): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"e2e\",\n root: resolveProjectRoot(options.root),\n services: [],\n });\n\n await orchestrator.startCompose();\n\n const appUrl = orchestrator.getAppUrl();\n if (!appUrl) {\n throw new Error(\n \"E2E: could not detect app URL from compose. Ensure an app service with ports is defined.\",\n );\n }\n\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new FetchAdapter(appUrl),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stopCompose();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create a CLI specification runner.\n * Runs CLI commands against fixture projects. Optionally starts infrastructure.\n *\n * @example\n * export const spec = await cli({\n * command: resolve(import.meta.dirname, \"../../bin/my-cli.sh\"),\n * root: \"../fixtures\",\n * });\n */\nasync function cli(options: CliOptions): Promise<SpecificationRunnerWithCleanup> {\n const root = resolveProjectRoot(options.root);\n const command = resolveCommand(options.command, root);\n\n let orchestrator: null | Orchestrator = null;\n let database: DatabasePort | undefined;\n let databases: Map<string, DatabasePort> | undefined;\n\n if (options.services?.length) {\n orchestrator = new Orchestrator({\n mode: \"integration\",\n root,\n services: options.services,\n });\n await orchestrator.start();\n database = orchestrator.getDatabase() ?? undefined;\n const dbMap = orchestrator.getDatabases();\n databases = dbMap.size > 0 ? dbMap : undefined;\n }\n\n const runner = createSpecificationRunner({\n command: new ExecAdapter(command),\n database,\n databases,\n fixturesRoot: root,\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = async () => {\n if (orchestrator) {\n await orchestrator.stop();\n }\n };\n runner.orchestrator = orchestrator!;\n\n return runner;\n}\n\n// Service factories\nexport { postgres } from \"../infrastructure/services/postgres.js\";\nexport { redis } from \"../infrastructure/services/redis.js\";\n\n// Types\nexport type { CommandPort, CommandResult } from \"./ports/command.port.js\";\nexport type { DatabasePort } from \"./ports/database.port.js\";\nexport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// Adapters (for advanced usage)\nexport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nexport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nexport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nexport { Orchestrator } from \"../infrastructure/orchestrator.js\";\n\n// Reporter (for testing output)\nexport { normalizeOutput, stripAnsi } from \"../infrastructure/reporter.js\";\n\n// Runners\nexport { cli, e2e, integration };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["parseYaml"],"sources":["../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts","../src/infrastructure/adapters/compose.adapter.ts","../src/infrastructure/adapters/testcontainers.adapter.ts","../src/infrastructure/compose-parser.ts","../src/infrastructure/reporter.ts","../src/infrastructure/services/postgres.ts","../src/infrastructure/services/redis.ts","../src/infrastructure/orchestrator.ts","../src/specification/adapters/exec.adapter.ts","../src/specification/adapters/fetch.adapter.ts","../src/specification/adapters/hono.adapter.ts","../src/specification/assertions/base.ts","../src/specification/assertions/file.ts","../src/specification/assertions/response.ts","../src/specification/assertions/string.ts","../src/specification/assertions/table.ts","../src/specification/assertions/value.ts","../src/specification/specification.ts","../src/specification/index.ts"],"sourcesContent":["import MockDatePackage from \"mockdate\";\n\nexport interface MockDatePort {\n reset: () => void;\n set: (date: Date | number | string) => void;\n}\n\nexport const mockOfDate: MockDatePort = MockDatePackage;\n","import { type DeepMockProxy, mockDeep } from \"vitest-mock-extended\";\n\nexport type MockPort = <T>() => DeepMockProxy<T>;\n\nexport const mockOf: MockPort = mockDeep;\n","import { execSync } from \"node:child_process\";\nimport { dirname } from \"node:path\";\n\nimport type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using docker compose — runs full compose stack.\n * Used by e2e() to start all services including the app.\n */\nexport class ComposeAdapter implements ContainerPort {\n private composeFile: string;\n private serviceName: string;\n private started = false;\n\n constructor(composeFile: string, serviceName: string) {\n this.composeFile = composeFile;\n this.serviceName = serviceName;\n }\n\n private exec(command: string): string {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }).trim();\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} up -d --wait ${this.serviceName}`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.exec(`docker compose -f ${this.composeFile} rm -fsv ${this.serviceName}`);\n this.started = false;\n }\n\n getMappedPort(containerPort: number): number {\n const output = this.exec(\n `docker compose -f ${this.composeFile} port ${this.serviceName} ${containerPort}`,\n );\n // Output: 0.0.0.0:54321\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(0)}`;\n }\n\n async getLogs(): Promise<string> {\n try {\n return this.exec(`docker compose -f ${this.composeFile} logs ${this.serviceName} --tail=50`);\n } catch {\n return \"\";\n }\n }\n}\n\n/**\n * Start the full compose stack and stop it all on cleanup.\n */\nexport class ComposeStackAdapter {\n private composeFile: string;\n private started = false;\n\n constructor(composeFile: string) {\n this.composeFile = composeFile;\n }\n\n private run(command: string): string {\n try {\n return execSync(command, {\n cwd: dirname(this.composeFile),\n encoding: \"utf8\",\n timeout: 120_000,\n }).trim();\n } catch (error: any) {\n const stderr = error.stderr?.toString().trim() ?? error.message;\n throw new Error(`docker compose failed: ${stderr}`, { cause: error });\n }\n }\n\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} up -d --wait`);\n this.started = true;\n }\n\n async stop(): Promise<void> {\n if (!this.started) {\n return;\n }\n\n this.run(`docker compose -f ${this.composeFile} down -v`);\n this.started = false;\n }\n\n getMappedPort(serviceName: string, containerPort: number): number {\n const output = this.run(\n `docker compose -f ${this.composeFile} port ${serviceName} ${containerPort}`,\n );\n const port = output.split(\":\").pop();\n return Number(port);\n }\n\n getHost(): string {\n return \"localhost\";\n }\n}\n","import type { ContainerPort } from \"../ports/container.port.js\";\n\n/**\n * Container adapter using testcontainers.\n * Wraps a GenericContainer for programmatic container lifecycle.\n */\nexport class TestcontainersAdapter implements ContainerPort {\n private image: string;\n private containerPort: number;\n private env: Record<string, string>;\n private reuse: boolean;\n private container: any = null;\n\n constructor(options: {\n image: string;\n port: number;\n env?: Record<string, string>;\n reuse?: boolean;\n }) {\n this.image = options.image;\n this.containerPort = options.port;\n this.env = options.env ?? {};\n this.reuse = options.reuse ?? false;\n }\n\n async start(): Promise<void> {\n const { GenericContainer, Wait } = await import(\"testcontainers\");\n\n let builder = new GenericContainer(this.image).withExposedPorts(this.containerPort);\n\n for (const [key, value] of Object.entries(this.env)) {\n builder = builder.withEnvironment({ [key]: value });\n }\n\n if (this.image.startsWith(\"postgres\")) {\n builder = builder.withWaitStrategy(\n Wait.forLogMessage(/database system is ready to accept connections/, 2),\n );\n }\n\n if (this.reuse) {\n builder = builder.withReuse();\n }\n\n this.container = await builder.start();\n }\n\n async stop(): Promise<void> {\n if (this.container && !this.reuse) {\n await this.container.stop();\n this.container = null;\n }\n }\n\n getMappedPort(containerPort: number): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(containerPort);\n }\n\n getHost(): string {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getHost();\n }\n\n getConnectionString(): string {\n return `${this.getHost()}:${this.getMappedPort(this.containerPort)}`;\n }\n\n async getLogs(): Promise<string> {\n if (!this.container) {\n return \"\";\n }\n\n const stream = await this.container.logs();\n return new Promise((resolve) => {\n let output = \"\";\n stream.on(\"data\", (chunk: Buffer) => {\n output += chunk.toString();\n });\n stream.on(\"end\", () => {\n resolve(output);\n });\n // Timeout after 1s if stream doesn't end\n setTimeout(() => {\n resolve(output);\n }, 1000);\n });\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\n\n/**\n * A parsed service from docker-compose.test.yaml.\n */\nexport interface ComposeService {\n name: string;\n image?: string;\n build?: string;\n ports: { container: number; host?: number }[];\n environment: Record<string, string>;\n volumes: string[];\n dependsOn: string[];\n}\n\n/**\n * Result of parsing a compose file.\n */\nexport interface ComposeConfig {\n services: ComposeService[];\n appService: ComposeService | null;\n infraServices: ComposeService[];\n}\n\n/**\n * Detect the service type from the image name.\n */\nexport function detectServiceType(\n image: string | undefined,\n): \"app\" | \"postgres\" | \"redis\" | \"unknown\" {\n if (!image) {\n return \"app\";\n }\n\n const lower = image.toLowerCase();\n\n if (lower.startsWith(\"postgres\")) {\n return \"postgres\";\n }\n if (lower.startsWith(\"redis\")) {\n return \"redis\";\n }\n\n return \"unknown\";\n}\n\n/**\n * Find the compose file in the project.\n * Looks for docker/compose.test.yaml or docker-compose.test.yaml.\n */\nexport function findComposeFile(projectRoot: string): null | string {\n const candidates = [\n resolve(projectRoot, \"docker/compose.test.yaml\"),\n resolve(projectRoot, \"docker/compose.test.yml\"),\n resolve(projectRoot, \"docker-compose.test.yaml\"),\n resolve(projectRoot, \"docker-compose.test.yml\"),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\n/**\n * Parse a docker-compose file and extract service definitions.\n */\nexport function parseComposeFile(filePath: string): ComposeConfig {\n const content = readFileSync(filePath, \"utf8\");\n const doc = parseYaml(content);\n\n if (!doc?.services) {\n return { services: [], appService: null, infraServices: [] };\n }\n\n const services: ComposeService[] = Object.entries(doc.services).map(\n ([name, def]: [string, any]) => {\n const ports: { container: number; host?: number }[] = [];\n if (def.ports) {\n for (const port of def.ports) {\n const str = String(port);\n if (str.includes(\":\")) {\n const [host, container] = str.split(\":\");\n ports.push({ container: Number(container), host: Number(host) });\n } else {\n ports.push({ container: Number(str) });\n }\n }\n }\n\n const environment: Record<string, string> = {};\n if (def.environment) {\n if (Array.isArray(def.environment)) {\n for (const env of def.environment) {\n const [key, ...rest] = String(env).split(\"=\");\n environment[key] = rest.join(\"=\");\n }\n } else {\n Object.assign(environment, def.environment);\n }\n }\n\n const volumes: string[] = def.volumes ? def.volumes.map((v: string) => String(v)) : [];\n\n let dependsOn: string[] = [];\n if (def.depends_on) {\n dependsOn = Array.isArray(def.depends_on) ? def.depends_on : Object.keys(def.depends_on);\n }\n\n return {\n name,\n image: def.image,\n build: def.build,\n ports,\n environment,\n volumes,\n dependsOn,\n };\n },\n );\n\n const appService = services.find((s) => s.build !== undefined) ?? null;\n const infraServices = services.filter((s) => s.build === undefined);\n\n return { services, appService, infraServices };\n}\n","// ── Colors ──\n\nconst GREEN = \"\\x1b[32m\";\nconst RED = \"\\x1b[31m\";\nconst DIM = \"\\x1b[2m\";\nconst BOLD = \"\\x1b[1m\";\nconst RESET = \"\\x1b[0m\";\nconst BG_CYAN = \"\\x1b[46m\";\nconst BLACK = \"\\x1b[30m\";\n\n// ── Symbols (vitest-native) ──\n\nconst CHECK = \"✓\";\nconst CROSS = \"×\";\nconst ARROW = \"→\";\nconst DASH = \"⎯\";\n\n// ── Types ──\n\nexport interface ServiceReport {\n name: string;\n type: string;\n connectionString?: string;\n durationMs: number;\n error?: string;\n logs?: string;\n}\n\nexport interface AppInfo {\n type: \"http\" | \"in-process\";\n url?: string;\n}\n\n// ── Startup report ──\n\nexport function formatStartupReport(\n mode: \"e2e\" | \"integration\",\n services: ServiceReport[],\n app?: AppInfo,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(`${BG_CYAN}${BLACK}${BOLD} INFRA ${RESET} Starting infrastructure...`);\n lines.push(\"\");\n\n for (const service of services) {\n if (service.error) {\n lines.push(\n ` ${RED}${CROSS}${RESET} ${service.type} (${service.name}) ${RED}${service.error}${RESET} ${DIM}${service.durationMs}ms${RESET}`,\n );\n if (service.logs) {\n const logLines = service.logs.trim().split(\"\\n\").slice(-10);\n for (const logLine of logLines) {\n lines.push(` ${DIM}${logLine}${RESET}`);\n }\n }\n } else {\n const conn = service.connectionString ? `${DIM}${service.connectionString}${RESET}` : \"\";\n lines.push(\n ` ${GREEN}${CHECK}${RESET} ${service.type} (${service.name}) ${conn} ${DIM}${service.durationMs}ms${RESET}`,\n );\n }\n }\n\n if (app) {\n lines.push(\"\");\n if (app.type === \"in-process\") {\n lines.push(` ${DIM}${ARROW} app: in-process (Hono)${RESET}`);\n } else {\n lines.push(` ${DIM}${ARROW} app: ${app.url}${RESET}`);\n }\n }\n\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ── Error divider ──\n\nexport function formatErrorDivider(label: string): string {\n const dashes = DASH.repeat(30);\n return `\\n${RED}${dashes} ${label} ${dashes}${RESET}\\n`;\n}\n\n// ── Status error ──\n\nexport function formatStatusError(\n expectedStatus: number,\n receivedStatus: number,\n request: { method: string; path: string; body?: unknown },\n responseBody: unknown,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected status: ${GREEN}${expectedStatus}${RESET}`);\n lines.push(`Received status: ${RED}${receivedStatus}${RESET}`);\n lines.push(\"\");\n lines.push(`${DIM}${request.method} ${request.path}${RESET}`);\n\n if (request.body) {\n lines.push(formatJson(request.body, DIM));\n }\n\n if (responseBody) {\n lines.push(\"\");\n lines.push(`${DIM}Response:${RESET}`);\n lines.push(formatJson(responseBody, RED));\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Table diff ──\n\nexport function formatTableDiff(\n table: string,\n columns: string[],\n expected: unknown[][],\n actual: unknown[][],\n): string {\n const lines: string[] = [];\n\n lines.push(`Table \"${table}\" mismatch`);\n lines.push(`${DIM} query: ${columns.join(\", \")}${RESET}`);\n lines.push(`${DIM} expected: ${rowLabel(expected.length)}${RESET}`);\n lines.push(`${DIM} received: ${rowLabel(actual.length)}${RESET}`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const header = columns.join(\" | \");\n lines.push(`${DIM} ${header}${RESET}`);\n\n const maxRows = Math.max(expected.length, actual.length);\n\n for (let i = 0; i < maxRows; i++) {\n const exp = expected[i];\n const act = actual[i];\n\n if (exp && !act) {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n } else if (!exp && act) {\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n } else if (exp && act) {\n const same = JSON.stringify(exp) === JSON.stringify(act);\n if (same) {\n lines.push(` ${formatRow(act)}`);\n } else {\n lines.push(`${GREEN}- ${formatRow(exp)}${RESET}`);\n lines.push(`${RED}+ ${formatRow(act)}${RESET}`);\n }\n }\n }\n\n if (expected.length === 0 && actual.length === 0) {\n lines.push(` (empty)`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Response diff ──\n\nexport function formatResponseDiff(file: string, expected: unknown, actual: unknown): string {\n const lines: string[] = [];\n\n lines.push(`Response mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = JSON.stringify(expected, null, 2).split(\"\\n\");\n const actualLines = JSON.stringify(actual, null, 2).split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Exit code error ──\n\nexport function formatExitCodeError(\n expected: number,\n received: number,\n stdout: string,\n stderr: string,\n): string {\n const lines: string[] = [];\n\n lines.push(`Expected exit code: ${GREEN}${expected}${RESET}`);\n lines.push(`Received exit code: ${RED}${received}${RESET}`);\n\n if (stdout.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stdout:${RESET}`);\n for (const line of stdout.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n if (stderr.trim()) {\n lines.push(\"\");\n lines.push(`${DIM}stderr:${RESET}`);\n for (const line of stderr.trim().split(\"\\n\").slice(-15)) {\n lines.push(` ${RED}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Stdout/stderr diff ──\n\nexport function formatStdoutDiff(file: string, expected: string, actual: string): string {\n const lines: string[] = [];\n\n lines.push(`Output mismatch (${file})`);\n lines.push(\"\");\n lines.push(`${GREEN}- Expected${RESET}`);\n lines.push(`${RED}+ Received${RESET}`);\n lines.push(\"\");\n\n const expectedLines = expected.split(\"\\n\");\n const actualLines = actual.split(\"\\n\");\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n\n for (let i = 0; i < maxLines; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n\n if (exp === act) {\n lines.push(` ${exp}`);\n } else {\n if (exp !== undefined) {\n lines.push(`${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(`${RED}+ ${act}${RESET}`);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── File assertions ──\n\nexport function formatFileMissing(path: string): string {\n return `Expected file to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileUnexpected(path: string): string {\n return `Expected file NOT to exist: ${RED}${path}${RESET}`;\n}\n\nexport function formatFileContentMismatch(path: string, expected: string, actual: string): string {\n const lines: string[] = [];\n lines.push(`File \"${path}\" does not contain expected content`);\n lines.push(\"\");\n lines.push(`${GREEN}Expected to contain:${RESET}`);\n lines.push(` ${GREEN}${expected}${RESET}`);\n lines.push(\"\");\n lines.push(`${RED}Actual content (first 20 lines):${RESET}`);\n for (const line of actual.split(\"\\n\").slice(0, 20)) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n return lines.join(\"\\n\");\n}\n\n// ── Service logs section ──\n\nexport function formatServiceLogs(services: { name: string; logs: string }[]): string {\n const lines: string[] = [];\n\n for (const { name, logs } of services) {\n if (!logs.trim()) {\n continue;\n }\n\n lines.push(\"\");\n lines.push(`${DIM}${name} logs (last 10 lines):${RESET}`);\n\n const logLines = logs.trim().split(\"\\n\").slice(-10);\n for (const line of logLines) {\n lines.push(` ${DIM}${line}${RESET}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n// ── Helpers ──\n\nfunction rowLabel(n: number): string {\n return n === 1 ? \"1 row\" : `${n} rows`;\n}\n\nfunction formatJson(value: unknown, color: string): string {\n return JSON.stringify(value, null, 2)\n .split(\"\\n\")\n .map((line) => `${color}${line}${RESET}`)\n .join(\"\\n\");\n}\n\nfunction formatRow(row: unknown[]): string {\n return row.map((v) => String(v ?? \"null\")).join(\" | \");\n}\n\n// ── Test utilities ──\n\nexport function stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n}\n\nexport function normalizeOutput(str: string): string {\n return stripAnsi(str)\n .replace(/localhost:\\d+/g, \"localhost:PORT\")\n .replace(/\\d+ms/g, \"Xms\")\n .replace(/\\d+\\.\\d+s/g, \"X.Xs\")\n .trim();\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { Client } from \"pg\";\n\nimport type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface PostgresOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n /** Override environment variables. */\n env?: Record<string, string>;\n}\n\nclass PostgresHandle implements DatabasePort, ServiceHandle {\n readonly type = \"postgres\";\n readonly composeName: null | string;\n readonly defaultPort = 5432;\n readonly defaultImage: string;\n readonly environment: Record<string, string>;\n\n connectionString = \"\";\n started = false;\n\n private client: Client | null = null;\n\n constructor(options: PostgresOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"postgres:17\";\n this.environment = {\n POSTGRES_DB: \"test\",\n POSTGRES_PASSWORD: \"test\",\n POSTGRES_USER: \"test\",\n ...options.env,\n };\n }\n\n buildConnectionString(host: string, port: number): string {\n const user = this.environment.POSTGRES_USER ?? \"test\";\n const password = this.environment.POSTGRES_PASSWORD ?? \"test\";\n const db = this.environment.POSTGRES_DB ?? \"test\";\n return `postgresql://${user}:${password}@${host}:${port}/${db}`;\n }\n\n createDatabaseAdapter(): DatabasePort {\n return this;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"postgres: cannot healthcheck — no connection string\");\n }\n\n // Healthcheck uses a throwaway client (connection might not be established yet)\n try {\n const client = new Client({ connectionString: this.connectionString });\n await client.connect();\n await client.query(\"SELECT 1\");\n await client.end();\n } catch (error: any) {\n throw new Error(\n `postgres healthcheck failed: ${error.message || error.code || String(error)}`,\n { cause: error },\n );\n }\n }\n\n async initialize(composeDir: string): Promise<void> {\n if (!this.composeName) {\n return;\n }\n\n const initPaths = [\n resolve(composeDir, `${this.composeName}/init.sql`),\n resolve(composeDir, \"postgres/init.sql\"),\n ];\n\n for (const initPath of initPaths) {\n if (existsSync(initPath)) {\n const sql = readFileSync(initPath, \"utf8\");\n try {\n await this.seed(sql);\n } catch (error: any) {\n throw new Error(`postgres init script failed (${initPath}):\\n${error.message}`, {\n cause: error,\n });\n }\n return;\n }\n }\n }\n\n private async getClient(): Promise<Client> {\n if (this.client) {\n return this.client;\n }\n const client = new Client({ connectionString: this.connectionString });\n client.on(\"error\", () => {\n // Connection dropped (container stopped) — reset so next call reconnects\n this.client = null;\n });\n await client.connect();\n this.client = client;\n return client;\n }\n\n async seed(sql: string): Promise<void> {\n const client = await this.getClient();\n await client.query(sql);\n }\n\n async query(table: string, columns: string[]): Promise<unknown[][]> {\n const client = await this.getClient();\n const columnList = columns.join(\", \");\n const result = await client.query(`SELECT ${columnList} FROM \"${table}\" ORDER BY 1`);\n return result.rows.map((row: Record<string, unknown>) => columns.map((col) => row[col]));\n }\n\n async reset(): Promise<void> {\n const client = await this.getClient();\n const result = await client.query(`\n SELECT tablename FROM pg_tables\n WHERE schemaname = 'public'\n AND tablename NOT LIKE '_prisma%'\n `);\n for (const row of result.rows) {\n await client.query(`TRUNCATE \"${row.tablename}\" CASCADE`);\n }\n }\n}\n\n/**\n * Create a PostgreSQL service handle.\n *\n * @example\n * const db = postgres({ compose: \"db\" });\n * // After start: db.connectionString is populated\n */\nexport function postgres(options: PostgresOptions = {}): PostgresHandle {\n return new PostgresHandle(options);\n}\n","import type { DatabasePort } from \"../../specification/ports/database.port.js\";\nimport type { ServiceHandle } from \"./service.port.js\";\n\ninterface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nclass RedisHandle implements ServiceHandle {\n readonly type = \"redis\";\n readonly composeName: null | string;\n readonly defaultPort = 6379;\n readonly defaultImage: string;\n readonly environment: Record<string, string> = {};\n\n connectionString = \"\";\n started = false;\n\n constructor(options: RedisOptions = {}) {\n this.composeName = options.compose ?? null;\n this.defaultImage = options.image ?? \"redis:7\";\n }\n\n buildConnectionString(host: string, port: number): string {\n return `redis://${host}:${port}`;\n }\n\n createDatabaseAdapter(): DatabasePort | null {\n return null;\n }\n\n async healthcheck(): Promise<void> {\n if (!this.connectionString) {\n throw new Error(\"redis: cannot healthcheck — no connection string\");\n }\n\n try {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n await client.ping();\n await client.disconnect();\n } catch (error: any) {\n throw new Error(`redis healthcheck failed: ${error.message || error.code || String(error)}`, {\n cause: error,\n });\n }\n }\n\n async initialize(): Promise<void> {\n // Redis doesn't need initialization scripts\n }\n\n async reset(): Promise<void> {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: this.connectionString });\n await client.connect();\n try {\n await client.flushAll();\n } finally {\n await client.disconnect();\n }\n }\n}\n\n/**\n * Create a Redis service handle.\n *\n * @example\n * const cache = redis({ compose: \"cache\" });\n * // After start: cache.connectionString is populated\n */\nexport function redis(options: RedisOptions = {}): RedisHandle {\n return new RedisHandle(options);\n}\n","import { dirname } from \"node:path\";\n\nimport type { DatabasePort } from \"../specification/ports/database.port.js\";\nimport { ComposeStackAdapter } from \"./adapters/compose.adapter.js\";\nimport { TestcontainersAdapter } from \"./adapters/testcontainers.adapter.js\";\nimport { detectServiceType, findComposeFile, parseComposeFile } from \"./compose-parser.js\";\nimport type { ContainerPort } from \"./ports/container.port.js\";\nimport { type AppInfo, formatStartupReport, type ServiceReport } from \"./reporter.js\";\nimport { postgres } from \"./services/postgres.js\";\nimport { redis } from \"./services/redis.js\";\nimport type { ServiceHandle } from \"./services/service.port.js\";\n\ninterface RunningService {\n handle: ServiceHandle;\n container: ContainerPort | null;\n}\n\ninterface OrchestratorOptions {\n services: ServiceHandle[];\n mode: \"e2e\" | \"integration\";\n root?: string;\n}\n\n/**\n * Orchestrator for test infrastructure.\n * Integration: starts services via testcontainers.\n * E2E: runs full docker compose up.\n */\nexport class Orchestrator {\n private services: ServiceHandle[];\n private mode: \"e2e\" | \"integration\";\n private root: string;\n private running: RunningService[] = [];\n private composeStack: ComposeStackAdapter | null = null;\n private composeHandles: ServiceHandle[] = [];\n private started = false;\n\n constructor(options: OrchestratorOptions) {\n this.services = options.services;\n this.mode = options.mode;\n this.root = options.root ?? process.cwd();\n }\n\n /**\n * Start declared services via testcontainers (integration mode).\n * Phase 1: start all containers in parallel (the slow part).\n * Phase 2: wire connections, healthcheck, and init sequentially (fast).\n */\n async start(): Promise<void> {\n if (this.started) {\n return;\n }\n\n const composePath = findComposeFile(this.root);\n const composeDir = composePath ? dirname(composePath) : this.root;\n const composeConfig = composePath ? parseComposeFile(composePath) : null;\n\n // Phase 1: resolve config and start all containers in parallel\n const containerTasks = this.services.map((handle) => {\n let image = handle.defaultImage;\n let env = { ...handle.environment };\n\n if (handle.composeName && composeConfig) {\n const composeService = composeConfig.services.find((s) => s.name === handle.composeName);\n if (composeService) {\n image = composeService.image ?? image;\n env = { ...env, ...composeService.environment };\n Object.assign(handle.environment, composeService.environment);\n }\n }\n\n const container = new TestcontainersAdapter({ image, port: handle.defaultPort, env });\n return { container, handle };\n });\n\n // Start all containers concurrently\n await Promise.all(containerTasks.map(({ container }) => container.start()));\n\n // Phase 2: wire connections, healthcheck, init (fast — containers already running)\n const reports: ServiceReport[] = [];\n\n for (const { container, handle } of containerTasks) {\n const serviceStartTime = Date.now();\n\n try {\n const host = container.getHost();\n const port = container.getMappedPort(handle.defaultPort);\n handle.connectionString = handle.buildConnectionString(host, port);\n\n await handle.healthcheck();\n await handle.initialize(composeDir);\n handle.started = true;\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n connectionString: handle.connectionString,\n durationMs: Date.now() - serviceStartTime,\n });\n this.running.push({ handle, container });\n } catch (error: any) {\n let logs = \"\";\n try {\n logs = await container.getLogs();\n } catch {\n /* Ignore log fetch errors */\n }\n try {\n await container.stop();\n } catch {\n /* Ignore stop errors */\n }\n\n reports.push({\n name: handle.composeName ?? handle.type,\n type: handle.type,\n durationMs: Date.now() - serviceStartTime,\n error: error.message,\n logs,\n });\n\n const output = formatStartupReport(\"integration\", reports, { type: \"in-process\" });\n console.error(output);\n throw error;\n }\n }\n\n this.started = true;\n\n const appInfo: AppInfo = { type: \"in-process\" };\n const output = formatStartupReport(\"integration\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop testcontainers (integration mode).\n */\n async stop(): Promise<void> {\n for (const { container } of this.running) {\n if (container) {\n await container.stop();\n }\n }\n this.running = [];\n this.started = false;\n }\n\n /**\n * Start full docker compose stack (e2e mode).\n * Auto-detects infra services and creates handles for them.\n */\n async startCompose(): Promise<void> {\n const composePath = findComposeFile(this.root);\n if (!composePath) {\n throw new Error(`E2E: no compose file found in ${this.root}`);\n }\n\n const startTime = Date.now();\n const composeDir = dirname(composePath);\n const composeConfig = parseComposeFile(composePath);\n\n this.composeStack = new ComposeStackAdapter(composePath);\n await this.composeStack.start();\n\n // Create handles for detected infra services\n for (const service of composeConfig.infraServices) {\n const type = detectServiceType(service.image);\n\n if (type === \"postgres\") {\n const handle = postgres({ compose: service.name, env: service.environment });\n const port = this.composeStack.getMappedPort(service.name, 5432);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n\n await handle.initialize(composeDir);\n handle.started = true;\n\n this.composeHandles.push(handle);\n } else if (type === \"redis\") {\n const handle = redis({ compose: service.name });\n const port = this.composeStack.getMappedPort(service.name, 6379);\n handle.connectionString = handle.buildConnectionString(\"localhost\", port);\n handle.started = true;\n\n this.composeHandles.push(handle);\n }\n }\n\n const durationMs = Date.now() - startTime;\n const reports: ServiceReport[] = this.composeHandles.map((h) => ({\n name: h.composeName ?? h.type,\n type: h.type,\n connectionString: h.connectionString,\n durationMs,\n }));\n\n const appUrl = this.getAppUrl();\n const appInfo: AppInfo = { type: \"http\", url: appUrl ?? undefined };\n const output = formatStartupReport(\"e2e\", reports, appInfo);\n console.log(output);\n }\n\n /**\n * Stop docker compose stack (e2e mode).\n */\n async stopCompose(): Promise<void> {\n if (this.composeStack) {\n await this.composeStack.stop();\n this.composeStack = null;\n }\n this.composeHandles = [];\n }\n\n /**\n * Get a database service by compose name, or the first one if no name given.\n */\n getDatabase(serviceName?: string): DatabasePort | null {\n for (const handle of [...this.services, ...this.composeHandles]) {\n if (serviceName && handle.composeName !== serviceName) {\n continue;\n }\n const adapter = handle.createDatabaseAdapter();\n if (adapter) {\n return adapter;\n }\n }\n return null;\n }\n\n /**\n * Get all database services keyed by compose name.\n */\n getDatabases(): Map<string, DatabasePort> {\n const map = new Map<string, DatabasePort>();\n for (const handle of [...this.services, ...this.composeHandles]) {\n const adapter = handle.createDatabaseAdapter();\n if (adapter && handle.composeName) {\n map.set(handle.composeName, adapter);\n }\n }\n return map;\n }\n\n /**\n * Get app URL from compose (e2e mode).\n */\n getAppUrl(): null | string {\n const composePath = findComposeFile(this.root);\n if (!composePath || !this.composeStack) {\n return null;\n }\n\n const config = parseComposeFile(composePath);\n const appService = config.appService;\n\n if (!appService || appService.ports.length === 0) {\n return null;\n }\n\n const port = this.composeStack.getMappedPort(appService.name, appService.ports[0].container);\n return `http://localhost:${port}`;\n }\n}\n","import { execSync, spawn } from \"node:child_process\";\n\nimport type { CommandPort, CommandResult, SpawnOptions } from \"../ports/command.port.js\";\n\n/**\n * Executes CLI commands via execSync (blocking) or spawn (long-running).\n * Used by cli() for local command execution.\n */\nexport class ExecAdapter implements CommandPort {\n private command: string;\n\n constructor(command: string) {\n this.command = command;\n }\n\n async exec(args: string, cwd: string): Promise<CommandResult> {\n // Clear INIT_CWD so CLI tools use the actual cwd, not npm's caller directory\n const env = { ...process.env, INIT_CWD: undefined };\n\n try {\n const stdout = execSync(`${this.command} ${args}`, {\n cwd,\n encoding: \"utf8\",\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return { exitCode: 0, stdout, stderr: \"\" };\n } catch (error: any) {\n return {\n exitCode: error.status ?? 1,\n stdout: error.stdout?.toString() ?? \"\",\n stderr: error.stderr?.toString() ?? \"\",\n };\n }\n }\n\n async spawn(args: string, cwd: string, options: SpawnOptions): Promise<CommandResult> {\n const env = { ...process.env, INIT_CWD: undefined };\n\n return new Promise((resolve) => {\n let stdout = \"\";\n let stderr = \"\";\n let resolved = false;\n\n const child = spawn(this.command, args.split(/\\s+/).filter(Boolean), {\n cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n const finish = (exitCode: number) => {\n if (resolved) {\n return;\n }\n resolved = true;\n child.kill(\"SIGTERM\");\n resolve({ exitCode, stdout, stderr });\n };\n\n let patternMatched = false;\n\n const checkPattern = () => {\n if (\n !patternMatched &&\n (stdout.includes(options.waitFor) || stderr.includes(options.waitFor))\n ) {\n patternMatched = true;\n finish(0);\n }\n };\n\n child.stdout?.on(\"data\", (data: Buffer) => {\n stdout += data.toString();\n checkPattern();\n });\n\n child.stderr?.on(\"data\", (data: Buffer) => {\n stderr += data.toString();\n checkPattern();\n });\n\n // Process exited before pattern matched\n child.on(\"exit\", (code) => {\n if (!patternMatched) {\n finish(code === 0 ? 1 : (code ?? 1));\n }\n });\n\n setTimeout(() => finish(124), options.timeout);\n });\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for real HTTP — sends actual fetch requests.\n * Used by e2e() specification runner.\n */\nexport class FetchAdapter implements ServerPort {\n private baseUrl: string;\n\n constructor(url: string) {\n this.baseUrl = url.replace(/\\/$/, \"\");\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(`${this.baseUrl}${path}`, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","import type { ServerPort, ServerResponse } from \"../ports/server.port.js\";\n\n/**\n * Server adapter for Hono — in-process requests, no real HTTP.\n * Used by integration() specification runner.\n */\nexport class HonoAdapter implements ServerPort {\n private app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n };\n\n constructor(app: {\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n }) {\n this.app = app;\n }\n\n async request(method: string, path: string, body?: unknown): Promise<ServerResponse> {\n const init: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\" },\n };\n\n if (body !== undefined) {\n init.body = JSON.stringify(body);\n }\n\n const response = await this.app.request(path, init);\n const responseBody = await response.json().catch(() => null);\n\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n body: responseBody,\n headers,\n };\n }\n}\n","/**\n * Base assertion that handles .not negation.\n * Subclasses call this.assert(condition, message, negatedMessage) for each predicate.\n */\nexport class BaseAssertion {\n protected negated = false;\n\n get not(): this {\n const clone = Object.create(Object.getPrototypeOf(this));\n Object.assign(clone, this);\n clone.negated = !this.negated;\n return clone;\n }\n\n protected assert(condition: boolean, message: string, negatedMessage: string): void {\n if (this.negated) {\n if (condition) {\n throw new Error(negatedMessage);\n }\n } else {\n if (!condition) {\n throw new Error(message);\n }\n }\n }\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport {\n formatFileContentMismatch,\n formatFileMissing,\n formatFileUnexpected,\n} from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a file in the working directory.\n * Usage: result.file(\"dist/index.js\").toExist()\n */\nexport class FileAssertion extends BaseAssertion {\n private filePath: string;\n private resolvedPath: string;\n\n constructor(filePath: string, workDir: string) {\n super();\n this.filePath = filePath;\n this.resolvedPath = resolve(workDir, filePath);\n }\n\n toExist(): void {\n const exists = existsSync(this.resolvedPath);\n this.assert(exists, formatFileMissing(this.filePath), formatFileUnexpected(this.filePath));\n }\n\n toContain(expected: string): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return; // File doesn't exist, so it certainly doesn't contain the string\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = content.includes(expected);\n this.assert(\n found,\n formatFileContentMismatch(this.filePath, expected, content),\n `Expected file \"${this.filePath}\" NOT to contain \"${expected}\"`,\n );\n }\n\n toMatch(pattern: RegExp): void {\n if (!existsSync(this.resolvedPath)) {\n if (this.negated) {\n return;\n }\n throw new Error(formatFileMissing(this.filePath));\n }\n const content = readFileSync(this.resolvedPath, \"utf8\");\n const found = pattern.test(content);\n this.assert(\n found,\n `Expected file \"${this.filePath}\" to match: ${pattern}\\n\\nActual content:\\n${content.slice(0, 500)}`,\n `Expected file \"${this.filePath}\" NOT to match: ${pattern}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatResponseDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on an HTTP response body.\n * Usage: result.response.toMatchFile(\"expected.json\")\n */\nexport class ResponseAssertion extends BaseAssertion {\n private body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\n super();\n this.body = body;\n this.testDir = testDir;\n }\n\n toMatchFile(file: string): void {\n const expected = JSON.parse(readFileSync(resolve(this.testDir, \"responses\", file), \"utf8\"));\n const match = JSON.stringify(this.body) === JSON.stringify(expected);\n this.assert(\n match,\n formatResponseDiff(file, expected, this.body),\n `Expected response NOT to match file \"${file}\", but it did`,\n );\n }\n\n toContain(subset: Record<string, unknown>): void {\n const bodyStr = JSON.stringify(this.body);\n const subsetStr = JSON.stringify(subset);\n // Check if all keys in subset exist with same values in body\n const bodyObj = typeof this.body === \"object\" && this.body !== null ? this.body : {};\n const match = Object.entries(subset).every(\n ([key, value]) =>\n JSON.stringify((bodyObj as Record<string, unknown>)[key]) === JSON.stringify(value),\n );\n this.assert(\n match,\n `Expected response to contain: ${subsetStr}\\n\\nActual response:\\n${bodyStr}`,\n `Expected response NOT to contain: ${subsetStr}`,\n );\n }\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nimport { formatStdoutDiff } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a string (stdout, stderr, response body).\n * Usage: result.stdout.toContain(\"hello\")\n */\nexport class StringAssertion extends BaseAssertion {\n private actual: string;\n private label: string;\n private testDir?: string;\n\n constructor(actual: string, label: string, testDir?: string) {\n super();\n this.actual = actual;\n this.label = label;\n this.testDir = testDir;\n }\n\n toContain(expected: string, options?: { near?: string }): void {\n if (options?.near) {\n const found = this.containsNear(expected, options.near);\n this.assert(\n found,\n `Expected ${this.label} to contain \"${expected}\" near \"${options.near}\"\\n\\n${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain \"${expected}\" near \"${options.near}\", but it was found`,\n );\n } else {\n const found = this.actual.includes(expected);\n this.assert(\n found,\n `Expected ${this.label} to contain: \"${expected}\"\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to contain: \"${expected}\"`,\n );\n }\n }\n\n toMatch(pattern: RegExp): void {\n const found = pattern.test(this.actual);\n this.assert(\n found,\n `Expected ${this.label} to match: ${pattern}\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to match: ${pattern}`,\n );\n }\n\n toMatchFile(file: string): void {\n if (!this.testDir) {\n throw new Error(\"toMatchFile requires a test directory context\");\n }\n const expected = readFileSync(resolve(this.testDir, \"expected\", file), \"utf8\").trim();\n const actual = this.actual.trim();\n const match = actual === expected;\n this.assert(\n match,\n formatStdoutDiff(file, expected, actual),\n `Expected ${this.label} NOT to match file \"${file}\", but it did`,\n );\n }\n\n toBeEmpty(): void {\n const empty = this.actual.trim() === \"\";\n this.assert(\n empty,\n `Expected ${this.label} to be empty\\n\\nActual ${this.label}:\\n${this.truncate(this.actual)}`,\n `Expected ${this.label} NOT to be empty`,\n );\n }\n\n // ── Private ──\n\n private containsNear(target: string, near: string, proximity = 500): boolean {\n const clean = this.stripAnsi(this.actual);\n const nearLower = near.toLowerCase();\n const targetLower = target.toLowerCase();\n\n // Find all occurrences of `near` and check if `target` appears within proximity\n let searchFrom = 0;\n while (true) {\n const idx = clean.toLowerCase().indexOf(nearLower, searchFrom);\n if (idx === -1) {\n break;\n }\n const windowStart = Math.max(0, idx - proximity);\n const windowEnd = Math.min(clean.length, idx + nearLower.length + proximity);\n const window = clean.substring(windowStart, windowEnd).toLowerCase();\n if (window.includes(targetLower)) {\n return true;\n }\n searchFrom = idx + 1;\n }\n return false;\n }\n\n private stripAnsi(str: string): string {\n // eslint-disable-next-line no-control-regex\n return str.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n }\n\n private truncate(str: string, maxLines = 20): string {\n const lines = str.split(\"\\n\");\n if (lines.length <= maxLines) {\n return str;\n }\n return `${lines.slice(0, maxLines).join(\"\\n\")}\\n... (${lines.length - maxLines} more lines)`;\n }\n}\n","import { formatTableDiff } from \"../../infrastructure/reporter.js\";\nimport type { DatabasePort } from \"../ports/database.port.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a database table.\n * Usage: await result.table(\"users\").toMatch({ columns: [\"name\"], rows: [[\"Alice\"]] })\n */\nexport class TableAssertion extends BaseAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\n super();\n this.tableName = tableName;\n this.db = db;\n }\n\n async toMatch(expected: { columns: string[]; rows: unknown[][] }): Promise<void> {\n const actual = await this.db.query(this.tableName, expected.columns);\n const match = JSON.stringify(actual) === JSON.stringify(expected.rows);\n this.assert(\n match,\n formatTableDiff(this.tableName, expected.columns, expected.rows, actual),\n `Expected table \"${this.tableName}\" NOT to match, but it did`,\n );\n }\n\n async toBeEmpty(): Promise<void> {\n // Query a single column to check if table has rows — use a lightweight approach\n const actual = await this.db.query(this.tableName, [\"*\"]);\n const empty = actual.length === 0;\n this.assert(\n empty,\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n `Expected table \"${this.tableName}\" NOT to be empty, but it is`,\n );\n }\n}\n","import { formatExitCodeError, formatStatusError } from \"../../infrastructure/reporter.js\";\nimport { BaseAssertion } from \"./base.js\";\n\n/**\n * Assertions on a single value (exit code, status code).\n * Usage: result.exitCode.toBe(0)\n */\nexport class ValueAssertion extends BaseAssertion {\n private actual: number;\n private label: string;\n private context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string };\n\n constructor(\n actual: number,\n label: string,\n context?: { request?: any; responseBody?: unknown; stdout?: string; stderr?: string },\n ) {\n super();\n this.actual = actual;\n this.label = label;\n this.context = context;\n }\n\n toBe(expected: number): void {\n const match = this.actual === expected;\n\n let message: string;\n if (this.label === \"exit code\" && this.context?.stdout !== undefined) {\n message = formatExitCodeError(\n expected,\n this.actual,\n this.context.stdout ?? \"\",\n this.context.stderr ?? \"\",\n );\n } else if (this.label === \"status\" && this.context?.request) {\n message = formatStatusError(\n expected,\n this.actual,\n this.context.request,\n this.context.responseBody,\n );\n } else {\n message = `Expected ${this.label}: ${expected}\\nReceived ${this.label}: ${this.actual}`;\n }\n\n this.assert(match, message, `Expected ${this.label} NOT to be ${expected}, but it was`);\n }\n}\n","import { cpSync, existsSync, mkdtempSync, readFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\nimport { FileAssertion } from \"./assertions/file.js\";\nimport { ResponseAssertion } from \"./assertions/response.js\";\nimport { StringAssertion } from \"./assertions/string.js\";\nimport { TableAssertion } from \"./assertions/table.js\";\nimport { ValueAssertion } from \"./assertions/value.js\";\nimport type { CommandPort, CommandResult, SpawnOptions } from \"./ports/command.port.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// ── Types ──\n\nexport interface SpecificationConfig {\n command?: CommandPort;\n database?: DatabasePort;\n databases?: Map<string, DatabasePort>;\n fixturesRoot?: string;\n server?: ServerPort;\n}\n\nexport interface SeedEntry {\n file: string;\n service?: string;\n}\n\nexport interface FixtureEntry {\n file: string;\n}\n\nexport interface MockEntry {\n file: string;\n}\n\nexport interface RequestEntry {\n bodyFile?: string;\n method: string;\n path: string;\n}\n\n// ── Result (after .run()) ──\n\ninterface RequestInfo {\n body?: unknown;\n method: string;\n path: string;\n}\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: RequestInfo;\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: RequestInfo;\n response?: ServerResponse;\n testDir: string;\n workDir?: string;\n }) {\n this.responseData = options.response;\n this.commandResult = options.commandResult;\n this.config = options.config;\n this.testDir = options.testDir;\n this.requestInfo = options.requestInfo;\n this.workDir = options.workDir;\n }\n\n // ── Scoped assertion accessors ──\n\n get exitCode(): ValueAssertion {\n if (!this.commandResult) {\n throw new Error(\".exitCode requires a CLI action (.exec())\");\n }\n return new ValueAssertion(this.commandResult.exitCode, \"exit code\", {\n stderr: this.commandResult.stderr,\n stdout: this.commandResult.stdout,\n });\n }\n\n get status(): ValueAssertion {\n if (!this.responseData || !this.requestInfo) {\n throw new Error(\".status requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ValueAssertion(this.responseData.status, \"status\", {\n request: this.requestInfo,\n responseBody: this.responseData.body,\n });\n }\n\n get response(): ResponseAssertion {\n if (!this.responseData) {\n throw new Error(\".response requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ResponseAssertion(this.responseData.body, this.testDir);\n }\n\n get stdout(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stdout requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stdout, \"stdout\", this.testDir);\n }\n\n get stderr(): StringAssertion {\n if (!this.commandResult) {\n throw new Error(\".stderr requires a CLI action (.exec())\");\n }\n return new StringAssertion(this.commandResult.stderr, \"stderr\", this.testDir);\n }\n\n file(path: string): FileAssertion {\n const baseDir = this.workDir ?? this.testDir;\n return new FileAssertion(path, baseDir);\n }\n\n table(tableName: string, options?: { service?: string }): TableAssertion {\n const db = this.resolveDatabase(options?.service);\n if (!db) {\n throw new Error(\n options?.service\n ? `table(\"${tableName}\") requires database \"${options.service}\" but it was not found`\n : `table(\"${tableName}\") requires a database adapter`,\n );\n }\n return new TableAssertion(tableName, db);\n }\n\n // ── Private ──\n\n private resolveDatabase(serviceName?: string): DatabasePort | undefined {\n if (serviceName && this.config.databases) {\n return this.config.databases.get(serviceName);\n }\n return this.config.database;\n }\n}\n\n// ── Builder (before .run()) ──\n\nexport class SpecificationBuilder {\n private commandArgs: null | string | string[] = null;\n private config: SpecificationConfig;\n private fixtures: FixtureEntry[] = [];\n private label: string;\n private mocks: MockEntry[] = [];\n private projectName: null | string = null;\n private request: null | RequestEntry = null;\n private seeds: SeedEntry[] = [];\n private spawnConfig: null | { args: string; options: SpawnOptions } = null;\n private testDir: string;\n\n constructor(config: SpecificationConfig, testDir: string, label: string) {\n this.config = config;\n this.testDir = testDir;\n this.label = label;\n }\n\n // ── Setup ──\n\n seed(file: string, options?: { service?: string }): this {\n this.seeds.push({ file, service: options?.service });\n return this;\n }\n\n fixture(file: string): this {\n this.fixtures.push({ file });\n return this;\n }\n\n project(name: string): this {\n this.projectName = name;\n return this;\n }\n\n mock(file: string): this {\n this.mocks.push({ file });\n return this;\n }\n\n // ── HTTP actions ──\n\n get(path: string): this {\n this.request = { method: \"GET\", path };\n return this;\n }\n\n post(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"POST\", path };\n return this;\n }\n\n put(path: string, bodyFile?: string): this {\n this.request = { bodyFile, method: \"PUT\", path };\n return this;\n }\n\n delete(path: string): this {\n this.request = { method: \"DELETE\", path };\n return this;\n }\n\n // ── CLI actions ──\n\n exec(args: string | string[]): this {\n this.commandArgs = args;\n return this;\n }\n\n spawn(args: string, options: SpawnOptions): this {\n this.spawnConfig = { args, options };\n return this;\n }\n\n // ── Run ──\n\n async run(): Promise<SpecificationResult> {\n const hasHttpAction = this.request !== null;\n const hasCliAction = this.commandArgs !== null || this.spawnConfig !== null;\n\n if (!hasHttpAction && !hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": no action defined. Call .get(), .post(), .exec(), etc. before .run()`,\n );\n }\n\n if (hasHttpAction && hasCliAction) {\n throw new Error(\n `Specification \"${this.label}\": cannot mix HTTP (.get/.post) and CLI (.exec/.spawn) actions`,\n );\n }\n\n // Resolve working directory for CLI mode\n let workDir: null | string = null;\n if (hasCliAction) {\n workDir = this.prepareWorkDir();\n }\n\n // Reset all databases\n if (this.config.databases) {\n for (const db of this.config.databases.values()) {\n await db.reset();\n }\n } else if (this.config.database) {\n await this.config.database.reset();\n }\n\n // Execute seeds\n for (const entry of this.seeds) {\n let db: DatabasePort | undefined;\n if (entry.service && this.config.databases) {\n db = this.config.databases.get(entry.service);\n if (!db) {\n throw new Error(\n `seed() targets database \"${entry.service}\" but it was not found. Available: ${[...this.config.databases.keys()].join(\", \")}`,\n );\n }\n } else {\n db = this.config.database;\n }\n\n if (!db) {\n throw new Error(\"seed() requires a database adapter\");\n }\n\n const sql = readFileSync(resolve(this.testDir, \"seeds\", entry.file), \"utf8\");\n await db.seed(sql);\n }\n\n // Copy fixture files into working directory\n if (this.fixtures.length > 0 && workDir) {\n for (const entry of this.fixtures) {\n const src = resolve(this.testDir, \"fixtures\", entry.file);\n const dest = resolve(workDir, entry.file);\n cpSync(src, dest, { recursive: true });\n }\n }\n\n // Register MSW mocks\n for (const entry of this.mocks) {\n const _mockData = JSON.parse(readFileSync(resolve(this.testDir, \"mock\", entry.file), \"utf8\"));\n // TODO: Register MSW handler from mock data\n }\n\n // Execute action\n if (hasHttpAction) {\n return this.runHttpAction();\n }\n return this.runCliAction(workDir!);\n }\n\n // ── Private ──\n\n private prepareWorkDir(): string {\n // No project or fixtures — run from fixturesRoot or cwd (no temp dir needed)\n if (!this.projectName && this.fixtures.length === 0) {\n return this.config.fixturesRoot ?? process.cwd();\n }\n\n const tempDir = mkdtempSync(resolve(tmpdir(), \"spec-cli-\"));\n\n if (this.projectName && this.config.fixturesRoot) {\n const projectDir = resolve(this.config.fixturesRoot, this.projectName);\n if (!existsSync(projectDir)) {\n throw new Error(\n `project(\"${this.projectName}\"): fixture project not found at ${projectDir}`,\n );\n }\n cpSync(projectDir, tempDir, { recursive: true });\n }\n\n return tempDir;\n }\n\n private async runHttpAction(): Promise<SpecificationResult> {\n if (!this.config.server) {\n throw new Error(\"HTTP actions require a server adapter (use integration() or e2e())\");\n }\n\n let body: unknown;\n if (this.request!.bodyFile) {\n body = JSON.parse(\n readFileSync(resolve(this.testDir, \"requests\", this.request!.bodyFile), \"utf8\"),\n );\n }\n\n const response = await this.config.server.request(\n this.request!.method,\n this.request!.path,\n body,\n );\n\n return new SpecificationResult({\n config: this.config,\n requestInfo: { body, method: this.request!.method, path: this.request!.path },\n response,\n testDir: this.testDir,\n });\n }\n\n private async runCliAction(workDir: string): Promise<SpecificationResult> {\n if (!this.config.command) {\n throw new Error(\"CLI actions require a command adapter (use cli())\");\n }\n\n let commandResult: CommandResult;\n\n if (this.spawnConfig) {\n commandResult = await this.config.command.spawn(\n this.spawnConfig.args,\n workDir,\n this.spawnConfig.options,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stdout: \"\", stderr: \"\" };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir);\n }\n\n return new SpecificationResult({\n commandResult,\n config: this.config,\n testDir: this.testDir,\n workDir,\n });\n }\n}\n\n// ── Caller detection ──\n\nfunction getCallerDir(): string {\n const stack = new Error(\"caller detection\").stack;\n if (!stack) {\n throw new Error(\"Cannot detect caller directory: no stack trace\");\n }\n\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n\n if (filePath.includes(\"node_modules\")) {\n continue;\n }\n if (filePath.includes(\"/src/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\");\n }\n\n throw new Error(\"Cannot detect caller directory from stack trace\");\n}\n\n// ── Factory functions ──\n\nexport type SpecificationRunner = (label: string) => SpecificationBuilder;\n\n/**\n * Create a specification runner.\n * Automatically detects the test directory from the call site.\n */\nexport function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner {\n return (label: string) => {\n const testDir = getCallerDir();\n return new SpecificationBuilder(config, testDir, label);\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport { Orchestrator } from \"../infrastructure/orchestrator.js\";\nimport type { ServiceHandle } from \"../infrastructure/services/service.port.js\";\nimport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nimport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nimport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nimport type { DatabasePort } from \"./ports/database.port.js\";\nimport { createSpecificationRunner, type SpecificationRunner } from \"./specification.js\";\n\n/**\n * Resolve root — if relative, resolves from the caller's directory.\n */\nfunction resolveProjectRoot(root: string | undefined): string {\n if (!root) {\n return process.cwd();\n }\n\n if (isAbsolute(root)) {\n return root;\n }\n\n const stack = new Error(\"resolve root\").stack;\n if (stack) {\n const lines = stack.split(\"\\n\");\n for (const line of lines) {\n const match = line.match(/at\\s+(?:.*?\\()?(?:file:\\/\\/)?([^:)]+):\\d+:\\d+/);\n if (!match) {\n continue;\n }\n\n const filePath = match[1];\n if (filePath.includes(\"node_modules\") || filePath.includes(\"/specification/\")) {\n continue;\n }\n\n return resolve(filePath, \"..\", root);\n }\n }\n\n return resolve(process.cwd(), root);\n}\n\n/**\n * Resolve a CLI command — checks node_modules/.bin, then treats as absolute/PATH.\n */\nfunction resolveCommand(command: string, root: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n // Check node_modules/.bin in fixture root\n const binPath = resolve(root, \"node_modules/.bin\", command);\n if (existsSync(binPath)) {\n return binPath;\n }\n\n // Check project root node_modules/.bin\n const cwdBinPath = resolve(process.cwd(), \"node_modules/.bin\", command);\n if (existsSync(cwdBinPath)) {\n return cwdBinPath;\n }\n\n // Treat as PATH command or absolute\n return command;\n}\n\ntype HonoApp = {\n fetch: (...args: any[]) => any;\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n};\n\ninterface IntegrationOptions {\n /** Factory that returns a Hono app — called after services start. */\n app: () => HonoApp;\n /** Project root for compose detection (relative paths supported). */\n root?: string;\n /** Declared services — started via testcontainers. */\n services: ServiceHandle[];\n}\n\ninterface E2eOptions {\n /** Project root — must contain docker/compose.test.yaml. */\n root?: string;\n}\n\ninterface CliOptions {\n /** CLI command to run (resolved from node_modules/.bin or PATH). */\n command: string;\n /** Project root — base dir for .project() fixture lookup (relative paths supported). */\n root?: string;\n /** Optional infrastructure services (started via testcontainers). */\n services?: ServiceHandle[];\n}\n\ninterface SpecificationRunnerWithCleanup extends SpecificationRunner {\n cleanup: () => Promise<void>;\n orchestrator: Orchestrator;\n}\n\n/**\n * Create an integration specification runner.\n * Starts infra containers via testcontainers, app runs in-process.\n */\nasync function integration(options: IntegrationOptions): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"integration\",\n root: resolveProjectRoot(options.root),\n services: options.services,\n });\n\n await orchestrator.start();\n\n const app = options.app();\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new HonoAdapter(app),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stop();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create an E2E specification runner.\n * Starts full docker compose stack. App URL and database auto-detected.\n */\nasync function e2e(options: E2eOptions = {}): Promise<SpecificationRunnerWithCleanup> {\n const orchestrator = new Orchestrator({\n mode: \"e2e\",\n root: resolveProjectRoot(options.root),\n services: [],\n });\n\n await orchestrator.startCompose();\n\n const appUrl = orchestrator.getAppUrl();\n if (!appUrl) {\n throw new Error(\n \"E2E: could not detect app URL from compose. Ensure an app service with ports is defined.\",\n );\n }\n\n const database = orchestrator.getDatabase() ?? undefined;\n const databases = orchestrator.getDatabases();\n\n const runner = createSpecificationRunner({\n database,\n databases: databases.size > 0 ? databases : undefined,\n server: new FetchAdapter(appUrl),\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = () => orchestrator.stopCompose();\n runner.orchestrator = orchestrator;\n\n return runner;\n}\n\n/**\n * Create a CLI specification runner.\n * Runs CLI commands against fixture projects. Optionally starts infrastructure.\n *\n * @example\n * export const spec = await cli({\n * command: resolve(import.meta.dirname, \"../../bin/my-cli.sh\"),\n * root: \"../fixtures\",\n * });\n */\nasync function cli(options: CliOptions): Promise<SpecificationRunnerWithCleanup> {\n const root = resolveProjectRoot(options.root);\n const command = resolveCommand(options.command, root);\n\n let orchestrator: null | Orchestrator = null;\n let database: DatabasePort | undefined;\n let databases: Map<string, DatabasePort> | undefined;\n\n if (options.services?.length) {\n orchestrator = new Orchestrator({\n mode: \"integration\",\n root,\n services: options.services,\n });\n await orchestrator.start();\n database = orchestrator.getDatabase() ?? undefined;\n const dbMap = orchestrator.getDatabases();\n databases = dbMap.size > 0 ? dbMap : undefined;\n }\n\n const runner = createSpecificationRunner({\n command: new ExecAdapter(command),\n database,\n databases,\n fixturesRoot: root,\n }) as SpecificationRunnerWithCleanup;\n\n runner.cleanup = async () => {\n if (orchestrator) {\n await orchestrator.stop();\n }\n };\n runner.orchestrator = orchestrator!;\n\n return runner;\n}\n\n// Service factories\nexport { postgres } from \"../infrastructure/services/postgres.js\";\nexport { redis } from \"../infrastructure/services/redis.js\";\n\n// Types\nexport type { CommandPort, CommandResult } from \"./ports/command.port.js\";\nexport type { DatabasePort } from \"./ports/database.port.js\";\nexport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\n\n// Adapters (for advanced usage)\nexport { ExecAdapter } from \"./adapters/exec.adapter.js\";\nexport { FetchAdapter } from \"./adapters/fetch.adapter.js\";\nexport { HonoAdapter } from \"./adapters/hono.adapter.js\";\nexport { Orchestrator } from \"../infrastructure/orchestrator.js\";\n\n// Reporter (for testing output)\nexport { normalizeOutput, stripAnsi } from \"../infrastructure/reporter.js\";\n\n// Runners\nexport { cli, e2e, integration };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jterrazz/test",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
5
5
  "repository": {
6
6
  "type": "git",