counterfact 2.7.0 → 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 (57) hide show
  1. package/README.md +5 -160
  2. package/bin/README.md +39 -14
  3. package/bin/counterfact.js +18 -539
  4. package/bin/ts-loader.mjs +1 -0
  5. package/dist/api-runner.js +202 -0
  6. package/dist/app.js +102 -114
  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 +3 -3
  13. package/dist/msw.js +78 -0
  14. package/dist/repl/raw-http-client.js +22 -1
  15. package/dist/repl/repl.js +250 -63
  16. package/dist/repl/route-builder.js +68 -0
  17. package/dist/server/constants.js +8 -0
  18. package/dist/server/context-registry.js +54 -1
  19. package/dist/server/determine-module-kind.js +14 -0
  20. package/dist/server/dispatcher.js +46 -0
  21. package/dist/server/file-discovery.js +21 -9
  22. package/dist/server/is-proxy-enabled-for-path.js +12 -0
  23. package/dist/server/json-to-xml.js +10 -0
  24. package/dist/server/load-openapi-document.js +4 -11
  25. package/dist/server/module-dependency-graph.js +25 -0
  26. package/dist/server/module-loader.js +52 -21
  27. package/dist/server/module-tree.js +36 -0
  28. package/dist/server/openapi-document.js +69 -0
  29. package/dist/server/registry.js +89 -0
  30. package/dist/server/response-builder.js +15 -0
  31. package/dist/server/scenario-registry.js +26 -0
  32. package/dist/server/tools.js +27 -0
  33. package/dist/server/transpiler.js +24 -9
  34. package/dist/server/{admin-api-middleware.js → web-server/admin-api-middleware.js} +19 -9
  35. package/dist/server/web-server/create-koa-app.js +68 -0
  36. package/dist/server/web-server/openapi-middleware.js +34 -0
  37. package/dist/server/{koa-middleware.js → web-server/routes-middleware.js} +26 -6
  38. package/dist/typescript-generator/code-generator.js +118 -4
  39. package/dist/typescript-generator/coder.js +76 -0
  40. package/dist/typescript-generator/operation-coder.js +12 -4
  41. package/dist/typescript-generator/operation-type-coder.js +39 -4
  42. package/dist/typescript-generator/parameters-type-coder.js +2 -4
  43. package/dist/typescript-generator/prune.js +3 -1
  44. package/dist/typescript-generator/repository.js +77 -20
  45. package/dist/typescript-generator/requirement.js +69 -0
  46. package/dist/typescript-generator/{generate.js → scenario-file-generator.js} +99 -81
  47. package/dist/typescript-generator/script.js +70 -7
  48. package/dist/typescript-generator/specification.js +27 -0
  49. package/dist/util/ensure-directory-exists.js +8 -0
  50. package/dist/util/forward-slash-path.js +63 -0
  51. package/dist/util/load-config-file.js +2 -2
  52. package/dist/util/read-file.js +27 -2
  53. package/dist/util/runtime-can-execute-erasable-ts.js +12 -0
  54. package/dist/util/windows-escape.js +18 -0
  55. package/package.json +5 -4
  56. package/dist/server/create-koa-app.js +0 -42
  57. package/dist/server/openapi-middleware.js +0 -19
@@ -1,8 +1,18 @@
1
- import nodePath from "node:path";
2
1
  import createDebugger from "debug";
3
2
  import { format } from "prettier";
4
3
  import { escapePathForWindows } from "../util/windows-escape.js";
4
+ import { pathJoin, pathRelative, pathDirname, } from "../util/forward-slash-path.js";
5
5
  const debug = createDebugger("counterfact:typescript-generator:script");
6
+ /**
7
+ * Represents a single TypeScript file being assembled by the code generator.
8
+ *
9
+ * A `Script` accumulates exports, imports, and external imports contributed by
10
+ * {@link Coder} instances. Once all coders have resolved, {@link contents}
11
+ * formats the result with Prettier and returns the final source text.
12
+ *
13
+ * Scripts are created and retrieved through a {@link Repository} so that the
14
+ * same module path always maps to the same `Script` instance.
15
+ */
6
16
  export class Script {
7
17
  repository;
8
18
  comments;
@@ -22,6 +32,10 @@ export class Script {
22
32
  this.typeCache = new Map();
23
33
  this.path = path;
24
34
  }
35
+ /**
36
+ * A `"../"` path fragment that points from this script's directory back to
37
+ * the repository root, used to resolve relative import paths.
38
+ */
25
39
  get relativePathToBase() {
26
40
  return this.path
27
41
  .split("/")
@@ -29,6 +43,13 @@ export class Script {
29
43
  .map(() => "..")
30
44
  .join("/");
31
45
  }
46
+ /**
47
+ * Picks the first name from `coder.names()` that is not already used as an
48
+ * import in this script, ensuring export/import name uniqueness.
49
+ *
50
+ * @param coder - The coder needing a name.
51
+ * @throws When all 100 candidate names are already taken.
52
+ */
32
53
  firstUniqueName(coder) {
33
54
  for (const name of coder.names()) {
34
55
  if (!this.imports.has(name)) {
@@ -37,6 +58,17 @@ export class Script {
37
58
  }
38
59
  throw new Error(`could not find a unique name for ${coder.id}`);
39
60
  }
61
+ /**
62
+ * Registers an export for `coder` in this script and returns the export name.
63
+ *
64
+ * If the same coder has already been exported (cache hit), the previously
65
+ * assigned name is returned without creating a duplicate.
66
+ *
67
+ * @param coder - The coder to export.
68
+ * @param isType - Emit `export type` instead of `export const`.
69
+ * @param isDefault - Emit `export default` instead of a named export.
70
+ * @returns The name under which the coder is exported.
71
+ */
40
72
  export(coder, isType = false, isDefault = false) {
41
73
  const cacheKey = isDefault ? "default" : `${coder.id}:${isType}`;
42
74
  if (this.cache.has(cacheKey)) {
@@ -75,6 +107,16 @@ export class Script {
75
107
  exportDefault(coder, isType = false) {
76
108
  this.export(coder, isType, true);
77
109
  }
110
+ /**
111
+ * Registers an import of `coder` from its owning module and returns the
112
+ * local alias used in this script.
113
+ *
114
+ * The coder is also exported from its home module as a side effect.
115
+ *
116
+ * @param coder - The coder to import.
117
+ * @param isType - Use a `import type` declaration.
118
+ * @param isDefault - Import the default export rather than a named export.
119
+ */
78
120
  import(coder, isType = false, isDefault = false) {
79
121
  debug("import coder: %s", coder.id);
80
122
  const modulePath = coder.modulePath();
@@ -103,24 +145,42 @@ export class Script {
103
145
  importDefault(coder, isType = false) {
104
146
  return this.import(coder, isType, true);
105
147
  }
148
+ /**
149
+ * Registers an import from an external npm package or absolute module path
150
+ * (not managed by the repository).
151
+ *
152
+ * @param name - The local binding name.
153
+ * @param modulePath - The module specifier (e.g. `"counterfact-types/index"`).
154
+ * @param isType - Use a `import type` declaration.
155
+ * @returns `name` (for convenience in method chaining).
156
+ */
106
157
  importExternal(name, modulePath, isType = false) {
107
158
  this.externalImport.set(name, { isType, modulePath });
108
159
  return name;
109
160
  }
161
+ /**
162
+ * Convenience wrapper that calls {@link importExternal} with `isType = true`.
163
+ */
110
164
  importExternalType(name, modulePath) {
111
165
  return this.importExternal(name, modulePath, true);
112
166
  }
167
+ /**
168
+ * Imports a type from the shared `counterfact-types/index.ts` module,
169
+ * resolving the path relative to this script's location in the repository.
170
+ *
171
+ * @param name - The type name to import (e.g. `"WideOperationArgument"`).
172
+ */
113
173
  importSharedType(name) {
114
- return this.importExternal(name, nodePath
115
- .join(this.relativePathToBase, "counterfact-types/index.ts")
116
- .replaceAll("\\", "/"), true);
174
+ return this.importExternal(name, pathJoin(this.relativePathToBase, "counterfact-types/index.ts"), true);
117
175
  }
118
176
  exportType(coder) {
119
177
  return this.export(coder, true);
120
178
  }
179
+ /** `true` while at least one export promise is still pending. */
121
180
  isInProgress() {
122
181
  return Array.from(this.exports.values()).some((exportStatement) => !exportStatement.done);
123
182
  }
183
+ /** Returns a promise that resolves when all pending export promises settle. */
124
184
  finished() {
125
185
  return Promise.all(Array.from(this.exports.values(), (value) => value.promise));
126
186
  }
@@ -129,9 +189,7 @@ export class Script {
129
189
  }
130
190
  importStatements() {
131
191
  return Array.from(this.imports, ([name, { isDefault, isType, script }]) => {
132
- const resolvedPath = escapePathForWindows(nodePath
133
- .relative(nodePath.dirname(this.path).replaceAll("\\", "/"), script.path.replace(/\.ts$/u, ".js"))
134
- .replaceAll("\\", "/"));
192
+ const resolvedPath = escapePathForWindows(pathRelative(pathDirname(this.path), script.path.replace(/\.ts$/u, ".js")));
135
193
  return `import${isType ? " type" : ""} ${isDefault ? name : `{ ${name} }`} from "${resolvedPath.includes("../") ? "" : "./"}${resolvedPath}";`;
136
194
  });
137
195
  }
@@ -150,6 +208,11 @@ export class Script {
150
208
  return `${jsdoc}${beforeExport}export ${keyword} ${name ?? ""}${typeAnnotation} = ${code};`;
151
209
  });
152
210
  }
211
+ /**
212
+ * Formats the fully assembled script source with Prettier and returns it.
213
+ *
214
+ * All pending export promises are awaited before formatting.
215
+ */
153
216
  contents() {
154
217
  return format([
155
218
  this.comments.map((comment) => `// ${comment}`).join("\n"),
@@ -2,6 +2,14 @@ import { bundle } from "@apidevtools/json-schema-ref-parser";
2
2
  import createDebug from "debug";
3
3
  import { Requirement } from "./requirement.js";
4
4
  const debug = createDebug("counterfact:typescript-generator:specification");
5
+ /**
6
+ * Represents a fully dereferenced OpenAPI specification as a navigable tree
7
+ * of {@link Requirement} nodes.
8
+ *
9
+ * Use {@link Specification.fromFile} to load a spec from disk or a URL; the
10
+ * static method bundles external `$ref` references into a single in-memory
11
+ * object before constructing the tree.
12
+ */
5
13
  export class Specification {
6
14
  cache;
7
15
  rootRequirement;
@@ -11,15 +19,34 @@ export class Specification {
11
19
  this.rootRequirement = rootRequirement;
12
20
  }
13
21
  }
22
+ /**
23
+ * Loads the OpenAPI document at `urlOrPath`, bundles all external `$ref`
24
+ * references, and returns a fully initialised {@link Specification}.
25
+ *
26
+ * @param urlOrPath - A local file path or HTTP(S) URL.
27
+ * @throws When the document cannot be found or parsed.
28
+ */
14
29
  static async fromFile(urlOrPath) {
15
30
  const specification = new Specification();
16
31
  await specification.load(urlOrPath);
17
32
  return specification;
18
33
  }
34
+ /**
35
+ * Returns the {@link Requirement} at `url` (a JSON Pointer such as
36
+ * `"#/paths"`).
37
+ *
38
+ * @param url - A JSON Pointer string (must start with `"#/"`).
39
+ */
19
40
  getRequirement(url) {
20
41
  debug("getting requirement at %s", url);
21
42
  return this.rootRequirement.select(url.slice(2));
22
43
  }
44
+ /**
45
+ * Loads (or reloads) the specification from `urlOrPath`.
46
+ *
47
+ * @param urlOrPath - A local file path or HTTP(S) URL.
48
+ * @throws When the document cannot be found or parsed.
49
+ */
23
50
  async load(urlOrPath) {
24
51
  try {
25
52
  this.rootRequirement = new Requirement((await bundle(urlOrPath)), urlOrPath, this);
@@ -1,5 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import nodePath from "node:path";
3
+ /* eslint-disable security/detect-non-literal-fs-filename -- helper creates parent directories for caller-provided output file paths. */
4
+ /**
5
+ * Synchronously ensures that the directory containing `filePath` exists,
6
+ * creating it (and any missing ancestors) if necessary.
7
+ *
8
+ * @param filePath - Path to a file; the *directory* part of this path is
9
+ * created, not the file itself.
10
+ */
3
11
  export function ensureDirectoryExists(filePath) {
4
12
  const directory = nodePath.dirname(filePath);
5
13
  try {
@@ -0,0 +1,63 @@
1
+ import nodePath from "node:path";
2
+ /**
3
+ * Converts a file-system path to use forward slashes as separators.
4
+ *
5
+ * On Windows, `node:path` methods such as `path.join` and `path.resolve`
6
+ * return paths with backslash separators. Many parts of Counterfact
7
+ * (chokidar watchers, ES module import specifiers, URL routing) require
8
+ * forward slashes. This function centralises that normalisation and returns
9
+ * a {@link ForwardSlashPath} branded type so that call sites that demand a
10
+ * forward-slash path are statically enforced.
11
+ *
12
+ * @param path - Any file-system path string.
13
+ * @returns The same path with every `\` replaced by `/`.
14
+ */
15
+ export function toForwardSlashPath(path) {
16
+ return path.replaceAll("\\", "/");
17
+ }
18
+ /**
19
+ * Joins path segments and returns a {@link ForwardSlashPath} with forward
20
+ * slashes regardless of the host operating system.
21
+ *
22
+ * Equivalent to `toForwardSlashPath(nodePath.join(...paths))`.
23
+ *
24
+ * @param paths - Path segments to join.
25
+ * @returns The joined path normalised to forward slashes.
26
+ */
27
+ export function pathJoin(...paths) {
28
+ return toForwardSlashPath(nodePath.join(...paths));
29
+ }
30
+ /**
31
+ * Returns the relative path from `from` to `to` using forward slashes.
32
+ *
33
+ * Equivalent to `toForwardSlashPath(nodePath.relative(from, to))`.
34
+ *
35
+ * @param from - The starting path.
36
+ * @param to - The destination path.
37
+ * @returns The relative path normalised to forward slashes.
38
+ */
39
+ export function pathRelative(from, to) {
40
+ return toForwardSlashPath(nodePath.relative(from, to));
41
+ }
42
+ /**
43
+ * Returns the directory portion of a path using forward slashes.
44
+ *
45
+ * Equivalent to `toForwardSlashPath(nodePath.dirname(path))`.
46
+ *
47
+ * @param path - The file path.
48
+ * @returns The directory portion normalised to forward slashes.
49
+ */
50
+ export function pathDirname(path) {
51
+ return toForwardSlashPath(nodePath.dirname(path));
52
+ }
53
+ /**
54
+ * Resolves a sequence of paths into an absolute path using forward slashes.
55
+ *
56
+ * Equivalent to `toForwardSlashPath(nodePath.resolve(...paths))`.
57
+ *
58
+ * @param paths - Path segments to resolve.
59
+ * @returns The resolved absolute path normalised to forward slashes.
60
+ */
61
+ export function pathResolve(...paths) {
62
+ return toForwardSlashPath(nodePath.resolve(...paths));
63
+ }
@@ -1,5 +1,5 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { load as loadYaml } from "js-yaml";
2
+ import { readFile } from "./read-file.js";
3
3
  function kebabToCamel(str) {
4
4
  return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
5
5
  }
@@ -17,7 +17,7 @@ function normalizeKeys(obj) {
17
17
  export async function loadConfigFile(configPath, required = false) {
18
18
  let content;
19
19
  try {
20
- content = await readFile(configPath, "utf8");
20
+ content = await readFile(configPath);
21
21
  }
22
22
  catch (error) {
23
23
  if (typeof error === "object" &&
@@ -1,12 +1,37 @@
1
1
  import fs from "node:fs/promises";
2
+ import nodePath from "node:path";
2
3
  import nodeFetch from "node-fetch";
4
+ function normalizeLocalPath(path) {
5
+ if (path.includes("\0")) {
6
+ throw new Error("File path cannot contain NUL bytes.");
7
+ }
8
+ return nodePath.resolve(path);
9
+ }
10
+ /**
11
+ * Reads the content of a file or URL and returns it as a UTF-8 string.
12
+ *
13
+ * Accepts three kinds of inputs:
14
+ * - **HTTP(S) URLs** — fetches with `node-fetch`.
15
+ * - **`file://` URLs** — reads via the Node.js `fs` module.
16
+ * - **File system paths** — reads via the Node.js `fs` module.
17
+ *
18
+ * @param urlOrPath - A URL string or file-system path.
19
+ * @returns The file contents as a string.
20
+ */
3
21
  export async function readFile(urlOrPath) {
4
22
  if (urlOrPath.startsWith("http")) {
5
23
  const response = await nodeFetch(urlOrPath);
6
24
  return await response.text();
7
25
  }
8
26
  if (urlOrPath.startsWith("file")) {
9
- return await fs.readFile(new URL(urlOrPath), "utf8");
27
+ const fileUrl = new URL(urlOrPath);
28
+ if (fileUrl.protocol !== "file:") {
29
+ throw new Error(`Unsupported URL protocol for file read: ${fileUrl.protocol}`);
30
+ }
31
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- file URL is parsed and protocol-validated immediately above.
32
+ return await fs.readFile(fileUrl, "utf8");
10
33
  }
11
- return await fs.readFile(urlOrPath, "utf8");
34
+ const normalizedPath = normalizeLocalPath(urlOrPath);
35
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- path is normalized and NUL-byte validated before filesystem access.
36
+ return await fs.readFile(normalizedPath, "utf8");
12
37
  }
@@ -2,6 +2,18 @@ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
+ /* eslint-disable security/detect-non-literal-fs-filename -- runtime probe only writes fixed filenames in a fresh temporary directory. */
6
+ /**
7
+ * Probes the current Node.js runtime to determine whether it can execute
8
+ * TypeScript source files directly (via `--experimental-strip-types` or
9
+ * equivalent).
10
+ *
11
+ * The check works by writing a tiny TypeScript module to a temporary directory
12
+ * and attempting to import it. If the import succeeds and returns the
13
+ * expected value, the runtime supports native TypeScript execution.
14
+ *
15
+ * @returns `true` when the runtime can execute `.ts` files natively.
16
+ */
5
17
  export async function runtimeCanExecuteErasableTs() {
6
18
  const dir = mkdtempSync(join(tmpdir(), "ts-probe-"));
7
19
  // helper.ts is imported via .js extension — the TypeScript convention used
@@ -1,5 +1,16 @@
1
1
  const UNICODE_RATIO_SYMBOL = "∶"; // U+2236
2
2
  const REGULAR_COLON = ":";
3
+ /**
4
+ * Escapes a Windows absolute path for use in an ES module import specifier.
5
+ *
6
+ * On Windows, drive letters produce colons (e.g. `C:\path`) which are invalid
7
+ * in URL-like import paths. The drive separator colon and any additional
8
+ * colons in the path are replaced with the Unicode ratio symbol `∶` (U+2236),
9
+ * which is visually identical but safe in import specifiers.
10
+ *
11
+ * @param path - The file-system path to escape.
12
+ * @returns An escaped path safe for use in an import specifier.
13
+ */
3
14
  export function escapePathForWindows(path) {
4
15
  if (path.at(1) === ":") {
5
16
  return (path.slice(0, 2) +
@@ -7,6 +18,13 @@ export function escapePathForWindows(path) {
7
18
  }
8
19
  return path.replaceAll(REGULAR_COLON, UNICODE_RATIO_SYMBOL);
9
20
  }
21
+ /**
22
+ * Reverses the transformation applied by {@link escapePathForWindows},
23
+ * converting `∶` back to `:`.
24
+ *
25
+ * @param path - A previously escaped path.
26
+ * @returns The original unescaped path.
27
+ */
10
28
  export function unescapePathForWindows(path) {
11
29
  return path.replaceAll(UNICODE_RATIO_SYMBOL, REGULAR_COLON);
12
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "Generate a TypeScript-based mock server from an OpenAPI spec in seconds — with stateful routes, hot reload, and REPL support.",
5
5
  "type": "module",
6
6
  "main": "./dist/app.js",
@@ -21,9 +21,9 @@
21
21
  "license": "MIT",
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "git+https://github.com/pmcelhaney/counterfact.git"
24
+ "url": "git+https://github.com/counterfact/api-simulator.git"
25
25
  },
26
- "bugs": "https://github.com/pmcelhaney/counterfact/issues",
26
+ "bugs": "https://github.com/counterfact/api-simulator/issues",
27
27
  "homepage": "https://counterfact.dev",
28
28
  "funding": {
29
29
  "type": "github",
@@ -76,7 +76,8 @@
76
76
  "lint:quickfix": "eslint --fix . eslint --fix demo-ts --rule=\"import/namespace: 0,etc/no-deprecated:0,import/no-cycle:0,no-explicit-type-exports/no-explicit-type-exports:0,import/no-deprecated:0,import/no-self-import:0,import/default:0,import/no-named-as-default:0\" --ignore-pattern dist --ignore-pattern out",
77
77
  "go:petstore": "yarn build && yarn counterfact https://petstore3.swagger.io/api/v3/openapi.json out",
78
78
  "go:petstore2": "yarn build && yarn counterfact https://petstore.swagger.io/v2/swagger.json out",
79
- "go:example": "yarn build && node ./bin/counterfact.js ./openapi-example.yaml out",
79
+ "go:example": "yarn build && node ./bin/counterfact.js ./test/fixtures/openapi/example.yaml out",
80
+ "go:multiple-apis": "yarn build && node ./bin/counterfact.js --config ./test/fixtures/config/multiple-apis.yaml",
80
81
  "counterfact": "./bin/counterfact.js",
81
82
  "postinstall": "patch-package"
82
83
  },
@@ -1,42 +0,0 @@
1
- import createDebug from "debug";
2
- import Koa from "koa";
3
- import bodyParser from "koa-bodyparser";
4
- import { koaSwagger } from "koa2-swagger-ui";
5
- import { adminApiMiddleware } from "./admin-api-middleware.js";
6
- import { openapiMiddleware } from "./openapi-middleware.js";
7
- const debug = createDebug("counterfact:server:create-koa-app");
8
- export function createKoaApp(registry, koaMiddleware, config, contextRegistry) {
9
- const app = new Koa();
10
- app.use(openapiMiddleware(config.openApiPath, `//localhost:${config.port}${config.routePrefix}`));
11
- app.use(koaSwagger({
12
- routePrefix: "/counterfact/swagger",
13
- swaggerOptions: {
14
- url: "/counterfact/openapi",
15
- },
16
- }));
17
- if (config.startAdminApi) {
18
- app.use(adminApiMiddleware(registry, contextRegistry, config));
19
- }
20
- debug("basePath: %s", config.basePath);
21
- debug("routes", registry.routes);
22
- app.use(async (ctx, next) => {
23
- if (ctx.URL.pathname === "/counterfact") {
24
- ctx.redirect("/counterfact/swagger");
25
- return;
26
- }
27
- await next();
28
- });
29
- app.use(bodyParser());
30
- app.use(async (ctx, next) => {
31
- await next();
32
- if (ctx.body !== null &&
33
- ctx.body !== undefined &&
34
- typeof ctx.body === "object" &&
35
- !Buffer.isBuffer(ctx.body)) {
36
- ctx.body = JSON.stringify(ctx.body, null, 2);
37
- ctx.type = "application/json";
38
- }
39
- });
40
- app.use(koaMiddleware);
41
- return app;
42
- }
@@ -1,19 +0,0 @@
1
- import { bundle } from "@apidevtools/json-schema-ref-parser";
2
- import { dump } from "js-yaml";
3
- export function openapiMiddleware(openApiPath, url) {
4
- return async (ctx, next) => {
5
- if (ctx.URL.pathname === "/counterfact/openapi") {
6
- const openApiDocument = (await bundle(openApiPath));
7
- openApiDocument.servers ??= [];
8
- openApiDocument.servers.unshift({
9
- description: "Counterfact",
10
- url,
11
- });
12
- // OpenApi 2 support:
13
- openApiDocument.host = url;
14
- ctx.body = dump(openApiDocument);
15
- return;
16
- }
17
- await next();
18
- };
19
- }