contract-drift-detection 0.1.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ ## 0.1.1 - 2026-03-16
6
+
7
+ ### Added
8
+
9
+ - New `serve` onboarding modes:
10
+ - `--spec-url <url>` to fetch remote OpenAPI specs
11
+ - `--discover <backend-url>` to auto-discover OpenAPI endpoints
12
+ - New `quickstart` command to generate starter contract and run instantly.
13
+ - `init --template rest-crud|none` for template-driven setup.
14
+ - Local cache for remote/discovered specs under `.cdd/`.
15
+ - New onboarding tests covering local, remote, and discovery-based spec resolution.
16
+
17
+ ### Improved
18
+
19
+ - README now includes no-friction first-run flows for users without an existing local spec file.
20
+
21
+ ## 0.1.0 - 2026-03-16
22
+
23
+ ### Added
24
+
25
+ - Initial public release with stateful OpenAPI mock server.
26
+ - CRUD route generation from OpenAPI 3.x documents.
27
+ - `x-mock-state` workflow action support.
28
+ - Proxy mode with contract drift detection.
29
+ - Browser CORS support and frontend/backend demo harness.
package/README.md CHANGED
@@ -64,23 +64,41 @@ npm run dev
64
64
 
65
65
  ## 30-second quick start
66
66
 
67
- Run the included example API:
67
+ No spec file yet? Start instantly:
68
68
 
69
69
  ```bash
70
- npx tsx src/cli.ts serve --spec ./examples/helpdesk.openapi.yaml --port 4010
70
+ npx contract-drift-detection@latest quickstart
71
71
  ```
72
72
 
73
- Create a starter config file in any project:
73
+ Already have an OpenAPI file? Start with local spec:
74
74
 
75
75
  ```bash
76
- npx tsx src/cli.ts init
76
+ npx contract-drift-detection@latest serve --spec ./openapi.yaml --port 4010
77
+ ```
78
+
79
+ Create a starter config + starter OpenAPI file in current directory:
80
+
81
+ ```bash
82
+ npx contract-drift-detection@latest init
83
+ ```
84
+
85
+ If your backend already exposes OpenAPI, avoid writing spec manually:
86
+
87
+ ```bash
88
+ npx contract-drift-detection@latest serve --discover http://localhost:8080 --port 4010
89
+ ```
90
+
91
+ Or use direct remote spec URL:
92
+
93
+ ```bash
94
+ npx contract-drift-detection@latest serve --spec-url http://localhost:8080/openapi.json --port 4010
77
95
  ```
78
96
 
79
97
  Start in drift detection mode against a real backend:
80
98
 
81
99
  ```bash
82
- npx tsx src/cli.ts serve \
83
- --spec ./examples/helpdesk.openapi.yaml \
100
+ npx contract-drift-detection@latest serve \
101
+ --discover http://localhost:8080 \
84
102
  --port 4010 \
85
103
  --drift-check http://localhost:8080
86
104
  ```
@@ -92,12 +110,21 @@ Point your frontend API base URL to `http://localhost:4010`.
92
110
  ## CLI
93
111
 
94
112
  ```text
95
- contract-drift-detection serve --spec <path> [--port 4010] [--host 0.0.0.0] [--db .mock-db.json] [--cors-origin *] [--drift-check <url>] [--fallback-to-mock] [--verbose]
96
- contract-drift-detection init [--spec openapi.yaml] [--db .mock-db.json] [--port 4010] [--host 0.0.0.0]
113
+ contract-drift-detection serve [--spec <path> | --spec-url <url> | --discover <backend-url>] [--port 4010] [--host 0.0.0.0] [--db .mock-db.json] [--cors-origin *] [--drift-check <url>] [--fallback-to-mock] [--verbose]
114
+ contract-drift-detection init [--spec openapi.yaml] [--template rest-crud|none] [--db .mock-db.json] [--port 4010] [--host 0.0.0.0]
115
+ contract-drift-detection quickstart [--spec openapi.yaml] [--port 4010] [--host 0.0.0.0] [--db .mock-db.json] [--cors-origin *] [--verbose]
97
116
  ```
98
117
 
99
118
  `--cors-origin` defaults to `*` for frictionless browser testing.
100
119
 
120
+ Spec resolution priority in `serve` is:
121
+
122
+ 1. `--spec`
123
+ 2. `--spec-url`
124
+ 3. `--discover`
125
+
126
+ Remote/discovered specs are cached locally under `.cdd/`.
127
+
101
128
  ## Recommended workflows
102
129
 
103
130
  ### A) Frontend-first (mock only)
package/dist/cli.js CHANGED
@@ -4,22 +4,253 @@
4
4
  import process from "process";
5
5
 
6
6
  // src/config.ts
7
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
8
+ import path2 from "path";
9
+ import { Command } from "commander";
10
+
11
+ // src/onboarding.ts
7
12
  import { mkdir, writeFile } from "fs/promises";
8
13
  import path from "path";
9
- import { Command } from "commander";
14
+ var DISCOVERY_PATHS = [
15
+ "/openapi.json",
16
+ "/openapi.yaml",
17
+ "/swagger.json",
18
+ "/v3/api-docs",
19
+ "/api-docs-json"
20
+ ];
21
+ var STARTER_SPEC = `openapi: 3.0.3
22
+ info:
23
+ title: Contract Drift Detection Starter API
24
+ version: 1.0.0
25
+ paths:
26
+ /users:
27
+ get:
28
+ responses:
29
+ '200':
30
+ description: ok
31
+ content:
32
+ application/json:
33
+ schema:
34
+ type: array
35
+ items:
36
+ $ref: '#/components/schemas/User'
37
+ post:
38
+ requestBody:
39
+ required: true
40
+ content:
41
+ application/json:
42
+ schema:
43
+ $ref: '#/components/schemas/UserCreateInput'
44
+ responses:
45
+ '201':
46
+ description: created
47
+ content:
48
+ application/json:
49
+ schema:
50
+ $ref: '#/components/schemas/User'
51
+ /users/{id}:
52
+ get:
53
+ parameters:
54
+ - $ref: '#/components/parameters/UserId'
55
+ responses:
56
+ '200':
57
+ description: ok
58
+ content:
59
+ application/json:
60
+ schema:
61
+ $ref: '#/components/schemas/User'
62
+ patch:
63
+ parameters:
64
+ - $ref: '#/components/parameters/UserId'
65
+ requestBody:
66
+ required: true
67
+ content:
68
+ application/json:
69
+ schema:
70
+ $ref: '#/components/schemas/UserPatchInput'
71
+ responses:
72
+ '200':
73
+ description: updated
74
+ content:
75
+ application/json:
76
+ schema:
77
+ $ref: '#/components/schemas/User'
78
+ delete:
79
+ parameters:
80
+ - $ref: '#/components/parameters/UserId'
81
+ responses:
82
+ '204':
83
+ description: deleted
84
+ /tickets:
85
+ get:
86
+ responses:
87
+ '200':
88
+ description: ok
89
+ content:
90
+ application/json:
91
+ schema:
92
+ type: array
93
+ items:
94
+ $ref: '#/components/schemas/Ticket'
95
+ /tickets/{id}/resolve:
96
+ post:
97
+ parameters:
98
+ - $ref: '#/components/parameters/TicketId'
99
+ x-mock-state:
100
+ action: update
101
+ target: tickets
102
+ find_by: id
103
+ set:
104
+ status: resolved
105
+ responses:
106
+ '200':
107
+ description: ok
108
+ content:
109
+ application/json:
110
+ schema:
111
+ $ref: '#/components/schemas/Ticket'
112
+ components:
113
+ parameters:
114
+ UserId:
115
+ name: id
116
+ in: path
117
+ required: true
118
+ schema:
119
+ type: integer
120
+ TicketId:
121
+ name: id
122
+ in: path
123
+ required: true
124
+ schema:
125
+ type: integer
126
+ schemas:
127
+ User:
128
+ type: object
129
+ required: [id, name, email]
130
+ properties:
131
+ id:
132
+ type: integer
133
+ name:
134
+ type: string
135
+ email:
136
+ type: string
137
+ format: email
138
+ UserCreateInput:
139
+ type: object
140
+ required: [name, email]
141
+ properties:
142
+ name:
143
+ type: string
144
+ email:
145
+ type: string
146
+ format: email
147
+ UserPatchInput:
148
+ type: object
149
+ properties:
150
+ name:
151
+ type: string
152
+ email:
153
+ type: string
154
+ format: email
155
+ Ticket:
156
+ type: object
157
+ required: [id, title, status]
158
+ properties:
159
+ id:
160
+ type: integer
161
+ title:
162
+ type: string
163
+ status:
164
+ type: string
165
+ enum: [open, resolved]
166
+ `;
167
+ function normalizeBaseUrl(value) {
168
+ return value.endsWith("/") ? value.slice(0, -1) : value;
169
+ }
170
+ function inferSpecExtension(contentType, content) {
171
+ const normalized = contentType.toLowerCase();
172
+ if (normalized.includes("json")) {
173
+ return "json";
174
+ }
175
+ const trimmed = content.trim();
176
+ return trimmed.startsWith("{") || trimmed.startsWith("[") ? "json" : "yaml";
177
+ }
178
+ async function cacheSpecContent(cwd, specContent, extension, filePrefix) {
179
+ const cacheDir = path.join(cwd, ".cdd");
180
+ await mkdir(cacheDir, { recursive: true });
181
+ const filePath = path.join(cacheDir, `${filePrefix}.${extension}`);
182
+ await writeFile(filePath, specContent, "utf8");
183
+ return filePath;
184
+ }
185
+ async function cacheSpecFromUrl(cwd, specUrl) {
186
+ const response = await fetch(specUrl);
187
+ if (!response.ok) {
188
+ throw new Error(`Failed to download OpenAPI spec from ${specUrl}: ${response.status} ${response.statusText}`);
189
+ }
190
+ const content = await response.text();
191
+ const extension = inferSpecExtension(response.headers.get("content-type") ?? "", content);
192
+ return cacheSpecContent(cwd, content, extension, "openapi.cached");
193
+ }
194
+ async function discoverSpecUrl(backendBaseUrl) {
195
+ const baseUrl = normalizeBaseUrl(backendBaseUrl);
196
+ for (const candidatePath of DISCOVERY_PATHS) {
197
+ const candidateUrl = `${baseUrl}${candidatePath}`;
198
+ try {
199
+ const response = await fetch(candidateUrl, { method: "GET" });
200
+ if (!response.ok) {
201
+ continue;
202
+ }
203
+ const body = await response.text();
204
+ const maybeSpec = body.includes("openapi") || body.includes("swagger");
205
+ if (!maybeSpec) {
206
+ continue;
207
+ }
208
+ return candidateUrl;
209
+ } catch {
210
+ continue;
211
+ }
212
+ }
213
+ throw new Error(
214
+ `Could not discover an OpenAPI spec under ${baseUrl}. Tried: ${DISCOVERY_PATHS.join(", ")}`
215
+ );
216
+ }
217
+ async function writeStarterSpec(cwd, specPath) {
218
+ const resolvedPath = path.isAbsolute(specPath) ? specPath : path.join(cwd, specPath);
219
+ await mkdir(path.dirname(resolvedPath), { recursive: true });
220
+ await writeFile(resolvedPath, STARTER_SPEC, "utf8");
221
+ return resolvedPath;
222
+ }
223
+
224
+ // src/config.ts
225
+ function resolvePathFromCwd(cwd, value) {
226
+ return path2.isAbsolute(value) ? value : path2.join(cwd, value);
227
+ }
10
228
  function createCli() {
11
229
  const program = new Command();
12
230
  program.name("contract-drift-detection").description("Stateful OpenAPI mock server with contract drift detection").version("0.1.0");
13
- program.command("serve").description("Start the mock engine").requiredOption("--spec <path>", "Path to an OpenAPI 3.x file").option("--port <port>", "Port to bind the server", "4010").option("--host <host>", "Host to bind the server", "0.0.0.0").option("--db <path>", "JSON database path", ".mock-db.json").option("--cors-origin <origin>", "CORS origin (default is *)", "*").option("--drift-check <url>", "Forward traffic to a real backend and validate responses").option("--fallback-to-mock", "Fallback to mock responses when proxying fails", false).option("--verbose", "Enable verbose logging", false);
14
- program.command("init").description("Create a starter config for the current workspace").option("--spec <path>", "Default OpenAPI path", "openapi.yaml").option("--db <path>", "Default JSON database path", ".mock-db.json").option("--port <port>", "Default port", "4010").option("--host <host>", "Default host", "0.0.0.0");
231
+ program.command("serve").description("Start the mock engine").option("--spec <path>", "Path to an OpenAPI 3.x file").option("--spec-url <url>", "Remote URL to an OpenAPI 3.x file").option("--discover <backend-url>", "Discover OpenAPI from a backend URL and cache it locally").option("--port <port>", "Port to bind the server", "4010").option("--host <host>", "Host to bind the server", "0.0.0.0").option("--db <path>", "JSON database path", ".mock-db.json").option("--cors-origin <origin>", "CORS origin (default is *)", "*").option("--drift-check <url>", "Forward traffic to a real backend and validate responses").option("--fallback-to-mock", "Fallback to mock responses when proxying fails", false).option("--verbose", "Enable verbose logging", false);
232
+ program.command("init").description("Create a starter config for the current workspace").option("--spec <path>", "Default OpenAPI path", "openapi.yaml").option("--template <name>", "Template to generate (rest-crud | none)", "rest-crud").option("--db <path>", "Default JSON database path", ".mock-db.json").option("--port <port>", "Default port", "4010").option("--host <host>", "Default host", "0.0.0.0");
233
+ program.command("quickstart").description("Generate a starter OpenAPI file and start the server immediately").option("--spec <path>", "Starter OpenAPI path", "openapi.yaml").option("--port <port>", "Port to bind the server", "4010").option("--host <host>", "Host to bind the server", "0.0.0.0").option("--db <path>", "JSON database path", ".mock-db.json").option("--cors-origin <origin>", "CORS origin (default is *)", "*").option("--verbose", "Enable verbose logging", false);
15
234
  return program;
16
235
  }
17
- function toServeConfig(options) {
236
+ async function resolveServeConfig(cwd, options) {
237
+ let resolvedSpecPath;
238
+ if (options.spec) {
239
+ resolvedSpecPath = resolvePathFromCwd(cwd, String(options.spec));
240
+ } else if (options.specUrl) {
241
+ resolvedSpecPath = await cacheSpecFromUrl(cwd, String(options.specUrl));
242
+ } else if (options.discover) {
243
+ const discoveredSpecUrl = await discoverSpecUrl(String(options.discover));
244
+ resolvedSpecPath = await cacheSpecFromUrl(cwd, discoveredSpecUrl);
245
+ }
246
+ if (!resolvedSpecPath) {
247
+ throw new Error("Provide one of --spec, --spec-url, or --discover");
248
+ }
18
249
  return {
19
- specPath: String(options.spec),
250
+ specPath: resolvedSpecPath,
20
251
  port: Number(options.port ?? 4010),
21
252
  host: String(options.host ?? "0.0.0.0"),
22
- dbPath: String(options.db ?? ".mock-db.json"),
253
+ dbPath: resolvePathFromCwd(cwd, String(options.db ?? ".mock-db.json")),
23
254
  corsOrigin: String(options.corsOrigin ?? "*"),
24
255
  driftCheckTarget: options.driftCheck ? String(options.driftCheck) : void 0,
25
256
  fallbackToMockOnProxyError: Boolean(options.fallbackToMock),
@@ -27,9 +258,12 @@ function toServeConfig(options) {
27
258
  };
28
259
  }
29
260
  async function writeStarterConfig(cwd, initConfig) {
30
- const targetPath = path.join(cwd, "contract-drift.config.json");
31
- await mkdir(cwd, { recursive: true });
32
- await writeFile(
261
+ if (initConfig.template === "rest-crud") {
262
+ await writeStarterSpec(cwd, initConfig.spec);
263
+ }
264
+ const targetPath = path2.join(cwd, "contract-drift.config.json");
265
+ await mkdir2(cwd, { recursive: true });
266
+ await writeFile2(
33
267
  targetPath,
34
268
  `${JSON.stringify(
35
269
  {
@@ -48,12 +282,12 @@ async function writeStarterConfig(cwd, initConfig) {
48
282
  }
49
283
 
50
284
  // src/server.ts
51
- import path4 from "path";
285
+ import path5 from "path";
52
286
  import Fastify from "fastify";
53
287
  import cors from "@fastify/cors";
54
288
 
55
289
  // src/utils.ts
56
- import path2 from "path";
290
+ import path3 from "path";
57
291
  function isSchemaObject(schema) {
58
292
  return Boolean(schema) && !("$ref" in schema);
59
293
  }
@@ -73,7 +307,7 @@ function singularize(value) {
73
307
  return value;
74
308
  }
75
309
  function resolveFile(baseDir, filePath) {
76
- return path2.isAbsolute(filePath) ? filePath : path2.join(baseDir, filePath);
310
+ return path3.isAbsolute(filePath) ? filePath : path3.join(baseDir, filePath);
77
311
  }
78
312
  function getOperationId(method, openApiPath, operation) {
79
313
  if (operation.operationId) {
@@ -215,7 +449,7 @@ var DriftDetector = class {
215
449
  addFormats(this.ajv);
216
450
  }
217
451
  ajv = new Ajv({ allErrors: true, strict: false });
218
- validate(method, path5, statusCode, schema, body) {
452
+ validate(method, path6, statusCode, schema, body) {
219
453
  if (!schema || body === void 0 || body === null) {
220
454
  return null;
221
455
  }
@@ -226,9 +460,9 @@ var DriftDetector = class {
226
460
  }
227
461
  const issue = {
228
462
  method: method.toUpperCase(),
229
- path: path5,
463
+ path: path6,
230
464
  statusCode,
231
- message: `Drift detected for ${method.toUpperCase()} ${path5} (${statusCode})`,
465
+ message: `Drift detected for ${method.toUpperCase()} ${path6} (${statusCode})`,
232
466
  errors: formatErrors(validate.errors)
233
467
  };
234
468
  this.logger.error(
@@ -383,8 +617,8 @@ async function loadOpenApiDocument(specPath) {
383
617
  }
384
618
 
385
619
  // src/state-store.ts
386
- import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
387
- import path3 from "path";
620
+ import { mkdir as mkdir3, readFile, writeFile as writeFile3 } from "fs/promises";
621
+ import path4 from "path";
388
622
  function normalizeDatabase(input) {
389
623
  return {
390
624
  collections: input?.collections ?? {},
@@ -397,7 +631,7 @@ var JsonStateStore = class {
397
631
  this.filePath = filePath;
398
632
  }
399
633
  async initialize(seedCollections) {
400
- await mkdir2(path3.dirname(this.filePath), { recursive: true });
634
+ await mkdir3(path4.dirname(this.filePath), { recursive: true });
401
635
  try {
402
636
  const existing = await this.read();
403
637
  let changed = false;
@@ -428,7 +662,7 @@ var JsonStateStore = class {
428
662
  return normalizeDatabase(JSON.parse(raw));
429
663
  }
430
664
  async write(database) {
431
- await writeFile2(this.filePath, `${JSON.stringify(database, null, 2)}
665
+ await writeFile3(this.filePath, `${JSON.stringify(database, null, 2)}
432
666
  `, "utf8");
433
667
  }
434
668
  async withDatabase(updater) {
@@ -440,8 +674,8 @@ var JsonStateStore = class {
440
674
  };
441
675
 
442
676
  // src/proxy.ts
443
- async function proxyRequest(targetBaseUrl, path5, init) {
444
- const response = await fetch(new URL(path5, targetBaseUrl), init);
677
+ async function proxyRequest(targetBaseUrl, path6, init) {
678
+ const response = await fetch(new URL(path6, targetBaseUrl), init);
445
679
  const contentType = readContentType(response.headers);
446
680
  let body;
447
681
  let rawBody;
@@ -679,7 +913,7 @@ async function createServer(config) {
679
913
  });
680
914
  const document = await loadOpenApiDocument(config.specPath);
681
915
  const seedCollections = inferSeedCollections(document);
682
- const dbPath = resolveFile(path4.dirname(config.specPath), config.dbPath);
916
+ const dbPath = resolveFile(path5.dirname(config.specPath), config.dbPath);
683
917
  const store = new JsonStateStore(dbPath);
684
918
  await store.initialize(seedCollections);
685
919
  app.get("/__health", async () => ({ status: "ok" }));
@@ -693,8 +927,9 @@ async function main() {
693
927
  const cli = createCli();
694
928
  const serveCommand = cli.commands.find((command) => command.name() === "serve");
695
929
  const initCommand = cli.commands.find((command) => command.name() === "init");
930
+ const quickstartCommand = cli.commands.find((command) => command.name() === "quickstart");
696
931
  serveCommand?.action(async function() {
697
- const config = toServeConfig(this.opts());
932
+ const config = await resolveServeConfig(process.cwd(), this.opts());
698
933
  const server = await createServer(config);
699
934
  await server.listen({ port: config.port, host: config.host });
700
935
  process.stdout.write(`Mock engine listening on http://${config.host}:${config.port}
@@ -706,9 +941,34 @@ async function main() {
706
941
  spec: String(options.spec),
707
942
  db: String(options.db),
708
943
  host: String(options.host),
709
- port: Number(options.port)
944
+ port: Number(options.port),
945
+ template: options.template === "none" ? "none" : "rest-crud"
710
946
  });
711
947
  process.stdout.write(`Created ${targetPath}
948
+ `);
949
+ });
950
+ quickstartCommand?.action(async function() {
951
+ const options = this.opts();
952
+ const cwd = process.cwd();
953
+ const specPath = String(options.spec ?? "openapi.yaml");
954
+ await writeStarterConfig(cwd, {
955
+ spec: specPath,
956
+ db: String(options.db ?? ".mock-db.json"),
957
+ host: String(options.host ?? "0.0.0.0"),
958
+ port: Number(options.port ?? 4010),
959
+ template: "rest-crud"
960
+ });
961
+ const config = await resolveServeConfig(cwd, {
962
+ spec: specPath,
963
+ db: String(options.db ?? ".mock-db.json"),
964
+ host: String(options.host ?? "0.0.0.0"),
965
+ port: String(options.port ?? 4010),
966
+ corsOrigin: String(options.corsOrigin ?? "*"),
967
+ verbose: Boolean(options.verbose)
968
+ });
969
+ const server = await createServer(config);
970
+ await server.listen({ port: config.port, host: config.host });
971
+ process.stdout.write(`Quickstart ready on http://${config.host}:${config.port} using ${config.specPath}
712
972
  `);
713
973
  });
714
974
  await cli.parseAsync(process.argv);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/server.ts","../src/utils.ts","../src/dsl.ts","../src/drift-detector.ts","../src/route-context.ts","../src/schema-seeder.ts","../src/spec-loader.ts","../src/state-store.ts","../src/proxy.ts"],"sourcesContent":["import process from 'node:process';\nimport { createCli, toServeConfig, writeStarterConfig } from './config.js';\nimport { createServer } from './server.js';\n\nasync function main(): Promise<void> {\n const cli = createCli();\n const serveCommand = cli.commands.find((command) => command.name() === 'serve');\n const initCommand = cli.commands.find((command) => command.name() === 'init');\n\n serveCommand?.action(async function () {\n const config = toServeConfig(this.opts());\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(`Mock engine listening on http://${config.host}:${config.port}\\n`);\n });\n\n initCommand?.action(async function () {\n const options = this.opts();\n const targetPath = await writeStarterConfig(process.cwd(), {\n spec: String(options.spec),\n db: String(options.db),\n host: String(options.host),\n port: Number(options.port),\n });\n process.stdout.write(`Created ${targetPath}\\n`);\n });\n\n await cli.parseAsync(process.argv);\n}\n\nawait main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\\n`);\n process.exitCode = 1;\n});","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport type { InitConfig, MockEngineConfig } from './types.js';\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name('contract-drift-detection')\n .description('Stateful OpenAPI mock server with contract drift detection')\n .version('0.1.0');\n\n program\n .command('serve')\n .description('Start the mock engine')\n .requiredOption('--spec <path>', 'Path to an OpenAPI 3.x file')\n .option('--port <port>', 'Port to bind the server', '4010')\n .option('--host <host>', 'Host to bind the server', '0.0.0.0')\n .option('--db <path>', 'JSON database path', '.mock-db.json')\n .option('--cors-origin <origin>', 'CORS origin (default is *)', '*')\n .option('--drift-check <url>', 'Forward traffic to a real backend and validate responses')\n .option('--fallback-to-mock', 'Fallback to mock responses when proxying fails', false)\n .option('--verbose', 'Enable verbose logging', false);\n\n program\n .command('init')\n .description('Create a starter config for the current workspace')\n .option('--spec <path>', 'Default OpenAPI path', 'openapi.yaml')\n .option('--db <path>', 'Default JSON database path', '.mock-db.json')\n .option('--port <port>', 'Default port', '4010')\n .option('--host <host>', 'Default host', '0.0.0.0');\n\n return program;\n}\n\nexport function toServeConfig(options: Record<string, string | boolean | undefined>): MockEngineConfig {\n return {\n specPath: String(options.spec),\n port: Number(options.port ?? 4010),\n host: String(options.host ?? '0.0.0.0'),\n dbPath: String(options.db ?? '.mock-db.json'),\n corsOrigin: String(options.corsOrigin ?? '*'),\n driftCheckTarget: options.driftCheck ? String(options.driftCheck) : undefined,\n fallbackToMockOnProxyError: Boolean(options.fallbackToMock),\n verbose: Boolean(options.verbose),\n };\n}\n\nexport async function writeStarterConfig(cwd: string, initConfig: InitConfig): Promise<string> {\n const targetPath = path.join(cwd, 'contract-drift.config.json');\n await mkdir(cwd, { recursive: true });\n await writeFile(\n targetPath,\n `${JSON.stringify(\n {\n spec: initConfig.spec,\n db: initConfig.db,\n host: initConfig.host,\n port: initConfig.port,\n },\n null,\n 2,\n )}\\n`,\n 'utf8',\n );\n return targetPath;\n}","import path from 'node:path';\nimport Fastify, { type FastifyInstance, type FastifyRequest } from 'fastify';\nimport cors from '@fastify/cors';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { applyDslMutation } from './dsl.js';\nimport { DriftDetector } from './drift-detector.js';\nimport { buildRouteContexts } from './route-context.js';\nimport { inferSeedCollections, seedFromSchema } from './schema-seeder.js';\nimport { loadOpenApiDocument } from './spec-loader.js';\nimport { JsonStateStore } from './state-store.js';\nimport type { MockEngineConfig, MockOperationObject, RouteContext } from './types.js';\nimport { proxyRequest } from './proxy.js';\nimport { deepMerge, resolveFile } from './utils.js';\n\nfunction sendResponse(\n reply: { code: (statusCode: number) => typeof reply; send: (body?: unknown) => unknown },\n statusCode: number,\n body: unknown,\n) {\n if (statusCode === 204) {\n return reply.code(204).send();\n }\n\n return reply.code(statusCode).send(body);\n}\n\nfunction defaultStatusCode(route: RouteContext): number {\n if (route.method === 'post') {\n return 201;\n }\n\n if (route.method === 'delete') {\n return 204;\n }\n\n return 200;\n}\n\nfunction getRequestBody(request: FastifyRequest): Record<string, unknown> {\n if (!request.body || typeof request.body !== 'object' || Array.isArray(request.body)) {\n return {};\n }\n\n return request.body as Record<string, unknown>;\n}\n\nfunction getCollection(database: { collections: Record<string, unknown[]>; counters: Record<string, number> }, route: RouteContext): Record<string, unknown>[] {\n if (!database.collections[route.resourceName]) {\n database.collections[route.resourceName] = [];\n database.counters[route.resourceName] = 0;\n }\n\n return database.collections[route.resourceName] as Record<string, unknown>[];\n}\n\nfunction getCollectionByName(\n database: { collections: Record<string, unknown[]>; counters: Record<string, number> },\n collectionName: string,\n): Record<string, unknown>[] {\n if (!database.collections[collectionName]) {\n database.collections[collectionName] = [];\n database.counters[collectionName] = 0;\n }\n\n return database.collections[collectionName] as Record<string, unknown>[];\n}\n\nfunction computeIdKey(route: RouteContext): string {\n return route.pathParamName ?? 'id';\n}\n\nfunction normalizeComparable(value: unknown): string | number | boolean | undefined {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n\n return undefined;\n}\n\nfunction nextId(database: { counters: Record<string, number> }, route: RouteContext): number {\n const current = database.counters[route.resourceName] ?? 0;\n const next = current + 1;\n database.counters[route.resourceName] = next;\n return next;\n}\n\nfunction allocateUniqueId(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n idKey: string,\n): number {\n let candidate = nextId(database, route);\n const hasCollision = (value: number) =>\n collection.some((item) => normalizeComparable(item[idKey]) === String(value));\n\n while (hasCollision(candidate)) {\n candidate = nextId(database, route);\n }\n\n return candidate;\n}\n\nfunction resolveEntity(route: RouteContext, collection: Record<string, unknown>[], request: FastifyRequest): Record<string, unknown> | undefined {\n const idKey = computeIdKey(route);\n const rawId = normalizeComparable(\n (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id,\n );\n return collection.find((item) => normalizeComparable(item[idKey]) === rawId);\n}\n\nfunction materializeMockBody(route: RouteContext, request: FastifyRequest): Record<string, unknown> {\n const requestBody = getRequestBody(request);\n const seeded = route.requestBodySchema ? seedFromSchema(route.requestBodySchema) : {};\n\n return deepMerge(\n (seeded && typeof seeded === 'object' && !Array.isArray(seeded) ? seeded : {}) as Record<string, unknown>,\n requestBody,\n );\n}\n\nfunction getNotFoundResponse(route: RouteContext): { statusCode: number; body: { message: string } } {\n return {\n statusCode: 404,\n body: { message: `${route.resourceName} not found` },\n };\n}\n\nfunction handleReadRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n if (route.isCollection) {\n return { statusCode: 200, body: collection };\n }\n\n const entity = resolveEntity(route, collection, request);\n return entity ? { statusCode: 200, body: entity } : getNotFoundResponse(route);\n}\n\nfunction handleCreateRoute(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body: Record<string, unknown> } {\n const entity = materializeMockBody(route, request);\n const idKey = computeIdKey(route);\n if (entity[idKey] === undefined) {\n entity[idKey] = allocateUniqueId(database, route, collection, idKey);\n }\n collection.push(entity);\n return { statusCode: route.successResponse?.statusCode ?? 201, body: entity };\n}\n\nfunction handleUpdateRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const current = resolveEntity(route, collection, request);\n if (!current) {\n return getNotFoundResponse(route);\n }\n\n const merged = route.method === 'put'\n ? materializeMockBody(route, request)\n : deepMerge(current, getRequestBody(request));\n\n Object.assign(current, merged);\n return { statusCode: 200, body: current };\n}\n\nfunction handleDeleteRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const entity = resolveEntity(route, collection, request);\n if (!entity) {\n return getNotFoundResponse(route);\n }\n\n const index = collection.indexOf(entity);\n collection.splice(index, 1);\n return { statusCode: 204 };\n}\n\nasync function handleMockRoute(\n store: JsonStateStore,\n route: RouteContext,\n request: FastifyRequest,\n): Promise<{ statusCode: number; body?: unknown }> {\n return store.withDatabase(async (database) => {\n const collection = getCollection(database, route);\n const extension = (route.operation as MockOperationObject)['x-mock-state'];\n\n if (extension) {\n const targetCollection = extension.target\n ? getCollectionByName(database, extension.target)\n : collection;\n const response = applyDslMutation(extension, request, targetCollection, computeIdKey(route));\n if (extension.response === 'none') {\n return { statusCode: 204 };\n }\n\n if (extension.response === 'collection') {\n return { statusCode: route.successResponse?.statusCode ?? 200, body: targetCollection };\n }\n\n return { statusCode: route.successResponse?.statusCode ?? defaultStatusCode(route), body: response };\n }\n\n switch (route.method) {\n case 'get': {\n return handleReadRoute(route, collection, request);\n }\n case 'post': {\n return handleCreateRoute(database, route, collection, request);\n }\n case 'put':\n case 'patch': {\n return handleUpdateRoute(route, collection, request);\n }\n case 'delete': {\n return handleDeleteRoute(route, collection, request);\n }\n }\n });\n}\n\nasync function handleProxyRoute(\n config: MockEngineConfig,\n route: RouteContext,\n detector: DriftDetector,\n request: FastifyRequest,\n): Promise<{ statusCode: number; headers: Record<string, string>; body?: unknown }> {\n const rawBody = request.body ? JSON.stringify(request.body) : undefined;\n const targetBaseUrl = config.driftCheckTarget;\n if (!targetBaseUrl) {\n throw new Error('Proxy target is not configured');\n }\n\n const result = await proxyRequest(targetBaseUrl, request.url, {\n method: request.method,\n headers: {\n 'content-type': request.headers['content-type'] ?? 'application/json',\n },\n body: rawBody,\n });\n\n detector.validate(\n route.method,\n route.path,\n result.statusCode,\n route.successResponse?.schema,\n result.body,\n );\n\n return {\n statusCode: result.statusCode,\n headers: Object.fromEntries(result.headers.entries()),\n body: result.body,\n };\n}\n\nasync function registerRoutes(\n app: FastifyInstance,\n document: OpenAPIV3.Document,\n config: MockEngineConfig,\n store: JsonStateStore,\n): Promise<void> {\n const routes = buildRouteContexts(document);\n const detector = new DriftDetector(app.log);\n\n for (const route of routes) {\n app.route({\n method: route.method.toUpperCase(),\n url: route.fastifyPath,\n handler: async (request, reply) => {\n if (config.driftCheckTarget) {\n try {\n const proxied = await handleProxyRoute(config, route, detector, request);\n for (const [headerName, headerValue] of Object.entries(proxied.headers)) {\n if (headerName.toLowerCase() === 'content-length') {\n continue;\n }\n reply.header(headerName, headerValue);\n }\n return reply.code(proxied.statusCode).send(proxied.body);\n } catch (error) {\n app.log.error({ error }, `Proxy execution failed for ${route.method.toUpperCase()} ${route.path}`);\n if (!config.fallbackToMockOnProxyError) {\n throw error;\n }\n }\n }\n\n const mockResponse = await handleMockRoute(store, route, request);\n return sendResponse(reply, mockResponse.statusCode, mockResponse.body);\n },\n });\n }\n}\n\nexport async function createServer(config: MockEngineConfig): Promise<FastifyInstance> {\n const app = Fastify({\n logger: {\n level: config.verbose ? 'debug' : 'error',\n },\n });\n\n await app.register(cors, {\n origin: config.corsOrigin === '*' ? true : config.corsOrigin,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['content-type', 'authorization'],\n credentials: false,\n });\n\n const document = await loadOpenApiDocument(config.specPath);\n const seedCollections = inferSeedCollections(document);\n const dbPath = resolveFile(path.dirname(config.specPath), config.dbPath);\n const store = new JsonStateStore(dbPath);\n await store.initialize(seedCollections);\n\n app.get('/__health', async () => ({ status: 'ok' }));\n app.get('/__spec', async () => document);\n\n await registerRoutes(app, document, config, store);\n return app;\n}","import path from 'node:path';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport function isSchemaObject(\n schema?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,\n): schema is OpenAPIV3.SchemaObject {\n return Boolean(schema) && !('$ref' in (schema as OpenAPIV3.ReferenceObject));\n}\n\nexport function normalizeCollectionName(raw: string): string {\n return raw\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .at(-1)\n ?.replace(/[^a-zA-Z0-9]+/g, '_')\n .toLowerCase() ?? 'items';\n}\n\nexport function toFastifyPath(openApiPath: string): string {\n return openApiPath.replace(/\\{([^}]+)\\}/g, ':$1');\n}\n\nexport function singularize(value: string): string {\n if (value.endsWith('ies')) {\n return `${value.slice(0, -3)}y`;\n }\n\n if (value.endsWith('s')) {\n return value.slice(0, -1);\n }\n\n return value;\n}\n\nexport function resolveFile(baseDir: string, filePath: string): string {\n return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);\n}\n\nexport function getOperationId(\n method: string,\n openApiPath: string,\n operation: OpenAPIV3.OperationObject,\n): string {\n if (operation.operationId) {\n return operation.operationId;\n }\n\n const sanitizedPath = openApiPath\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .join('_');\n\n return `${method}_${sanitizedPath || 'root'}`;\n}\n\nexport function inferPathParamName(openApiPath: string): string | undefined {\n const match = openApiPath.match(/\\{([^}]+)\\}/);\n return match?.[1];\n}\n\nexport function deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(\n base: T,\n patch: Record<string, unknown>,\n): T {\n const output: Record<string, unknown> = { ...base };\n\n for (const [key, value] of Object.entries(patch)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n output[key] &&\n typeof output[key] === 'object' &&\n !Array.isArray(output[key])\n ) {\n output[key] = deepMerge(\n output[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n output[key] = value;\n }\n }\n\n return output as T;\n}\n\nexport function readContentType(headers: Headers): string {\n return headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n}\n\nexport function isJsonLikeContentType(contentType: string): boolean {\n return contentType === 'application/json' || contentType.endsWith('+json');\n}\n\nexport function toArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) {\n return [];\n }\n\n return Array.isArray(value) ? value : [value];\n}","import type { FastifyRequest } from 'fastify';\nimport type { MockStateExtension } from './types.js';\nimport { deepMerge } from './utils.js';\n\nfunction resolvePathExpression(source: unknown, expression: string): unknown {\n return expression\n .split('.')\n .reduce<unknown>((value, segment) => (value && typeof value === 'object' ? (value as Record<string, unknown>)[segment] : undefined), source);\n}\n\nfunction evaluateTemplate(value: unknown, request: FastifyRequest): unknown {\n if (typeof value === 'string') {\n const match = value.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (!match) {\n return value;\n }\n\n return resolvePathExpression(\n {\n params: request.params,\n query: request.query,\n body: request.body,\n headers: request.headers,\n },\n match[1],\n );\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => evaluateTemplate(entry, request));\n }\n\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [key, evaluateTemplate(entry, request)]),\n );\n }\n\n return value;\n}\n\nexport function applyDslMutation(\n extension: MockStateExtension,\n request: FastifyRequest,\n collection: Record<string, unknown>[],\n defaultIdKey: string,\n): Record<string, unknown> | Record<string, unknown>[] | null {\n const idKey = extension.find_by ?? defaultIdKey;\n const targetId = (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id;\n const evaluatedAssign = (evaluateTemplate(extension.assign ?? {}, request) ?? {}) as Record<string, unknown>;\n const evaluatedSet = (evaluateTemplate(extension.set ?? {}, request) ?? {}) as Record<string, unknown>;\n\n switch (extension.action) {\n case 'create': {\n const candidate = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(candidate);\n return candidate;\n }\n case 'append': {\n const item = {\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(item);\n return collection;\n }\n case 'replace': {\n const nextValue = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n collection[index] = nextValue;\n }\n return nextValue;\n }\n case 'delete': {\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n const [removed] = collection.splice(index, 1);\n return removed;\n }\n return null;\n }\n case 'update':\n default: {\n const entity = collection.find((entry) => String(entry[idKey]) === String(targetId));\n if (!entity) {\n return null;\n }\n const merged = deepMerge(entity, {\n ...evaluatedAssign,\n ...evaluatedSet,\n });\n Object.assign(entity, merged);\n return entity;\n }\n }\n}","import Ajv, { type ErrorObject } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport pc from 'picocolors';\nimport type { FastifyBaseLogger } from 'fastify';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport type { DriftIssue } from './types.js';\n\nfunction formatErrors(errors: ErrorObject[] | null | undefined): string[] {\n return (errors ?? []).map((error) => {\n const location = error.instancePath || '/';\n return `${location} ${error.message ?? 'failed validation'}`.trim();\n });\n}\n\nexport class DriftDetector {\n private readonly ajv = new Ajv({ allErrors: true, strict: false });\n\n constructor(private readonly logger: FastifyBaseLogger) {\n addFormats(this.ajv);\n }\n\n validate(\n method: string,\n path: string,\n statusCode: number,\n schema: OpenAPIV3.SchemaObject | undefined,\n body: unknown,\n ): DriftIssue | null {\n if (!schema || body === undefined || body === null) {\n return null;\n }\n\n const validate = this.ajv.compile(schema);\n const valid = validate(body);\n\n if (valid) {\n return null;\n }\n\n const issue: DriftIssue = {\n method: method.toUpperCase(),\n path,\n statusCode,\n message: `Drift detected for ${method.toUpperCase()} ${path} (${statusCode})`,\n errors: formatErrors(validate.errors),\n };\n\n this.logger.error(\n `${pc.red('DRIFT DETECTED')} ${issue.method} ${issue.path} -> ${issue.errors.join('; ')}`,\n );\n\n return issue;\n }\n}","import type { OpenAPIV3 } from 'openapi-types';\nimport type { HttpMethod, RouteContext } from './types.js';\nimport {\n getOperationId,\n inferPathParamName,\n isSchemaObject,\n normalizeCollectionName,\n toFastifyPath,\n} from './utils.js';\n\nconst SUPPORTED_METHODS: HttpMethod[] = ['get', 'post', 'put', 'patch', 'delete'];\n\nfunction getRequestBodySchema(\n operation: OpenAPIV3.OperationObject,\n): OpenAPIV3.SchemaObject | undefined {\n const content = operation.requestBody && !('$ref' in operation.requestBody)\n ? operation.requestBody.content?.['application/json']\n : undefined;\n\n return isSchemaObject(content?.schema) ? content.schema : undefined;\n}\n\nfunction getSuccessResponse(\n operation: OpenAPIV3.OperationObject,\n): RouteContext['successResponse'] {\n const preferredCodes = ['200', '201', '202', '204'];\n\n for (const code of preferredCodes) {\n const response = operation.responses?.[code];\n if (!response || '$ref' in response) {\n continue;\n }\n\n const schema = response.content?.['application/json']?.schema;\n return {\n statusCode: Number(code),\n schema: isSchemaObject(schema) ? schema : undefined,\n };\n }\n\n return undefined;\n}\n\nexport function buildRouteContexts(document: OpenAPIV3.Document): RouteContext[] {\n const routes: RouteContext[] = [];\n\n for (const [openApiPath, pathItem] of Object.entries(document.paths ?? {})) {\n if (!pathItem || '$ref' in pathItem) {\n continue;\n }\n\n for (const method of SUPPORTED_METHODS) {\n const operation = pathItem[method];\n if (!operation || '$ref' in operation) {\n continue;\n }\n\n const pathParamName = inferPathParamName(openApiPath);\n const resourceName = normalizeCollectionName(\n pathParamName ? openApiPath.replace(/\\/\\{[^}]+\\}$/, '') : openApiPath,\n );\n const route: RouteContext = {\n method,\n path: openApiPath,\n fastifyPath: toFastifyPath(openApiPath),\n operation,\n operationId: getOperationId(method, openApiPath, operation),\n resourceName,\n isCollection: !pathParamName,\n pathParamName,\n requestBodySchema: getRequestBodySchema(operation),\n successResponse: getSuccessResponse(operation),\n };\n routes.push(route);\n }\n }\n\n return routes;\n}","import { faker } from '@faker-js/faker';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { isSchemaObject, singularize } from './utils.js';\n\nfunction seedPrimitive(schema: OpenAPIV3.SchemaObject): unknown {\n if (schema.enum?.length) {\n return schema.enum[0];\n }\n\n switch (schema.type) {\n case 'string': {\n if (schema.format === 'email') {\n return faker.internet.email();\n }\n\n if (schema.format === 'date-time') {\n return faker.date.recent().toISOString();\n }\n\n if (schema.format === 'uuid') {\n return faker.string.uuid();\n }\n\n return faker.lorem.words(2);\n }\n case 'integer':\n return faker.number.int({ min: 1, max: 1000 });\n case 'number':\n return faker.number.float({ min: 1, max: 1000, fractionDigits: 2 });\n case 'boolean':\n return faker.datatype.boolean();\n default:\n return null;\n }\n}\n\nexport function seedFromSchema(\n schema?: OpenAPIV3.SchemaObject,\n depth = 0,\n): unknown {\n if (!schema || depth > 4) {\n return null;\n }\n\n if (schema.oneOf?.length && isSchemaObject(schema.oneOf[0])) {\n return seedFromSchema(schema.oneOf[0], depth + 1);\n }\n\n if (schema.anyOf?.length && isSchemaObject(schema.anyOf[0])) {\n return seedFromSchema(schema.anyOf[0], depth + 1);\n }\n\n if (schema.allOf?.length) {\n return schema.allOf.reduce<Record<string, unknown>>((accumulator, item) => {\n if (!isSchemaObject(item)) {\n return accumulator;\n }\n\n const seeded = seedFromSchema(item, depth + 1);\n if (seeded && typeof seeded === 'object' && !Array.isArray(seeded)) {\n Object.assign(accumulator, seeded);\n }\n\n return accumulator;\n }, {});\n }\n\n if (schema.type === 'array') {\n const item = isSchemaObject(schema.items) ? schema.items : undefined;\n return [seedFromSchema(item, depth + 1), seedFromSchema(item, depth + 1)].filter(\n (value) => value !== null,\n );\n }\n\n if (schema.type === 'object' || schema.properties) {\n const entries = Object.entries(schema.properties ?? {}).map(([key, value]) => {\n if (!isSchemaObject(value)) {\n return [key, null] as const;\n }\n\n return [key, seedFromSchema(value, depth + 1)] as const;\n });\n\n return Object.fromEntries(entries);\n }\n\n return seedPrimitive(schema);\n}\n\nexport function inferSeedCollections(document: OpenAPIV3.Document): Record<string, unknown[]> {\n const collections: Record<string, unknown[]> = {};\n\n for (const [schemaName, schemaValue] of Object.entries(document.components?.schemas ?? {})) {\n if (!isSchemaObject(schemaValue) || schemaValue.type !== 'object') {\n continue;\n }\n\n const collectionName = `${singularize(schemaName).toLowerCase()}s`;\n collections[collectionName] = Array.from({ length: 3 }, () => seedFromSchema(schemaValue)) as unknown[];\n }\n\n return collections;\n}","import SwaggerParser from '@apidevtools/swagger-parser';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport async function loadOpenApiDocument(specPath: string): Promise<OpenAPIV3.Document> {\n const document = (await SwaggerParser.dereference(specPath)) as OpenAPIV3.Document;\n\n if (!document.openapi?.startsWith('3.')) {\n throw new Error(`Only OpenAPI 3.x specs are supported. Received: ${document.openapi}`);\n }\n\n return document;\n}","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { MockDatabase } from './types.js';\nimport { deepClone } from './utils.js';\n\nfunction normalizeDatabase(input: Partial<MockDatabase> | undefined): MockDatabase {\n return {\n collections: input?.collections ?? {},\n counters: input?.counters ?? {},\n };\n}\n\nexport class JsonStateStore {\n readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n async initialize(seedCollections: Record<string, unknown[]>): Promise<MockDatabase> {\n await mkdir(path.dirname(this.filePath), { recursive: true });\n\n try {\n const existing = await this.read();\n let changed = false;\n\n for (const [collectionName, items] of Object.entries(seedCollections)) {\n if (!existing.collections[collectionName]) {\n existing.collections[collectionName] = deepClone(items);\n existing.counters[collectionName] = items.length;\n changed = true;\n }\n }\n\n if (changed) {\n await this.write(existing);\n }\n\n return existing;\n } catch {\n const initial: MockDatabase = {\n collections: deepClone(seedCollections),\n counters: Object.fromEntries(\n Object.entries(seedCollections).map(([name, items]) => [name, items.length]),\n ),\n };\n await this.write(initial);\n return initial;\n }\n }\n\n async read(): Promise<MockDatabase> {\n const raw = await readFile(this.filePath, 'utf8');\n return normalizeDatabase(JSON.parse(raw) as Partial<MockDatabase>);\n }\n\n async write(database: MockDatabase): Promise<void> {\n await writeFile(this.filePath, `${JSON.stringify(database, null, 2)}\\n`, 'utf8');\n }\n\n async withDatabase<T>(\n updater: (database: MockDatabase) => Promise<T> | T,\n ): Promise<T> {\n const database = await this.read();\n const result = await updater(database);\n await this.write(database);\n return result;\n }\n}","import { readContentType, isJsonLikeContentType } from './utils.js';\nimport type { ProxyExecutionResult } from './types.js';\n\nexport async function proxyRequest(\n targetBaseUrl: string,\n path: string,\n init: RequestInit,\n): Promise<ProxyExecutionResult> {\n const response = await fetch(new URL(path, targetBaseUrl), init);\n const contentType = readContentType(response.headers);\n\n let body: unknown;\n let rawBody: string | undefined;\n\n if (response.status !== 204) {\n rawBody = await response.text();\n if (rawBody && isJsonLikeContentType(contentType)) {\n body = JSON.parse(rawBody);\n } else if (rawBody) {\n body = rawBody;\n }\n }\n\n return {\n ok: response.ok,\n statusCode: response.status,\n headers: response.headers,\n body,\n rawBody,\n };\n}"],"mappings":"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","names":["path","path","path","mkdir","writeFile","path","mkdir","path","writeFile","path","path"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/config.ts","../src/onboarding.ts","../src/server.ts","../src/utils.ts","../src/dsl.ts","../src/drift-detector.ts","../src/route-context.ts","../src/schema-seeder.ts","../src/spec-loader.ts","../src/state-store.ts","../src/proxy.ts"],"sourcesContent":["import process from 'node:process';\nimport { createCli, resolveServeConfig, writeStarterConfig } from './config.js';\nimport { createServer } from './server.js';\n\nasync function main(): Promise<void> {\n const cli = createCli();\n const serveCommand = cli.commands.find((command) => command.name() === 'serve');\n const initCommand = cli.commands.find((command) => command.name() === 'init');\n const quickstartCommand = cli.commands.find((command) => command.name() === 'quickstart');\n\n serveCommand?.action(async function () {\n const config = await resolveServeConfig(process.cwd(), this.opts());\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(`Mock engine listening on http://${config.host}:${config.port}\\n`);\n });\n\n initCommand?.action(async function () {\n const options = this.opts();\n const targetPath = await writeStarterConfig(process.cwd(), {\n spec: String(options.spec),\n db: String(options.db),\n host: String(options.host),\n port: Number(options.port),\n template: options.template === 'none' ? 'none' : 'rest-crud',\n });\n process.stdout.write(`Created ${targetPath}\\n`);\n });\n\n quickstartCommand?.action(async function () {\n const options = this.opts();\n const cwd = process.cwd();\n const specPath = String(options.spec ?? 'openapi.yaml');\n\n await writeStarterConfig(cwd, {\n spec: specPath,\n db: String(options.db ?? '.mock-db.json'),\n host: String(options.host ?? '0.0.0.0'),\n port: Number(options.port ?? 4010),\n template: 'rest-crud',\n });\n\n const config = await resolveServeConfig(cwd, {\n spec: specPath,\n db: String(options.db ?? '.mock-db.json'),\n host: String(options.host ?? '0.0.0.0'),\n port: String(options.port ?? 4010),\n corsOrigin: String(options.corsOrigin ?? '*'),\n verbose: Boolean(options.verbose),\n });\n\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(`Quickstart ready on http://${config.host}:${config.port} using ${config.specPath}\\n`);\n });\n\n await cli.parseAsync(process.argv);\n}\n\nawait main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\\n`);\n process.exitCode = 1;\n});","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport type { InitConfig, MockEngineConfig } from './types.js';\nimport { cacheSpecFromUrl, discoverSpecUrl, writeStarterSpec } from './onboarding.js';\n\nfunction resolvePathFromCwd(cwd: string, value: string): string {\n return path.isAbsolute(value) ? value : path.join(cwd, value);\n}\n\nexport function createCli(): Command {\n const program = new Command();\n\n program\n .name('contract-drift-detection')\n .description('Stateful OpenAPI mock server with contract drift detection')\n .version('0.1.0');\n\n program\n .command('serve')\n .description('Start the mock engine')\n .option('--spec <path>', 'Path to an OpenAPI 3.x file')\n .option('--spec-url <url>', 'Remote URL to an OpenAPI 3.x file')\n .option('--discover <backend-url>', 'Discover OpenAPI from a backend URL and cache it locally')\n .option('--port <port>', 'Port to bind the server', '4010')\n .option('--host <host>', 'Host to bind the server', '0.0.0.0')\n .option('--db <path>', 'JSON database path', '.mock-db.json')\n .option('--cors-origin <origin>', 'CORS origin (default is *)', '*')\n .option('--drift-check <url>', 'Forward traffic to a real backend and validate responses')\n .option('--fallback-to-mock', 'Fallback to mock responses when proxying fails', false)\n .option('--verbose', 'Enable verbose logging', false);\n\n program\n .command('init')\n .description('Create a starter config for the current workspace')\n .option('--spec <path>', 'Default OpenAPI path', 'openapi.yaml')\n .option('--template <name>', 'Template to generate (rest-crud | none)', 'rest-crud')\n .option('--db <path>', 'Default JSON database path', '.mock-db.json')\n .option('--port <port>', 'Default port', '4010')\n .option('--host <host>', 'Default host', '0.0.0.0');\n\n program\n .command('quickstart')\n .description('Generate a starter OpenAPI file and start the server immediately')\n .option('--spec <path>', 'Starter OpenAPI path', 'openapi.yaml')\n .option('--port <port>', 'Port to bind the server', '4010')\n .option('--host <host>', 'Host to bind the server', '0.0.0.0')\n .option('--db <path>', 'JSON database path', '.mock-db.json')\n .option('--cors-origin <origin>', 'CORS origin (default is *)', '*')\n .option('--verbose', 'Enable verbose logging', false);\n\n return program;\n}\n\nexport async function resolveServeConfig(\n cwd: string,\n options: Record<string, string | boolean | undefined>,\n): Promise<MockEngineConfig> {\n let resolvedSpecPath: string | undefined;\n\n if (options.spec) {\n resolvedSpecPath = resolvePathFromCwd(cwd, String(options.spec));\n } else if (options.specUrl) {\n resolvedSpecPath = await cacheSpecFromUrl(cwd, String(options.specUrl));\n } else if (options.discover) {\n const discoveredSpecUrl = await discoverSpecUrl(String(options.discover));\n resolvedSpecPath = await cacheSpecFromUrl(cwd, discoveredSpecUrl);\n }\n\n if (!resolvedSpecPath) {\n throw new Error('Provide one of --spec, --spec-url, or --discover');\n }\n\n return {\n specPath: resolvedSpecPath,\n port: Number(options.port ?? 4010),\n host: String(options.host ?? '0.0.0.0'),\n dbPath: resolvePathFromCwd(cwd, String(options.db ?? '.mock-db.json')),\n corsOrigin: String(options.corsOrigin ?? '*'),\n driftCheckTarget: options.driftCheck ? String(options.driftCheck) : undefined,\n fallbackToMockOnProxyError: Boolean(options.fallbackToMock),\n verbose: Boolean(options.verbose),\n };\n}\n\nexport async function writeStarterConfig(cwd: string, initConfig: InitConfig): Promise<string> {\n if (initConfig.template === 'rest-crud') {\n await writeStarterSpec(cwd, initConfig.spec);\n }\n\n const targetPath = path.join(cwd, 'contract-drift.config.json');\n await mkdir(cwd, { recursive: true });\n await writeFile(\n targetPath,\n `${JSON.stringify(\n {\n spec: initConfig.spec,\n db: initConfig.db,\n host: initConfig.host,\n port: initConfig.port,\n },\n null,\n 2,\n )}\\n`,\n 'utf8',\n );\n return targetPath;\n}","import { mkdir, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nconst DISCOVERY_PATHS = [\n '/openapi.json',\n '/openapi.yaml',\n '/swagger.json',\n '/v3/api-docs',\n '/api-docs-json',\n] as const;\n\nconst STARTER_SPEC = `openapi: 3.0.3\ninfo:\n title: Contract Drift Detection Starter API\n version: 1.0.0\npaths:\n /users:\n get:\n responses:\n '200':\n description: ok\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: '#/components/schemas/User'\n post:\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UserCreateInput'\n responses:\n '201':\n description: created\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/User'\n /users/{id}:\n get:\n parameters:\n - $ref: '#/components/parameters/UserId'\n responses:\n '200':\n description: ok\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/User'\n patch:\n parameters:\n - $ref: '#/components/parameters/UserId'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UserPatchInput'\n responses:\n '200':\n description: updated\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/User'\n delete:\n parameters:\n - $ref: '#/components/parameters/UserId'\n responses:\n '204':\n description: deleted\n /tickets:\n get:\n responses:\n '200':\n description: ok\n content:\n application/json:\n schema:\n type: array\n items:\n $ref: '#/components/schemas/Ticket'\n /tickets/{id}/resolve:\n post:\n parameters:\n - $ref: '#/components/parameters/TicketId'\n x-mock-state:\n action: update\n target: tickets\n find_by: id\n set:\n status: resolved\n responses:\n '200':\n description: ok\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/Ticket'\ncomponents:\n parameters:\n UserId:\n name: id\n in: path\n required: true\n schema:\n type: integer\n TicketId:\n name: id\n in: path\n required: true\n schema:\n type: integer\n schemas:\n User:\n type: object\n required: [id, name, email]\n properties:\n id:\n type: integer\n name:\n type: string\n email:\n type: string\n format: email\n UserCreateInput:\n type: object\n required: [name, email]\n properties:\n name:\n type: string\n email:\n type: string\n format: email\n UserPatchInput:\n type: object\n properties:\n name:\n type: string\n email:\n type: string\n format: email\n Ticket:\n type: object\n required: [id, title, status]\n properties:\n id:\n type: integer\n title:\n type: string\n status:\n type: string\n enum: [open, resolved]\n`;\n\nfunction normalizeBaseUrl(value: string): string {\n return value.endsWith('/') ? value.slice(0, -1) : value;\n}\n\nfunction inferSpecExtension(contentType: string, content: string): 'json' | 'yaml' {\n const normalized = contentType.toLowerCase();\n if (normalized.includes('json')) {\n return 'json';\n }\n\n const trimmed = content.trim();\n return trimmed.startsWith('{') || trimmed.startsWith('[') ? 'json' : 'yaml';\n}\n\nexport async function cacheSpecContent(\n cwd: string,\n specContent: string,\n extension: 'json' | 'yaml',\n filePrefix: string,\n): Promise<string> {\n const cacheDir = path.join(cwd, '.cdd');\n await mkdir(cacheDir, { recursive: true });\n const filePath = path.join(cacheDir, `${filePrefix}.${extension}`);\n await writeFile(filePath, specContent, 'utf8');\n return filePath;\n}\n\nexport async function cacheSpecFromUrl(cwd: string, specUrl: string): Promise<string> {\n const response = await fetch(specUrl);\n if (!response.ok) {\n throw new Error(`Failed to download OpenAPI spec from ${specUrl}: ${response.status} ${response.statusText}`);\n }\n\n const content = await response.text();\n const extension = inferSpecExtension(response.headers.get('content-type') ?? '', content);\n return cacheSpecContent(cwd, content, extension, 'openapi.cached');\n}\n\nexport async function discoverSpecUrl(backendBaseUrl: string): Promise<string> {\n const baseUrl = normalizeBaseUrl(backendBaseUrl);\n\n for (const candidatePath of DISCOVERY_PATHS) {\n const candidateUrl = `${baseUrl}${candidatePath}`;\n try {\n const response = await fetch(candidateUrl, { method: 'GET' });\n if (!response.ok) {\n continue;\n }\n\n const body = await response.text();\n const maybeSpec = body.includes('openapi') || body.includes('swagger');\n if (!maybeSpec) {\n continue;\n }\n\n return candidateUrl;\n } catch {\n continue;\n }\n }\n\n throw new Error(\n `Could not discover an OpenAPI spec under ${baseUrl}. Tried: ${DISCOVERY_PATHS.join(', ')}`,\n );\n}\n\nexport async function writeStarterSpec(cwd: string, specPath: string): Promise<string> {\n const resolvedPath = path.isAbsolute(specPath) ? specPath : path.join(cwd, specPath);\n await mkdir(path.dirname(resolvedPath), { recursive: true });\n await writeFile(resolvedPath, STARTER_SPEC, 'utf8');\n return resolvedPath;\n}","import path from 'node:path';\nimport Fastify, { type FastifyInstance, type FastifyRequest } from 'fastify';\nimport cors from '@fastify/cors';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { applyDslMutation } from './dsl.js';\nimport { DriftDetector } from './drift-detector.js';\nimport { buildRouteContexts } from './route-context.js';\nimport { inferSeedCollections, seedFromSchema } from './schema-seeder.js';\nimport { loadOpenApiDocument } from './spec-loader.js';\nimport { JsonStateStore } from './state-store.js';\nimport type { MockEngineConfig, MockOperationObject, RouteContext } from './types.js';\nimport { proxyRequest } from './proxy.js';\nimport { deepMerge, resolveFile } from './utils.js';\n\nfunction sendResponse(\n reply: { code: (statusCode: number) => typeof reply; send: (body?: unknown) => unknown },\n statusCode: number,\n body: unknown,\n) {\n if (statusCode === 204) {\n return reply.code(204).send();\n }\n\n return reply.code(statusCode).send(body);\n}\n\nfunction defaultStatusCode(route: RouteContext): number {\n if (route.method === 'post') {\n return 201;\n }\n\n if (route.method === 'delete') {\n return 204;\n }\n\n return 200;\n}\n\nfunction getRequestBody(request: FastifyRequest): Record<string, unknown> {\n if (!request.body || typeof request.body !== 'object' || Array.isArray(request.body)) {\n return {};\n }\n\n return request.body as Record<string, unknown>;\n}\n\nfunction getCollection(database: { collections: Record<string, unknown[]>; counters: Record<string, number> }, route: RouteContext): Record<string, unknown>[] {\n if (!database.collections[route.resourceName]) {\n database.collections[route.resourceName] = [];\n database.counters[route.resourceName] = 0;\n }\n\n return database.collections[route.resourceName] as Record<string, unknown>[];\n}\n\nfunction getCollectionByName(\n database: { collections: Record<string, unknown[]>; counters: Record<string, number> },\n collectionName: string,\n): Record<string, unknown>[] {\n if (!database.collections[collectionName]) {\n database.collections[collectionName] = [];\n database.counters[collectionName] = 0;\n }\n\n return database.collections[collectionName] as Record<string, unknown>[];\n}\n\nfunction computeIdKey(route: RouteContext): string {\n return route.pathParamName ?? 'id';\n}\n\nfunction normalizeComparable(value: unknown): string | number | boolean | undefined {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n\n return undefined;\n}\n\nfunction nextId(database: { counters: Record<string, number> }, route: RouteContext): number {\n const current = database.counters[route.resourceName] ?? 0;\n const next = current + 1;\n database.counters[route.resourceName] = next;\n return next;\n}\n\nfunction allocateUniqueId(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n idKey: string,\n): number {\n let candidate = nextId(database, route);\n const hasCollision = (value: number) =>\n collection.some((item) => normalizeComparable(item[idKey]) === String(value));\n\n while (hasCollision(candidate)) {\n candidate = nextId(database, route);\n }\n\n return candidate;\n}\n\nfunction resolveEntity(route: RouteContext, collection: Record<string, unknown>[], request: FastifyRequest): Record<string, unknown> | undefined {\n const idKey = computeIdKey(route);\n const rawId = normalizeComparable(\n (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id,\n );\n return collection.find((item) => normalizeComparable(item[idKey]) === rawId);\n}\n\nfunction materializeMockBody(route: RouteContext, request: FastifyRequest): Record<string, unknown> {\n const requestBody = getRequestBody(request);\n const seeded = route.requestBodySchema ? seedFromSchema(route.requestBodySchema) : {};\n\n return deepMerge(\n (seeded && typeof seeded === 'object' && !Array.isArray(seeded) ? seeded : {}) as Record<string, unknown>,\n requestBody,\n );\n}\n\nfunction getNotFoundResponse(route: RouteContext): { statusCode: number; body: { message: string } } {\n return {\n statusCode: 404,\n body: { message: `${route.resourceName} not found` },\n };\n}\n\nfunction handleReadRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n if (route.isCollection) {\n return { statusCode: 200, body: collection };\n }\n\n const entity = resolveEntity(route, collection, request);\n return entity ? { statusCode: 200, body: entity } : getNotFoundResponse(route);\n}\n\nfunction handleCreateRoute(\n database: { counters: Record<string, number> },\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body: Record<string, unknown> } {\n const entity = materializeMockBody(route, request);\n const idKey = computeIdKey(route);\n if (entity[idKey] === undefined) {\n entity[idKey] = allocateUniqueId(database, route, collection, idKey);\n }\n collection.push(entity);\n return { statusCode: route.successResponse?.statusCode ?? 201, body: entity };\n}\n\nfunction handleUpdateRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const current = resolveEntity(route, collection, request);\n if (!current) {\n return getNotFoundResponse(route);\n }\n\n const merged = route.method === 'put'\n ? materializeMockBody(route, request)\n : deepMerge(current, getRequestBody(request));\n\n Object.assign(current, merged);\n return { statusCode: 200, body: current };\n}\n\nfunction handleDeleteRoute(\n route: RouteContext,\n collection: Record<string, unknown>[],\n request: FastifyRequest,\n): { statusCode: number; body?: unknown } {\n const entity = resolveEntity(route, collection, request);\n if (!entity) {\n return getNotFoundResponse(route);\n }\n\n const index = collection.indexOf(entity);\n collection.splice(index, 1);\n return { statusCode: 204 };\n}\n\nasync function handleMockRoute(\n store: JsonStateStore,\n route: RouteContext,\n request: FastifyRequest,\n): Promise<{ statusCode: number; body?: unknown }> {\n return store.withDatabase(async (database) => {\n const collection = getCollection(database, route);\n const extension = (route.operation as MockOperationObject)['x-mock-state'];\n\n if (extension) {\n const targetCollection = extension.target\n ? getCollectionByName(database, extension.target)\n : collection;\n const response = applyDslMutation(extension, request, targetCollection, computeIdKey(route));\n if (extension.response === 'none') {\n return { statusCode: 204 };\n }\n\n if (extension.response === 'collection') {\n return { statusCode: route.successResponse?.statusCode ?? 200, body: targetCollection };\n }\n\n return { statusCode: route.successResponse?.statusCode ?? defaultStatusCode(route), body: response };\n }\n\n switch (route.method) {\n case 'get': {\n return handleReadRoute(route, collection, request);\n }\n case 'post': {\n return handleCreateRoute(database, route, collection, request);\n }\n case 'put':\n case 'patch': {\n return handleUpdateRoute(route, collection, request);\n }\n case 'delete': {\n return handleDeleteRoute(route, collection, request);\n }\n }\n });\n}\n\nasync function handleProxyRoute(\n config: MockEngineConfig,\n route: RouteContext,\n detector: DriftDetector,\n request: FastifyRequest,\n): Promise<{ statusCode: number; headers: Record<string, string>; body?: unknown }> {\n const rawBody = request.body ? JSON.stringify(request.body) : undefined;\n const targetBaseUrl = config.driftCheckTarget;\n if (!targetBaseUrl) {\n throw new Error('Proxy target is not configured');\n }\n\n const result = await proxyRequest(targetBaseUrl, request.url, {\n method: request.method,\n headers: {\n 'content-type': request.headers['content-type'] ?? 'application/json',\n },\n body: rawBody,\n });\n\n detector.validate(\n route.method,\n route.path,\n result.statusCode,\n route.successResponse?.schema,\n result.body,\n );\n\n return {\n statusCode: result.statusCode,\n headers: Object.fromEntries(result.headers.entries()),\n body: result.body,\n };\n}\n\nasync function registerRoutes(\n app: FastifyInstance,\n document: OpenAPIV3.Document,\n config: MockEngineConfig,\n store: JsonStateStore,\n): Promise<void> {\n const routes = buildRouteContexts(document);\n const detector = new DriftDetector(app.log);\n\n for (const route of routes) {\n app.route({\n method: route.method.toUpperCase(),\n url: route.fastifyPath,\n handler: async (request, reply) => {\n if (config.driftCheckTarget) {\n try {\n const proxied = await handleProxyRoute(config, route, detector, request);\n for (const [headerName, headerValue] of Object.entries(proxied.headers)) {\n if (headerName.toLowerCase() === 'content-length') {\n continue;\n }\n reply.header(headerName, headerValue);\n }\n return reply.code(proxied.statusCode).send(proxied.body);\n } catch (error) {\n app.log.error({ error }, `Proxy execution failed for ${route.method.toUpperCase()} ${route.path}`);\n if (!config.fallbackToMockOnProxyError) {\n throw error;\n }\n }\n }\n\n const mockResponse = await handleMockRoute(store, route, request);\n return sendResponse(reply, mockResponse.statusCode, mockResponse.body);\n },\n });\n }\n}\n\nexport async function createServer(config: MockEngineConfig): Promise<FastifyInstance> {\n const app = Fastify({\n logger: {\n level: config.verbose ? 'debug' : 'error',\n },\n });\n\n await app.register(cors, {\n origin: config.corsOrigin === '*' ? true : config.corsOrigin,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['content-type', 'authorization'],\n credentials: false,\n });\n\n const document = await loadOpenApiDocument(config.specPath);\n const seedCollections = inferSeedCollections(document);\n const dbPath = resolveFile(path.dirname(config.specPath), config.dbPath);\n const store = new JsonStateStore(dbPath);\n await store.initialize(seedCollections);\n\n app.get('/__health', async () => ({ status: 'ok' }));\n app.get('/__spec', async () => document);\n\n await registerRoutes(app, document, config, store);\n return app;\n}","import path from 'node:path';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport function isSchemaObject(\n schema?: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject,\n): schema is OpenAPIV3.SchemaObject {\n return Boolean(schema) && !('$ref' in (schema as OpenAPIV3.ReferenceObject));\n}\n\nexport function normalizeCollectionName(raw: string): string {\n return raw\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .at(-1)\n ?.replace(/[^a-zA-Z0-9]+/g, '_')\n .toLowerCase() ?? 'items';\n}\n\nexport function toFastifyPath(openApiPath: string): string {\n return openApiPath.replace(/\\{([^}]+)\\}/g, ':$1');\n}\n\nexport function singularize(value: string): string {\n if (value.endsWith('ies')) {\n return `${value.slice(0, -3)}y`;\n }\n\n if (value.endsWith('s')) {\n return value.slice(0, -1);\n }\n\n return value;\n}\n\nexport function resolveFile(baseDir: string, filePath: string): string {\n return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);\n}\n\nexport function getOperationId(\n method: string,\n openApiPath: string,\n operation: OpenAPIV3.OperationObject,\n): string {\n if (operation.operationId) {\n return operation.operationId;\n }\n\n const sanitizedPath = openApiPath\n .replace(/[{}]/g, '')\n .split('/')\n .filter(Boolean)\n .join('_');\n\n return `${method}_${sanitizedPath || 'root'}`;\n}\n\nexport function inferPathParamName(openApiPath: string): string | undefined {\n const match = openApiPath.match(/\\{([^}]+)\\}/);\n return match?.[1];\n}\n\nexport function deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(\n base: T,\n patch: Record<string, unknown>,\n): T {\n const output: Record<string, unknown> = { ...base };\n\n for (const [key, value] of Object.entries(patch)) {\n if (\n value &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n output[key] &&\n typeof output[key] === 'object' &&\n !Array.isArray(output[key])\n ) {\n output[key] = deepMerge(\n output[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n output[key] = value;\n }\n }\n\n return output as T;\n}\n\nexport function readContentType(headers: Headers): string {\n return headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() ?? '';\n}\n\nexport function isJsonLikeContentType(contentType: string): boolean {\n return contentType === 'application/json' || contentType.endsWith('+json');\n}\n\nexport function toArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) {\n return [];\n }\n\n return Array.isArray(value) ? value : [value];\n}","import type { FastifyRequest } from 'fastify';\nimport type { MockStateExtension } from './types.js';\nimport { deepMerge } from './utils.js';\n\nfunction resolvePathExpression(source: unknown, expression: string): unknown {\n return expression\n .split('.')\n .reduce<unknown>((value, segment) => (value && typeof value === 'object' ? (value as Record<string, unknown>)[segment] : undefined), source);\n}\n\nfunction evaluateTemplate(value: unknown, request: FastifyRequest): unknown {\n if (typeof value === 'string') {\n const match = value.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (!match) {\n return value;\n }\n\n return resolvePathExpression(\n {\n params: request.params,\n query: request.query,\n body: request.body,\n headers: request.headers,\n },\n match[1],\n );\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => evaluateTemplate(entry, request));\n }\n\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [key, evaluateTemplate(entry, request)]),\n );\n }\n\n return value;\n}\n\nexport function applyDslMutation(\n extension: MockStateExtension,\n request: FastifyRequest,\n collection: Record<string, unknown>[],\n defaultIdKey: string,\n): Record<string, unknown> | Record<string, unknown>[] | null {\n const idKey = extension.find_by ?? defaultIdKey;\n const targetId = (request.params as Record<string, unknown>)[idKey] ?? (request.params as Record<string, unknown>).id;\n const evaluatedAssign = (evaluateTemplate(extension.assign ?? {}, request) ?? {}) as Record<string, unknown>;\n const evaluatedSet = (evaluateTemplate(extension.set ?? {}, request) ?? {}) as Record<string, unknown>;\n\n switch (extension.action) {\n case 'create': {\n const candidate = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(candidate);\n return candidate;\n }\n case 'append': {\n const item = {\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n collection.push(item);\n return collection;\n }\n case 'replace': {\n const nextValue = {\n ...((request.body as Record<string, unknown> | undefined) ?? {}),\n ...evaluatedAssign,\n ...evaluatedSet,\n };\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n collection[index] = nextValue;\n }\n return nextValue;\n }\n case 'delete': {\n const index = collection.findIndex((entry) => String(entry[idKey]) === String(targetId));\n if (index >= 0) {\n const [removed] = collection.splice(index, 1);\n return removed;\n }\n return null;\n }\n case 'update':\n default: {\n const entity = collection.find((entry) => String(entry[idKey]) === String(targetId));\n if (!entity) {\n return null;\n }\n const merged = deepMerge(entity, {\n ...evaluatedAssign,\n ...evaluatedSet,\n });\n Object.assign(entity, merged);\n return entity;\n }\n }\n}","import Ajv, { type ErrorObject } from 'ajv';\nimport addFormats from 'ajv-formats';\nimport pc from 'picocolors';\nimport type { FastifyBaseLogger } from 'fastify';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport type { DriftIssue } from './types.js';\n\nfunction formatErrors(errors: ErrorObject[] | null | undefined): string[] {\n return (errors ?? []).map((error) => {\n const location = error.instancePath || '/';\n return `${location} ${error.message ?? 'failed validation'}`.trim();\n });\n}\n\nexport class DriftDetector {\n private readonly ajv = new Ajv({ allErrors: true, strict: false });\n\n constructor(private readonly logger: FastifyBaseLogger) {\n addFormats(this.ajv);\n }\n\n validate(\n method: string,\n path: string,\n statusCode: number,\n schema: OpenAPIV3.SchemaObject | undefined,\n body: unknown,\n ): DriftIssue | null {\n if (!schema || body === undefined || body === null) {\n return null;\n }\n\n const validate = this.ajv.compile(schema);\n const valid = validate(body);\n\n if (valid) {\n return null;\n }\n\n const issue: DriftIssue = {\n method: method.toUpperCase(),\n path,\n statusCode,\n message: `Drift detected for ${method.toUpperCase()} ${path} (${statusCode})`,\n errors: formatErrors(validate.errors),\n };\n\n this.logger.error(\n `${pc.red('DRIFT DETECTED')} ${issue.method} ${issue.path} -> ${issue.errors.join('; ')}`,\n );\n\n return issue;\n }\n}","import type { OpenAPIV3 } from 'openapi-types';\nimport type { HttpMethod, RouteContext } from './types.js';\nimport {\n getOperationId,\n inferPathParamName,\n isSchemaObject,\n normalizeCollectionName,\n toFastifyPath,\n} from './utils.js';\n\nconst SUPPORTED_METHODS: HttpMethod[] = ['get', 'post', 'put', 'patch', 'delete'];\n\nfunction getRequestBodySchema(\n operation: OpenAPIV3.OperationObject,\n): OpenAPIV3.SchemaObject | undefined {\n const content = operation.requestBody && !('$ref' in operation.requestBody)\n ? operation.requestBody.content?.['application/json']\n : undefined;\n\n return isSchemaObject(content?.schema) ? content.schema : undefined;\n}\n\nfunction getSuccessResponse(\n operation: OpenAPIV3.OperationObject,\n): RouteContext['successResponse'] {\n const preferredCodes = ['200', '201', '202', '204'];\n\n for (const code of preferredCodes) {\n const response = operation.responses?.[code];\n if (!response || '$ref' in response) {\n continue;\n }\n\n const schema = response.content?.['application/json']?.schema;\n return {\n statusCode: Number(code),\n schema: isSchemaObject(schema) ? schema : undefined,\n };\n }\n\n return undefined;\n}\n\nexport function buildRouteContexts(document: OpenAPIV3.Document): RouteContext[] {\n const routes: RouteContext[] = [];\n\n for (const [openApiPath, pathItem] of Object.entries(document.paths ?? {})) {\n if (!pathItem || '$ref' in pathItem) {\n continue;\n }\n\n for (const method of SUPPORTED_METHODS) {\n const operation = pathItem[method];\n if (!operation || '$ref' in operation) {\n continue;\n }\n\n const pathParamName = inferPathParamName(openApiPath);\n const resourceName = normalizeCollectionName(\n pathParamName ? openApiPath.replace(/\\/\\{[^}]+\\}$/, '') : openApiPath,\n );\n const route: RouteContext = {\n method,\n path: openApiPath,\n fastifyPath: toFastifyPath(openApiPath),\n operation,\n operationId: getOperationId(method, openApiPath, operation),\n resourceName,\n isCollection: !pathParamName,\n pathParamName,\n requestBodySchema: getRequestBodySchema(operation),\n successResponse: getSuccessResponse(operation),\n };\n routes.push(route);\n }\n }\n\n return routes;\n}","import { faker } from '@faker-js/faker';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { isSchemaObject, singularize } from './utils.js';\n\nfunction seedPrimitive(schema: OpenAPIV3.SchemaObject): unknown {\n if (schema.enum?.length) {\n return schema.enum[0];\n }\n\n switch (schema.type) {\n case 'string': {\n if (schema.format === 'email') {\n return faker.internet.email();\n }\n\n if (schema.format === 'date-time') {\n return faker.date.recent().toISOString();\n }\n\n if (schema.format === 'uuid') {\n return faker.string.uuid();\n }\n\n return faker.lorem.words(2);\n }\n case 'integer':\n return faker.number.int({ min: 1, max: 1000 });\n case 'number':\n return faker.number.float({ min: 1, max: 1000, fractionDigits: 2 });\n case 'boolean':\n return faker.datatype.boolean();\n default:\n return null;\n }\n}\n\nexport function seedFromSchema(\n schema?: OpenAPIV3.SchemaObject,\n depth = 0,\n): unknown {\n if (!schema || depth > 4) {\n return null;\n }\n\n if (schema.oneOf?.length && isSchemaObject(schema.oneOf[0])) {\n return seedFromSchema(schema.oneOf[0], depth + 1);\n }\n\n if (schema.anyOf?.length && isSchemaObject(schema.anyOf[0])) {\n return seedFromSchema(schema.anyOf[0], depth + 1);\n }\n\n if (schema.allOf?.length) {\n return schema.allOf.reduce<Record<string, unknown>>((accumulator, item) => {\n if (!isSchemaObject(item)) {\n return accumulator;\n }\n\n const seeded = seedFromSchema(item, depth + 1);\n if (seeded && typeof seeded === 'object' && !Array.isArray(seeded)) {\n Object.assign(accumulator, seeded);\n }\n\n return accumulator;\n }, {});\n }\n\n if (schema.type === 'array') {\n const item = isSchemaObject(schema.items) ? schema.items : undefined;\n return [seedFromSchema(item, depth + 1), seedFromSchema(item, depth + 1)].filter(\n (value) => value !== null,\n );\n }\n\n if (schema.type === 'object' || schema.properties) {\n const entries = Object.entries(schema.properties ?? {}).map(([key, value]) => {\n if (!isSchemaObject(value)) {\n return [key, null] as const;\n }\n\n return [key, seedFromSchema(value, depth + 1)] as const;\n });\n\n return Object.fromEntries(entries);\n }\n\n return seedPrimitive(schema);\n}\n\nexport function inferSeedCollections(document: OpenAPIV3.Document): Record<string, unknown[]> {\n const collections: Record<string, unknown[]> = {};\n\n for (const [schemaName, schemaValue] of Object.entries(document.components?.schemas ?? {})) {\n if (!isSchemaObject(schemaValue) || schemaValue.type !== 'object') {\n continue;\n }\n\n const collectionName = `${singularize(schemaName).toLowerCase()}s`;\n collections[collectionName] = Array.from({ length: 3 }, () => seedFromSchema(schemaValue)) as unknown[];\n }\n\n return collections;\n}","import SwaggerParser from '@apidevtools/swagger-parser';\nimport type { OpenAPIV3 } from 'openapi-types';\n\nexport async function loadOpenApiDocument(specPath: string): Promise<OpenAPIV3.Document> {\n const document = (await SwaggerParser.dereference(specPath)) as OpenAPIV3.Document;\n\n if (!document.openapi?.startsWith('3.')) {\n throw new Error(`Only OpenAPI 3.x specs are supported. Received: ${document.openapi}`);\n }\n\n return document;\n}","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { MockDatabase } from './types.js';\nimport { deepClone } from './utils.js';\n\nfunction normalizeDatabase(input: Partial<MockDatabase> | undefined): MockDatabase {\n return {\n collections: input?.collections ?? {},\n counters: input?.counters ?? {},\n };\n}\n\nexport class JsonStateStore {\n readonly filePath: string;\n\n constructor(filePath: string) {\n this.filePath = filePath;\n }\n\n async initialize(seedCollections: Record<string, unknown[]>): Promise<MockDatabase> {\n await mkdir(path.dirname(this.filePath), { recursive: true });\n\n try {\n const existing = await this.read();\n let changed = false;\n\n for (const [collectionName, items] of Object.entries(seedCollections)) {\n if (!existing.collections[collectionName]) {\n existing.collections[collectionName] = deepClone(items);\n existing.counters[collectionName] = items.length;\n changed = true;\n }\n }\n\n if (changed) {\n await this.write(existing);\n }\n\n return existing;\n } catch {\n const initial: MockDatabase = {\n collections: deepClone(seedCollections),\n counters: Object.fromEntries(\n Object.entries(seedCollections).map(([name, items]) => [name, items.length]),\n ),\n };\n await this.write(initial);\n return initial;\n }\n }\n\n async read(): Promise<MockDatabase> {\n const raw = await readFile(this.filePath, 'utf8');\n return normalizeDatabase(JSON.parse(raw) as Partial<MockDatabase>);\n }\n\n async write(database: MockDatabase): Promise<void> {\n await writeFile(this.filePath, `${JSON.stringify(database, null, 2)}\\n`, 'utf8');\n }\n\n async withDatabase<T>(\n updater: (database: MockDatabase) => Promise<T> | T,\n ): Promise<T> {\n const database = await this.read();\n const result = await updater(database);\n await this.write(database);\n return result;\n }\n}","import { readContentType, isJsonLikeContentType } from './utils.js';\nimport type { ProxyExecutionResult } from './types.js';\n\nexport async function proxyRequest(\n targetBaseUrl: string,\n path: string,\n init: RequestInit,\n): Promise<ProxyExecutionResult> {\n const response = await fetch(new URL(path, targetBaseUrl), init);\n const contentType = readContentType(response.headers);\n\n let body: unknown;\n let rawBody: string | undefined;\n\n if (response.status !== 204) {\n rawBody = await response.text();\n if (rawBody && isJsonLikeContentType(contentType)) {\n body = JSON.parse(rawBody);\n } else if (rawBody) {\n body = rawBody;\n }\n }\n\n return {\n ok: response.ok,\n statusCode: response.status,\n headers: response.headers,\n body,\n rawBody,\n };\n}"],"mappings":"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","names":["mkdir","writeFile","path","path","mkdir","writeFile","path","path","path","mkdir","writeFile","path","mkdir","path","writeFile","path","path"]}
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ interface InitConfig {
17
17
  db: string;
18
18
  port: number;
19
19
  host: string;
20
+ template: 'rest-crud' | 'none';
20
21
  }
21
22
  interface MockDatabase {
22
23
  collections: Record<string, unknown[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contract-drift-detection",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Stateful OpenAPI mock server with contract drift detection",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,7 @@
29
29
  "files": [
30
30
  "dist",
31
31
  "README.md",
32
+ "CHANGELOG.md",
32
33
  "LICENSE"
33
34
  ],
34
35
  "scripts": {
@@ -88,4 +89,4 @@
88
89
  "tsx": "^4.20.5",
89
90
  "vitest": "^3.2.4"
90
91
  }
91
- }
92
+ }