contract-drift-detection 0.1.2 → 0.1.4
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 +22 -0
- package/README.md +90 -150
- package/dist/cli.js +24 -3
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.4 - 2026-03-16
|
|
6
|
+
|
|
7
|
+
### Improved
|
|
8
|
+
|
|
9
|
+
- Reworked README for npm users with clearer onboarding and prerequisites.
|
|
10
|
+
- Added explicit first-run flows for `quickstart`, `--spec`, `--discover`, and `--spec-url`.
|
|
11
|
+
- Expanded troubleshooting section with actionable recovery steps.
|
|
12
|
+
- Clarified CLI usage, runtime diagnostics endpoints, and CORS guidance.
|
|
13
|
+
|
|
14
|
+
## 0.1.3 - 2026-03-16
|
|
15
|
+
|
|
16
|
+
### Improved
|
|
17
|
+
|
|
18
|
+
- Expanded OpenAPI auto-discovery endpoints (for example `/api/openapi.json`, `/swagger/v1/swagger.json`, `/api-docs`).
|
|
19
|
+
- Discovery failures now return actionable recovery guidance (use `--spec-url`, `quickstart`, or `init`) instead of opaque stack-first output.
|
|
20
|
+
- CLI default error output is now user-friendly (`Error: <message>`), with optional stack traces via `CDD_SHOW_STACK=1`.
|
|
21
|
+
- Demo backend now exposes `/openapi.yaml`, enabling out-of-the-box `--discover` success in local demos.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Test coverage for `/api/openapi.json` discovery fallback.
|
|
26
|
+
|
|
5
27
|
## 0.1.2 - 2026-03-16
|
|
6
28
|
|
|
7
29
|
### Added
|
package/README.md
CHANGED
|
@@ -1,159 +1,111 @@
|
|
|
1
1
|
# Contract Drift Detection
|
|
2
2
|
|
|
3
|
-
Contract Drift Detection is a stateful OpenAPI
|
|
3
|
+
Contract Drift Detection is a developer CLI that gives you a stateful mock API from OpenAPI and detects contract drift against real backend responses.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use it when:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
- frontend is blocked by incomplete backend endpoints,
|
|
8
|
+
- your team has an OpenAPI contract but real responses drift from it,
|
|
9
|
+
- you want one local endpoint that supports mock mode and proxy mode.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## What this tool does
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- Generates API routes from OpenAPI 3.x (`.yaml` or `.json`).
|
|
14
|
+
- Persists API state in a local `.mock-db.json` file.
|
|
15
|
+
- Supports workflow actions using OpenAPI vendor extensions (`x-mock-state`).
|
|
16
|
+
- In proxy mode, forwards traffic to real backend and validates responses against your spec.
|
|
17
|
+
- Prints visible drift alerts when schema mismatches happen.
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
## What you need before starting
|
|
16
20
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
21
|
+
- Node.js `>= 20`
|
|
22
|
+
- One of these:
|
|
23
|
+
- local OpenAPI file (`openapi.yaml` / `openapi.json`), or
|
|
24
|
+
- backend URL that exposes OpenAPI, or
|
|
25
|
+
- nothing (use `quickstart` to generate starter contract)
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
## Fastest way to start
|
|
21
28
|
|
|
22
|
-
###
|
|
23
|
-
|
|
24
|
-
- Frontend can develop end-to-end before backend is complete.
|
|
25
|
-
- Backend drift is detected immediately in development, not in QA/production.
|
|
26
|
-
- Teams converge faster on a stable API contract.
|
|
27
|
-
|
|
28
|
-
## Features
|
|
29
|
-
|
|
30
|
-
- Generates Fastify routes directly from an OpenAPI 3.x file.
|
|
31
|
-
- Persists state in a local JSON file so POST, PATCH, and DELETE affect future requests.
|
|
32
|
-
- Seeds collections from component schemas using realistic sample data.
|
|
33
|
-
- Supports action-style workflows with `x-mock-state` vendor extensions instead of a separate DSL.
|
|
34
|
-
- Proxies to a live backend with `--drift-check` and validates real responses against the contract.
|
|
35
|
-
- Ships as a CLI and as a reusable library.
|
|
36
|
-
|
|
37
|
-
## Install and run
|
|
38
|
-
|
|
39
|
-
### Use directly with `npx` (no install)
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx contract-drift-detection@latest --spec ./openapi.yaml --port 4010
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
(`serve` subcommand is optional; root command accepts serve options.)
|
|
46
|
-
|
|
47
|
-
### Install globally
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm i -g contract-drift-detection
|
|
51
|
-
cdd --help
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Install in a project
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npm install
|
|
58
|
-
npm run build
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
For local development:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
npm run dev
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## 30-second quick start
|
|
68
|
-
|
|
69
|
-
No spec file yet? Start instantly:
|
|
29
|
+
### Option A: no spec yet (recommended first run)
|
|
70
30
|
|
|
71
31
|
```bash
|
|
72
32
|
npx contract-drift-detection@latest quickstart
|
|
73
33
|
```
|
|
74
34
|
|
|
75
|
-
|
|
35
|
+
This creates a starter OpenAPI file and starts the server immediately.
|
|
36
|
+
|
|
37
|
+
### Option B: you already have a local spec
|
|
76
38
|
|
|
77
39
|
```bash
|
|
78
|
-
npx contract-drift-detection@latest
|
|
40
|
+
npx contract-drift-detection@latest --spec ./openapi.yaml --port 4010
|
|
79
41
|
```
|
|
80
42
|
|
|
81
|
-
|
|
43
|
+
### Option C: backend exposes OpenAPI
|
|
82
44
|
|
|
83
45
|
```bash
|
|
84
|
-
npx contract-drift-detection@latest
|
|
46
|
+
npx contract-drift-detection@latest --discover http://localhost:8080 --port 4010
|
|
85
47
|
```
|
|
86
48
|
|
|
87
|
-
If
|
|
49
|
+
If discover still fails, use explicit endpoint:
|
|
88
50
|
|
|
89
51
|
```bash
|
|
90
|
-
npx contract-drift-detection@latest
|
|
52
|
+
npx contract-drift-detection@latest --spec-url http://localhost:8080/v3/api-docs --port 4010
|
|
91
53
|
```
|
|
92
54
|
|
|
93
|
-
|
|
55
|
+
## Typical frontend + backend integration flow
|
|
94
56
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
57
|
+
1. Start backend (optional for mock-only mode).
|
|
58
|
+
2. Start this tool on `http://localhost:4010`.
|
|
59
|
+
3. Point frontend API base URL to `http://localhost:4010`.
|
|
60
|
+
4. Use mock mode during backend gaps.
|
|
61
|
+
5. Turn on drift-check mode when backend is available.
|
|
98
62
|
|
|
99
|
-
|
|
63
|
+
Drift-check example:
|
|
100
64
|
|
|
101
65
|
```bash
|
|
102
|
-
npx contract-drift-detection@latest
|
|
66
|
+
npx contract-drift-detection@latest \
|
|
103
67
|
--discover http://localhost:8080 \
|
|
104
|
-
--
|
|
105
|
-
--
|
|
68
|
+
--drift-check http://localhost:8080 \
|
|
69
|
+
--port 4010
|
|
106
70
|
```
|
|
107
71
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Point your frontend API base URL to `http://localhost:4010`.
|
|
72
|
+
## CLI usage
|
|
111
73
|
|
|
112
|
-
|
|
74
|
+
`serve` subcommand is optional. Root command accepts serve options directly.
|
|
113
75
|
|
|
114
76
|
```text
|
|
115
|
-
contract-drift-detection
|
|
77
|
+
contract-drift-detection [--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]
|
|
78
|
+
contract-drift-detection serve [same options as above]
|
|
116
79
|
contract-drift-detection init [--spec openapi.yaml] [--template rest-crud|none] [--db .mock-db.json] [--port 4010] [--host 0.0.0.0]
|
|
117
80
|
contract-drift-detection quickstart [--spec openapi.yaml] [--port 4010] [--host 0.0.0.0] [--db .mock-db.json] [--cors-origin *] [--verbose]
|
|
118
|
-
|
|
119
|
-
# Root command (equivalent to serve)
|
|
120
|
-
contract-drift-detection [--spec <path> | --spec-url <url> | --discover <backend-url>] [--port 4010] ...
|
|
121
81
|
```
|
|
122
82
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Spec resolution priority in `serve` is:
|
|
83
|
+
Spec resolution priority:
|
|
126
84
|
|
|
127
85
|
1. `--spec`
|
|
128
86
|
2. `--spec-url`
|
|
129
87
|
3. `--discover`
|
|
130
88
|
|
|
131
|
-
Remote/discovered specs are cached
|
|
132
|
-
|
|
133
|
-
## Recommended workflows
|
|
134
|
-
|
|
135
|
-
### A) Frontend-first (mock only)
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
npx cdd serve --spec ./openapi.yaml --port 4010
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Use this when backend endpoints are not ready.
|
|
89
|
+
Remote/discovered specs are cached under `.cdd/`.
|
|
142
90
|
|
|
143
|
-
|
|
91
|
+
## Backend OpenAPI endpoints commonly discovered
|
|
144
92
|
|
|
145
|
-
|
|
146
|
-
npx cdd serve \
|
|
147
|
-
--spec ./openapi.yaml \
|
|
148
|
-
--port 4010 \
|
|
149
|
-
--drift-check http://localhost:8080
|
|
150
|
-
```
|
|
93
|
+
`--discover` checks common paths, including:
|
|
151
94
|
|
|
152
|
-
|
|
95
|
+
- `/openapi.json`
|
|
96
|
+
- `/openapi.yaml`
|
|
97
|
+
- `/api/openapi.json`
|
|
98
|
+
- `/api/openapi.yaml`
|
|
99
|
+
- `/swagger.json`
|
|
100
|
+
- `/swagger/v1/swagger.json`
|
|
101
|
+
- `/v3/api-docs`
|
|
102
|
+
- `/api-docs-json`
|
|
103
|
+
- `/api-docs`
|
|
104
|
+
- `/docs/openapi.json`
|
|
153
105
|
|
|
154
|
-
## OpenAPI workflow actions
|
|
106
|
+
## OpenAPI workflow actions (`x-mock-state`)
|
|
155
107
|
|
|
156
|
-
|
|
108
|
+
Example:
|
|
157
109
|
|
|
158
110
|
```yaml
|
|
159
111
|
paths:
|
|
@@ -175,70 +127,58 @@ Supported actions:
|
|
|
175
127
|
- `replace`
|
|
176
128
|
- `append`
|
|
177
129
|
|
|
178
|
-
|
|
130
|
+
Template values are supported in `assign` and `set`, for example:
|
|
179
131
|
|
|
180
|
-
|
|
132
|
+
- `{{body.field}}`
|
|
133
|
+
- `{{params.id}}`
|
|
134
|
+
- `{{query.key}}`
|
|
135
|
+
- `{{headers.authorization}}`
|
|
181
136
|
|
|
182
|
-
|
|
183
|
-
- `GET /__spec` returns the dereferenced OpenAPI document currently in memory.
|
|
184
|
-
- `GET /__routes` returns generated route diagnostics (method/path/operation).
|
|
137
|
+
## Runtime diagnostic endpoints
|
|
185
138
|
|
|
186
|
-
|
|
139
|
+
- `GET /__health` → health check
|
|
140
|
+
- `GET /__spec` → resolved OpenAPI in memory
|
|
141
|
+
- `GET /__routes` → generated route summary
|
|
187
142
|
|
|
188
|
-
|
|
189
|
-
|
|
143
|
+
## CORS notes
|
|
144
|
+
|
|
145
|
+
- Default CORS is enabled for all origins (`*`) for fast local onboarding.
|
|
146
|
+
- For stricter browser setup:
|
|
190
147
|
|
|
191
148
|
```bash
|
|
192
|
-
npx
|
|
149
|
+
npx contract-drift-detection@latest --spec ./openapi.yaml --cors-origin http://localhost:5173
|
|
193
150
|
```
|
|
194
151
|
|
|
195
|
-
|
|
152
|
+
## Troubleshooting
|
|
153
|
+
|
|
154
|
+
### `Could not discover an OpenAPI spec ...`
|
|
196
155
|
|
|
197
|
-
|
|
156
|
+
- Try explicit endpoint with `--spec-url`.
|
|
157
|
+
- Or use `quickstart` if backend does not expose OpenAPI.
|
|
158
|
+
- Or run `init` to scaffold starter files.
|
|
198
159
|
|
|
199
|
-
|
|
200
|
-
2. Build the frontend against persistent local state instead of hand-written fixtures.
|
|
201
|
-
3. Switch on `--drift-check` when backend endpoints start coming online.
|
|
202
|
-
4. Keep frontend traffic pointed at this tool until contract mismatches are resolved.
|
|
160
|
+
### `EADDRINUSE: address already in use`
|
|
203
161
|
|
|
204
|
-
|
|
162
|
+
- Port already taken. Choose another port:
|
|
205
163
|
|
|
206
164
|
```bash
|
|
207
|
-
|
|
208
|
-
npm test
|
|
209
|
-
npm run lint
|
|
210
|
-
npm run build
|
|
165
|
+
npx contract-drift-detection@latest --spec ./openapi.yaml --port 4020
|
|
211
166
|
```
|
|
212
167
|
|
|
213
|
-
|
|
168
|
+
### Need full stack traces for debugging
|
|
214
169
|
|
|
215
170
|
```bash
|
|
216
|
-
|
|
171
|
+
CDD_SHOW_STACK=1 npx contract-drift-detection@latest --spec ./openapi.yaml
|
|
217
172
|
```
|
|
218
173
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
## Real frontend + backend test
|
|
222
|
-
|
|
223
|
-
Use the built-in demo harness to validate this package in a full request chain:
|
|
224
|
-
|
|
225
|
-
1. `npm run demo:backend`
|
|
226
|
-
2. `npm run demo:mock:drift`
|
|
227
|
-
3. `npm run demo:frontend`
|
|
228
|
-
|
|
229
|
-
Then open `http://localhost:5173` and exercise requests through the browser.
|
|
230
|
-
|
|
231
|
-
For the complete flow and drift simulation, see `docs/REAL_STACK_TEST.md`.
|
|
232
|
-
|
|
233
|
-
## Launch
|
|
234
|
-
|
|
235
|
-
For release automation and go-to-market steps, follow:
|
|
174
|
+
## Package and binaries
|
|
236
175
|
|
|
237
|
-
- `
|
|
238
|
-
- `
|
|
176
|
+
- npm package: `contract-drift-detection`
|
|
177
|
+
- binaries: `contract-drift-detection`, `cdd`
|
|
178
|
+
- license: MIT
|
|
239
179
|
|
|
240
|
-
##
|
|
180
|
+
## Links
|
|
241
181
|
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
182
|
+
- Changelog: `CHANGELOG.md`
|
|
183
|
+
- Release process: `docs/RELEASE.md`
|
|
184
|
+
- Real stack demo test flow: `docs/REAL_STACK_TEST.md`
|
package/dist/cli.js
CHANGED
|
@@ -14,9 +14,14 @@ import path from "path";
|
|
|
14
14
|
var DISCOVERY_PATHS = [
|
|
15
15
|
"/openapi.json",
|
|
16
16
|
"/openapi.yaml",
|
|
17
|
+
"/api/openapi.json",
|
|
18
|
+
"/api/openapi.yaml",
|
|
17
19
|
"/swagger.json",
|
|
20
|
+
"/swagger/v1/swagger.json",
|
|
18
21
|
"/v3/api-docs",
|
|
19
|
-
"/api-docs-json"
|
|
22
|
+
"/api-docs-json",
|
|
23
|
+
"/api-docs",
|
|
24
|
+
"/docs/openapi.json"
|
|
20
25
|
];
|
|
21
26
|
var STARTER_SPEC = `openapi: 3.0.3
|
|
22
27
|
info:
|
|
@@ -211,7 +216,14 @@ async function discoverSpecUrl(backendBaseUrl) {
|
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
throw new Error(
|
|
214
|
-
|
|
219
|
+
[
|
|
220
|
+
`Could not discover an OpenAPI spec under ${baseUrl}.`,
|
|
221
|
+
`Tried: ${DISCOVERY_PATHS.join(", ")}`,
|
|
222
|
+
"Next steps:",
|
|
223
|
+
"1) Use --spec-url with your exact endpoint (for example, /v3/api-docs or /swagger/v1/swagger.json).",
|
|
224
|
+
"2) If backend does not expose OpenAPI, run quickstart: npx contract-drift-detection@latest quickstart",
|
|
225
|
+
"3) Or generate a starter file: npx contract-drift-detection@latest init"
|
|
226
|
+
].join("\n")
|
|
215
227
|
);
|
|
216
228
|
}
|
|
217
229
|
async function writeStarterSpec(cwd, specPath) {
|
|
@@ -1040,8 +1052,17 @@ async function main() {
|
|
|
1040
1052
|
await cli.parseAsync(process.argv);
|
|
1041
1053
|
}
|
|
1042
1054
|
await main().catch((error) => {
|
|
1043
|
-
|
|
1055
|
+
if (error instanceof Error) {
|
|
1056
|
+
process.stderr.write(`Error: ${error.message}
|
|
1044
1057
|
`);
|
|
1058
|
+
if (process.env.CDD_SHOW_STACK === "1") {
|
|
1059
|
+
process.stderr.write(`${error.stack}
|
|
1060
|
+
`);
|
|
1061
|
+
}
|
|
1062
|
+
} else {
|
|
1063
|
+
process.stderr.write(`${String(error)}
|
|
1064
|
+
`);
|
|
1065
|
+
}
|
|
1045
1066
|
process.exitCode = 1;
|
|
1046
1067
|
});
|
|
1047
1068
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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\nfunction renderStartupBanner(config: {\n host: string;\n port: number;\n specPath: string;\n dbPath: string;\n driftCheckTarget?: string;\n}): string {\n const lines = [\n `🚀 Contract Drift Detection running at http://${config.host}:${config.port}`,\n `- Spec: ${config.specPath}`,\n `- DB: ${config.dbPath}`,\n `- Mode: ${config.driftCheckTarget ? `proxy + drift-check (${config.driftCheckTarget})` : 'stateful mock'}`,\n '- Health: GET /__health',\n '- Routes: GET /__routes',\n ];\n\n return `${lines.join('\\n')}\\n`;\n}\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 const startServer = async (rawOptions: Record<string, string | boolean | undefined>) => {\n const config = await resolveServeConfig(process.cwd(), rawOptions);\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(renderStartupBanner(config));\n };\n\n serveCommand?.action(async function () {\n await startServer(this.opts());\n });\n\n cli.action(async () => {\n const options = cli.opts<Record<string, string | boolean | undefined>>();\n if (!options.spec && !options.specUrl && !options.discover) {\n cli.outputHelp();\n return;\n }\n\n await startServer(options);\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(renderStartupBanner(config));\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\nfunction applyServeOptions(command: Command): Command {\n return command\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\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 applyServeOptions(program);\n\n applyServeOptions(program.command('serve').description('Start the mock engine'));\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\ntype ProxyBody = string | Uint8Array;\n\nfunction toProxyHeaders(inputHeaders: FastifyRequest['headers']): Record<string, string> {\n const passthrough = ['host', 'content-length', 'connection'] as const;\n const headers: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(inputHeaders)) {\n if (passthrough.includes(key as (typeof passthrough)[number])) {\n continue;\n }\n\n if (value === undefined) {\n continue;\n }\n\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n\n return headers;\n}\n\nfunction toProxyBody(request: FastifyRequest): ProxyBody | undefined {\n if (request.method === 'GET' || request.method === 'HEAD') {\n return undefined;\n }\n\n if (request.body === undefined || request.body === null) {\n return undefined;\n }\n\n if (typeof request.body === 'string' || request.body instanceof Uint8Array) {\n return request.body;\n }\n\n return JSON.stringify(request.body);\n}\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 targetBaseUrl = config.driftCheckTarget;\n if (!targetBaseUrl) {\n throw new Error('Proxy target is not configured');\n }\n\n const headers = toProxyHeaders(request.headers);\n if (!headers['content-type'] && request.body !== undefined) {\n headers['content-type'] = 'application/json';\n }\n\n const result = await proxyRequest(targetBaseUrl, request.url, {\n method: request.method,\n headers,\n body: toProxyBody(request),\n });\n\n if (result.statusCode >= 200 && result.statusCode < 300) {\n detector.validate(\n route.method,\n route.path,\n result.statusCode,\n route.successResponse?.schema,\n result.body,\n );\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 app.get('/__routes', async () =>\n routes.map((route) => ({\n method: route.method.toUpperCase(),\n path: route.fastifyPath,\n operationId: route.operationId,\n resource: route.resourceName,\n hasDsl: Boolean((route.operation as MockOperationObject)['x-mock-state']),\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 const errorLines = issue.errors.map((entry) => ` • ${entry}`).join('\\n');\n this.logger.error([\n `${pc.bgRed(pc.black(' 🚨 DRIFT DETECTED '))} ${pc.bold(issue.method)} ${issue.path} (${statusCode})`,\n errorLines,\n ].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"]}
|
|
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\nfunction renderStartupBanner(config: {\n host: string;\n port: number;\n specPath: string;\n dbPath: string;\n driftCheckTarget?: string;\n}): string {\n const lines = [\n `🚀 Contract Drift Detection running at http://${config.host}:${config.port}`,\n `- Spec: ${config.specPath}`,\n `- DB: ${config.dbPath}`,\n `- Mode: ${config.driftCheckTarget ? `proxy + drift-check (${config.driftCheckTarget})` : 'stateful mock'}`,\n '- Health: GET /__health',\n '- Routes: GET /__routes',\n ];\n\n return `${lines.join('\\n')}\\n`;\n}\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 const startServer = async (rawOptions: Record<string, string | boolean | undefined>) => {\n const config = await resolveServeConfig(process.cwd(), rawOptions);\n const server = await createServer(config);\n await server.listen({ port: config.port, host: config.host });\n process.stdout.write(renderStartupBanner(config));\n };\n\n serveCommand?.action(async function () {\n await startServer(this.opts());\n });\n\n cli.action(async () => {\n const options = cli.opts<Record<string, string | boolean | undefined>>();\n if (!options.spec && !options.specUrl && !options.discover) {\n cli.outputHelp();\n return;\n }\n\n await startServer(options);\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(renderStartupBanner(config));\n });\n\n await cli.parseAsync(process.argv);\n}\n\nawait main().catch((error) => {\n if (error instanceof Error) {\n process.stderr.write(`Error: ${error.message}\\n`);\n if (process.env.CDD_SHOW_STACK === '1') {\n process.stderr.write(`${error.stack}\\n`);\n }\n } else {\n process.stderr.write(`${String(error)}\\n`);\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\nfunction applyServeOptions(command: Command): Command {\n return command\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\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 applyServeOptions(program);\n\n applyServeOptions(program.command('serve').description('Start the mock engine'));\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 '/api/openapi.json',\n '/api/openapi.yaml',\n '/swagger.json',\n '/swagger/v1/swagger.json',\n '/v3/api-docs',\n '/api-docs-json',\n '/api-docs',\n '/docs/openapi.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 [\n `Could not discover an OpenAPI spec under ${baseUrl}.`,\n `Tried: ${DISCOVERY_PATHS.join(', ')}`,\n 'Next steps:',\n '1) Use --spec-url with your exact endpoint (for example, /v3/api-docs or /swagger/v1/swagger.json).',\n '2) If backend does not expose OpenAPI, run quickstart: npx contract-drift-detection@latest quickstart',\n '3) Or generate a starter file: npx contract-drift-detection@latest init',\n ].join('\\n'),\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\ntype ProxyBody = string | Uint8Array;\n\nfunction toProxyHeaders(inputHeaders: FastifyRequest['headers']): Record<string, string> {\n const passthrough = ['host', 'content-length', 'connection'] as const;\n const headers: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(inputHeaders)) {\n if (passthrough.includes(key as (typeof passthrough)[number])) {\n continue;\n }\n\n if (value === undefined) {\n continue;\n }\n\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n\n return headers;\n}\n\nfunction toProxyBody(request: FastifyRequest): ProxyBody | undefined {\n if (request.method === 'GET' || request.method === 'HEAD') {\n return undefined;\n }\n\n if (request.body === undefined || request.body === null) {\n return undefined;\n }\n\n if (typeof request.body === 'string' || request.body instanceof Uint8Array) {\n return request.body;\n }\n\n return JSON.stringify(request.body);\n}\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 targetBaseUrl = config.driftCheckTarget;\n if (!targetBaseUrl) {\n throw new Error('Proxy target is not configured');\n }\n\n const headers = toProxyHeaders(request.headers);\n if (!headers['content-type'] && request.body !== undefined) {\n headers['content-type'] = 'application/json';\n }\n\n const result = await proxyRequest(targetBaseUrl, request.url, {\n method: request.method,\n headers,\n body: toProxyBody(request),\n });\n\n if (result.statusCode >= 200 && result.statusCode < 300) {\n detector.validate(\n route.method,\n route.path,\n result.statusCode,\n route.successResponse?.schema,\n result.body,\n );\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 app.get('/__routes', async () =>\n routes.map((route) => ({\n method: route.method.toUpperCase(),\n path: route.fastifyPath,\n operationId: route.operationId,\n resource: route.resourceName,\n hasDsl: Boolean((route.operation as MockOperationObject)['x-mock-state']),\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 const errorLines = issue.errors.map((entry) => ` • ${entry}`).join('\\n');\n this.logger.error([\n `${pc.bgRed(pc.black(' 🚨 DRIFT DETECTED '))} ${pc.bold(issue.method)} ${issue.path} (${statusCode})`,\n errorLines,\n ].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"]}
|