counterfact 2.8.1 → 2.9.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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/bin/README.md +39 -14
  3. package/bin/counterfact.js +18 -547
  4. package/bin/ts-loader.mjs +1 -0
  5. package/dist/api-runner.js +202 -0
  6. package/dist/app.js +72 -138
  7. package/dist/cli/banner.js +81 -0
  8. package/dist/cli/check-for-updates.js +45 -0
  9. package/dist/cli/run.js +304 -0
  10. package/dist/cli/telemetry.js +50 -0
  11. package/dist/migrate/paths-to-routes.js +1 -0
  12. package/dist/migrate/update-route-types.js +1 -0
  13. package/dist/msw.js +78 -0
  14. package/dist/repl/raw-http-client.js +3 -1
  15. package/dist/repl/repl.js +228 -60
  16. package/dist/server/determine-module-kind.js +1 -0
  17. package/dist/server/file-discovery.js +1 -0
  18. package/dist/server/module-loader.js +8 -0
  19. package/dist/server/transpiler.js +1 -0
  20. package/dist/server/{admin-api-middleware.js → web-server/admin-api-middleware.js} +19 -9
  21. package/dist/server/web-server/create-koa-app.js +68 -0
  22. package/dist/server/web-server/openapi-middleware.js +34 -0
  23. package/dist/server/{koa-middleware.js → web-server/routes-middleware.js} +11 -8
  24. package/dist/typescript-generator/code-generator.js +1 -0
  25. package/dist/typescript-generator/prune.js +1 -0
  26. package/dist/typescript-generator/repository.js +1 -0
  27. package/dist/typescript-generator/scenario-file-generator.js +1 -0
  28. package/dist/util/ensure-directory-exists.js +1 -0
  29. package/dist/util/load-config-file.js +2 -2
  30. package/dist/util/read-file.js +16 -2
  31. package/dist/util/runtime-can-execute-erasable-ts.js +1 -0
  32. package/package.json +3 -2
  33. package/dist/server/create-koa-app.js +0 -65
  34. package/dist/server/openapi-middleware.js +0 -48
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <br>
6
6
 
7
- ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![TypeScript](./typescript-badge.png)](https://github.com/ellerbrock/typescript-badges/) [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact)
7
+ ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![TypeScript](./typescript-badge.png)](https://github.com/ellerbrock/typescript-badges/) [![Coverage Status](https://coveralls.io/repos/github/counterfact/api-simulator/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact)
8
8
 
9
9
  </div>
10
10
 
package/bin/README.md CHANGED
@@ -6,7 +6,19 @@ This directory contains the executable script that is run when a developer invok
6
6
 
7
7
  | File | Description |
8
8
  |---|---|
9
- | `counterfact.js` | Parses command-line arguments with [Commander](https://github.com/tj/commander.js), validates inputs, and calls `counterfact()` from `src/app.ts` to start the server, code generator, file watcher, and/or REPL |
9
+ | `counterfact.js` | Thin bootstrap: enforces minimum Node version, probes for native TypeScript execution, then delegates to `src/cli/run.ts` (or `dist/cli/run.js`) |
10
+ | `taglines.txt` | One-per-line list of random taglines shown in the startup banner |
11
+
12
+ ## Architecture
13
+
14
+ Most of the CLI logic lives in **`src/cli/`** as TypeScript:
15
+
16
+ | Module | Description |
17
+ |---|---|
18
+ | `src/cli/run.ts` | Commander program setup, `main()` action handler, and the `runCli()` entry point |
19
+ | `src/cli/banner.ts` | Startup banner utilities: `padTagLine`, `createWatchMessage`, `createIntroduction` |
20
+ | `src/cli/check-for-updates.ts` | npm update check: `isOutdated`, `checkForUpdates` |
21
+ | `src/cli/telemetry.ts` | PostHog telemetry: `isTelemetryEnabled`, `sendTelemetry` |
10
22
 
11
23
  ## How It Works
12
24
 
@@ -14,19 +26,32 @@ This directory contains the executable script that is run when a developer invok
14
26
  npx counterfact@latest openapi.yaml ./api [options]
15
27
 
16
28
 
17
- ┌────────────────────────────┐
18
- counterfact.js
19
-
20
- │ 1. Parse args (Commander)
21
- │ 2. Load counterfact.yaml
22
- │ 3. Merge config + args
23
- 4. Resolve paths
24
- 5. Build Config object
25
- │ 6. Run migrations if │
26
- old layout detected │
27
- │ 7. Call start(config) │
28
- │ from src/app.ts │
29
- └────────────────────────────┘
29
+ ┌────────────────────────────────────────────────┐
30
+ bin/counterfact.js (thin bootstrap)
31
+
32
+ │ 1. Enforce minimum Node.js version
33
+ │ 2. Probe native TypeScript execution
34
+ │ 3. Import runCli() from src/cli/run.ts
35
+ (or dist/cli/run.js when compiled)
36
+ 4. Call runCli(process.argv)
37
+ └────────────────────────────────────────────────┘
38
+
39
+
40
+ ┌────────────────────────────────────────────────┐
41
+ │ src/cli/run.ts (all CLI logic) │
42
+ │ │
43
+ │ 1. Read version from package.json │
44
+ │ 2. Read taglines from bin/taglines.txt │
45
+ │ 3. Fire telemetry (if enabled) │
46
+ │ 4. Parse args (Commander) │
47
+ │ 5. Load counterfact.yaml │
48
+ │ 6. Merge config + args │
49
+ │ 7. Resolve paths │
50
+ │ 8. Build Config object │
51
+ │ 9. Run migrations if old layout detected │
52
+ │ 10. Print startup banner │
53
+ │ 11. Call start(config) from src/app.ts │
54
+ └────────────────────────────────────────────────┘
30
55
  ```
31
56
 
32
57
  ### Key CLI Options
@@ -1,43 +1,26 @@
1
1
  #!/usr/bin/env node
2
+ /* eslint-disable security/detect-non-literal-fs-filename -- this bootstrap writes only fixed probe files under a fresh mkdtemp directory it just created. */
2
3
 
3
4
  /**
4
- * bin/counterfact.js — CLI entry point for the `counterfact` command.
5
+ * bin/counterfact.js — Minimal bootstrap for the `counterfact` CLI.
5
6
  *
6
- * Responsibilities:
7
- * 1. Parse CLI arguments and build a `Config` object via Commander.
8
- * 2. Run any pending migrations (paths → routes directory layout).
9
- * 3. Delegate to `counterfact()` from `src/app.ts` to start the server,
10
- * code generator, transpiler, module loader, and optional REPL.
11
- * 4. Print the startup banner and open the browser when requested.
12
- * 5. Check for available updates against the npm registry.
7
+ * This file is intentionally kept as thin as possible. Its sole
8
+ * responsibilities are:
13
9
  *
14
- * Architecture (high-level data flow):
10
+ * 1. Enforce the minimum Node.js version (must run before any imports).
11
+ * 2. Detect whether the current runtime can execute TypeScript natively
12
+ * (must run before any .ts imports).
13
+ * 3. Dynamically import the real CLI entry point from `src/cli/run.ts`
14
+ * (native TS) or `dist/cli/run.js` (compiled JS) and delegate to it.
15
15
  *
16
- * CLI args ──▶ Commander ──▶ Config
17
- *
18
- * ┌───────────▼───────────┐
19
- * │ counterfact() │
20
- * │ (src/app.ts) │
21
- * │ │
22
- * │ CodeGenerator │ reads OpenAPI spec, emits .ts route/type files
23
- * │ Transpiler │ compiles .ts → .cjs and watches for changes
24
- * │ ModuleLoader │ loads compiled modules into Registry
25
- * │ Dispatcher + KoaApp │ handles HTTP requests
26
- * │ REPL (optional) │ interactive terminal session
27
- * └────────────────────────┘
16
+ * All CLI logic — Commander setup, option parsing, migration, telemetry,
17
+ * banner, update check, and server startup — lives in src/cli/ as TypeScript.
28
18
  */
29
19
 
30
20
  import fs from "node:fs";
31
- import { readFile } from "node:fs/promises";
32
21
  import { tmpdir } from "node:os";
33
22
  import nodePath from "node:path";
34
23
  import { fileURLToPath, pathToFileURL } from "node:url";
35
- import { randomUUID } from "node:crypto";
36
-
37
- import { program } from "commander";
38
- import createDebug from "debug";
39
- import open from "open";
40
- import { PostHog } from "posthog-node";
41
24
 
42
25
  const MIN_NODE_VERSION = 17;
43
26
 
@@ -53,61 +36,10 @@ if (
53
36
 
54
37
  const __binDir = nodePath.dirname(fileURLToPath(import.meta.url));
55
38
 
56
- const packageJson = JSON.parse(
57
- await readFile(nodePath.join(__binDir, "../package.json"), "utf8"),
58
- );
59
-
60
- const CURRENT_VERSION = packageJson.version;
61
-
62
- // Telemetry — fire-and-forget, never blocks startup
63
- const POSTHOG_API_KEY = "phc_msXmBxiL8FVugNMLCx9bnPQGqfEMqmyBjnVkKhHkN3m7";
64
- const POSTHOG_HOST = "https://us.i.posthog.com";
65
-
66
- const telemetryKey = process.env.POSTHOG_API_KEY ?? POSTHOG_API_KEY;
67
- const telemetryHost = process.env.POSTHOG_HOST ?? POSTHOG_HOST;
68
-
69
- const isCI = Boolean(process.env.CI);
70
- const isBeforeRollout = new Date() < new Date("2026-05-01");
71
- const telemetryDisabledEnv = process.env.COUNTERFACT_TELEMETRY_DISABLED;
72
-
73
- const isTelemetryDisabled =
74
- isCI ||
75
- telemetryDisabledEnv === "true" ||
76
- (isBeforeRollout && telemetryDisabledEnv !== "false");
77
-
78
- if (!isTelemetryDisabled) {
79
- try {
80
- const posthog = new PostHog(telemetryKey, { host: telemetryHost });
81
-
82
- posthog.capture({
83
- distinctId: randomUUID(),
84
- event: "counterfact_started",
85
- properties: {
86
- version: CURRENT_VERSION,
87
- nodeVersion: process.version,
88
- platform: process.platform,
89
- arch: process.arch,
90
- source: "counterfact-cli",
91
- },
92
- });
93
-
94
- posthog.shutdownAsync().catch(() => {
95
- // ignore errors — telemetry is best-effort
96
- });
97
- } catch {
98
- // ignore errors — telemetry must never surface to the user
99
- }
100
- }
101
-
102
- const taglinesFile = await readFile(
103
- nodePath.join(__binDir, "taglines.txt"),
104
- "utf8",
105
- );
106
-
107
- const taglines = taglinesFile.split("\n").slice(0, -1);
108
-
109
39
  // Probe whether the current runtime can natively execute TypeScript with
110
- // erasable type annotations AND resolve .js imports to .ts files (tsx-style).
40
+ // erasable type annotations AND resolve .js imports to .ts files.
41
+ // This must be inlined here because we need the result before we can
42
+ // import any .ts files from src/.
111
43
  async function runtimeCanExecuteErasableTs() {
112
44
  const dir = fs.mkdtempSync(nodePath.join(tmpdir(), "ts-probe-"));
113
45
  // helper.ts is imported via .js extension — the TypeScript convention used
@@ -135,471 +67,10 @@ async function runtimeCanExecuteErasableTs() {
135
67
 
136
68
  const nativeTs = await runtimeCanExecuteErasableTs();
137
69
 
138
- const resolve = (rel) => pathToFileURL(nodePath.join(__binDir, rel)).href;
139
-
140
- const { counterfact } = await import(
141
- resolve(nativeTs ? "../src/app.ts" : "../dist/app.js")
142
- );
143
- const { pathsToRoutes } = await import(
144
- resolve(
145
- nativeTs
146
- ? "../src/migrate/paths-to-routes.js"
147
- : "../dist/migrate/paths-to-routes.js",
148
- )
149
- );
150
- const { updateRouteTypes } = await import(
151
- resolve(
152
- nativeTs
153
- ? "../src/migrate/update-route-types.js"
154
- : "../dist/migrate/update-route-types.js",
155
- )
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
- );
70
+ const toFileUrl = (rel) => pathToFileURL(nodePath.join(__binDir, rel)).href;
164
71
 
165
- const { pathResolve } = await import(
166
- resolve(
167
- nativeTs
168
- ? "../src/util/forward-slash-path.js"
169
- : "../dist/util/forward-slash-path.js",
170
- )
72
+ const { runCli } = await import(
73
+ toFileUrl(nativeTs ? "../src/cli/run.ts" : "../dist/cli/run.js")
171
74
  );
172
75
 
173
- const DEFAULT_PORT = 3100;
174
-
175
- const debug = createDebug("counterfact:bin:counterfact");
176
-
177
- function isOutdated(current, latest) {
178
- const [cMajor, cMinor, cPatch] = current.split(".").map(Number);
179
- const [lMajor, lMinor, lPatch] = latest.split(".").map(Number);
180
-
181
- if (lMajor > cMajor) return true;
182
- if (lMajor === cMajor && lMinor > cMinor) return true;
183
- if (lMajor === cMajor && lMinor === cMinor && lPatch > cPatch) return true;
184
-
185
- return false;
186
- }
187
-
188
- async function checkForUpdates(currentVersion) {
189
- if (process.env.CI) {
190
- debug("skipping update check in CI environment");
191
- return;
192
- }
193
-
194
- try {
195
- const response = await fetch(
196
- "https://registry.npmjs.org/counterfact/latest",
197
- );
198
-
199
- if (!response.ok) {
200
- debug("update check failed with status %d", response.status);
201
- return;
202
- }
203
-
204
- const data = await response.json();
205
- const latestVersion = data.version;
206
-
207
- if (isOutdated(currentVersion, latestVersion)) {
208
- process.stdout.write(
209
- `\n⚠️ You're running counterfact ${currentVersion}\n`,
210
- );
211
- process.stdout.write(` Latest version is ${latestVersion}\n`);
212
- process.stdout.write(` Run: npx counterfact@latest\n`);
213
- }
214
- } catch (error) {
215
- debug("update check error: %o", error);
216
- }
217
- }
218
-
219
- debug("running ./bin/counterfact.js");
220
-
221
- function padTagLine(tagLine) {
222
- const headerLength = 51;
223
- const padding = " ".repeat((headerLength - tagLine.length) / 2);
224
-
225
- return `${padding}${tagLine}`;
226
- }
227
-
228
- function createWatchMessage(config) {
229
- let watchMessage;
230
-
231
- switch (true) {
232
- case config.watch.routes && config.watch.types: {
233
- watchMessage = " Watching for changes";
234
-
235
- break;
236
- }
237
- case config.watch.routes: {
238
- watchMessage = " Watching routes for changes";
239
-
240
- break;
241
- }
242
- case config.watch.types: {
243
- watchMessage = " Watching types for changes";
244
-
245
- break;
246
- }
247
-
248
- default: {
249
- watchMessage = undefined;
250
- }
251
- }
252
-
253
- if (!watchMessage) {
254
- switch (true) {
255
- case config.generate.routes && config.generate.types: {
256
- watchMessage = "Generating routes and types";
257
-
258
- break;
259
- }
260
- case config.generate.routes: {
261
- watchMessage = "Generating routes";
262
-
263
- break;
264
- }
265
- case config.generate.types: {
266
- watchMessage = "Generating types";
267
-
268
- break;
269
- }
270
-
271
- default: {
272
- watchMessage = undefined;
273
- }
274
- }
275
- }
276
-
277
- return watchMessage;
278
- }
279
- async function main(source, destination) {
280
- debug("executing the main function");
281
-
282
- const options = program.opts();
283
-
284
- const updateCheckPromise =
285
- options.updateCheck === false
286
- ? Promise.resolve()
287
- : checkForUpdates(CURRENT_VERSION);
288
-
289
- // Load the config file (counterfact.yaml by default, or --config <path>).
290
- // CLI options always take precedence over config file settings.
291
- const configFilePath = nodePath.resolve(options.config ?? "counterfact.yaml");
292
- const fileConfig = await loadConfigFile(
293
- configFilePath,
294
- options.config !== undefined,
295
- );
296
- debug("fileConfig: %o", fileConfig);
297
-
298
- // Apply config file values for any option that was not explicitly set on the
299
- // command line (i.e. its source is "default" or it was never defined).
300
- for (const [key, value] of Object.entries(fileConfig)) {
301
- const optionSource = program.getOptionValueSource(key);
302
-
303
- if (optionSource !== "cli") {
304
- options[key] = value;
305
- }
306
- }
307
-
308
- // If the config file specifies a destination and none was given on the CLI,
309
- // use it (destination has no Commander option — it's a positional argument).
310
- if (fileConfig.destination !== undefined && destination === ".") {
311
- destination = String(fileConfig.destination);
312
- }
313
-
314
- // --spec takes precedence over the positional [openapi.yaml] argument.
315
- // When --spec is provided, the [openapi.yaml] positional slot shifts to
316
- // become the [destination] argument (so `counterfact --spec api.yaml ./api`
317
- // works the same as `counterfact api.yaml ./api`).
318
- if (options.spec) {
319
- if (source !== "_") {
320
- destination = source;
321
- }
322
- source = options.spec;
323
- }
324
-
325
- const destinationPath = pathResolve(destination);
326
-
327
- const basePath = pathResolve(destinationPath);
328
-
329
- // If no action-related option is provided, default to all options
330
-
331
- const actions = ["repl", "serve", "watch", "generate", "buildCache"];
332
- if (
333
- !Object.keys(options).some((argument) =>
334
- actions.some((action) => argument.startsWith(action)),
335
- )
336
- ) {
337
- for (const action of actions) {
338
- options[action] = true;
339
- }
340
- }
341
-
342
- debug("options: %o", options);
343
- debug("source: %s", source);
344
- debug("destination: %s", destination);
345
-
346
- const openBrowser = options.open;
347
-
348
- const url = `http://localhost:${options.port}`;
349
-
350
- const guiUrl = `${url}/counterfact/`;
351
-
352
- const swaggerUrl = `${url}/counterfact/swagger/`;
353
-
354
- const config = {
355
- adminApiToken:
356
- options.adminApiToken ?? process.env.COUNTERFACT_ADMIN_API_TOKEN ?? "",
357
- alwaysFakeOptionals: options.alwaysFakeOptionals,
358
- basePath,
359
-
360
- generate: {
361
- routes:
362
- options.generate ||
363
- options.generateRoutes ||
364
- options.watch ||
365
- options.watchRoutes ||
366
- options.buildCache,
367
-
368
- types:
369
- options.generate ||
370
- options.generateTypes ||
371
- options.watch ||
372
- options.watchTypes ||
373
- options.buildCache,
374
-
375
- prune: Boolean(options.prune),
376
- },
377
-
378
- openApiPath: source,
379
- port: options.port,
380
- proxyPaths: new Map([["", Boolean(options.proxyUrl)]]),
381
- proxyUrl: options.proxyUrl ?? "",
382
- routePrefix: options.prefix,
383
- startAdminApi: options.adminApi,
384
- startRepl: options.repl,
385
- startServer: options.serve,
386
- buildCache: options.buildCache || false,
387
- validateRequests: options.validateRequest !== false,
388
- validateResponses: options.validateResponse !== false,
389
-
390
- watch: {
391
- routes: options.watch || options.watchRoutes,
392
- types: options.watch || options.watchTypes,
393
- },
394
- };
395
-
396
- const configForLogging = {
397
- ...config,
398
- adminApiToken: config.adminApiToken ? "[REDACTED]" : "",
399
- };
400
-
401
- debug("loading counterfact (%o)", configForLogging);
402
-
403
- if (config.startAdminApi && !config.adminApiToken) {
404
- process.stderr.write(
405
- "⚠️ WARNING: The admin API is enabled without an authentication token.\n" +
406
- " Any process on this machine can read and modify server state via /_counterfact/api/*.\n" +
407
- " Set --admin-api-token or COUNTERFACT_ADMIN_API_TOKEN to restrict access.\n\n",
408
- );
409
- }
410
-
411
- let didMigrate = false;
412
- let didMigrateRouteTypes;
413
-
414
- if (fs.existsSync(nodePath.join(config.basePath, "paths"))) {
415
- await pathsToRoutes(config.basePath);
416
- await fs.promises.rmdir(nodePath.join(config.basePath, "paths"), {
417
- recursive: true,
418
- });
419
- await fs.promises.rmdir(nodePath.join(config.basePath, "path-types"), {
420
- recursive: true,
421
- });
422
- await fs.promises.rmdir(nodePath.join(config.basePath, "components"), {
423
- recursive: true,
424
- });
425
-
426
- didMigrate = true;
427
- }
428
-
429
- let start;
430
- let startRepl;
431
- try {
432
- ({ start, startRepl } = await counterfact(config));
433
- } catch (error) {
434
- process.stderr.write(
435
- `\n❌ ${error instanceof Error ? error.message : String(error)}\n\n`,
436
- );
437
- process.exit(1);
438
- }
439
-
440
- debug("loaded counterfact", configForLogging);
441
-
442
- // Migrate route type imports if needed
443
- debug("checking if route type migration is needed");
444
- didMigrateRouteTypes = await updateRouteTypes(
445
- config.basePath,
446
- config.openApiPath,
447
- );
448
- debug("route type migration check complete: %s", didMigrateRouteTypes);
449
-
450
- const watchMessage = createWatchMessage(config);
451
-
452
- const telemetryWarning = isTelemetryDisabled
453
- ? []
454
- : [
455
- "⚠️ Telemetry will be enabled by default starting May 1, 2026.",
456
- " Learn more and how to disable: https://counterfact.dev/telemetry-discussion",
457
- "",
458
- ];
459
-
460
- const introduction = [
461
- " ____ ____ _ _ _ _ ___ ____ ____ ____ ____ ____ ___",
462
- String.raw` |___ [__] |__| |\| | |=== |--< |--- |--| |___ | `,
463
- " " + padTagLine(taglines[Math.floor(Math.random() * taglines.length)]),
464
- "",
465
- ` Version ${CURRENT_VERSION}`,
466
- ` API Base URL ${url}`,
467
- source === "_" ? undefined : ` Swagger UI ${swaggerUrl}`,
468
- "",
469
- " Instructions https://counterfact.dev/docs/usage.html",
470
- " Help/feedback https://github.com/pmcelhaney/counterfact/issues",
471
- "",
472
- ...telemetryWarning,
473
- watchMessage,
474
- config.startServer ? " Starting server" : undefined,
475
- config.startRepl
476
- ? " Starting REPL (type .help for more info)"
477
- : undefined,
478
- ];
479
-
480
- process.stdout.write(
481
- introduction.filter((line) => line !== undefined).join("\n"),
482
- );
483
-
484
- process.stdout.write("\n\n");
485
-
486
- debug("starting server");
487
- try {
488
- await start(config);
489
- } catch (error) {
490
- process.stderr.write(
491
- `\n❌ ${error instanceof Error ? error.message : String(error)}\n\n`,
492
- );
493
- process.exit(1);
494
- }
495
- debug("started server");
496
-
497
- await updateCheckPromise;
498
-
499
- if (config.startRepl) {
500
- startRepl();
501
- }
502
-
503
- if (openBrowser) {
504
- debug("opening browser");
505
- await open(guiUrl);
506
- debug("opened browser");
507
- }
508
-
509
- if (didMigrate) {
510
- process.stdout.write("\n\n\n*******************************\n");
511
- process.stdout.write("MIGRATING TO NEW FILE STRUCTURE\n\n");
512
- process.stdout.write(
513
- "In preparation for version 1.0, Counterfact has migrated to a new file structure.\n",
514
- );
515
- process.stdout.write("- The paths directory has been renamed to routes.\n");
516
- process.stdout.write(
517
- "- The path-types and components directories are now stored under types.\n",
518
- );
519
- process.stdout.write("Your files have automatically been migrated.\n");
520
- process.stdout.write(
521
- "Please report any issues to https://github.com/pmcelhaney/counterfact/issues\n",
522
- );
523
- process.stdout.write("*******************************\n\n\n");
524
- }
525
-
526
- if (didMigrateRouteTypes) {
527
- process.stdout.write("\n\n\n*******************************\n");
528
- process.stdout.write("MIGRATING ROUTE TYPE IMPORTS\n\n");
529
- process.stdout.write(
530
- "Operation types now use operationId from your OpenAPI spec when available.\n",
531
- );
532
- process.stdout.write(
533
- "Your route files have been automatically updated to use the new type names.\n",
534
- );
535
- process.stdout.write(
536
- "Example: 'HTTP_GET' may now be 'getPetById' if operationId is defined.\n",
537
- );
538
- process.stdout.write(
539
- "Please review the changes and report any issues to:\n",
540
- );
541
- process.stdout.write("https://github.com/pmcelhaney/counterfact/issues\n");
542
- process.stdout.write("*******************************\n\n\n");
543
- }
544
- }
545
-
546
- program
547
- .name("counterfact")
548
- .description(
549
- "Counterfact is a tool for mocking REST APIs in development. See https://counterfact.dev for more info.",
550
- )
551
- .argument(
552
- "[openapi.yaml]",
553
- 'path or URL to OpenAPI document or "_" to run without OpenAPI',
554
- "_",
555
- )
556
- .argument("[destination]", "path to generated code", ".")
557
- .option("-p, --port <number>", "server port number", DEFAULT_PORT)
558
- .option("-o, --open", "open a browser")
559
- .option("-g, --generate", "generate all code for both routes and types")
560
- .option("--generate-types", "generate types")
561
- .option("--generate-routes", "generate routes")
562
- .option("-w, --watch", "generate + watch all code for changes")
563
- .option("--watch-types", "generate + watch types for changes")
564
- .option("--watch-routes", "generate + watch routes for changes")
565
- .option("-s, --serve", "start the server")
566
- .option("-b, --build-cache", "builds the cache of compiled routes and types")
567
- .option("--no-admin-api", "disable the admin API at /_counterfact/api/*")
568
- .option("-r, --repl", "start the REPL")
569
- .option("--proxy-url <string>", "proxy URL")
570
- .option(
571
- "--admin-api-token <string>",
572
- "bearer token required for /_counterfact/api/* endpoints (defaults to COUNTERFACT_ADMIN_API_TOKEN)",
573
- )
574
- .option(
575
- "--prefix <string>",
576
- "base path from which routes will be served (e.g. /api/v1)",
577
- "",
578
- )
579
- .option(
580
- "--always-fake-optionals",
581
- "random responses will include optional fields",
582
- )
583
- .option(
584
- "--prune",
585
- "remove route files that no longer exist in the OpenAPI spec",
586
- )
587
- .option(
588
- "--spec <string>",
589
- "path or URL to OpenAPI document (alternative to the positional [openapi.yaml] argument)",
590
- )
591
- .option("--no-update-check", "disable the npm update check on startup")
592
- .option(
593
- "--no-validate-request",
594
- "disable request validation against the OpenAPI spec",
595
- )
596
- .option(
597
- "--no-validate-response",
598
- "disable response validation against the OpenAPI spec",
599
- )
600
- .option(
601
- "--config <path>",
602
- "path to a counterfact.yaml config file (default: counterfact.yaml in the current directory)",
603
- )
604
- .action(main)
605
- .parse(process.argv);
76
+ await runCli(process.argv);
package/bin/ts-loader.mjs CHANGED
@@ -10,6 +10,7 @@
10
10
  * uses the TypeScript convention of writing .js extensions in import paths
11
11
  * (which resolve to .ts files at authoring time). This loader bridges that gap.
12
12
  */
13
+ /* eslint-disable security/detect-non-literal-fs-filename -- loader checks file existence for Node-resolved internal module URLs only. */
13
14
 
14
15
  import { existsSync } from "node:fs";
15
16
  import { fileURLToPath } from "node:url";