@jterrazz/test 5.1.0 → 5.3.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.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/directory.ts","../src/specification/specification.ts","../src/specification/grep.ts","../src/specification/index.ts","../src/infrastructure/docker/docker-adapter.ts","../src/infrastructure/docker/docker-assertion.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// ── Directory diff ──\n\ninterface DirectoryDiffData {\n added: string[];\n changed: { path: string; expected: string; actual: string }[];\n removed: string[];\n}\n\nexport function formatDirectoryDiff(\n fixtureName: string,\n diff: DirectoryDiffData,\n hint: string,\n): string {\n const lines: string[] = [];\n\n const total = diff.added.length + diff.removed.length + diff.changed.length;\n lines.push(`Directory mismatch: ${BOLD}${fixtureName}${RESET}`);\n lines.push(\n `${DIM} ${total} difference${total === 1 ? \"\" : \"s\"}: ${diff.added.length} added, ${diff.removed.length} removed, ${diff.changed.length} changed${RESET}`,\n );\n lines.push(\"\");\n lines.push(`${GREEN}- Expected (fixture)${RESET}`);\n lines.push(`${RED}+ Received (generated)${RESET}`);\n lines.push(\"\");\n\n for (const path of diff.added) {\n lines.push(`${RED}+ added ${path}${RESET} ${DIM}(not in fixture)${RESET}`);\n }\n for (const path of diff.removed) {\n lines.push(`${GREEN}- removed ${path}${RESET} ${DIM}(in fixture, not generated)${RESET}`);\n }\n for (const { path, expected, actual } of diff.changed) {\n const expectedLines = expected.split(\"\\n\");\n const actualLines = actual.split(\"\\n\");\n const changedCount = countLineDifferences(expectedLines, actualLines);\n lines.push(\n `${BOLD}~ changed ${path}${RESET} ${DIM}(${changedCount} line${changedCount === 1 ? \"\" : \"s\"} differ)${RESET}`,\n );\n\n let shown = 0;\n const maxShown = 5;\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n for (let i = 0; i < maxLines && shown < maxShown; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n if (exp !== act) {\n lines.push(`${DIM} line ${i + 1}:${RESET}`);\n if (exp !== undefined) {\n lines.push(` ${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(` ${RED}+ ${act}${RESET}`);\n }\n shown++;\n }\n }\n if (changedCount > maxShown) {\n lines.push(` ${DIM}... ${changedCount - maxShown} more line(s)${RESET}`);\n }\n }\n\n lines.push(\"\");\n lines.push(`${DIM}${hint}${RESET}`);\n\n return lines.join(\"\\n\");\n}\n\nfunction countLineDifferences(expected: string[], actual: string[]): number {\n let count = 0;\n const max = Math.max(expected.length, actual.length);\n for (let i = 0; i < max; i++) {\n if (expected[i] !== actual[i]) {\n count++;\n }\n }\n return count;\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 {\n CommandEnv,\n CommandPort,\n CommandResult,\n SpawnOptions,\n} from \"../ports/command.port.js\";\n\n/**\n * Build a child-process env from the parent env plus user overrides.\n * `null` overrides delete keys (e.g. `INIT_CWD: null`).\n */\nfunction buildEnv(extra?: CommandEnv): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env, INIT_CWD: undefined };\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) {\n delete env[key];\n } else {\n env[key] = value;\n }\n }\n }\n return env;\n}\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, extraEnv?: CommandEnv): Promise<CommandResult> {\n const env = buildEnv(extraEnv);\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(\n args: string,\n cwd: string,\n options: SpawnOptions,\n extraEnv?: CommandEnv,\n ): Promise<CommandResult> {\n const env = buildEnv(extraEnv);\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","import { readFileSync, statSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { relative, resolve, sep } from \"node:path\";\n\n/**\n * Default ignore patterns — paths that should never appear in a tracked snapshot.\n * Each entry is matched against any path segment OR a path prefix.\n */\nconst DEFAULT_IGNORES = [\".git\", \".DS_Store\", \"node_modules\", \".next\", \"dist\", \".turbo\", \".cache\"];\n\nexport interface DirectoryDiff {\n added: string[];\n changed: { path: string; expected: string; actual: string }[];\n removed: string[];\n}\n\ninterface WalkOptions {\n ignore?: string[];\n}\n\n/**\n * Recursively walk a directory, returning sorted relative paths of files only.\n * Ignored entries (default + caller-supplied) are skipped.\n */\nexport async function walkDirectory(root: string, options: WalkOptions = {}): Promise<string[]> {\n const ignores = new Set([...DEFAULT_IGNORES, ...(options.ignore ?? [])]);\n const out: string[] = [];\n\n async function walk(current: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(current);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (ignores.has(entry)) {\n continue;\n }\n const abs = resolve(current, entry);\n const stat = statSync(abs);\n if (stat.isDirectory()) {\n await walk(abs);\n } else if (stat.isFile()) {\n out.push(relative(root, abs).split(sep).join(\"/\"));\n }\n }\n }\n\n await walk(root);\n out.sort();\n return out;\n}\n\n/**\n * Compare two directory trees file-by-file.\n * Binary files are compared by byte equality but reported without inline diff.\n */\nexport async function diffDirectories(\n expectedRoot: string,\n actualRoot: string,\n options: WalkOptions = {},\n): Promise<DirectoryDiff> {\n const expectedFiles = await walkDirectory(expectedRoot, options);\n const actualFiles = await walkDirectory(actualRoot, options);\n\n const expectedSet = new Set(expectedFiles);\n const actualSet = new Set(actualFiles);\n\n const added = actualFiles.filter((f) => !expectedSet.has(f));\n const removed = expectedFiles.filter((f) => !actualSet.has(f));\n const changed: DirectoryDiff[\"changed\"] = [];\n\n for (const file of expectedFiles) {\n if (!actualSet.has(file)) {\n continue;\n }\n const expected = readFileSync(resolve(expectedRoot, file), \"utf8\");\n const actual = readFileSync(resolve(actualRoot, file), \"utf8\");\n if (expected !== actual) {\n changed.push({ actual, expected, path: file });\n }\n }\n\n return { added, changed, removed };\n}\n","import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\nimport {\n formatDirectoryDiff,\n formatResponseDiff,\n formatTableDiff,\n} from \"../infrastructure/reporter.js\";\nimport { diffDirectories, type DirectoryDiff, walkDirectory } from \"./directory.js\";\nimport type { CommandEnv, 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// ── File accessor ──\n\nexport interface FileAccessor {\n readonly content: string;\n readonly exists: boolean;\n}\n\n// ── Table assertion (async — vitest can't do DB queries) ──\n\nexport class TableAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\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 if (JSON.stringify(actual) !== JSON.stringify(expected.rows)) {\n throw new Error(formatTableDiff(this.tableName, expected.columns, expected.rows, actual));\n }\n }\n\n async toBeEmpty(): Promise<void> {\n const actual = await this.db.query(this.tableName, [\"*\"]);\n if (actual.length !== 0) {\n throw new Error(\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n );\n }\n }\n}\n\n// ── Directory accessor ──\n\nexport interface DirectorySnapshotOptions {\n /** Extra path segments to ignore (in addition to default: .git, node_modules, etc.). */\n ignore?: string[];\n /**\n * Force update mode regardless of vitest flags / env vars.\n * `true` writes the fixture, `false` always asserts. Defaults to auto-detect.\n */\n update?: boolean;\n}\n\n/**\n * Detect whether the user wants to update snapshots — `true` for any of:\n * - vitest run with `-u` / `--update`\n * - JTERRAZZ_TEST_UPDATE=1\n * - UPDATE_SNAPSHOTS=1\n */\nfunction shouldUpdateSnapshots(): boolean {\n if (process.env.JTERRAZZ_TEST_UPDATE === \"1\") {\n return true;\n }\n if (process.env.UPDATE_SNAPSHOTS === \"1\") {\n return true;\n }\n // Vitest sets these on its config; we read them best-effort.\n if (process.argv.includes(\"-u\") || process.argv.includes(\"--update\")) {\n return true;\n }\n return false;\n}\n\nexport class DirectoryAccessor {\n private absPath: string;\n private testDir: string;\n\n constructor(absPath: string, testDir: string) {\n this.absPath = absPath;\n this.testDir = testDir;\n }\n\n /**\n * Compare the directory tree against `expected/{name}/` (relative to the test file).\n * On mismatch, throws with a structured diff. With update mode enabled, the\n * fixture is overwritten with the current contents instead.\n */\n async toMatchFixture(name: string, options: DirectorySnapshotOptions = {}): Promise<void> {\n const fixtureDir = resolve(this.testDir, \"expected\", name);\n const update = options.update ?? shouldUpdateSnapshots();\n\n if (update) {\n rmSync(fixtureDir, { force: true, recursive: true });\n mkdirSync(fixtureDir, { recursive: true });\n cpSync(this.absPath, fixtureDir, { recursive: true });\n return;\n }\n\n if (!existsSync(fixtureDir)) {\n throw new Error(\n `Directory fixture \"${name}\" does not exist at ${fixtureDir}.\\n` +\n `Run with JTERRAZZ_TEST_UPDATE=1 (or vitest -u) to create it.`,\n );\n }\n\n const diff: DirectoryDiff = await diffDirectories(fixtureDir, this.absPath, {\n ignore: options.ignore,\n });\n\n if (diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0) {\n return;\n }\n\n throw new Error(\n formatDirectoryDiff(name, diff, \"Run with JTERRAZZ_TEST_UPDATE=1 to update the fixture.\"),\n );\n }\n\n /**\n * List all files in the directory (recursive, sorted, ignoring defaults).\n * Useful for ad-hoc assertions when you don't want a full snapshot.\n */\n async files(options: { ignore?: string[] } = {}): Promise<string[]> {\n return walkDirectory(this.absPath, options);\n }\n}\n\n// ── Response accessor ──\n\nexport class ResponseAccessor {\n readonly body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\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 if (JSON.stringify(this.body) !== JSON.stringify(expected)) {\n throw new Error(formatResponseDiff(file, expected, this.body));\n }\n }\n}\n\n// ── Result (after .run()) ──\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: { body?: unknown; method: string; path: string };\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: { body?: unknown; method: string; path: string };\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 // ── Raw value accessors ──\n\n get exitCode(): number {\n if (!this.commandResult) {\n throw new Error(\".exitCode requires a CLI action (.exec())\");\n }\n return this.commandResult.exitCode;\n }\n\n get status(): number {\n if (!this.responseData) {\n throw new Error(\".status requires an HTTP action (.get(), .post(), etc.)\");\n }\n return this.responseData.status;\n }\n\n get stdout(): string {\n if (!this.commandResult) {\n throw new Error(\".stdout requires a CLI action (.exec())\");\n }\n return this.commandResult.stdout;\n }\n\n get stderr(): string {\n if (!this.commandResult) {\n throw new Error(\".stderr requires a CLI action (.exec())\");\n }\n return this.commandResult.stderr;\n }\n\n // ── Structured accessors ──\n\n get response(): ResponseAccessor {\n if (!this.responseData) {\n throw new Error(\".response requires an HTTP action (.get(), .post(), etc.)\");\n }\n return new ResponseAccessor(this.responseData.body, this.testDir);\n }\n\n directory(path: string = \".\"): DirectoryAccessor {\n const baseDir = this.workDir ?? this.testDir;\n return new DirectoryAccessor(resolve(baseDir, path), this.testDir);\n }\n\n file(path: string): FileAccessor {\n const baseDir = this.workDir ?? this.testDir;\n const resolvedPath = resolve(baseDir, path);\n const exists = existsSync(resolvedPath);\n return {\n get content(): string {\n if (!exists) {\n throw new Error(`File not found: ${path}`);\n }\n return readFileSync(resolvedPath, \"utf8\");\n },\n exists,\n };\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 commandEnv: CommandEnv = {};\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 /**\n * Set environment variables for the CLI process. Merged on top of process.env.\n * Use `null` to unset a variable. Multiple calls merge.\n *\n * The token `$WORKDIR` (in any value) is replaced with the actual working\n * directory at run-time — useful for tests that need a fully isolated `HOME`.\n *\n * @example\n * spec(\"...\").env({ HOME: \"$WORKDIR\", TZ: \"UTC\" }).exec(\"status\").run();\n */\n env(env: CommandEnv): this {\n this.commandEnv = { ...this.commandEnv, ...env };\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 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 resolveEnv(workDir: string): CommandEnv | undefined {\n const keys = Object.keys(this.commandEnv);\n if (keys.length === 0) {\n return undefined;\n }\n\n const resolved: CommandEnv = {};\n for (const key of keys) {\n const value = this.commandEnv[key];\n resolved[key] = typeof value === \"string\" ? value.replace(/\\$WORKDIR/g, workDir) : value;\n }\n return resolved;\n }\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 const env = this.resolveEnv(workDir);\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 env,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stderr: \"\", stdout: \"\" };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir, env);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir, env);\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\nexport function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner {\n return (label: string) => {\n const testDir = getCallerDir();\n return new SpecificationBuilder(config, testDir, label);\n };\n}\n","/**\n * Extract text blocks from output that contain a pattern.\n * Splits by blank lines (how linter/compiler output is structured),\n * returns only blocks matching the pattern.\n *\n * @example\n * expect(grep(result.stdout, \"unused-var.ts\")).toContain(\"no-unused-vars\")\n * expect(grep(result.stdout, \"valid/sorted.ts\")).not.toContain(\"sort-imports\")\n */\nexport function grep(output: string, pattern: string): string {\n // eslint-disable-next-line no-control-regex\n const clean = output.replace(/\\x1b\\[[0-9;]*m/g, \"\");\n const blocks = clean.split(/\\n\\s*\\n/);\n return blocks.filter((block) => block.includes(pattern)).join(\"\\n\\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 { CommandEnv, CommandPort, CommandResult } from \"./ports/command.port.js\";\nexport type { DatabasePort } from \"./ports/database.port.js\";\nexport type { ServerPort, ServerResponse } from \"./ports/server.port.js\";\nexport type {\n DirectoryAccessor,\n DirectorySnapshotOptions,\n SpecificationResult,\n} from \"./specification.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// Utilities\nexport { grep } from \"./grep.js\";\nexport { normalizeOutput, stripAnsi } from \"../infrastructure/reporter.js\";\n\n// Runners\nexport { cli, e2e, integration };\n","import { execSync } from \"node:child_process\";\n\nimport type { DockerContainerPort, DockerInspectResult } from \"./docker-port.js\";\n\nexport class DockerAdapter implements DockerContainerPort {\n private containerId: string;\n\n constructor(containerId: string) {\n this.containerId = containerId;\n }\n\n async exec(cmd: string[]): Promise<string> {\n const result = execSync(\n `docker exec ${this.containerId} ${cmd.map((c) => `'${c}'`).join(\" \")}`,\n { encoding: \"utf8\", timeout: 10_000 },\n );\n return result.trim();\n }\n\n async file(path: string): Promise<{ exists: boolean; content: string }> {\n try {\n const content = await this.exec([\"cat\", path]);\n return { exists: true, content };\n } catch {\n return { exists: false, content: \"\" };\n }\n }\n\n async isRunning(): Promise<boolean> {\n try {\n const result = execSync(`docker inspect --format='{{.State.Running}}' ${this.containerId}`, {\n encoding: \"utf8\",\n timeout: 5000,\n });\n return result.trim() === \"true\";\n } catch {\n return false;\n }\n }\n\n async logs(tail?: number): Promise<string> {\n const tailFlag = tail ? `--tail ${tail}` : \"\";\n const result = execSync(`docker logs ${tailFlag} ${this.containerId}`, {\n encoding: \"utf8\",\n timeout: 10_000,\n });\n return result;\n }\n\n async inspect(): Promise<DockerInspectResult> {\n const raw = execSync(`docker inspect ${this.containerId}`, {\n encoding: \"utf8\",\n timeout: 5000,\n });\n const data = JSON.parse(raw)[0];\n return {\n id: data.Id,\n name: data.Name,\n state: {\n running: data.State.Running,\n exitCode: data.State.ExitCode,\n status: data.State.Status,\n },\n config: {\n image: data.Config.Image,\n env: data.Config.Env || [],\n },\n hostConfig: {\n memory: data.HostConfig.Memory || 0,\n cpuQuota: data.HostConfig.CpuQuota || 0,\n networkMode: data.HostConfig.NetworkMode || \"\",\n mounts: (data.Mounts || []).map((m: any) => ({\n source: m.Source,\n destination: m.Destination,\n type: m.Type,\n })),\n },\n networkSettings: {\n networks: Object.fromEntries(\n Object.entries(data.NetworkSettings.Networks || {}).map(([name, net]: [string, any]) => [\n name,\n { gateway: net.Gateway, ipAddress: net.IPAddress },\n ]),\n ),\n },\n };\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.exec([\"test\", \"-e\", path]);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Create a Docker container port for an existing container */\nexport function dockerContainer(containerId: string): DockerContainerPort {\n return new DockerAdapter(containerId);\n}\n","import type { DockerContainerPort } from \"./docker-port.js\";\n\n/** Fluent assertion builder for Docker containers */\nexport class DockerAssertion {\n private container: DockerContainerPort;\n\n constructor(container: DockerContainerPort) {\n this.container = container;\n }\n\n /** Assert the container is running */\n async toBeRunning(): Promise<this> {\n const running = await this.container.isRunning();\n if (!running) {\n throw new Error(\"Expected container to be running\");\n }\n return this;\n }\n\n /** Assert the container is NOT running / doesn't exist */\n async toNotExist(): Promise<this> {\n const running = await this.container.isRunning();\n if (running) {\n throw new Error(\"Expected container to not exist or not be running\");\n }\n return this;\n }\n\n /** Assert a file exists inside the container */\n async toHaveFile(path: string, opts?: { containing?: string }): Promise<this> {\n const file = await this.container.file(path);\n if (!file.exists) {\n throw new Error(`Expected file ${path} to exist in container`);\n }\n if (opts?.containing && !file.content.includes(opts.containing)) {\n throw new Error(\n `Expected file ${path} to contain \"${opts.containing}\", got: ${file.content.slice(0, 200)}`,\n );\n }\n return this;\n }\n\n /** Assert a file does NOT exist */\n async toNotHaveFile(path: string): Promise<this> {\n const exists = await this.container.exists(path);\n if (exists) {\n throw new Error(`Expected ${path} to not exist in container`);\n }\n return this;\n }\n\n /** Assert a directory exists */\n async toHaveDirectory(path: string): Promise<this> {\n const exists = await this.container.exists(path);\n if (!exists) {\n throw new Error(`Expected directory ${path} to exist in container`);\n }\n return this;\n }\n\n /** Assert a mount exists */\n async toHaveMount(destination: string): Promise<this> {\n const info = await this.container.inspect();\n const mount = info.hostConfig.mounts.find((m) => m.destination === destination);\n if (!mount) {\n const available = info.hostConfig.mounts.map((m) => m.destination).join(\", \");\n throw new Error(`Expected mount at ${destination}, found: [${available}]`);\n }\n return this;\n }\n\n /** Assert network mode */\n async toHaveNetwork(mode: string): Promise<this> {\n const info = await this.container.inspect();\n if (!info.hostConfig.networkMode.includes(mode)) {\n throw new Error(`Expected network mode \"${mode}\", got \"${info.hostConfig.networkMode}\"`);\n }\n return this;\n }\n\n /** Assert memory limit */\n async toHaveMemoryLimit(bytes: number): Promise<this> {\n const info = await this.container.inspect();\n if (info.hostConfig.memory !== bytes) {\n throw new Error(`Expected memory limit ${bytes}, got ${info.hostConfig.memory}`);\n }\n return this;\n }\n\n /** Assert CPU quota */\n async toHaveCpuQuota(quota: number): Promise<this> {\n const info = await this.container.inspect();\n if (info.hostConfig.cpuQuota !== quota) {\n throw new Error(`Expected CPU quota ${quota}, got ${info.hostConfig.cpuQuota}`);\n }\n return this;\n }\n\n /** Execute a command and return output for custom assertions */\n async exec(cmd: string[]): Promise<string> {\n return this.container.exec(cmd);\n }\n\n /** Read a file for custom assertions */\n async readFile(path: string): Promise<string> {\n const file = await this.container.file(path);\n if (!file.exists) {\n throw new Error(`File ${path} does not exist`);\n }\n return file.content;\n }\n\n /** Get logs for custom assertions */\n async getLogs(tail?: number): Promise<string> {\n return this.container.logs(tail);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["parseYaml"],"sources":["../src/adapters/exec.adapter.ts","../src/utilities/directory.ts","../src/utilities/reporter.ts","../src/builder/directory-accessor.ts","../src/builder/response-accessor.ts","../src/builder/table-assertion.ts","../src/builder/specification-result.ts","../src/builder/specification-builder.ts","../src/adapters/compose.adapter.ts","../src/adapters/postgres.adapter.ts","../src/adapters/redis.adapter.ts","../src/adapters/testcontainers.adapter.ts","../src/orchestrator/compose-parser.ts","../src/orchestrator/orchestrator.ts","../src/runner/resolve.ts","../src/runner/cli.ts","../src/adapters/fetch.adapter.ts","../src/runner/e2e.ts","../src/adapters/hono.adapter.ts","../src/runner/integration.ts","../src/docker/docker-adapter.ts","../src/docker/docker-assertion.ts","../src/utilities/grep.ts","../src/mocking/mock-of-date.ts","../src/mocking/mock-of.ts"],"sourcesContent":["import { execSync, spawn } from 'node:child_process';\n\nimport type {\n CommandEnv,\n CommandPort,\n CommandResult,\n SpawnOptions,\n} from '../ports/command.port.js';\n\n/**\n * Build a child-process env from the parent env plus user overrides.\n * `null` overrides delete keys (e.g. `INIT_CWD: null`).\n */\nfunction buildEnv(extra?: CommandEnv): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env, INIT_CWD: undefined };\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) {\n delete env[key];\n } else {\n env[key] = value;\n }\n }\n }\n return env;\n}\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, extraEnv?: CommandEnv): Promise<CommandResult> {\n const env = buildEnv(extraEnv);\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(\n args: string,\n cwd: string,\n options: SpawnOptions,\n extraEnv?: CommandEnv,\n ): Promise<CommandResult> {\n const env = buildEnv(extraEnv);\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 { readFileSync, statSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\n\n/**\n * Default ignore patterns — paths that should never appear in a tracked snapshot.\n * Each entry is matched against any path segment OR a path prefix.\n */\nconst DEFAULT_IGNORES = ['.git', '.DS_Store', 'node_modules', '.next', 'dist', '.turbo', '.cache'];\n\nexport interface DirectoryDiff {\n added: string[];\n changed: { path: string; expected: string; actual: string }[];\n removed: string[];\n}\n\ninterface WalkOptions {\n ignore?: string[];\n}\n\n/**\n * Recursively walk a directory, returning sorted relative paths of files only.\n * Ignored entries (default + caller-supplied) are skipped.\n */\nexport async function walkDirectory(root: string, options: WalkOptions = {}): Promise<string[]> {\n const ignores = new Set([...DEFAULT_IGNORES, ...(options.ignore ?? [])]);\n const out: string[] = [];\n\n async function walk(current: string): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(current);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (ignores.has(entry)) {\n continue;\n }\n const abs = resolve(current, entry);\n const stat = statSync(abs);\n if (stat.isDirectory()) {\n await walk(abs);\n } else if (stat.isFile()) {\n out.push(relative(root, abs).split(sep).join('/'));\n }\n }\n }\n\n await walk(root);\n out.sort();\n return out;\n}\n\n/**\n * Compare two directory trees file-by-file.\n * Binary files are compared by byte equality but reported without inline diff.\n */\nexport async function diffDirectories(\n expectedRoot: string,\n actualRoot: string,\n options: WalkOptions = {},\n): Promise<DirectoryDiff> {\n const expectedFiles = await walkDirectory(expectedRoot, options);\n const actualFiles = await walkDirectory(actualRoot, options);\n\n const expectedSet = new Set(expectedFiles);\n const actualSet = new Set(actualFiles);\n\n const added = actualFiles.filter((f) => !expectedSet.has(f));\n const removed = expectedFiles.filter((f) => !actualSet.has(f));\n const changed: DirectoryDiff['changed'] = [];\n\n for (const file of expectedFiles) {\n if (!actualSet.has(file)) {\n continue;\n }\n const expected = readFileSync(resolve(expectedRoot, file), 'utf8');\n const actual = readFileSync(resolve(actualRoot, file), 'utf8');\n if (expected !== actual) {\n changed.push({ actual, expected, path: file });\n }\n }\n\n return { added, changed, removed };\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\n ? `${DIM}${service.connectionString}${RESET}`\n : '';\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// ── Directory diff ──\n\ninterface DirectoryDiffData {\n added: string[];\n changed: { path: string; expected: string; actual: string }[];\n removed: string[];\n}\n\nexport function formatDirectoryDiff(\n fixtureName: string,\n diff: DirectoryDiffData,\n hint: string,\n): string {\n const lines: string[] = [];\n\n const total = diff.added.length + diff.removed.length + diff.changed.length;\n lines.push(`Directory mismatch: ${BOLD}${fixtureName}${RESET}`);\n lines.push(\n `${DIM} ${total} difference${total === 1 ? '' : 's'}: ${diff.added.length} added, ${diff.removed.length} removed, ${diff.changed.length} changed${RESET}`,\n );\n lines.push('');\n lines.push(`${GREEN}- Expected (fixture)${RESET}`);\n lines.push(`${RED}+ Received (generated)${RESET}`);\n lines.push('');\n\n for (const path of diff.added) {\n lines.push(`${RED}+ added ${path}${RESET} ${DIM}(not in fixture)${RESET}`);\n }\n for (const path of diff.removed) {\n lines.push(`${GREEN}- removed ${path}${RESET} ${DIM}(in fixture, not generated)${RESET}`);\n }\n for (const { path, expected, actual } of diff.changed) {\n const expectedLines = expected.split('\\n');\n const actualLines = actual.split('\\n');\n const changedCount = countLineDifferences(expectedLines, actualLines);\n lines.push(\n `${BOLD}~ changed ${path}${RESET} ${DIM}(${changedCount} line${changedCount === 1 ? '' : 's'} differ)${RESET}`,\n );\n\n let shown = 0;\n const maxShown = 5;\n const maxLines = Math.max(expectedLines.length, actualLines.length);\n for (let i = 0; i < maxLines && shown < maxShown; i++) {\n const exp = expectedLines[i];\n const act = actualLines[i];\n if (exp !== act) {\n lines.push(`${DIM} line ${i + 1}:${RESET}`);\n if (exp !== undefined) {\n lines.push(` ${GREEN}- ${exp}${RESET}`);\n }\n if (act !== undefined) {\n lines.push(` ${RED}+ ${act}${RESET}`);\n }\n shown++;\n }\n }\n if (changedCount > maxShown) {\n lines.push(` ${DIM}... ${changedCount - maxShown} more line(s)${RESET}`);\n }\n }\n\n lines.push('');\n lines.push(`${DIM}${hint}${RESET}`);\n\n return lines.join('\\n');\n}\n\nfunction countLineDifferences(expected: string[], actual: string[]): number {\n let count = 0;\n const max = Math.max(expected.length, actual.length);\n for (let i = 0; i < max; i++) {\n if (expected[i] !== actual[i]) {\n count++;\n }\n }\n return count;\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 { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { diffDirectories, type DirectoryDiff, walkDirectory } from '../utilities/directory.js';\nimport { formatDirectoryDiff } from '../utilities/reporter.js';\n\nexport interface DirectorySnapshotOptions {\n /** Extra path segments to ignore (in addition to default: .git, node_modules, etc.). */\n ignore?: string[];\n /**\n * Force update mode regardless of vitest flags / env vars.\n * `true` writes the fixture, `false` always asserts. Defaults to auto-detect.\n */\n update?: boolean;\n}\n\n/**\n * Detect whether the user wants to update snapshots — `true` for any of:\n * - vitest run with `-u` / `--update`\n * - JTERRAZZ_TEST_UPDATE=1\n * - UPDATE_SNAPSHOTS=1\n */\nfunction shouldUpdateSnapshots(): boolean {\n if (process.env.JTERRAZZ_TEST_UPDATE === '1') {\n return true;\n }\n if (process.env.UPDATE_SNAPSHOTS === '1') {\n return true;\n }\n if (process.argv.includes('-u') || process.argv.includes('--update')) {\n return true;\n }\n return false;\n}\n\nexport class DirectoryAccessor {\n private absPath: string;\n private testDir: string;\n\n constructor(absPath: string, testDir: string) {\n this.absPath = absPath;\n this.testDir = testDir;\n }\n\n /**\n * Compare the directory tree against `expected/{name}/` (relative to the test file).\n * On mismatch, throws with a structured diff. With update mode enabled, the\n * fixture is overwritten with the current contents instead.\n */\n async toMatchFixture(name: string, options: DirectorySnapshotOptions = {}): Promise<void> {\n const fixtureDir = resolve(this.testDir, 'expected', name);\n const update = options.update ?? shouldUpdateSnapshots();\n\n if (update) {\n rmSync(fixtureDir, { force: true, recursive: true });\n mkdirSync(fixtureDir, { recursive: true });\n cpSync(this.absPath, fixtureDir, { recursive: true });\n return;\n }\n\n if (!existsSync(fixtureDir)) {\n throw new Error(\n `Directory fixture \"${name}\" does not exist at ${fixtureDir}.\\n` +\n `Run with JTERRAZZ_TEST_UPDATE=1 (or vitest -u) to create it.`,\n );\n }\n\n const diff: DirectoryDiff = await diffDirectories(fixtureDir, this.absPath, {\n ignore: options.ignore,\n });\n\n if (diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0) {\n return;\n }\n\n throw new Error(\n formatDirectoryDiff(\n name,\n diff,\n 'Run with JTERRAZZ_TEST_UPDATE=1 to update the fixture.',\n ),\n );\n }\n\n /**\n * List all files in the directory (recursive, sorted, ignoring defaults).\n * Useful for ad-hoc assertions when you don't want a full snapshot.\n */\n async files(options: { ignore?: string[] } = {}): Promise<string[]> {\n return walkDirectory(this.absPath, options);\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport { formatResponseDiff } from '../utilities/reporter.js';\n\nexport class ResponseAccessor {\n readonly body: unknown;\n private testDir: string;\n\n constructor(body: unknown, testDir: string) {\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 if (JSON.stringify(this.body) !== JSON.stringify(expected)) {\n throw new Error(formatResponseDiff(file, expected, this.body));\n }\n }\n}\n","import type { DatabasePort } from '../ports/database.port.js';\nimport { formatTableDiff } from '../utilities/reporter.js';\n\nexport class TableAssertion {\n private tableName: string;\n private db: DatabasePort;\n\n constructor(tableName: string, db: DatabasePort) {\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 if (JSON.stringify(actual) !== JSON.stringify(expected.rows)) {\n throw new Error(\n formatTableDiff(this.tableName, expected.columns, expected.rows, actual),\n );\n }\n }\n\n async toBeEmpty(): Promise<void> {\n const actual = await this.db.query(this.tableName, ['*']);\n if (actual.length !== 0) {\n throw new Error(\n `Expected table \"${this.tableName}\" to be empty, but it has ${actual.length} rows`,\n );\n }\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { CommandResult } from '../ports/command.port.js';\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { ServerResponse } from '../ports/server.port.js';\nimport { DirectoryAccessor } from './directory-accessor.js';\nimport { ResponseAccessor } from './response-accessor.js';\nimport type { SpecificationConfig } from './specification-builder.js';\nimport { TableAssertion } from './table-assertion.js';\n\nexport interface FileAccessor {\n readonly content: string;\n readonly exists: boolean;\n}\n\nexport class SpecificationResult {\n private commandResult?: CommandResult;\n private config: SpecificationConfig;\n private requestInfo?: { body?: unknown; method: string; path: string };\n private responseData?: ServerResponse;\n private testDir: string;\n private workDir?: string;\n\n constructor(options: {\n commandResult?: CommandResult;\n config: SpecificationConfig;\n requestInfo?: { body?: unknown; method: string; path: string };\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 // ── Raw value accessors ──\n\n get exitCode(): number {\n if (!this.commandResult) {\n throw new Error('.exitCode requires a CLI action (.exec())');\n }\n return this.commandResult.exitCode;\n }\n\n get status(): number {\n if (!this.responseData) {\n throw new Error('.status requires an HTTP action (.get(), .post(), etc.)');\n }\n return this.responseData.status;\n }\n\n get stdout(): string {\n if (!this.commandResult) {\n throw new Error('.stdout requires a CLI action (.exec())');\n }\n return this.commandResult.stdout;\n }\n\n get stderr(): string {\n if (!this.commandResult) {\n throw new Error('.stderr requires a CLI action (.exec())');\n }\n return this.commandResult.stderr;\n }\n\n // ── Structured accessors ──\n\n get response(): ResponseAccessor {\n if (!this.responseData) {\n throw new Error('.response requires an HTTP action (.get(), .post(), etc.)');\n }\n return new ResponseAccessor(this.responseData.body, this.testDir);\n }\n\n directory(path: string = '.'): DirectoryAccessor {\n const baseDir = this.workDir ?? this.testDir;\n return new DirectoryAccessor(resolve(baseDir, path), this.testDir);\n }\n\n file(path: string): FileAccessor {\n const baseDir = this.workDir ?? this.testDir;\n const resolvedPath = resolve(baseDir, path);\n const exists = existsSync(resolvedPath);\n return {\n get content(): string {\n if (!exists) {\n throw new Error(`File not found: ${path}`);\n }\n return readFileSync(resolvedPath, 'utf8');\n },\n exists,\n };\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","import { cpSync, existsSync, mkdtempSync, readFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { resolve } from 'node:path';\n\nimport type {\n CommandEnv,\n CommandPort,\n CommandResult,\n SpawnOptions,\n} from '../ports/command.port.js';\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { ServerPort } from '../ports/server.port.js';\nimport { SpecificationResult } from './specification-result.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// ── Builder (before .run()) ──\n\nexport class SpecificationBuilder {\n private commandArgs: null | string | string[] = null;\n private commandEnv: CommandEnv = {};\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 /**\n * Set environment variables for the CLI process. Merged on top of process.env.\n * Use `null` to unset a variable. Multiple calls merge.\n *\n * The token `$WORKDIR` (in any value) is replaced with the actual working\n * directory at run-time — useful for tests that need a fully isolated `HOME`.\n *\n * @example\n * spec(\"...\").env({ HOME: \"$WORKDIR\", TZ: \"UTC\" }).exec(\"status\").run();\n */\n env(env: CommandEnv): this {\n this.commandEnv = { ...this.commandEnv, ...env };\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 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(\n readFileSync(resolve(this.testDir, 'mock', entry.file), 'utf8'),\n );\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 resolveEnv(workDir: string): CommandEnv | undefined {\n const keys = Object.keys(this.commandEnv);\n if (keys.length === 0) {\n return undefined;\n }\n\n const resolved: CommandEnv = {};\n for (const key of keys) {\n const value = this.commandEnv[key];\n resolved[key] =\n typeof value === 'string' ? value.replace(/\\$WORKDIR/g, workDir) : value;\n }\n return resolved;\n }\n\n private prepareWorkDir(): string {\n // Every CLI spec runs in a fresh, empty temp directory unless a project\n // Fixture is explicitly copied in via .project() (or files via .fixture()).\n // This guarantees isolation — the runner never writes into fixturesRoot.\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 const env = this.resolveEnv(workDir);\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 env,\n );\n } else if (Array.isArray(this.commandArgs)) {\n commandResult = { exitCode: 0, stderr: '', stdout: '' };\n for (const args of this.commandArgs) {\n commandResult = await this.config.command.exec(args, workDir, env);\n if (commandResult.exitCode !== 0) {\n break;\n }\n }\n } else {\n commandResult = await this.config.command.exec(this.commandArgs!, workDir, env);\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/builder/') || filePath.includes('/src/runner/')) {\n continue;\n }\n // Skip framework code when installed as local symlink (no node_modules in path)\n if (filePath.includes('/package-test/dist/') || filePath.includes('/package-test/src/')) {\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\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 { 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(\n `docker compose -f ${this.composeFile} logs ${this.serviceName} --tail=50`,\n );\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 { existsSync, readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { Client } from 'pg';\n\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\n\nexport interface 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\nexport class 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(\n `postgres init script failed (${initPath}):\\n${error.message}`,\n {\n cause: error,\n },\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 '../ports/database.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\n\nexport interface RedisOptions {\n /** Map to a service in docker-compose.test.yaml. */\n compose?: string;\n /** Override image. */\n image?: string;\n}\n\nexport class 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(\n `redis healthcheck failed: ${error.message || error.code || String(error)}`,\n {\n cause: error,\n },\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 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)\n ? def.depends_on\n : 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","import { dirname } from 'node:path';\n\nimport { ComposeStackAdapter } from '../adapters/compose.adapter.js';\nimport { postgres } from '../adapters/postgres.adapter.js';\nimport { redis } from '../adapters/redis.adapter.js';\nimport { TestcontainersAdapter } from '../adapters/testcontainers.adapter.js';\nimport type { ContainerPort } from '../ports/container.port.js';\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\nimport { type AppInfo, formatStartupReport, type ServiceReport } from '../utilities/reporter.js';\nimport { detectServiceType, findComposeFile, parseComposeFile } from './compose-parser.js';\n\ninterface RunningService {\n handle: ServiceHandle;\n container: ContainerPort | null;\n}\n\nexport interface 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(\n (s) => s.name === handle.composeName,\n );\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(\n appService.name,\n appService.ports[0].container,\n );\n return `http://localhost:${port}`;\n }\n}\n","import { existsSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\n\n/**\n * Resolve root — if relative, resolves from the caller's directory.\n */\nexport function 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')) {\n continue;\n }\n if (filePath.includes('/src/runner/') || filePath.includes('/dist/')) {\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 */\nexport function resolveCommand(command: string, root: string): string {\n if (isAbsolute(command)) {\n return command;\n }\n\n const binPath = resolve(root, 'node_modules/.bin', command);\n if (existsSync(binPath)) {\n return binPath;\n }\n\n const cwdBinPath = resolve(process.cwd(), 'node_modules/.bin', command);\n if (existsSync(cwdBinPath)) {\n return cwdBinPath;\n }\n\n return command;\n}\n","import { ExecAdapter } from '../adapters/exec.adapter.js';\nimport { createSpecificationRunner } from '../builder/specification-builder.js';\nimport { Orchestrator } from '../orchestrator/orchestrator.js';\nimport type { DatabasePort } from '../ports/database.port.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\nimport type { SpecificationRunnerWithCleanup } from './integration.js';\nimport { resolveCommand, resolveProjectRoot } from './resolve.js';\n\nexport interface 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\n/**\n * Create a CLI specification runner.\n * Runs CLI commands against fixture projects. Optionally starts infrastructure.\n */\nexport async 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","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 { FetchAdapter } from '../adapters/fetch.adapter.js';\nimport { createSpecificationRunner } from '../builder/specification-builder.js';\nimport { Orchestrator } from '../orchestrator/orchestrator.js';\nimport type { SpecificationRunnerWithCleanup } from './integration.js';\nimport { resolveProjectRoot } from './resolve.js';\n\nexport interface E2eOptions {\n /** Project root — must contain docker/compose.test.yaml. */\n root?: string;\n}\n\n/**\n * Create an E2E specification runner.\n * Starts full docker compose stack. App URL and database auto-detected.\n */\nexport async 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","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","import { HonoAdapter } from '../adapters/hono.adapter.js';\nimport {\n createSpecificationRunner,\n type SpecificationRunner,\n} from '../builder/specification-builder.js';\nimport { Orchestrator } from '../orchestrator/orchestrator.js';\nimport type { ServiceHandle } from '../ports/service.port.js';\nimport { resolveProjectRoot } from './resolve.js';\n\ntype HonoApp = {\n fetch: (...args: any[]) => any;\n request: (path: string, init?: RequestInit) => Promise<Response> | Response;\n};\n\nexport interface 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\nexport interface 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 */\nexport async function integration(\n options: IntegrationOptions,\n): 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","import { execSync } from 'node:child_process';\n\nimport type { DockerContainerPort, DockerInspectResult } from './docker-port.js';\n\nexport class DockerAdapter implements DockerContainerPort {\n private containerId: string;\n\n constructor(containerId: string) {\n this.containerId = containerId;\n }\n\n async exec(cmd: string[]): Promise<string> {\n const result = execSync(\n `docker exec ${this.containerId} ${cmd.map((c) => `'${c}'`).join(' ')}`,\n { encoding: 'utf8', timeout: 10_000 },\n );\n return result.trim();\n }\n\n async file(path: string): Promise<{ exists: boolean; content: string }> {\n try {\n const content = await this.exec(['cat', path]);\n return { exists: true, content };\n } catch {\n return { exists: false, content: '' };\n }\n }\n\n async isRunning(): Promise<boolean> {\n try {\n const result = execSync(\n `docker inspect --format='{{.State.Running}}' ${this.containerId}`,\n {\n encoding: 'utf8',\n timeout: 5000,\n },\n );\n return result.trim() === 'true';\n } catch {\n return false;\n }\n }\n\n async logs(tail?: number): Promise<string> {\n const tailFlag = tail ? `--tail ${tail}` : '';\n const result = execSync(`docker logs ${tailFlag} ${this.containerId}`, {\n encoding: 'utf8',\n timeout: 10_000,\n });\n return result;\n }\n\n async inspect(): Promise<DockerInspectResult> {\n const raw = execSync(`docker inspect ${this.containerId}`, {\n encoding: 'utf8',\n timeout: 5000,\n });\n const data = JSON.parse(raw)[0];\n return {\n id: data.Id,\n name: data.Name,\n state: {\n running: data.State.Running,\n exitCode: data.State.ExitCode,\n status: data.State.Status,\n },\n config: {\n image: data.Config.Image,\n env: data.Config.Env || [],\n },\n hostConfig: {\n memory: data.HostConfig.Memory || 0,\n cpuQuota: data.HostConfig.CpuQuota || 0,\n networkMode: data.HostConfig.NetworkMode || '',\n mounts: (data.Mounts || []).map((m: any) => ({\n source: m.Source,\n destination: m.Destination,\n type: m.Type,\n })),\n },\n networkSettings: {\n networks: Object.fromEntries(\n Object.entries(data.NetworkSettings.Networks || {}).map(\n ([name, net]: [string, any]) => [\n name,\n { gateway: net.Gateway, ipAddress: net.IPAddress },\n ],\n ),\n ),\n },\n };\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await this.exec(['test', '-e', path]);\n return true;\n } catch {\n return false;\n }\n }\n}\n\n/** Create a Docker container port for an existing container */\nexport function dockerContainer(containerId: string): DockerContainerPort {\n return new DockerAdapter(containerId);\n}\n","import type { DockerContainerPort } from './docker-port.js';\n\n/** Fluent assertion builder for Docker containers */\nexport class DockerAssertion {\n private container: DockerContainerPort;\n\n constructor(container: DockerContainerPort) {\n this.container = container;\n }\n\n /** Assert the container is running */\n async toBeRunning(): Promise<this> {\n const running = await this.container.isRunning();\n if (!running) {\n throw new Error('Expected container to be running');\n }\n return this;\n }\n\n /** Assert the container is NOT running / doesn't exist */\n async toNotExist(): Promise<this> {\n const running = await this.container.isRunning();\n if (running) {\n throw new Error('Expected container to not exist or not be running');\n }\n return this;\n }\n\n /** Assert a file exists inside the container */\n async toHaveFile(path: string, opts?: { containing?: string }): Promise<this> {\n const file = await this.container.file(path);\n if (!file.exists) {\n throw new Error(`Expected file ${path} to exist in container`);\n }\n if (opts?.containing && !file.content.includes(opts.containing)) {\n throw new Error(\n `Expected file ${path} to contain \"${opts.containing}\", got: ${file.content.slice(0, 200)}`,\n );\n }\n return this;\n }\n\n /** Assert a file does NOT exist */\n async toNotHaveFile(path: string): Promise<this> {\n const exists = await this.container.exists(path);\n if (exists) {\n throw new Error(`Expected ${path} to not exist in container`);\n }\n return this;\n }\n\n /** Assert a directory exists */\n async toHaveDirectory(path: string): Promise<this> {\n const exists = await this.container.exists(path);\n if (!exists) {\n throw new Error(`Expected directory ${path} to exist in container`);\n }\n return this;\n }\n\n /** Assert a mount exists */\n async toHaveMount(destination: string): Promise<this> {\n const info = await this.container.inspect();\n const mount = info.hostConfig.mounts.find((m) => m.destination === destination);\n if (!mount) {\n const available = info.hostConfig.mounts.map((m) => m.destination).join(', ');\n throw new Error(`Expected mount at ${destination}, found: [${available}]`);\n }\n return this;\n }\n\n /** Assert network mode */\n async toHaveNetwork(mode: string): Promise<this> {\n const info = await this.container.inspect();\n if (!info.hostConfig.networkMode.includes(mode)) {\n throw new Error(\n `Expected network mode \"${mode}\", got \"${info.hostConfig.networkMode}\"`,\n );\n }\n return this;\n }\n\n /** Assert memory limit */\n async toHaveMemoryLimit(bytes: number): Promise<this> {\n const info = await this.container.inspect();\n if (info.hostConfig.memory !== bytes) {\n throw new Error(`Expected memory limit ${bytes}, got ${info.hostConfig.memory}`);\n }\n return this;\n }\n\n /** Assert CPU quota */\n async toHaveCpuQuota(quota: number): Promise<this> {\n const info = await this.container.inspect();\n if (info.hostConfig.cpuQuota !== quota) {\n throw new Error(`Expected CPU quota ${quota}, got ${info.hostConfig.cpuQuota}`);\n }\n return this;\n }\n\n /** Execute a command and return output for custom assertions */\n async exec(cmd: string[]): Promise<string> {\n return this.container.exec(cmd);\n }\n\n /** Read a file for custom assertions */\n async readFile(path: string): Promise<string> {\n const file = await this.container.file(path);\n if (!file.exists) {\n throw new Error(`File ${path} does not exist`);\n }\n return file.content;\n }\n\n /** Get logs for custom assertions */\n async getLogs(tail?: number): Promise<string> {\n return this.container.logs(tail);\n }\n}\n","/**\n * Extract text blocks from output that contain a pattern.\n * Splits by blank lines (how linter/compiler output is structured),\n * returns only blocks matching the pattern.\n *\n * @example\n * expect(grep(result.stdout, \"unused-var.ts\")).toContain(\"no-unused-vars\")\n * expect(grep(result.stdout, \"valid/sorted.ts\")).not.toContain(\"sort-imports\")\n */\nexport function grep(output: string, pattern: string): string {\n // eslint-disable-next-line no-control-regex\n const clean = output.replace(/\\x1b\\[[0-9;]*m/g, '');\n const blocks = clean.split(/\\n\\s*\\n/);\n return blocks.filter((block) => block.includes(pattern)).join('\\n\\n');\n}\n","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"],"mappings":"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"}