counterfact 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -141
- package/bin/README.md +24 -4
- package/bin/counterfact.js +44 -1
- package/dist/app.js +15 -16
- package/dist/counterfact-types/cookie-options.js +1 -0
- package/dist/counterfact-types/counterfact-response.js +7 -0
- package/dist/counterfact-types/example-names.js +1 -0
- package/dist/counterfact-types/example.js +1 -0
- package/dist/counterfact-types/generic-response-builder.js +1 -0
- package/dist/counterfact-types/http-status-code.js +1 -0
- package/dist/counterfact-types/if-has-key.js +1 -0
- package/dist/counterfact-types/index.js +0 -1
- package/dist/counterfact-types/maybe-promise.js +1 -0
- package/dist/counterfact-types/media-type.js +1 -0
- package/dist/counterfact-types/omit-all.js +1 -0
- package/dist/counterfact-types/omit-value-when-never.js +1 -0
- package/dist/counterfact-types/open-api-content.js +1 -0
- package/dist/counterfact-types/open-api-operation.js +1 -0
- package/dist/counterfact-types/open-api-parameters.js +1 -0
- package/dist/counterfact-types/open-api-response.js +1 -0
- package/dist/counterfact-types/random-function.js +1 -0
- package/dist/counterfact-types/response-builder-factory.js +1 -0
- package/dist/counterfact-types/response-builder.js +1 -0
- package/dist/counterfact-types/wide-operation-argument.js +1 -0
- package/dist/counterfact-types/wide-response-builder.js +1 -0
- package/dist/repl/repl.js +96 -3
- package/dist/server/context-registry.js +17 -1
- package/dist/server/counterfact-types/cookie-options.ts +14 -0
- package/dist/server/counterfact-types/counterfact-response.ts +15 -0
- package/dist/server/counterfact-types/example-names.ts +13 -0
- package/dist/server/counterfact-types/example.ts +10 -0
- package/dist/server/counterfact-types/generic-response-builder.ts +164 -0
- package/dist/server/counterfact-types/http-status-code.ts +62 -0
- package/dist/server/counterfact-types/if-has-key.ts +19 -0
- package/dist/server/counterfact-types/index.ts +20 -338
- package/dist/server/counterfact-types/maybe-promise.ts +6 -0
- package/dist/server/counterfact-types/media-type.ts +6 -0
- package/dist/server/counterfact-types/omit-all.ts +11 -0
- package/dist/server/counterfact-types/omit-value-when-never.ts +11 -0
- package/dist/server/counterfact-types/open-api-content.ts +8 -0
- package/dist/server/counterfact-types/open-api-operation.ts +36 -0
- package/dist/server/counterfact-types/open-api-parameters.ts +16 -0
- package/dist/server/counterfact-types/open-api-response.ts +22 -0
- package/dist/server/counterfact-types/random-function.ts +9 -0
- package/dist/server/counterfact-types/response-builder-factory.ts +16 -0
- package/dist/server/counterfact-types/response-builder.ts +31 -0
- package/dist/server/counterfact-types/wide-operation-argument.ts +17 -0
- package/dist/server/counterfact-types/wide-response-builder.ts +26 -0
- package/dist/server/create-koa-app.js +1 -20
- package/dist/server/dispatcher.js +18 -5
- package/dist/server/json-to-xml.js +1 -1
- package/dist/server/koa-middleware.js +7 -1
- package/dist/server/load-openapi-document.js +13 -0
- package/dist/server/module-loader.js +76 -4
- package/dist/server/openapi-watcher.js +35 -0
- package/dist/server/request-validator.js +3 -7
- package/dist/server/response-builder.js +3 -0
- package/dist/server/response-validator.js +58 -0
- package/dist/server/scenario-registry.js +29 -0
- package/dist/server/tools.js +2 -2
- package/dist/typescript-generator/coder.js +4 -2
- package/dist/typescript-generator/generate.js +155 -0
- package/dist/typescript-generator/operation-coder.js +1 -1
- package/dist/typescript-generator/operation-type-coder.js +1 -49
- package/dist/typescript-generator/read-only-comments.js +1 -1
- package/dist/typescript-generator/requirement.js +8 -1
- package/dist/typescript-generator/reserved-words.js +50 -0
- package/dist/util/load-config-file.js +44 -0
- package/package.json +7 -8
- package/dist/client/README.md +0 -14
- package/dist/client/index.html.hbs +0 -244
- package/dist/client/rapi-doc.html.hbs +0 -36
- package/dist/server/page-middleware.js +0 -23
package/README.md
CHANGED
|
@@ -4,227 +4,189 @@
|
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Your backend isn't ready. Your frontend can't wait.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Counterfact turns your OpenAPI spec into a live, stateful API you can program in TypeScript.**
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
<br>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
 [](https://github.com/ellerbrock/typescript-badges/) [](https://coveralls.io/github/pmcelhaney/counterfact)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
</div>
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
This is a five-minute walkthrough. By the end, you’ll have a **stateful, type-safe, hot-reloading API simulator** running locally—and you’ll understand why it’s different from traditional mock servers.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Built for frontend developers, test engineers, and AI agents that need a predictable API to work against.
|
|
20
20
|
|
|
21
|
-
> Built by Patrick McElhaney · Currently available for the right opportunity → https://patrickmcelhaney.org
|
|
22
21
|
|
|
23
|
-
---
|
|
24
22
|
|
|
25
|
-
##
|
|
23
|
+
## Minute 1 — Start the server
|
|
26
24
|
|
|
27
25
|
```sh
|
|
28
26
|
npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
> **Requires Node ≥ 17.0.0**
|
|
29
|
+
> **Requires Node ≥ 22.0.0**
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
That’s it.
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
Counterfact reads your spec, generates a TypeScript handler for every endpoint, and starts a server at `http://localhost:3100`.
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
- 🔒 **Type-safe by default** — route handlers are typed directly from your OpenAPI spec
|
|
41
|
-
- 🔄 **Hot reload** — edit route files while the server is running; state is preserved
|
|
42
|
-
- 🧠 **State management** — POST data and GET it back; share state across routes with context objects
|
|
43
|
-
- 🖥 **Live REPL** — inspect and modify server state from your terminal without touching files
|
|
44
|
-
- 🔀 **Hybrid proxy** — route some paths to the real API while mocking others
|
|
45
|
-
- 🎲 **Smart random data** — uses OpenAPI examples and schema metadata to generate realistic responses
|
|
46
|
-
- 📖 **Built-in Swagger UI** — browse and test your mock API in a browser automatically
|
|
47
|
-
- 🔌 **Middleware support** — add custom middleware with `_.middleware.ts` files
|
|
35
|
+
Open `http://localhost:3100/counterfact/swagger/`.
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
Every endpoint is already live, returning random, schema-valid responses. No code written yet.
|
|
50
38
|
|
|
51
|
-
## How It Works
|
|
52
39
|
|
|
53
|
-
1. **Generate** — Counterfact reads your OpenAPI spec and creates a `routes/` directory with a `.ts` file for each path, plus a `types/` directory with fully typed request/response interfaces.
|
|
54
|
-
2. **Customize** — Edit the route files to return exactly the data your frontend needs. The full power of TypeScript is at your disposal.
|
|
55
|
-
3. **Run** — The server hot-reloads on every save. No restart, no lost state.
|
|
56
40
|
|
|
57
|
-
|
|
41
|
+
## Minute 2 — Make a route return real data
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### Zero effort: random responses out of the box
|
|
62
|
-
|
|
63
|
-
Generated route files return random, schema-valid responses immediately — no editing required.
|
|
43
|
+
Open the generated file for `GET /pet/{petId}`:
|
|
64
44
|
|
|
65
45
|
```ts
|
|
66
|
-
|
|
67
|
-
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
|
|
46
|
+
import type { HTTP_GET } from "../../types/paths/pet/{petId}.types.js";
|
|
68
47
|
|
|
69
|
-
export const GET: HTTP_GET = ($) =>
|
|
70
|
-
return $.response[200].random();
|
|
71
|
-
};
|
|
48
|
+
export const GET: HTTP_GET = ($) => $.response[200].random();
|
|
72
49
|
```
|
|
73
50
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Replace `.random()` with `.json()` to return specific data. TypeScript (via your IDE's autocomplete) guides you to a valid response.
|
|
51
|
+
Replace `.random()` with your own logic:
|
|
77
52
|
|
|
78
53
|
```ts
|
|
79
|
-
import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
|
|
80
|
-
import type { HTTP_DELETE } from "../../../types/paths/store/order/{orderId}.types.js";
|
|
81
|
-
|
|
82
54
|
export const GET: HTTP_GET = ($) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export const DELETE: HTTP_DELETE = ($) => {
|
|
95
|
-
return $.response[200];
|
|
55
|
+
if ($.path.petId === 99) {
|
|
56
|
+
return $.response[404].text("Pet not found");
|
|
57
|
+
}
|
|
58
|
+
return $.response[200].json({
|
|
59
|
+
id: $.path.petId,
|
|
60
|
+
name: "Fluffy",
|
|
61
|
+
status: "available",
|
|
62
|
+
photoUrls: []
|
|
63
|
+
});
|
|
96
64
|
};
|
|
97
65
|
```
|
|
98
66
|
|
|
99
|
-
|
|
67
|
+
Save the file. The server reloads instantly—no restart, no lost state.
|
|
100
68
|
|
|
101
|
-
If your
|
|
69
|
+
TypeScript enforces the contract. If your response doesn’t match the spec, you’ll know before you make the request.
|
|
102
70
|
|
|
103
|
-
|
|
104
|
-
export const GET: HTTP_GET = ($) => {
|
|
105
|
-
return $.response[200].example("successResponse");
|
|
106
|
-
};
|
|
107
|
-
```
|
|
71
|
+
## Minute 3 — Add state that survives across requests
|
|
108
72
|
|
|
109
|
-
|
|
73
|
+
Real APIs have memory. Yours should too.
|
|
110
74
|
|
|
111
|
-
|
|
75
|
+
Create `api/routes/_.context.ts`:
|
|
112
76
|
|
|
113
77
|
```ts
|
|
114
|
-
|
|
78
|
+
import type { Pet } from "../types/components/pet.types.js";
|
|
79
|
+
|
|
115
80
|
export class Context {
|
|
116
|
-
pets
|
|
81
|
+
private pets = new Map<number, Pet>();
|
|
82
|
+
private nextId = 1;
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
this.pets.
|
|
121
|
-
return
|
|
84
|
+
add(data: Omit<Pet, "id">): Pet {
|
|
85
|
+
const pet = { ...data, id: this.nextId++ };
|
|
86
|
+
this.pets.set(pet.id, pet);
|
|
87
|
+
return pet;
|
|
122
88
|
}
|
|
123
89
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
90
|
+
get(id: number): Pet | undefined { return this.pets.get(id); }
|
|
91
|
+
list(): Pet[] { return [...this.pets.values()]; }
|
|
92
|
+
remove(id: number): void { this.pets.delete(id); }
|
|
127
93
|
}
|
|
128
94
|
```
|
|
129
95
|
|
|
130
|
-
|
|
131
|
-
// mock-api/routes/pet.ts
|
|
132
|
-
export const POST: HTTP_POST = ($) => {
|
|
133
|
-
return $.response[200].json($.context.addPet($.body));
|
|
134
|
-
};
|
|
96
|
+
Use it in your routes:
|
|
135
97
|
|
|
136
|
-
|
|
137
|
-
export const GET: HTTP_GET = ($) =>
|
|
138
|
-
|
|
139
|
-
if (!pet) return $.response[404].text(`Pet ${$.path.petId} not found.`);
|
|
140
|
-
return $.response[200].json(pet);
|
|
141
|
-
};
|
|
98
|
+
```ts
|
|
99
|
+
export const GET: HTTP_GET = ($) => $.response[200].json($.context.list());
|
|
100
|
+
export const POST: HTTP_POST = ($) => $.response[200].json($.context.add($.body));
|
|
142
101
|
```
|
|
143
102
|
|
|
144
|
-
|
|
103
|
+
Now your API behaves like a real system:
|
|
104
|
+
- POST creates data
|
|
105
|
+
- GET returns it
|
|
106
|
+
- DELETE removes it
|
|
107
|
+
|
|
108
|
+
State survives hot reloads. Restarting resets everything—perfect for clean test runs.
|
|
145
109
|
|
|
146
|
-
---
|
|
147
110
|
|
|
148
|
-
## Key Capabilities
|
|
149
111
|
|
|
150
|
-
|
|
112
|
+
## Minute 4 — Control the system at runtime (REPL)
|
|
151
113
|
|
|
152
|
-
|
|
114
|
+
This is where Counterfact becomes more than a mock.
|
|
153
115
|
|
|
154
|
-
|
|
116
|
+
The built-in REPL lets you inspect and control the system while it’s running.
|
|
155
117
|
|
|
156
|
-
|
|
118
|
+
Seed data:
|
|
157
119
|
|
|
158
120
|
```
|
|
159
|
-
⬣> context.
|
|
160
|
-
|
|
161
|
-
⬣> context.addPet({ name: "Fluffy", photoUrls: [] })
|
|
162
|
-
⬣> client.get("/pet/3")
|
|
163
|
-
⬣> .proxy on /payments # forward /payments to the real API
|
|
164
|
-
⬣> .proxy off # stop all proxying
|
|
121
|
+
⬣> context.add({ name: "Fluffy", status: "available", photoUrls: [] })
|
|
122
|
+
⬣> context.add({ name: "Rex", status: "pending", photoUrls: [] })
|
|
165
123
|
```
|
|
166
124
|
|
|
167
|
-
|
|
125
|
+
Make requests:
|
|
168
126
|
|
|
169
|
-
|
|
127
|
+
```
|
|
128
|
+
⬣> client.get("/pet/1")
|
|
129
|
+
```
|
|
170
130
|
|
|
171
|
-
|
|
172
|
-
|
|
131
|
+
Simulate failures instantly:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
⬣> context.rateLimitExceeded = true
|
|
135
|
+
⬣> client.get("/pet/1")
|
|
136
|
+
{ status: 429, body: "Too Many Requests" }
|
|
173
137
|
```
|
|
174
138
|
|
|
175
|
-
|
|
139
|
+
No HTTP scripts. No restarts. Just direct control.
|
|
176
140
|
|
|
177
|
-
Every route handler is typed to match your OpenAPI spec. When the spec changes, regenerating the types surfaces any mismatches at compile time — before they become bugs.
|
|
178
141
|
|
|
179
|
-
```ts
|
|
180
|
-
export const GET: HTTP_GET = ($) => {
|
|
181
|
-
return $.response[200]
|
|
182
|
-
.header("x-request-id", $.headers["x-request-id"])
|
|
183
|
-
.json({
|
|
184
|
-
id: $.path.userId,
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
```
|
|
188
142
|
|
|
189
|
-
|
|
143
|
+
## Minute 5 — Proxy to the real backend
|
|
144
|
+
|
|
145
|
+
When parts of your backend are ready, forward them through.
|
|
190
146
|
|
|
191
|
-
|
|
147
|
+
Everything else stays simulated.
|
|
192
148
|
|
|
193
149
|
```sh
|
|
194
|
-
npx counterfact@latest
|
|
150
|
+
npx counterfact@latest openapi.yaml api --proxy-url https://api.example.com
|
|
195
151
|
```
|
|
196
152
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
| `-r, --repl` | Start the interactive REPL |
|
|
205
|
-
| `--spec <path>` | Path or URL to the OpenAPI document |
|
|
206
|
-
| `--proxy-url <url>` | Forward all requests to this URL by default |
|
|
207
|
-
| `--prefix <path>` | Base path prefix (e.g. `/api/v1`) |
|
|
208
|
-
| `--no-validate-request` | Disable request validation against the OpenAPI spec |
|
|
153
|
+
Toggle paths live:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
⬣> .proxy on /payments
|
|
157
|
+
⬣> .proxy on /auth
|
|
158
|
+
⬣> .proxy off
|
|
159
|
+
```
|
|
209
160
|
|
|
210
|
-
Run `npx counterfact@latest --help` for the full list of options.
|
|
211
161
|
|
|
212
|
-
---
|
|
213
162
|
|
|
214
|
-
##
|
|
163
|
+
## What you just built
|
|
215
164
|
|
|
216
|
-
|
|
165
|
+
In five minutes, you turned a static spec into a working system:
|
|
217
166
|
|
|
218
|
-
|
|
167
|
+
- **Schema-valid responses** from the moment it starts
|
|
168
|
+
- **Type-safe handlers** generated from your spec
|
|
169
|
+
- **Shared state** across all routes
|
|
170
|
+
- **Hot reloading** without losing that state
|
|
171
|
+
- A **live control surface (REPL)** for runtime behavior
|
|
172
|
+
- **Selective proxying** to real services
|
|
219
173
|
|
|
220
|
-
Counterfact is one way of removing that friction.
|
|
221
174
|
|
|
222
|
-
I’m currently available — not for long.
|
|
223
175
|
|
|
224
|
-
|
|
176
|
+
## Go deeper
|
|
177
|
+
|
|
178
|
+
| | |
|
|
179
|
+
|---|---|
|
|
180
|
+
| [Getting started](./docs/getting-started.md) | Detailed walkthrough with state, REPL, and proxy |
|
|
181
|
+
| [Usage](./docs/usage.md) | Feature index: routes, context, REPL, proxy, middleware, and more |
|
|
182
|
+
| [Patterns](./docs/patterns/index.md) | Failures, latency, AI sandboxes, integration tests |
|
|
183
|
+
| [Reference](./docs/reference.md) | `$` API, CLI flags, architecture |
|
|
184
|
+
| [How it compares](./docs/comparison.md) | json-server, WireMock, Prism, Microcks, MSW |
|
|
185
|
+
| [FAQ](./docs/faq.md) | State, types, regeneration |
|
|
186
|
+
| [Petstore example](https://github.com/counterfact/example-petstore) | Full working example |
|
|
225
187
|
|
|
226
188
|
<div align="center" markdown="1">
|
|
227
189
|
|
|
228
|
-
[
|
|
190
|
+
[Changelog](./CHANGELOG.md) · [Contributing](./CONTRIBUTING.md)
|
|
229
191
|
|
|
230
|
-
</div>
|
|
192
|
+
</div>
|
package/bin/README.md
CHANGED
|
@@ -18,11 +18,13 @@ npx counterfact@latest openapi.yaml ./api [options]
|
|
|
18
18
|
│ counterfact.js │
|
|
19
19
|
│ │
|
|
20
20
|
│ 1. Parse args (Commander) │
|
|
21
|
-
│ 2.
|
|
22
|
-
│ 3.
|
|
23
|
-
│ 4.
|
|
21
|
+
│ 2. Load counterfact.yaml │
|
|
22
|
+
│ 3. Merge config + args │
|
|
23
|
+
│ 4. Resolve paths │
|
|
24
|
+
│ 5. Build Config object │
|
|
25
|
+
│ 6. Run migrations if │
|
|
24
26
|
│ old layout detected │
|
|
25
|
-
│
|
|
27
|
+
│ 7. Call start(config) │
|
|
26
28
|
│ from src/app.ts │
|
|
27
29
|
└────────────────────────────┘
|
|
28
30
|
```
|
|
@@ -42,5 +44,23 @@ npx counterfact@latest openapi.yaml ./api [options]
|
|
|
42
44
|
| `--prefix <path>` | Base path prefix for all routes (e.g. `/api/v1`) |
|
|
43
45
|
| `--no-update-check` | Disable the npm update check on startup |
|
|
44
46
|
| `--no-validate-request` | Disable request validation against the OpenAPI spec |
|
|
47
|
+
| `--config <path>` | Path to a `counterfact.yaml` config file (default: `counterfact.yaml` in the current directory) |
|
|
45
48
|
|
|
46
49
|
Run `npx counterfact@latest --help` to see the full option list.
|
|
50
|
+
|
|
51
|
+
### Config File
|
|
52
|
+
|
|
53
|
+
Any CLI option can also be specified in a `counterfact.yaml` file in the current working directory. Command-line options always take precedence.
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
# counterfact.yaml
|
|
57
|
+
spec: ./openapi.yaml
|
|
58
|
+
port: 8080
|
|
59
|
+
serve: true
|
|
60
|
+
repl: true
|
|
61
|
+
watch: true
|
|
62
|
+
proxy-url: https://api.example.com
|
|
63
|
+
prefix: /api/v1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use `--config <path>` to load a config file from a non-default location.
|
package/bin/counterfact.js
CHANGED
|
@@ -41,7 +41,9 @@ import { PostHog } from "posthog-node";
|
|
|
41
41
|
|
|
42
42
|
const MIN_NODE_VERSION = 17;
|
|
43
43
|
|
|
44
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
Number.parseInt(process.versions.node.split(".")[0], 10) < MIN_NODE_VERSION
|
|
46
|
+
) {
|
|
45
47
|
process.stdout.write(
|
|
46
48
|
`Counterfact works with Node version ${MIN_NODE_VERSION}+. You are running version ${process.version}`,
|
|
47
49
|
);
|
|
@@ -152,6 +154,13 @@ const { updateRouteTypes } = await import(
|
|
|
152
154
|
: "../dist/migrate/update-route-types.js",
|
|
153
155
|
)
|
|
154
156
|
);
|
|
157
|
+
const { loadConfigFile } = await import(
|
|
158
|
+
resolve(
|
|
159
|
+
nativeTs
|
|
160
|
+
? "../src/util/load-config-file.js"
|
|
161
|
+
: "../dist/util/load-config-file.js",
|
|
162
|
+
)
|
|
163
|
+
);
|
|
155
164
|
|
|
156
165
|
const DEFAULT_PORT = 3100;
|
|
157
166
|
|
|
@@ -269,6 +278,31 @@ async function main(source, destination) {
|
|
|
269
278
|
? Promise.resolve()
|
|
270
279
|
: checkForUpdates(CURRENT_VERSION);
|
|
271
280
|
|
|
281
|
+
// Load the config file (counterfact.yaml by default, or --config <path>).
|
|
282
|
+
// CLI options always take precedence over config file settings.
|
|
283
|
+
const configFilePath = nodePath.resolve(options.config ?? "counterfact.yaml");
|
|
284
|
+
const fileConfig = await loadConfigFile(
|
|
285
|
+
configFilePath,
|
|
286
|
+
options.config !== undefined,
|
|
287
|
+
);
|
|
288
|
+
debug("fileConfig: %o", fileConfig);
|
|
289
|
+
|
|
290
|
+
// Apply config file values for any option that was not explicitly set on the
|
|
291
|
+
// command line (i.e. its source is "default" or it was never defined).
|
|
292
|
+
for (const [key, value] of Object.entries(fileConfig)) {
|
|
293
|
+
const optionSource = program.getOptionValueSource(key);
|
|
294
|
+
|
|
295
|
+
if (optionSource !== "cli") {
|
|
296
|
+
options[key] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// If the config file specifies a destination and none was given on the CLI,
|
|
301
|
+
// use it (destination has no Commander option — it's a positional argument).
|
|
302
|
+
if (fileConfig.destination !== undefined && destination === ".") {
|
|
303
|
+
destination = String(fileConfig.destination);
|
|
304
|
+
}
|
|
305
|
+
|
|
272
306
|
// --spec takes precedence over the positional [openapi.yaml] argument.
|
|
273
307
|
// When --spec is provided, the [openapi.yaml] positional slot shifts to
|
|
274
308
|
// become the [destination] argument (so `counterfact --spec api.yaml ./api`
|
|
@@ -343,6 +377,7 @@ async function main(source, destination) {
|
|
|
343
377
|
startServer: options.serve,
|
|
344
378
|
buildCache: options.buildCache || false,
|
|
345
379
|
validateRequests: options.validateRequest !== false,
|
|
380
|
+
validateResponses: options.validateResponse !== false,
|
|
346
381
|
|
|
347
382
|
watch: {
|
|
348
383
|
routes: options.watch || options.watchRoutes,
|
|
@@ -550,5 +585,13 @@ program
|
|
|
550
585
|
"--no-validate-request",
|
|
551
586
|
"disable request validation against the OpenAPI spec",
|
|
552
587
|
)
|
|
588
|
+
.option(
|
|
589
|
+
"--no-validate-response",
|
|
590
|
+
"disable response validation against the OpenAPI spec",
|
|
591
|
+
)
|
|
592
|
+
.option(
|
|
593
|
+
"--config <path>",
|
|
594
|
+
"path to a counterfact.yaml config file (default: counterfact.yaml in the current directory)",
|
|
595
|
+
)
|
|
553
596
|
.action(main)
|
|
554
597
|
.parse(process.argv);
|
package/dist/app.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import fs, { rm } from "node:fs/promises";
|
|
2
2
|
import nodePath from "node:path";
|
|
3
|
-
import { dereference } from "@apidevtools/json-schema-ref-parser";
|
|
4
|
-
import createDebug from "debug";
|
|
5
3
|
import { createHttpTerminator } from "http-terminator";
|
|
6
4
|
import { startRepl as startReplServer } from "./repl/repl.js";
|
|
7
5
|
import { ContextRegistry } from "./server/context-registry.js";
|
|
8
6
|
import { createKoaApp } from "./server/create-koa-app.js";
|
|
9
|
-
import { Dispatcher
|
|
7
|
+
import { Dispatcher } from "./server/dispatcher.js";
|
|
10
8
|
import { koaMiddleware } from "./server/koa-middleware.js";
|
|
9
|
+
import { loadOpenApiDocument } from "./server/load-openapi-document.js";
|
|
11
10
|
import { ModuleLoader } from "./server/module-loader.js";
|
|
11
|
+
import { OpenApiWatcher } from "./server/openapi-watcher.js";
|
|
12
12
|
import { Registry } from "./server/registry.js";
|
|
13
|
+
import { ScenarioRegistry } from "./server/scenario-registry.js";
|
|
13
14
|
import { Transpiler } from "./server/transpiler.js";
|
|
14
15
|
import { CodeGenerator } from "./typescript-generator/code-generator.js";
|
|
16
|
+
import { writeApplyContextType } from "./typescript-generator/generate.js";
|
|
15
17
|
import { runtimeCanExecuteErasableTs } from "./util/runtime-can-execute-erasable-ts.js";
|
|
16
|
-
|
|
18
|
+
export { loadOpenApiDocument } from "./server/load-openapi-document.js";
|
|
17
19
|
const allowedMethods = [
|
|
18
20
|
"all",
|
|
19
21
|
"head",
|
|
@@ -24,16 +26,6 @@ const allowedMethods = [
|
|
|
24
26
|
"patch",
|
|
25
27
|
"options",
|
|
26
28
|
];
|
|
27
|
-
export async function loadOpenApiDocument(source) {
|
|
28
|
-
try {
|
|
29
|
-
return (await dereference(source));
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
debug("could not load OpenAPI document from %s: %o", source, error);
|
|
33
|
-
const details = error instanceof Error ? error.message : String(error);
|
|
34
|
-
throw new Error(`Could not load the OpenAPI spec from "${source}".\n${details}`, { cause: error });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
29
|
const mswHandlers = {};
|
|
38
30
|
export async function handleMswRequest(request) {
|
|
39
31
|
const { method, rawPath } = request;
|
|
@@ -86,15 +78,20 @@ export async function counterfact(config) {
|
|
|
86
78
|
}
|
|
87
79
|
const registry = new Registry();
|
|
88
80
|
const contextRegistry = new ContextRegistry();
|
|
81
|
+
const scenarioRegistry = new ScenarioRegistry();
|
|
89
82
|
const codeGenerator = new CodeGenerator(config.openApiPath, config.basePath, config.generate);
|
|
90
83
|
const openApiDocument = config.openApiPath === "_"
|
|
91
84
|
? undefined
|
|
92
85
|
: await loadOpenApiDocument(config.openApiPath);
|
|
93
86
|
const dispatcher = new Dispatcher(registry, contextRegistry, openApiDocument, config);
|
|
94
87
|
const transpiler = new Transpiler(nodePath.join(modulesPath, "routes").replaceAll("\\", "/"), compiledPathsDirectory, "commonjs");
|
|
95
|
-
const moduleLoader = new ModuleLoader(compiledPathsDirectory, registry, contextRegistry);
|
|
88
|
+
const moduleLoader = new ModuleLoader(compiledPathsDirectory, registry, contextRegistry, nodePath.join(modulesPath, "scenarios").replaceAll("\\", "/"), scenarioRegistry);
|
|
89
|
+
contextRegistry.addEventListener("context-changed", () => {
|
|
90
|
+
void writeApplyContextType(modulesPath);
|
|
91
|
+
});
|
|
96
92
|
const middleware = koaMiddleware(dispatcher, config);
|
|
97
93
|
const koaApp = createKoaApp(registry, middleware, config, contextRegistry);
|
|
94
|
+
const openApiWatcher = new OpenApiWatcher(config.openApiPath, dispatcher);
|
|
98
95
|
async function start(options) {
|
|
99
96
|
const { generate, startServer, watch, buildCache } = options;
|
|
100
97
|
if (config.openApiPath !== "_" && (generate.routes || generate.types)) {
|
|
@@ -105,6 +102,7 @@ export async function counterfact(config) {
|
|
|
105
102
|
}
|
|
106
103
|
let httpTerminator;
|
|
107
104
|
if (startServer) {
|
|
105
|
+
await openApiWatcher.watch();
|
|
108
106
|
if (!nativeTs) {
|
|
109
107
|
await transpiler.watch();
|
|
110
108
|
}
|
|
@@ -127,6 +125,7 @@ export async function counterfact(config) {
|
|
|
127
125
|
await codeGenerator.stopWatching();
|
|
128
126
|
await transpiler.stopWatching();
|
|
129
127
|
await moduleLoader.stopWatching();
|
|
128
|
+
await openApiWatcher.stopWatching();
|
|
130
129
|
await httpTerminator?.terminate();
|
|
131
130
|
},
|
|
132
131
|
};
|
|
@@ -138,6 +137,6 @@ export async function counterfact(config) {
|
|
|
138
137
|
registry,
|
|
139
138
|
start,
|
|
140
139
|
startRepl: () => startReplServer(contextRegistry, registry, config, undefined, // use the default print function (stdout)
|
|
141
|
-
openApiDocument),
|
|
140
|
+
openApiDocument, scenarioRegistry),
|
|
142
141
|
};
|
|
143
142
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|