@jterrazz/test 7.0.0 → 8.0.0

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.d.cts CHANGED
@@ -1,8 +1,150 @@
1
- import { a as PostgresOptions, c as IsolationStrategy, i as redis, l as DatabasePort, n as sqlite, o as postgres, r as RedisOptions, s as ServiceHandle, t as SqliteOptions } from "./sqlite.adapter.cjs";
1
+ import { a as PostgresOptions, c as IsolationStrategy, i as redis, l as DatabasePort, n as sqlite, o as postgres, r as RedisOptions, s as ServiceHandle, t as SqliteOptions } from "./sqlite.cjs";
2
2
  import { n as InterceptResponse, r as InterceptTrigger } from "./types.cjs";
3
3
  import { i as mockOfDate, n as mockOf, r as MockDatePort, t as MockPort } from "./mock-of.cjs";
4
4
 
5
- //#region src/builder/cli/command.port.d.ts
5
+ //#region src/infra/docker/docker.port.d.ts
6
+ /** Abstract interface for interacting with a Docker container (exec, inspect, file I/O). */
7
+ interface DockerContainerPort {
8
+ /** Execute a command inside the container, return stdout */
9
+ exec(cmd: string[]): Promise<string>;
10
+ /** Read a file from inside the container */
11
+ file(path: string): Promise<{
12
+ exists: boolean;
13
+ content: string;
14
+ }>;
15
+ /** Check if container is running */
16
+ isRunning(): Promise<boolean>;
17
+ /** Get container logs */
18
+ logs(tail?: number): Promise<string>;
19
+ /** Get full docker inspect JSON */
20
+ inspect(): Promise<DockerInspectResult>;
21
+ /** Check if a file/directory exists */
22
+ exists(path: string): Promise<boolean>;
23
+ }
24
+ /** Normalized subset of `docker inspect` output for assertion use. */
25
+ interface DockerInspectResult {
26
+ id: string;
27
+ name: string;
28
+ state: {
29
+ running: boolean;
30
+ exitCode: number;
31
+ status: string;
32
+ };
33
+ config: {
34
+ image: string;
35
+ env: string[];
36
+ };
37
+ hostConfig: {
38
+ memory: number;
39
+ cpuQuota: number;
40
+ networkMode: string;
41
+ mounts: Array<{
42
+ source: string;
43
+ destination: string;
44
+ type: string;
45
+ }>;
46
+ };
47
+ networkSettings: {
48
+ networks: Record<string, {
49
+ gateway: string;
50
+ ipAddress: string;
51
+ }>;
52
+ };
53
+ }
54
+ //#endregion
55
+ //#region src/infra/docker/docker-assertion.d.ts
56
+ /**
57
+ * Fluent assertion builder for Docker containers.
58
+ * Chain async assertions like `toBeRunning()`, `toHaveFile()`, `toHaveMount()`.
59
+ */
60
+ declare class DockerAssertion {
61
+ private container;
62
+ constructor(container: DockerContainerPort);
63
+ /** Assert the container is running */
64
+ toBeRunning(): Promise<this>;
65
+ /** Assert the container is NOT running / doesn't exist */
66
+ toNotExist(): Promise<this>;
67
+ /** Assert a file exists inside the container */
68
+ toHaveFile(path: string, opts?: {
69
+ containing?: string;
70
+ }): Promise<this>;
71
+ /** Assert a file does NOT exist */
72
+ toNotHaveFile(path: string): Promise<this>;
73
+ /** Assert a directory exists */
74
+ toHaveDirectory(path: string): Promise<this>;
75
+ /** Assert a mount exists */
76
+ toHaveMount(destination: string): Promise<this>;
77
+ /** Assert network mode */
78
+ toHaveNetwork(mode: string): Promise<this>;
79
+ /** Assert memory limit */
80
+ toHaveMemoryLimit(bytes: number): Promise<this>;
81
+ /** Assert CPU quota */
82
+ toHaveCpuQuota(quota: number): Promise<this>;
83
+ /** Execute a command and return output for custom assertions */
84
+ exec(cmd: string[]): Promise<string>;
85
+ /** Read a file for custom assertions */
86
+ readFile(path: string): Promise<string>;
87
+ /** Get logs for custom assertions */
88
+ getLogs(tail?: number): Promise<string>;
89
+ }
90
+ //#endregion
91
+ //#region src/infra/orchestrator.d.ts
92
+ interface OrchestratorOptions {
93
+ services: ServiceHandle[];
94
+ mode: 'e2e' | 'integration';
95
+ root?: string;
96
+ /** Compose project name — used for per-worker stack isolation. */
97
+ projectName?: string;
98
+ }
99
+ /**
100
+ * Orchestrator for test infrastructure.
101
+ * Integration: starts services via testcontainers.
102
+ * E2E: runs full docker compose up.
103
+ */
104
+ declare class Orchestrator {
105
+ private services;
106
+ private mode;
107
+ private root;
108
+ private projectName;
109
+ private running;
110
+ private composeStack;
111
+ private composeHandles;
112
+ private started;
113
+ constructor(options: OrchestratorOptions);
114
+ /**
115
+ * Start declared services via testcontainers (integration mode).
116
+ * Phase 1: start all containers in parallel (the slow part).
117
+ * Phase 2: wire connections, healthcheck, and init sequentially (fast).
118
+ */
119
+ start(): Promise<void>;
120
+ /**
121
+ * Stop testcontainers (integration mode).
122
+ */
123
+ stop(): Promise<void>;
124
+ /**
125
+ * Start full docker compose stack (e2e mode).
126
+ * Auto-detects infra services and creates handles for them.
127
+ */
128
+ startCompose(): Promise<void>;
129
+ /**
130
+ * Stop docker compose stack (e2e mode).
131
+ */
132
+ stopCompose(): Promise<void>;
133
+ /**
134
+ * Get a database service by compose name, or the first one if no name given.
135
+ */
136
+ getDatabase(serviceName?: string): DatabasePort | null;
137
+ /**
138
+ * Get all database services keyed by compose name.
139
+ */
140
+ getDatabases(): Map<string, DatabasePort>;
141
+ /**
142
+ * Get app URL from compose (e2e mode).
143
+ */
144
+ getAppUrl(): null | string;
145
+ }
146
+ //#endregion
147
+ //#region src/spec/modes/cli/command.port.d.ts
6
148
  /**
7
149
  * Result of executing a CLI command, including exit code and captured output streams.
8
150
  */
@@ -39,76 +181,87 @@ interface CommandPort {
39
181
  spawn(args: string, cwd: string, options: SpawnOptions, env?: CommandEnv): Promise<CommandResult>;
40
182
  }
41
183
  //#endregion
42
- //#region src/builder/http/server.port.d.ts
43
- /**
44
- * HTTP response returned by a server port, with parsed JSON body.
45
- */
46
- interface ServerResponse {
47
- /** HTTP status code. */
48
- status: number;
49
- /** Parsed JSON response body, or null if parsing failed. */
50
- body: unknown;
51
- /** Response headers as a flat key-value map. */
52
- headers: Record<string, string>;
53
- }
54
- /**
55
- * Abstract server interface for specification runners.
56
- * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
57
- */
58
- interface ServerPort {
59
- /** Send an HTTP request and return the parsed response. */
60
- request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
61
- }
62
- //#endregion
63
- //#region src/builder/common/directory-accessor.d.ts
184
+ //#region src/spec/result/directory.d.ts
64
185
  interface DirectorySnapshotOptions {
65
- /** Extra path segments to ignore (in addition to default: .git, node_modules, etc.). */
66
186
  ignore?: string[];
67
- /**
68
- * Force update mode regardless of vitest flags / env vars.
69
- * `true` writes the fixture, `false` always asserts. Defaults to auto-detect.
70
- */
71
187
  update?: boolean;
72
188
  }
73
- /**
74
- * Handle to a directory produced by a specification run.
75
- * Supports snapshot-based assertions via {@link toMatchFixture} and file listing via {@link files}.
76
- */
77
189
  declare class DirectoryAccessor {
78
190
  private absPath;
79
191
  private testDir;
80
192
  constructor(absPath: string, testDir: string);
81
- /**
82
- * Compare the directory tree against `expected/{name}/` (relative to the test file).
83
- * On mismatch, throws with a structured diff. With update mode enabled, the
84
- * fixture is overwritten with the current contents instead.
85
- */
86
193
  toMatchFixture(name: string, options?: DirectorySnapshotOptions): Promise<void>;
87
- /**
88
- * List all files in the directory (recursive, sorted, ignoring defaults).
89
- * Useful for ad-hoc assertions when you don't want a full snapshot.
90
- */
91
194
  files(options?: {
92
195
  ignore?: string[];
93
196
  }): Promise<string[]>;
94
197
  }
95
198
  //#endregion
96
- //#region src/builder/common/response-accessor.d.ts
97
- /** Accessor for an HTTP response body with file-based assertion support. */
98
- declare class ResponseAccessor {
99
- readonly body: unknown;
100
- private testDir;
101
- constructor(body: unknown, testDir: string);
199
+ //#region src/spec/result/filesystem.d.ts
200
+ /**
201
+ * Accessor for the whole temporary working directory used by a CLI spec.
202
+ *
203
+ * Generalization of {@link DirectoryAccessor} that snapshots the entire CLI
204
+ * working directory into `<test-file-dir>/expected/filesystem/<name>/`.
205
+ */
206
+ declare class FilesystemAccessor {
207
+ /** The absolute path of the temporary working directory. */
208
+ readonly cwd: string;
209
+ private readonly testDir;
210
+ constructor(cwd: string, testDir: string);
211
+ /** List all files (recursively) under the working directory, sorted. */
212
+ files(options?: {
213
+ ignore?: string[];
214
+ }): Promise<string[]>;
102
215
  /**
103
- * Assert that the response body matches the JSON in `responses/{file}`.
216
+ * Assert the working directory matches a convention-based fixture tree:
217
+ * `<test-file-dir>/expected/filesystem/<name>/`.
218
+ */
219
+ toMatch(name: string, options?: DirectorySnapshotOptions): Promise<void>;
220
+ }
221
+ //#endregion
222
+ //#region src/spec/result/json.d.ts
223
+ interface JsonSnapshotOptions {
224
+ update?: boolean;
225
+ }
226
+ /**
227
+ * Accessor for a JSON payload parsed from a text stream (stdout).
228
+ *
229
+ * Lazily parses the JSON on first use; parse errors are deferred until an
230
+ * assertion is performed so that callers can still read the raw stream text
231
+ * without triggering a throw.
232
+ *
233
+ * When a `transform` is configured on the spec runner, it is applied to the
234
+ * raw stdout text BEFORE `JSON.parse`. This lets consumers strip ANSI codes,
235
+ * scrub machine-specific paths, etc. before structural comparison.
236
+ */
237
+ declare class JsonAccessor {
238
+ private readonly rawText;
239
+ private readonly testDir;
240
+ private readonly transform?;
241
+ constructor(rawText: string, testDir: string, transform?: (text: string) => string);
242
+ /** The parsed JSON value. Throws if the text is not valid JSON. */
243
+ get value(): unknown;
244
+ /**
245
+ * Assert the parsed JSON deep-equals the JSON in the given file on disk.
104
246
  *
105
- * @example
106
- * result.response.toMatchFile("expected-items.json");
247
+ * Path is resolved relative to the test file directory unless absolute.
248
+ * If `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`), the file is (re)written
249
+ * with the pretty-printed parsed value.
107
250
  */
108
- toMatchFile(file: string): void;
251
+ toMatchFile(path: string, options?: JsonSnapshotOptions): void;
252
+ /**
253
+ * Assert the parsed JSON matches a convention-based fixture:
254
+ * `<test-file-dir>/expected/json/<name>`.
255
+ *
256
+ * The extension is part of the name and must be included by the caller —
257
+ * e.g. `toMatch('error.json')`, not `toMatch('error')`. Explicit extensions
258
+ * are clearer at the call site and remove magic from the resolution.
259
+ */
260
+ toMatch(name: string, options?: JsonSnapshotOptions): void;
261
+ private parse;
109
262
  }
110
263
  //#endregion
111
- //#region src/builder/common/table-assertion.d.ts
264
+ //#region src/spec/result/table.d.ts
112
265
  /** Assertion helper for verifying database table contents after a specification run. */
113
266
  declare class TableAssertion {
114
267
  private tableName;
@@ -131,79 +284,323 @@ declare class TableAssertion {
131
284
  toBeEmpty(): Promise<void>;
132
285
  }
133
286
  //#endregion
134
- //#region src/builder/common/result.d.ts
135
- /** Read-only handle to a single file produced by a CLI action. */
287
+ //#region src/spec/result/result.d.ts
288
+ /** Read-only handle to a single file produced by a spec action. */
136
289
  interface FileAccessor {
137
290
  /** The UTF-8 text content. Throws if the file does not exist. */
138
291
  readonly content: string;
139
292
  readonly exists: boolean;
140
293
  }
294
+ interface BaseResultOptions {
295
+ config: SpecificationConfig;
296
+ testDir: string;
297
+ workDir?: string;
298
+ }
141
299
  /**
142
- * The outcome of a single specification run.
143
- * Provides accessors for CLI output, HTTP responses, files, directories, and database tables.
300
+ * Base result - common accessors available after any action type.
301
+ * Extended by HttpResult, CliResult, and used directly by JobResult.
144
302
  */
145
- declare class SpecificationResult {
146
- private commandResult?;
147
- private config;
148
- private requestInfo?;
149
- private responseData?;
150
- private testDir;
151
- private workDir?;
303
+ declare class BaseResult {
304
+ protected config: SpecificationConfig;
305
+ protected testDir: string;
306
+ protected workDir?: string;
307
+ constructor(options: BaseResultOptions);
308
+ /** Access a directory (relative to the working directory) for snapshot assertions. */
309
+ directory(path?: string): DirectoryAccessor;
310
+ /** Access a single file (relative to the working directory) for content assertions. */
311
+ file(path: string): FileAccessor;
312
+ /** Access a database table for row-level assertions. */
313
+ table(tableName: string, options?: {
314
+ service?: string;
315
+ }): TableAssertion;
316
+ private resolveDatabase;
317
+ }
318
+ //#endregion
319
+ //#region src/spec/result/stream.d.ts
320
+ interface StreamSnapshotOptions {
321
+ update?: boolean;
322
+ }
323
+ /**
324
+ * Accessor for a captured text stream (stdout/stderr) with file-based
325
+ * assertion support.
326
+ *
327
+ * Backed by a primitive string, exposed via {@link text}, but also provides
328
+ * `toString()` / `valueOf()` for string coercion, so common patterns like
329
+ * `String(result.stdout)` and template-literal interpolation still work.
330
+ */
331
+ declare class StreamAccessor {
332
+ private readonly streamName;
333
+ private readonly testDir;
334
+ private readonly transform?;
335
+ readonly text: string;
336
+ constructor(text: string, streamName: string, testDir: string, transform?: (text: string) => string);
337
+ /**
338
+ * Assert the captured text matches the given file on disk.
339
+ *
340
+ * Path is resolved relative to the test file directory unless absolute.
341
+ * If `JTERRAZZ_TEST_UPDATE=1` (or vitest `-u`), the file is (re)written
342
+ * with the actual text.
343
+ *
344
+ * When a `transform` is configured on the spec runner, it is applied to
345
+ * the actual text before comparison (and before writing in update mode).
346
+ * The fixture file is treated as authoritative and is NOT transformed.
347
+ */
348
+ toMatchFile(path: string, options?: StreamSnapshotOptions): void;
349
+ /**
350
+ * Assert the captured text matches a convention-based fixture:
351
+ * `<test-file-dir>/expected/<stream>/<name>`.
352
+ *
353
+ * The extension is part of the name and must be included by the caller —
354
+ * e.g. `toMatch('valid.txt')`, not `toMatch('valid')`. Explicit extensions
355
+ * are clearer at the call site and remove magic from the resolution.
356
+ */
357
+ toMatch(name: string, options?: StreamSnapshotOptions): void;
358
+ /**
359
+ * Assert the captured text contains the given substring. The runner's
360
+ * `transform` (if any) is applied before the check so callers can rely
361
+ * on the same normalised view as {@link toMatch}.
362
+ *
363
+ * Throws with a tight diff-style error on miss so tests read uniformly
364
+ * with the other accessor assertions (no reaching through `.text` into
365
+ * `expect(...).toContain(...)`).
366
+ */
367
+ toContain(substring: string): void;
368
+ toString(): string;
369
+ valueOf(): string;
370
+ }
371
+ //#endregion
372
+ //#region src/spec/modes/cli/container-accessor.d.ts
373
+ /**
374
+ * Assertion accessor for a single Docker container captured by the docker()
375
+ * spec mode. Mirrors the shape of {@link CliResult} so tests use the same
376
+ * vocabulary (`stdout.toContain`, `file(...).content`, etc.) regardless of
377
+ * where output came from.
378
+ *
379
+ * Sync state (`exists`, `running`, `status`) is derived from the `docker
380
+ * inspect` payload captured at result-construction time. Logs are fetched
381
+ * lazily on first access to `stdout`/`stderr`.
382
+ */
383
+ declare class ContainerAccessor {
384
+ private cachedLogs;
385
+ private readonly containerId;
386
+ readonly exists: boolean;
387
+ private readonly inspectData;
388
+ readonly running: boolean;
389
+ readonly status: string;
390
+ private readonly testDir;
391
+ private readonly transform?;
392
+ /**
393
+ * Underlying Docker container ID, or `null` if no container was captured
394
+ * for this name. Useful when a follow-up CLI command needs to reference
395
+ * the container by id (e.g. `spwn world inspect <id>`). Prefer the other
396
+ * accessors for common reads — pulling the raw id is the escape hatch.
397
+ */
398
+ get id(): null | string;
399
+ constructor(containerId: null | string, inspectData: unknown, testDir: string, transform?: (text: string) => string);
400
+ /**
401
+ * Raw `docker inspect` object for this container. Throws if the container
402
+ * was not captured (i.e. `.exists === false`).
403
+ */
404
+ get inspect(): JsonAccessor;
405
+ /** Captured logs (stdout+stderr combined for v1). */
406
+ get stdout(): StreamAccessor;
407
+ /** Captured logs (stdout+stderr combined for v1). */
408
+ get stderr(): StreamAccessor;
409
+ /**
410
+ * Read a file from inside the container via `docker exec cat`. The
411
+ * returned object satisfies the same {@link FileAccessor} shape used by
412
+ * the host filesystem accessor, so tests do not need to learn a new API.
413
+ */
414
+ file(path: string): FileAccessor;
415
+ /**
416
+ * Run a shell command inside the container and get back the same
417
+ * {@link CliResult} used for host-side executions.
418
+ */
419
+ exec(cmd: string): Promise<CliResult>;
420
+ private requireExists;
421
+ private loadLogs;
422
+ private containerExec;
423
+ }
424
+ //#endregion
425
+ //#region src/spec/modes/cli/result.d.ts
426
+ /**
427
+ * Result from a CLI action (.exec(), .spawn()).
428
+ *
429
+ * When the runner was configured with a `docker` option, the result also
430
+ * exposes container accessors and participates in `Symbol.asyncDispose`
431
+ * cleanup. The Docker shell-outs are **lazy**: a test that never calls
432
+ * `.container(...)` never queries the Docker daemon, so CLI-only tests
433
+ * pay zero Docker cost even when the runner is Docker-aware.
434
+ *
435
+ * Dispose always runs one final label-filtered `docker ps` to catch
436
+ * containers that were spawned during `.exec` but never asserted on —
437
+ * tests still get leak-free cleanup even if they forget to reach into
438
+ * a container.
439
+ */
440
+ declare class CliResult extends BaseResult {
441
+ private readonly commandResult;
442
+ private containersCache;
443
+ private readonly dockerConfig?;
444
+ private readonly testRunId?;
445
+ private readonly transform?;
152
446
  constructor(options: {
153
- commandResult?: CommandResult;
447
+ commandResult: CommandResult;
154
448
  config: SpecificationConfig;
155
- requestInfo?: {
156
- body?: unknown;
157
- method: string;
158
- path: string;
159
- };
160
- response?: ServerResponse;
449
+ dockerConfig?: DockerSpecConfig;
161
450
  testDir: string;
162
- workDir?: string;
451
+ testRunId?: string;
452
+ transform?: (text: string) => string;
453
+ workDir: string;
163
454
  });
164
- /** The process exit code. Only available after a CLI action. */
455
+ /** The process exit code. */
165
456
  get exitCode(): number;
166
- /** The HTTP response status code. Only available after an HTTP action. */
167
- get status(): number;
168
- /** The captured standard output. Only available after a CLI action. */
169
- get stdout(): string;
170
- /** The captured standard error. Only available after a CLI action. */
171
- get stderr(): string;
457
+ /** Accessor for the captured standard output with file-based assertions. */
458
+ get stdout(): StreamAccessor;
459
+ /** Accessor for the captured standard error with file-based assertions. */
460
+ get stderr(): StreamAccessor;
461
+ /** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
462
+ get json(): JsonAccessor;
463
+ /** Accessor for the temporary working directory the command ran in. */
464
+ get filesystem(): FilesystemAccessor;
465
+ /**
466
+ * Look up a container the CLI spawned during this run by the value of
467
+ * its name-label (as declared in `SpecOptions.docker.nameLabel`).
468
+ * First access triggers a one-shot `docker ps` + `docker inspect`
469
+ * query and caches the result for the rest of the result's lifetime.
470
+ * Tests that don't call this never touch Docker.
471
+ *
472
+ * Returns an accessor with `.exists === false` when no container
473
+ * carries that name — callers can still assert absence without a try.
474
+ */
475
+ container(name: string): ContainerAccessor;
476
+ /** All captured container IDs. Triggers the same lazy query as `container()`. */
477
+ get containerIds(): string[];
478
+ [Symbol.asyncDispose](): Promise<void>;
172
479
  /**
173
480
  * Extract text blocks from stdout that contain a pattern.
174
- * Useful for parsing structured CLI output (linters, compilers).
175
481
  *
176
482
  * @example
177
483
  * expect(result.grep('error.ts')).toContain('no-unused-vars');
178
484
  */
179
485
  grep(pattern: string): string;
180
- /** Access the HTTP response body for assertions. Only available after an HTTP action. */
486
+ private ensureDockerAware;
487
+ private loadContainers;
488
+ }
489
+ //#endregion
490
+ //#region src/spec/result/response.d.ts
491
+ /** Accessor for an HTTP response body with file-based assertion support. */
492
+ declare class ResponseAccessor {
493
+ readonly body: unknown;
494
+ private testDir;
495
+ constructor(body: unknown, testDir: string);
496
+ /**
497
+ * Assert that the response body matches the JSON in `responses/{file}`.
498
+ *
499
+ * @example
500
+ * result.response.toMatchFile("expected-items.json");
501
+ */
502
+ toMatchFile(file: string): void;
503
+ }
504
+ //#endregion
505
+ //#region src/spec/modes/http/server.port.d.ts
506
+ /**
507
+ * HTTP response returned by a server port, with parsed JSON body.
508
+ */
509
+ interface ServerResponse {
510
+ /** HTTP status code. */
511
+ status: number;
512
+ /** Parsed JSON response body, or null if parsing failed. */
513
+ body: unknown;
514
+ /** Response headers as a flat key-value map. */
515
+ headers: Record<string, string>;
516
+ }
517
+ /**
518
+ * Abstract server interface for specification runners.
519
+ * Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
520
+ */
521
+ interface ServerPort {
522
+ /** Send an HTTP request and return the parsed response. */
523
+ request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
524
+ }
525
+ //#endregion
526
+ //#region src/spec/modes/http/result.d.ts
527
+ /** Result from an HTTP action (.get(), .post(), .put(), .delete()). */
528
+ declare class HttpResult extends BaseResult {
529
+ private responseData;
530
+ constructor(options: {
531
+ config: SpecificationConfig;
532
+ response: ServerResponse;
533
+ testDir: string;
534
+ });
535
+ /** The HTTP response status code. */
536
+ get status(): number;
537
+ /** Access the HTTP response body for assertions. */
181
538
  get response(): ResponseAccessor;
182
- /** Access a directory (relative to the working directory) for snapshot assertions. */
183
- directory(path?: string): DirectoryAccessor;
184
- /** Access a single file (relative to the working directory) for content assertions. */
185
- file(path: string): FileAccessor;
186
- /** Access a database table for row-level assertions. */
187
- table(tableName: string, options?: {
188
- service?: string;
189
- }): TableAssertion;
190
- private resolveDatabase;
191
539
  }
192
540
  //#endregion
193
- //#region src/builder/specification-builder.d.ts
541
+ //#region src/spec/builder.d.ts
194
542
  /** A named job that can be triggered via .job(). */
195
543
  interface JobHandle$1 {
196
544
  name: string;
197
545
  execute: () => Promise<void>;
198
546
  }
547
+ /**
548
+ * Context passed to a {@link SeedHandler} when a non-SQL seed is dispatched.
549
+ */
550
+ interface SeedHandlerContext {
551
+ /** Absolute path of the temporary working directory for the current spec. */
552
+ cwd: string;
553
+ /** Absolute path of the test file directory (where `seeds/` lives). */
554
+ testDir: string;
555
+ }
556
+ /**
557
+ * Handler invoked for a non-SQL seed entry. The first path segment of the
558
+ * seed file selects the handler; the handler is given the absolute path of
559
+ * the source fragment (file or directory) and the working directory to mutate.
560
+ */
561
+ type SeedHandler = (ctx: SeedHandlerContext, fragmentPath: string) => Promise<void> | void;
562
+ /**
563
+ * Configuration for the docker() spec mode. When set on
564
+ * {@link SpecificationConfig}, the CLI runner generates a test-run id, injects
565
+ * it into the child env under `envVar`, then queries Docker for every
566
+ * container carrying `testRunLabel=<id>` after the command exits.
567
+ */
568
+ interface DockerSpecConfig {
569
+ envVar: string;
570
+ nameLabel: string;
571
+ testRunLabel: string;
572
+ }
199
573
  /** Adapter configuration passed to the specification runner at setup time. */
200
574
  interface SpecificationConfig {
201
575
  command?: CommandPort;
202
576
  database?: DatabasePort;
203
577
  databases?: Map<string, DatabasePort>;
578
+ dockerConfig?: DockerSpecConfig;
579
+ /**
580
+ * Unique id shared by every `.run()` from this runner instance.
581
+ * Stable for the runner's lifetime so multi-step tests (spawn in
582
+ * one run, inspect in another) see the same container scope. The
583
+ * public `createSpecificationRunner` auto-populates this when
584
+ * `dockerConfig` is present — callers should leave it unset.
585
+ */
586
+ dockerTestRunId?: string;
204
587
  fixturesRoot?: string;
205
588
  jobs?: JobHandle$1[];
589
+ /**
590
+ * Pluggable seed handlers for CLI-mode tests. Keys are leading path
591
+ * segments (e.g. `"spwn.yaml/"`, `"agent/"`). When `.seed(relPath)` is
592
+ * called with a path whose first segment matches, the handler is invoked
593
+ * with the full absolute path of the seed fragment under
594
+ * `<test-file-dir>/seeds/<relPath>`.
595
+ */
596
+ seedHandlers?: Record<string, SeedHandler>;
206
597
  server?: ServerPort;
598
+ /**
599
+ * Optional normaliser applied to CLI stdout/stderr before every
600
+ * .toMatch / .toMatchFile comparison. Does not mutate the raw
601
+ * `.text` accessor.
602
+ */
603
+ transform?: (text: string) => string;
207
604
  }
208
605
  /**
209
606
  * Fluent builder for declaring a single test specification.
@@ -327,7 +724,8 @@ declare class SpecificationBuilder {
327
724
  * const result = await spec("test").exec("status").run();
328
725
  * expect(result.exitCode).toBe(0);
329
726
  */
330
- run(): Promise<SpecificationResult>;
727
+ run(): Promise<BaseResult | CliResult | HttpResult>;
728
+ private resolveSeedHandler;
331
729
  private resolveEnv;
332
730
  private prepareWorkDir;
333
731
  private runHttpAction;
@@ -342,151 +740,9 @@ type SpecificationRunner = (label: string) => SpecificationBuilder;
342
740
  */
343
741
  declare function createSpecificationRunner(config: SpecificationConfig): SpecificationRunner;
344
742
  //#endregion
345
- //#region src/docker/docker-port.d.ts
346
- /** Abstract interface for interacting with a Docker container (exec, inspect, file I/O). */
347
- interface DockerContainerPort {
348
- /** Execute a command inside the container, return stdout */
349
- exec(cmd: string[]): Promise<string>;
350
- /** Read a file from inside the container */
351
- file(path: string): Promise<{
352
- exists: boolean;
353
- content: string;
354
- }>;
355
- /** Check if container is running */
356
- isRunning(): Promise<boolean>;
357
- /** Get container logs */
358
- logs(tail?: number): Promise<string>;
359
- /** Get full docker inspect JSON */
360
- inspect(): Promise<DockerInspectResult>;
361
- /** Check if a file/directory exists */
362
- exists(path: string): Promise<boolean>;
363
- }
364
- /** Normalized subset of `docker inspect` output for assertion use. */
365
- interface DockerInspectResult {
366
- id: string;
367
- name: string;
368
- state: {
369
- running: boolean;
370
- exitCode: number;
371
- status: string;
372
- };
373
- config: {
374
- image: string;
375
- env: string[];
376
- };
377
- hostConfig: {
378
- memory: number;
379
- cpuQuota: number;
380
- networkMode: string;
381
- mounts: Array<{
382
- source: string;
383
- destination: string;
384
- type: string;
385
- }>;
386
- };
387
- networkSettings: {
388
- networks: Record<string, {
389
- gateway: string;
390
- ipAddress: string;
391
- }>;
392
- };
393
- }
394
- //#endregion
395
- //#region src/docker/docker-assertion.d.ts
396
- /**
397
- * Fluent assertion builder for Docker containers.
398
- * Chain async assertions like `toBeRunning()`, `toHaveFile()`, `toHaveMount()`.
399
- */
400
- declare class DockerAssertion {
401
- private container;
402
- constructor(container: DockerContainerPort);
403
- /** Assert the container is running */
404
- toBeRunning(): Promise<this>;
405
- /** Assert the container is NOT running / doesn't exist */
406
- toNotExist(): Promise<this>;
407
- /** Assert a file exists inside the container */
408
- toHaveFile(path: string, opts?: {
409
- containing?: string;
410
- }): Promise<this>;
411
- /** Assert a file does NOT exist */
412
- toNotHaveFile(path: string): Promise<this>;
413
- /** Assert a directory exists */
414
- toHaveDirectory(path: string): Promise<this>;
415
- /** Assert a mount exists */
416
- toHaveMount(destination: string): Promise<this>;
417
- /** Assert network mode */
418
- toHaveNetwork(mode: string): Promise<this>;
419
- /** Assert memory limit */
420
- toHaveMemoryLimit(bytes: number): Promise<this>;
421
- /** Assert CPU quota */
422
- toHaveCpuQuota(quota: number): Promise<this>;
423
- /** Execute a command and return output for custom assertions */
424
- exec(cmd: string[]): Promise<string>;
425
- /** Read a file for custom assertions */
426
- readFile(path: string): Promise<string>;
427
- /** Get logs for custom assertions */
428
- getLogs(tail?: number): Promise<string>;
429
- }
430
- //#endregion
431
- //#region src/infra/orchestrator.d.ts
432
- interface OrchestratorOptions {
433
- services: ServiceHandle[];
434
- mode: 'e2e' | 'integration';
435
- root?: string;
436
- /** Compose project name — used for per-worker stack isolation. */
437
- projectName?: string;
438
- }
439
- /**
440
- * Orchestrator for test infrastructure.
441
- * Integration: starts services via testcontainers.
442
- * E2E: runs full docker compose up.
443
- */
444
- declare class Orchestrator {
445
- private services;
446
- private mode;
447
- private root;
448
- private projectName;
449
- private running;
450
- private composeStack;
451
- private composeHandles;
452
- private started;
453
- constructor(options: OrchestratorOptions);
454
- /**
455
- * Start declared services via testcontainers (integration mode).
456
- * Phase 1: start all containers in parallel (the slow part).
457
- * Phase 2: wire connections, healthcheck, and init sequentially (fast).
458
- */
459
- start(): Promise<void>;
460
- /**
461
- * Stop testcontainers (integration mode).
462
- */
463
- stop(): Promise<void>;
464
- /**
465
- * Start full docker compose stack (e2e mode).
466
- * Auto-detects infra services and creates handles for them.
467
- */
468
- startCompose(): Promise<void>;
469
- /**
470
- * Stop docker compose stack (e2e mode).
471
- */
472
- stopCompose(): Promise<void>;
473
- /**
474
- * Get a database service by compose name, or the first one if no name given.
475
- */
476
- getDatabase(serviceName?: string): DatabasePort | null;
477
- /**
478
- * Get all database services keyed by compose name.
479
- */
480
- getDatabases(): Map<string, DatabasePort>;
481
- /**
482
- * Get app URL from compose (e2e mode).
483
- */
484
- getAppUrl(): null | string;
485
- }
486
- //#endregion
487
743
  //#region src/spec/targets.d.ts
488
744
  /** Any object with a request method compatible with Hono's app.request(). */
489
- type HonoApp$1 = {
745
+ type HonoApp = {
490
746
  request: (path: string, init?: RequestInit) => Promise<Response> | Response;
491
747
  };
492
748
  /** A named job that can be triggered via .job(). */
@@ -500,8 +756,8 @@ type AppServices = Record<string, ServiceHandle>;
500
756
  * Return value from the app() factory. Either a bare server (backward compat)
501
757
  * or an object with server + jobs for job-based testing.
502
758
  */
503
- type AppFactoryResult = HonoApp$1 | {
504
- server: HonoApp$1;
759
+ type AppFactoryResult = HonoApp | {
760
+ server: HonoApp;
505
761
  jobs: JobHandle[];
506
762
  };
507
763
  /** In-process Hono app target. Created by {@link app}. */
@@ -536,7 +792,7 @@ type SpecTarget = AppTarget | CommandTarget | StackTarget;
536
792
  * { services: [db] },
537
793
  * );
538
794
  */
539
- declare function app(factory: (services: AppServices) => HonoApp$1): AppTarget;
795
+ declare function app(factory: (services: AppServices) => HonoApp): AppTarget;
540
796
  /**
541
797
  * Test against a full docker compose stack. The stack is started with
542
798
  * `docker compose up` and real HTTP requests are sent to the app service.
@@ -550,20 +806,67 @@ declare function stack(root: string): StackTarget;
550
806
  /**
551
807
  * Test a CLI binary. Each spec runs in a fresh temp directory.
552
808
  *
809
+ * Pass a `docker: { envVar, nameLabel, testRunLabel }` option on
810
+ * {@link SpecOptions} to make the runner Docker-aware — the runner
811
+ * stamps a unique test-run id into `envVar`, and results expose
812
+ * `.container(name)` accessors that lazily query Docker. Tests that
813
+ * never call `.container(...)` pay zero Docker cost.
814
+ *
553
815
  * @param bin - Path to the CLI binary or command name (resolved from node_modules/.bin or PATH).
554
816
  *
555
817
  * @example
818
+ * // CLI-only
556
819
  * await spec(command('my-cli'), { root: '../fixtures' });
820
+ *
821
+ * // CLI binary that also spawns containers — same runner, just
822
+ * // opt into container accessors via the docker option:
823
+ * await spec(command('my-cli'), {
824
+ * root: '../fixtures',
825
+ * docker: {
826
+ * envVar: 'MYCLI_TEST_LABEL',
827
+ * nameLabel: 'com.mycli.world.name',
828
+ * testRunLabel: 'com.mycli.test.run',
829
+ * },
830
+ * });
557
831
  */
558
832
  declare function command(bin: string): CommandTarget;
559
833
  //#endregion
560
834
  //#region src/spec/spec.d.ts
561
835
  /** Shared options for all spec targets. */
562
836
  interface SpecOptions {
837
+ /**
838
+ * Opt-in Docker awareness for CLI-mode runners. When set, every
839
+ * `.run()` generates a unique test-run id, injects it into the
840
+ * child process env under `envVar`, and exposes `.container(name)`
841
+ * accessors on the result that lazily query Docker. A test that
842
+ * never calls `.container(...)` never touches the Docker daemon —
843
+ * CLI-only and container-asserting tests share one runner.
844
+ *
845
+ * Always wrap calls that may spawn containers with `await using`
846
+ * so leaked containers get force-removed at scope exit (the
847
+ * runner cleans up by label filter).
848
+ */
849
+ docker?: DockerSpecConfig;
563
850
  /** Project root for fixture lookup and compose detection (relative paths supported). */
564
851
  root?: string;
852
+ /**
853
+ * Pluggable seed handlers for CLI-mode tests. Keys are leading path
854
+ * segments (e.g. `"spwn.yaml/"`); values receive the seed context and the
855
+ * absolute path of the source fragment under `<test-file-dir>/seeds/`.
856
+ */
857
+ seedHandlers?: Record<string, SeedHandler>;
565
858
  /** Infrastructure services to start via testcontainers. */
566
859
  services?: ServiceHandle[];
860
+ /**
861
+ * Optional normaliser applied to result.stdout.text and
862
+ * result.stderr.text before every .toMatch / .toMatchFile
863
+ * comparison. Useful for stripping ANSI codes, masking
864
+ * machine-specific paths, scrubbing timestamps, etc.
865
+ *
866
+ * The transform does NOT mutate the raw `.text` accessor —
867
+ * callers can still inspect the pristine output.
868
+ */
869
+ transform?: (text: string) => string;
567
870
  }
568
871
  /** A specification runner with teardown support and orchestrator access. */
569
872
  interface SpecRunner {
@@ -599,7 +902,15 @@ interface SpecRunner {
599
902
  */
600
903
  declare function spec(target: SpecTarget, options?: SpecOptions): Promise<SpecRunner>;
601
904
  //#endregion
602
- //#region src/infra/ports/container.port.d.ts
905
+ //#region src/spec/modes/cli/docker-lookup.d.ts
906
+ /** Return all container IDs (running or stopped) that carry `key=value`. */
907
+ declare function findContainersByLabel(key: string, value: string): string[];
908
+ /** Return the raw `docker inspect` payload (object, not array) for a container. */
909
+ declare function inspectContainer(id: string): unknown;
910
+ /** Force-remove the given container IDs in a single call. Errors are swallowed. */
911
+ declare function removeContainers(ids: string[]): void;
912
+ //#endregion
913
+ //#region src/infra/containers/container.port.d.ts
603
914
  /**
604
915
  * Abstract container interface.
605
916
  * Represents a running service (database, cache, etc.)
@@ -619,11 +930,16 @@ interface ContainerPort {
619
930
  getLogs(): Promise<string>;
620
931
  }
621
932
  //#endregion
622
- //#region src/builder/cli/adapters/exec.adapter.d.ts
933
+ //#region src/spec/modes/cli/adapters/exec.adapter.d.ts
623
934
  /**
624
935
  * Executes CLI commands via Node.js child_process.
625
- * Uses `execSync` for one-shot commands and `spawn` for long-running processes.
626
- * Used by the `cli()` specification runner.
936
+ * Uses `spawnSync` for one-shot commands and `spawn` for long-running processes.
937
+ * Used by the command() specification runner.
938
+ *
939
+ * `spawnSync` is used (over the simpler `execSync`) so stdout AND stderr are
940
+ * captured regardless of exit code. Many CLIs follow the Unix convention of
941
+ * writing status banners to stderr on success — `execSync` would silently
942
+ * discard them, leaving snapshot tests with no output to assert on.
627
943
  */
628
944
  declare class ExecAdapter implements CommandPort {
629
945
  private command;
@@ -632,7 +948,7 @@ declare class ExecAdapter implements CommandPort {
632
948
  spawn(args: string, cwd: string, options: SpawnOptions, extraEnv?: CommandEnv): Promise<CommandResult>;
633
949
  }
634
950
  //#endregion
635
- //#region src/builder/http/adapters/fetch.adapter.d.ts
951
+ //#region src/spec/modes/http/adapters/fetch.adapter.d.ts
636
952
  /**
637
953
  * Server adapter that sends real HTTP requests via the Fetch API.
638
954
  * Used by the `e2e()` specification runner to hit a live server.
@@ -643,7 +959,7 @@ declare class FetchAdapter implements ServerPort {
643
959
  request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
644
960
  }
645
961
  //#endregion
646
- //#region src/builder/http/adapters/hono.adapter.d.ts
962
+ //#region src/spec/modes/http/adapters/hono.adapter.d.ts
647
963
  /**
648
964
  * Server adapter that dispatches requests in-process through a Hono app instance.
649
965
  * Used by the `integration()` specification runner -- no network overhead.
@@ -656,79 +972,9 @@ declare class HonoAdapter implements ServerPort {
656
972
  request(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<ServerResponse>;
657
973
  }
658
974
  //#endregion
659
- //#region src/spec/legacy-integration.d.ts
660
- type HonoApp = {
661
- fetch: (...args: any[]) => any;
662
- request: (path: string, init?: RequestInit) => Promise<Response> | Response;
663
- };
664
- interface IntegrationOptions {
665
- /** Factory that returns a Hono app — called after services start. */
666
- app: () => HonoApp;
667
- /** Project root for compose detection (relative paths supported). */
668
- root?: string;
669
- /** Declared services — started via testcontainers. */
670
- services: ServiceHandle[];
671
- }
672
- /** A specification runner that also exposes teardown and the underlying orchestrator. */
673
- interface SpecificationRunnerWithCleanup extends SpecificationRunner {
674
- /** Stop all infrastructure containers started by this runner. */
675
- cleanup: () => Promise<void>;
676
- /** The orchestrator managing the test infrastructure lifecycle. */
677
- orchestrator: Orchestrator;
678
- }
679
- /**
680
- * Create an integration specification runner.
681
- * Starts infra containers via testcontainers, app runs in-process.
682
- */
683
- declare function integration(options: IntegrationOptions): Promise<SpecificationRunnerWithCleanup>;
684
- //#endregion
685
- //#region src/spec/legacy-cli.d.ts
686
- interface CliOptions {
687
- /** CLI command to run (resolved from node_modules/.bin or PATH). */
688
- command: string;
689
- /** Project root — base dir for .project() fixture lookup (relative paths supported). */
690
- root?: string;
691
- /** Optional infrastructure services (started via testcontainers). */
692
- services?: ServiceHandle[];
693
- }
694
- /**
695
- * Create a CLI specification runner.
696
- * Runs CLI commands against fixture projects. Optionally starts infrastructure.
697
- */
698
- declare function cli(options: CliOptions): Promise<SpecificationRunnerWithCleanup>;
699
- //#endregion
700
- //#region src/spec/legacy-e2e.d.ts
701
- interface E2eOptions {
702
- /** Project root — must contain docker/compose.test.yaml. */
703
- root?: string;
704
- }
705
- /**
706
- * Create an E2E specification runner.
707
- * Starts full docker compose stack. App URL and database auto-detected.
708
- */
709
- declare function e2e(options?: E2eOptions): Promise<SpecificationRunnerWithCleanup>;
710
- //#endregion
711
- //#region src/builder/common/grep.d.ts
712
- /**
713
- * Extract text blocks from output that contain a pattern.
714
- * Splits by blank lines (how linter/compiler output is structured),
715
- * returns only blocks matching the pattern.
716
- *
717
- * @example
718
- * expect(grep(result.stdout, "unused-var.ts")).toContain("no-unused-vars")
719
- * expect(grep(result.stdout, "valid/sorted.ts")).not.toContain("sort-imports")
720
- */
721
- declare function grep(output: string, pattern: string): string;
722
- //#endregion
723
- //#region src/builder/common/reporter.d.ts
724
- /** Strip ANSI escape codes from a string. */
725
- declare function stripAnsi(str: string): string;
726
- /** Strip ANSI codes and replace volatile tokens (ports, durations) with placeholders. */
727
- declare function normalizeOutput(str: string): string;
728
- //#endregion
729
- //#region src/docker/docker-adapter.d.ts
975
+ //#region src/infra/docker/docker.d.ts
730
976
  /** Create a {@link DockerContainerPort} bound to an existing container by ID. */
731
977
  declare function dockerContainer(containerId: string): DockerContainerPort;
732
978
  //#endregion
733
- export { type AppFactoryResult, type AppServices, type AppTarget, type CliOptions, type CommandEnv, type CommandPort, type CommandResult, type CommandTarget, type ContainerPort, type DatabasePort, DirectoryAccessor, type DirectorySnapshotOptions, DockerAssertion, type DockerContainerPort, type DockerInspectResult, type E2eOptions, ExecAdapter, FetchAdapter, type FileAccessor, HonoAdapter, type HttpTarget, type IntegrationOptions, type IsolationStrategy, type JobHandle, type MockDatePort, type MockPort, Orchestrator, type PostgresOptions, type RedisOptions, ResponseAccessor, type ServerPort, type ServerResponse, type ServiceHandle, type SpawnOptions, type SpecOptions, type SpecRunner, type SpecTarget, SpecificationBuilder, type SpecificationConfig, SpecificationResult, type SpecificationRunner, type SpecificationRunnerWithCleanup, type SqliteOptions, type StackTarget, TableAssertion, app, cli, command, createSpecificationRunner, dockerContainer, e2e, grep, integration, mockOf, mockOfDate, normalizeOutput, postgres, redis, spec, sqlite, stack, stripAnsi };
979
+ export { type AppFactoryResult, type AppServices, type AppTarget, BaseResult, CliResult, type CommandEnv, type CommandPort, type CommandResult, type CommandTarget, ContainerAccessor, type ContainerPort, type DatabasePort, DirectoryAccessor, type DirectorySnapshotOptions, DockerAssertion, type DockerContainerPort, type DockerInspectResult, type DockerSpecConfig, ExecAdapter, FetchAdapter, type FileAccessor, FilesystemAccessor, HonoAdapter, HttpResult, type HttpTarget, type IsolationStrategy, type JobHandle, JsonAccessor, type JsonSnapshotOptions, type MockDatePort, type MockPort, Orchestrator, type PostgresOptions, type RedisOptions, ResponseAccessor, type SeedHandler, type SeedHandlerContext, type ServerPort, type ServerResponse, type ServiceHandle, type SpawnOptions, type SpecOptions, type SpecRunner, type SpecTarget, SpecificationBuilder, type SpecificationConfig, type SpecificationRunner, type SqliteOptions, type StackTarget, StreamAccessor, type StreamSnapshotOptions, TableAssertion, app, command, createSpecificationRunner, dockerContainer, findContainersByLabel, inspectContainer, mockOf, mockOfDate, postgres, redis, removeContainers, spec, sqlite, stack };
734
980
  //# sourceMappingURL=index.d.cts.map