counterfact 2.6.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +14 -207
  2. package/bin/README.md +24 -4
  3. package/bin/counterfact.js +54 -3
  4. package/dist/app.js +81 -28
  5. package/dist/counterfact-types/cookie-options.js +1 -0
  6. package/dist/counterfact-types/counterfact-response.js +7 -0
  7. package/dist/counterfact-types/example-names.js +1 -0
  8. package/dist/counterfact-types/example.js +1 -0
  9. package/dist/counterfact-types/generic-response-builder.js +1 -0
  10. package/dist/counterfact-types/http-status-code.js +1 -0
  11. package/dist/counterfact-types/if-has-key.js +1 -0
  12. package/dist/counterfact-types/index.js +0 -1
  13. package/dist/counterfact-types/maybe-promise.js +1 -0
  14. package/dist/counterfact-types/media-type.js +1 -0
  15. package/dist/counterfact-types/omit-all.js +1 -0
  16. package/dist/counterfact-types/omit-value-when-never.js +1 -0
  17. package/dist/counterfact-types/open-api-content.js +1 -0
  18. package/dist/counterfact-types/open-api-operation.js +1 -0
  19. package/dist/counterfact-types/open-api-parameters.js +1 -0
  20. package/dist/counterfact-types/open-api-response.js +1 -0
  21. package/dist/counterfact-types/random-function.js +1 -0
  22. package/dist/counterfact-types/response-builder-factory.js +1 -0
  23. package/dist/counterfact-types/response-builder.js +1 -0
  24. package/dist/counterfact-types/wide-operation-argument.js +1 -0
  25. package/dist/counterfact-types/wide-response-builder.js +1 -0
  26. package/dist/migrate/update-route-types.js +2 -3
  27. package/dist/repl/raw-http-client.js +19 -0
  28. package/dist/repl/repl.js +116 -4
  29. package/dist/repl/route-builder.js +68 -0
  30. package/dist/server/constants.js +8 -0
  31. package/dist/server/context-registry.js +70 -1
  32. package/dist/server/counterfact-types/cookie-options.ts +14 -0
  33. package/dist/server/counterfact-types/counterfact-response.ts +15 -0
  34. package/dist/server/counterfact-types/example-names.ts +13 -0
  35. package/dist/server/counterfact-types/example.ts +10 -0
  36. package/dist/server/counterfact-types/generic-response-builder.ts +164 -0
  37. package/dist/server/counterfact-types/http-status-code.ts +62 -0
  38. package/dist/server/counterfact-types/if-has-key.ts +19 -0
  39. package/dist/server/counterfact-types/index.ts +20 -338
  40. package/dist/server/counterfact-types/maybe-promise.ts +6 -0
  41. package/dist/server/counterfact-types/media-type.ts +6 -0
  42. package/dist/server/counterfact-types/omit-all.ts +11 -0
  43. package/dist/server/counterfact-types/omit-value-when-never.ts +11 -0
  44. package/dist/server/counterfact-types/open-api-content.ts +8 -0
  45. package/dist/server/counterfact-types/open-api-operation.ts +36 -0
  46. package/dist/server/counterfact-types/open-api-parameters.ts +16 -0
  47. package/dist/server/counterfact-types/open-api-response.ts +22 -0
  48. package/dist/server/counterfact-types/random-function.ts +9 -0
  49. package/dist/server/counterfact-types/response-builder-factory.ts +16 -0
  50. package/dist/server/counterfact-types/response-builder.ts +31 -0
  51. package/dist/server/counterfact-types/wide-operation-argument.ts +17 -0
  52. package/dist/server/counterfact-types/wide-response-builder.ts +26 -0
  53. package/dist/server/create-koa-app.js +28 -24
  54. package/dist/server/determine-module-kind.js +13 -0
  55. package/dist/server/dispatcher.js +64 -5
  56. package/dist/server/file-discovery.js +20 -9
  57. package/dist/server/is-proxy-enabled-for-path.js +12 -0
  58. package/dist/server/json-to-xml.js +11 -1
  59. package/dist/server/koa-middleware.js +25 -2
  60. package/dist/server/load-openapi-document.js +6 -0
  61. package/dist/server/module-dependency-graph.js +25 -0
  62. package/dist/server/module-loader.js +112 -17
  63. package/dist/server/module-tree.js +36 -0
  64. package/dist/server/openapi-document.js +69 -0
  65. package/dist/server/openapi-middleware.js +34 -5
  66. package/dist/server/openapi-watcher.js +35 -0
  67. package/dist/server/registry.js +89 -0
  68. package/dist/server/request-validator.js +3 -7
  69. package/dist/server/response-builder.js +18 -0
  70. package/dist/server/response-validator.js +58 -0
  71. package/dist/server/scenario-registry.js +55 -0
  72. package/dist/server/tools.js +29 -2
  73. package/dist/server/transpiler.js +23 -9
  74. package/dist/typescript-generator/code-generator.js +117 -4
  75. package/dist/typescript-generator/coder.js +80 -2
  76. package/dist/typescript-generator/operation-coder.js +13 -5
  77. package/dist/typescript-generator/operation-type-coder.js +40 -53
  78. package/dist/typescript-generator/parameters-type-coder.js +2 -4
  79. package/dist/typescript-generator/prune.js +2 -1
  80. package/dist/typescript-generator/read-only-comments.js +1 -1
  81. package/dist/typescript-generator/repository.js +76 -20
  82. package/dist/typescript-generator/requirement.js +77 -1
  83. package/dist/typescript-generator/reserved-words.js +50 -0
  84. package/dist/typescript-generator/scenario-file-generator.js +235 -0
  85. package/dist/typescript-generator/script.js +70 -7
  86. package/dist/typescript-generator/specification.js +27 -0
  87. package/dist/util/ensure-directory-exists.js +7 -0
  88. package/dist/util/forward-slash-path.js +63 -0
  89. package/dist/util/load-config-file.js +44 -0
  90. package/dist/util/read-file.js +11 -0
  91. package/dist/util/runtime-can-execute-erasable-ts.js +11 -0
  92. package/dist/util/windows-escape.js +18 -0
  93. package/package.json +9 -10
  94. package/dist/client/README.md +0 -14
  95. package/dist/client/index.html.hbs +0 -244
  96. package/dist/client/rapi-doc.html.hbs +0 -36
  97. package/dist/server/page-middleware.js +0 -23
  98. package/dist/typescript-generator/generate.js +0 -63
@@ -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
+ }
@@ -0,0 +1,44 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { load as loadYaml } from "js-yaml";
3
+ function kebabToCamel(str) {
4
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
5
+ }
6
+ function normalizeKeys(obj) {
7
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [kebabToCamel(key), value]));
8
+ }
9
+ /**
10
+ * Loads and parses a counterfact YAML config file.
11
+ *
12
+ * @param configPath - Absolute or relative path to the config file.
13
+ * @param required - When true, throws if the file does not exist.
14
+ * When false (default), returns an empty object for missing files.
15
+ * @returns A plain object of config keys (camelCase) to values.
16
+ */
17
+ export async function loadConfigFile(configPath, required = false) {
18
+ let content;
19
+ try {
20
+ content = await readFile(configPath, "utf8");
21
+ }
22
+ catch (error) {
23
+ if (typeof error === "object" &&
24
+ error !== null &&
25
+ "code" in error &&
26
+ error.code === "ENOENT") {
27
+ if (required) {
28
+ throw new Error(`Config file not found: ${configPath}`, {
29
+ cause: error,
30
+ });
31
+ }
32
+ return {};
33
+ }
34
+ throw error;
35
+ }
36
+ const parsed = loadYaml(content);
37
+ if (parsed === null || parsed === undefined) {
38
+ return {};
39
+ }
40
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
41
+ throw new Error(`Config file must be a YAML object (mapping): ${configPath}`);
42
+ }
43
+ return normalizeKeys(parsed);
44
+ }
@@ -1,5 +1,16 @@
1
1
  import fs from "node:fs/promises";
2
2
  import nodeFetch from "node-fetch";
3
+ /**
4
+ * Reads the content of a file or URL and returns it as a UTF-8 string.
5
+ *
6
+ * Accepts three kinds of inputs:
7
+ * - **HTTP(S) URLs** — fetches with `node-fetch`.
8
+ * - **`file://` URLs** — reads via the Node.js `fs` module.
9
+ * - **File system paths** — reads via the Node.js `fs` module.
10
+ *
11
+ * @param urlOrPath - A URL string or file-system path.
12
+ * @returns The file contents as a string.
13
+ */
3
14
  export async function readFile(urlOrPath) {
4
15
  if (urlOrPath.startsWith("http")) {
5
16
  const response = await nodeFetch(urlOrPath);
@@ -2,6 +2,17 @@ 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
+ /**
6
+ * Probes the current Node.js runtime to determine whether it can execute
7
+ * TypeScript source files directly (via `--experimental-strip-types` or
8
+ * equivalent).
9
+ *
10
+ * The check works by writing a tiny TypeScript module to a temporary directory
11
+ * and attempting to import it. If the import succeeds and returns the
12
+ * expected value, the runtime supports native TypeScript execution.
13
+ *
14
+ * @returns `true` when the runtime can execute `.ts` files natively.
15
+ */
5
16
  export async function runtimeCanExecuteErasableTs() {
6
17
  const dir = mkdtempSync(join(tmpdir(), "ts-probe-"));
7
18
  // 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.6.0",
3
+ "version": "2.8.1",
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": "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",
@@ -56,7 +56,7 @@
56
56
  "node": ">=22"
57
57
  },
58
58
  "bin": {
59
- "counterfact": "./bin/counterfact.js"
59
+ "counterfact": "bin/counterfact.js"
60
60
  },
61
61
  "files": [
62
62
  "bin",
@@ -67,7 +67,7 @@
67
67
  "test": "yarn node --experimental-vm-modules ./node_modules/jest-cli/bin/jest --testPathIgnorePatterns=black-box",
68
68
  "test:black-box": "rimraf dist && yarn build && python3 -m pytest test-black-box/ -v",
69
69
  "test:tsd": "tsd --typings ./dist/server/counterfact-types/index.ts --files ./test/**/*.test-d.ts",
70
- "build": "rm -rf dist && tsc && copyfiles -f \"src/client/**\" dist/client && copyfiles -f \"src/counterfact-types/*.ts\" dist/server/counterfact-types && copyfiles -f \"src/server/*.cjs\" dist/server",
70
+ "build": "rm -rf dist && tsc && copyfiles -f \"src/counterfact-types/*.ts\" dist/server/counterfact-types && copyfiles -f \"src/server/*.cjs\" dist/server",
71
71
  "prepack": "yarn build",
72
72
  "release": "npx changeset publish",
73
73
  "prepare": "husky install",
@@ -76,7 +76,7 @@
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
80
  "counterfact": "./bin/counterfact.js",
81
81
  "postinstall": "patch-package"
82
82
  },
@@ -84,7 +84,7 @@
84
84
  "@changesets/cli": "2.30.0",
85
85
  "@eslint/js": "10.0.1",
86
86
  "@jest/globals": "^30.3.0",
87
- "@swc/core": "1.15.21",
87
+ "@swc/core": "1.15.24",
88
88
  "@swc/jest": "0.2.39",
89
89
  "@testing-library/dom": "10.4.1",
90
90
  "@types/debug": "^4.1.12",
@@ -98,7 +98,7 @@
98
98
  "@typescript-eslint/eslint-plugin": "^8.58.0",
99
99
  "@typescript-eslint/parser": "^8.58.0",
100
100
  "copyfiles": "2.4.1",
101
- "eslint": "10.1.0",
101
+ "eslint": "10.2.0",
102
102
  "eslint-formatter-github-annotations": "0.1.0",
103
103
  "eslint-import-resolver-typescript": "4.4.4",
104
104
  "eslint-plugin-etc": "2.0.3",
@@ -126,12 +126,11 @@
126
126
  "@apidevtools/json-schema-ref-parser": "13.0.5",
127
127
  "@hapi/accept": "6.0.3",
128
128
  "@types/json-schema": "7.0.15",
129
- "ajv": "6.14.0",
129
+ "ajv": "8.18.0",
130
130
  "chokidar": "5.0.0",
131
131
  "commander": "14.0.3",
132
132
  "debug": "4.4.3",
133
133
  "fs-extra": "11.3.4",
134
- "handlebars": "4.7.9",
135
134
  "http-terminator": "3.2.0",
136
135
  "js-yaml": "4.1.1",
137
136
  "json-schema-faker": "0.6.0",
@@ -1,14 +0,0 @@
1
- # `src/client/` — Built-in UI Templates
2
-
3
- This directory contains [Handlebars](https://handlebarsjs.com/) (`.hbs`) templates that are rendered by `page-middleware.ts` to produce the browser-facing pages bundled with Counterfact.
4
-
5
- ## Files
6
-
7
- | File | Description |
8
- |---|---|
9
- | `index.html.hbs` | Template for the Counterfact dashboard (`/counterfact/`); lists registered routes and shows server status |
10
- | `rapi-doc.html.hbs` | Template for the interactive API documentation page (`/counterfact/swagger/`); embeds the [RapiDoc](https://rapidocweb.com/) viewer and adds VSCode "open file" links |
11
-
12
- ## How It Works
13
-
14
- When a request arrives for `/counterfact/` or `/counterfact/swagger/`, `page-middleware.ts` compiles the appropriate template with runtime data (routes, port, base path, etc.) and sends the resulting HTML to the browser. No build step is required; templates are rendered on the fly.
@@ -1,244 +0,0 @@
1
- <!--
2
- This code is hideous. I'm trying to get a feel for the layout and it's easier
3
- for me to push pixels in devtools than use a real tool like Figma. I'll clean
4
- it up later. In the mean time, I welcome pull requests. :)
5
- --->
6
-
7
- <!DOCTYPE html>
8
- <html lang="en">
9
- <head>
10
- <title>Counterfact</title>
11
- <meta charset="utf-8">
12
- <meta name="description" content="Counterfact Dashboard" />
13
- <meta name="viewport" content="width=device-width, initial-scale=1" />
14
-
15
- <style type="text/css">
16
- body {
17
- font-family: Helvetica, Arial, sans-serif;
18
- max-width: 800px;
19
- margin: 0 auto;
20
- background-color: white;
21
- color: black;
22
- }
23
-
24
- a {
25
- color: #fff;
26
- }
27
-
28
- h1 {
29
- text-align: center;
30
- }
31
-
32
- table {
33
- width: 100%;
34
- border-collapse: collapse;
35
- }
36
-
37
- .tilt {
38
- width: 1.5em;
39
- position: relative;
40
- padding-top: 75px;
41
- }
42
-
43
- .tilt > * {
44
- display: block;
45
- position: absolute;
46
- transform: rotate(-90deg);
47
- transform-origin: left;
48
- text-align: center;
49
- bottom: 0;
50
- left: 20px;
51
-
52
- }
53
-
54
- thead {
55
- position: sticky;
56
- top: 0;
57
- }
58
-
59
- th.path > * {
60
- visibility: hidden;
61
- }
62
-
63
- td.path {
64
- font-family: monospace;
65
- font-size: 1.4em;
66
- }
67
-
68
- .path {
69
- text-align: left;
70
- padding-left: 20px;
71
- }
72
-
73
- .method-identifier {
74
- text-align: center;
75
- font-size: 1.5em;
76
- font-weight: bold;
77
- color: #333;
78
- padding: 0 0.5em;
79
- }
80
-
81
- tbody tr:hover {
82
- background-color: #ccc;
83
- }
84
-
85
- .path a {
86
- color: black;
87
- text-decoration: none;
88
- }
89
-
90
-
91
- .buttons {
92
- position: relative;
93
- display: flex;
94
- justify-content: center;
95
- top: -40px;
96
- left: 72px;
97
-
98
- }
99
- .buttons > a {
100
- display: inline-block;
101
- margin: 0 0.5em;
102
- padding: 0.5em 1em;
103
- background-color: #0071b5;
104
- border-radius: 5px;
105
- color: white;
106
- text-decoration: none;
107
-
108
- }
109
-
110
- #paths-container {
111
- margin-top: -35px;
112
- }
113
-
114
- </style>
115
- <body>
116
-
117
-
118
-
119
- <h1>
120
-
121
- <svg
122
- width="616.38678"
123
- height="104.03941"
124
- viewBox="0 0 163.08567 27.527093"
125
- version="1.1"
126
- id="svg1"
127
- inkscape:version="1.3.2 (091e20e, 2023-11-25)"
128
- sodipodi:docname="counterfact.svg"
129
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
130
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
131
- xmlns="http://www.w3.org/2000/svg"
132
- xmlns:svg="http://www.w3.org/2000/svg">
133
- <sodipodi:namedview
134
- id="namedview1"
135
- pagecolor="#ffffff"
136
- bordercolor="#000000"
137
- borderopacity="0.25"
138
- inkscape:showpageshadow="2"
139
- inkscape:pageopacity="0.0"
140
- inkscape:pagecheckerboard="0"
141
- inkscape:deskcolor="#d1d1d1"
142
- inkscape:document-units="mm"
143
- inkscape:zoom="1.4873456"
144
- inkscape:cx="308.93964"
145
- inkscape:cy="100.17847"
146
- inkscape:window-width="1728"
147
- inkscape:window-height="1051"
148
- inkscape:window-x="0"
149
- inkscape:window-y="38"
150
- inkscape:window-maximized="1"
151
- inkscape:current-layer="svg1" />
152
- <defs
153
- id="defs1" />
154
- <g
155
- id="g6"
156
- transform="matrix(1.0103886,0,0,1.0103886,-29.555241,-50.917297)">
157
- <g
158
- id="g1"
159
- transform="matrix(0.27630185,0,0,0.27630185,16.871974,36.385623)">
160
- <polygon
161
- points="155.19615,103 130.19615,146.30127 105.19615,103 "
162
- fill="#f58e00"
163
- id="polygon1" />
164
- <polygon
165
- points="125,149.30127 75,149.30127 100,106 "
166
- fill="#0071b5"
167
- id="polygon2" />
168
- <polygon
169
- points="69.803848,146.30127 44.803848,103 94.803848,103 "
170
- fill="#0071b5"
171
- id="polygon3" />
172
- <polygon
173
- points="44.803848,97 69.803848,53.69873 94.803848,97 "
174
- fill="#0071b5"
175
- id="polygon4" />
176
- <polygon
177
- points="75,50.69873 125,50.69873 100,94 "
178
- fill="#0071b5"
179
- id="polygon5" />
180
- <polygon
181
- points="130.19615,53.69873 155.19615,97 105.19615,97 "
182
- fill="#0071b5"
183
- id="polygon6" />
184
- </g>
185
- </g>
186
- <g
187
- inkscape:label="Layer 1"
188
- inkscape:groupmode="layer"
189
- id="layer1"
190
- transform="translate(8.8561952,-0.31740497)">
191
- <path
192
- style="font-weight:bold;font-size:16.9333px;font-family:Futura;-inkscape-font-specification:'Futura, Bold';fill:#0071b5;fill-opacity:1;stroke-width:0.322562"
193
- d="M 39.469822,10.780228 Q 38.066034,9.0667813 36.00164,9.0667813 q -0.908333,0 -1.692803,0.330303 -0.763826,0.330302 -1.321212,0.9083327 -0.557387,0.557386 -0.887689,1.341856 -0.309659,0.78447 -0.309659,1.692803 0,0.928977 0.309659,1.713448 0.330302,0.78447 0.887689,1.3625 0.578031,0.57803 1.341857,0.908334 0.763825,0.330303 1.651515,0.330303 1.94053,0 3.488825,-1.651516 v 4.789394 l -0.412879,0.144508 q -0.928976,0.330303 -1.734091,0.474811 -0.805114,0.165151 -1.589583,0.165151 -1.610227,0 -3.096591,-0.598674 -1.46572,-0.619319 -2.601137,-1.713447 -1.114773,-1.114773 -1.796023,-2.621781 -0.68125,-1.527651 -0.68125,-3.323675 0,-1.796023 0.660606,-3.282386 0.681251,-1.5070074 1.796023,-2.5804934 1.135417,-1.094128 2.621781,-1.692803 1.486364,-0.619317 3.117235,-0.619317 0.928978,0 1.816667,0.206439 0.908333,0.185795 1.899242,0.598673 z m 5.739013,5.016479 q 0,0.536741 0.185795,0.990908 0.20644,0.433522 0.5161,0.763826 0.330302,0.330302 0.763825,0.516099 0.454168,0.185794 0.949622,0.185794 0.495453,0 0.928977,-0.185794 0.454166,-0.185797 0.763825,-0.516099 0.330303,-0.330304 0.516099,-0.763826 0.20644,-0.454167 0.20644,-0.970265 0,-0.495454 -0.20644,-0.928978 -0.185796,-0.454167 -0.516099,-0.784469 -0.309659,-0.330304 -0.763825,-0.5161 -0.433524,-0.185794 -0.928977,-0.185794 -0.495454,0 -0.949622,0.185794 -0.433523,0.185796 -0.763825,0.5161 -0.30966,0.330302 -0.5161,0.763825 -0.185795,0.433522 -0.185795,0.928979 z m -3.942993,-0.04129 q 0,-1.176705 0.474811,-2.188258 0.474811,-1.032196 1.321211,-1.775379 0.846403,-0.763826 2.002464,-1.197348 1.176704,-0.433522 2.559849,-0.433522 1.3625,0 2.51856,0.433522 1.176704,0.412878 2.023106,1.176704 0.867045,0.743182 1.341856,1.796023 0.474811,1.032198 0.474811,2.291477 0,1.259281 -0.495454,2.312123 -0.474811,1.032196 -1.321213,1.796022 -0.846402,0.743182 -2.04375,1.156061 -1.176705,0.412879 -2.559848,0.412879 -1.362501,0 -2.518563,-0.412879 -1.15606,-0.412879 -2.002462,-1.176704 -0.825756,-0.763826 -1.300567,-1.816668 -0.474811,-1.073484 -0.474811,-2.374053 z m 18.600208,-5.20227 v 5.9661 q 0,1.961175 1.899242,1.961175 1.899242,0 1.899242,-1.961175 v -5.9661 h 3.736555 v 6.688638 q 0,2.14697 -1.424433,3.220454 -1.403788,1.073485 -4.211364,1.073485 -2.807577,0 -4.232008,-1.073485 -1.403788,-1.073488 -1.403788,-3.220458 v -6.688638 z m 10.074252,0 h 3.736558 v 1.341857 q 0.763823,-0.928978 1.548299,-1.238636 0.784462,-0.330304 1.837303,-0.330304 1.114777,0 1.89924,0.371591 0.805115,0.350947 1.362508,1.011553 0.454175,0.536743 0.619315,1.197349 0.165157,0.660606 0.165157,1.507007 v 6.729926 h -3.736558 v -5.34678 q 0,-0.784472 -0.123862,-1.259282 -0.103262,-0.495455 -0.392233,-0.78447 -0.247728,-0.247727 -0.557388,-0.350946 -0.30966,-0.103224 -0.660611,-0.103224 -0.949619,0 -1.465715,0.578031 -0.495455,0.557385 -0.495455,1.610227 v 5.656437 h -3.736558 z m 17.877642,3.117236 v 7.473107 h -3.736542 v -7.473107 h -1.238638 v -3.117236 h 1.238638 V 7.3739806 h 3.736542 v 3.1791664 h 2.126327 v 3.117236 z m 11.478028,0.639962 q -0.185801,-0.78447 -0.763829,-1.259281 -0.578036,-0.474811 -1.403791,-0.474811 -0.867046,0 -1.42443,0.454167 -0.536741,0.454167 -0.681237,1.279925 z m -4.376513,2.167613 q 0,2.415342 2.270832,2.415342 1.217991,0 1.837315,-0.99091 h 3.612684 q -1.09413,3.633334 -5.470643,3.633334 -1.341865,0 -2.456623,-0.392234 -1.114777,-0.41288 -1.919893,-1.15606 -0.784472,-0.743183 -1.218004,-1.77538 -0.433505,-1.032197 -0.433505,-2.312122 0,-1.321211 0.41287,-2.374053 0.41288,-1.073485 1.176708,-1.816667 0.76382,-0.743182 1.837301,-1.135417 1.094143,-0.412878 2.456641,-0.412878 1.341844,0 2.415334,0.412878 1.073484,0.392235 1.816664,1.15606 0.74319,0.763828 1.13541,1.878601 0.39225,1.094128 0.39225,2.477272 v 0.392234 z m 9.991641,-5.924811 h 3.73656 v 1.734092 q 0.59868,-0.949622 1.46571,-1.445077 0.86705,-0.516098 2.02311,-0.516098 0.1445,0 0.30966,0 0.1858,0 0.41289,0.04129 v 3.571402 q -0.74318,-0.371592 -1.61023,-0.371592 -1.30057,0 -1.96117,0.78447 -0.63997,0.763826 -0.63997,2.25019 v 4.541667 h -3.73656 z m 14.18238,3.117236 v 7.473107 h -3.73656 v -7.473107 h -1.34185 v -3.117236 h 1.34185 V 8.9635643 q 0,-1.3212127 0.24772,-2.1056817 0.20646,-0.660606 0.66062,-1.217993 0.45416,-0.578029 1.05284,-0.990909 0.61932,-0.412878 1.32122,-0.639963 0.70189,-0.227083 1.38313,-0.227083 0.47481,0 0.86705,0.103224 0.41287,0.103224 0.82576,0.289017 v 3.261742 q -0.37159,-0.185797 -0.70189,-0.268371 -0.30966,-0.103224 -0.66062,-0.103224 -0.22707,0 -0.4748,0.08258 -0.22708,0.06193 -0.43352,0.289014 -0.20645,0.227083 -0.28902,0.660608 -0.0619,0.412878 -0.0619,1.1354157 v 1.3212117 h 2.62178 v 3.117236 z m 7.72084,2.14697 q 0,0.516098 0.18579,0.970265 0.1858,0.433522 0.49545,0.763826 0.33031,0.330302 0.76384,0.516099 0.45416,0.185794 0.97025,0.185794 0.49546,0 0.92898,-0.185794 0.45417,-0.185797 0.76384,-0.516099 0.3303,-0.330304 0.51609,-0.763826 0.20643,-0.433523 0.20643,-0.928978 0,-0.495454 -0.20643,-0.928977 -0.18579,-0.454167 -0.51609,-0.784469 -0.30967,-0.330304 -0.76384,-0.516099 -0.43352,-0.185796 -0.92898,-0.185796 -0.51609,0 -0.97025,0.185796 -0.43353,0.185795 -0.76384,0.516099 -0.30965,0.330302 -0.49545,0.763825 -0.18579,0.41288 -0.18579,0.908334 z m 4.70682,-5.264206 h 3.7572 V 21.14349 h -3.7572 v -1.176702 q -1.19735,1.507008 -3.2411,1.507008 -1.15606,0 -2.12634,-0.412879 -0.97025,-0.433522 -1.6928,-1.197347 -0.72253,-0.763827 -1.13541,-1.796023 -0.39223,-1.032198 -0.39223,-2.250191 0,-1.135417 0.39223,-2.146969 0.39223,-1.032198 1.09414,-1.796023 0.70188,-0.763828 1.67215,-1.19735 0.97026,-0.454166 2.14696,-0.454166 1.98182,0 3.2824,1.383145 z m 14.28561,3.426895 q -0.97028,-0.660607 -1.96119,-0.660607 -0.53672,0 -1.01154,0.185796 -0.45417,0.185795 -0.80512,0.536742 -0.35094,0.330304 -0.55738,0.805115 -0.1858,0.454166 -0.1858,1.032197 0,0.557386 0.1858,1.032197 0.20644,0.454166 0.53674,0.805113 0.35095,0.330303 0.82576,0.516099 0.47482,0.185795 1.01154,0.185795 1.05286,0 1.96119,-0.722538 v 3.179167 q -1.38315,0.598674 -2.62179,0.598674 -1.15606,0 -2.20889,-0.392235 -1.0322,-0.392234 -1.83732,-1.114772 -0.78446,-0.743182 -1.25928,-1.754735 -0.4748,-1.032197 -0.4748,-2.291479 0,-1.259279 0.45416,-2.291476 0.45416,-1.052842 1.23863,-1.796024 0.78448,-0.763825 1.85796,-1.176703 1.09413,-0.433523 2.33277,-0.433523 1.36249,0 2.51856,0.57803 z m 6.2964,-0.309659 v 7.473107 h -3.73655 v -7.473107 h -1.23864 v -3.117236 h 1.23864 V 7.3739806 h 3.73655 v 3.1791664 h 2.12633 v 3.117236 z"
194
- id="text1"
195
- fill="#0071b5"
196
- aria-label="Counterfact" />
197
- </g>
198
- </svg>
199
- </h1>
200
-
201
-
202
-
203
-
204
- <div class="buttons">
205
- <a href="./swagger" class="button" target="_blank">Swagger UI</a>
206
- <a href="{{openApiHref}}" class="button" target="_blank">OpenAPI</a>
207
- <a href="https://counterfact.dev/docs/usage.html" target="_blank">Usage Guide</a>
208
- <a href="https://github.com/pmcelhaney/counterfact" target="_blank">GitHub</a>
209
- </div>
210
-
211
-
212
-
213
- <div id="paths-container">
214
- <table>
215
- <thead>
216
- <tr>
217
- <th class="tilt"><span>GET</span></th>
218
- <th class="tilt"><span>POST</span></th>
219
- <th class="tilt"><span>PUT</span></th>
220
- <th class="tilt"><span>DELETE</span></th>
221
- <th class="tilt"><span>PATCH</span></th>
222
- <th class="path"><span>Path</span></th>
223
- </tr>
224
- </thead>
225
- <tbody>
226
- {{#each routes as |route|}}
227
- <tr>
228
- <td class="method-identifier" title="{{route.methods.GET}}">{{#if route.methods.GET }}⚪️{{/if}}</td>
229
- <td class="method-identifier" title="{{route.methods.POST}}">{{#if route.methods.POST }}⚪️{{/if}}</td>
230
- <td class="method-identifier" title="{{route.methods.PUT}}">{{#if route.methods.PUT }}⚪️{{/if}}</td>
231
- <td class="method-identifier" title="{{route.methods.DELETE}}">{{#if route.methods.DELETE }}⚪️{{/if}}</td>
232
- <td class="method-identifier" title="{{route.methods.PATH}}">{{#if route.methods.PATCH }}⚪️{{/if}}</td>
233
- <td class="path"><a href="vscode://file/{{../basePath}}/routes{{route.path}}.ts">{{route.path}}</a></td>
234
- </tr>
235
- {{/each}}
236
- </tbody>
237
- </table>
238
- </div>
239
-
240
-
241
-
242
-
243
- </body>
244
- </html>
@@ -1,36 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <title>Counterfact</title>
5
- <meta charset="utf-8">
6
- <meta name="description" content="Rapidoc" />
7
- <meta name="viewport" content="width=device-width, initial-scale=1" />
8
- <script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
9
- </head>
10
- <body>
11
-
12
-
13
-
14
- <rapi-doc spec-url="/counterfact/openapi" show-method-in-nav-bar="as-colored-block" nav-active-item-marker="left-bar" use-path-in-nav-bar="true">
15
-
16
- <div slot="overview" style="background-color: #eee">
17
- <p>Put an explanation of Counterfact here.</p>
18
- <a
19
- href="https://github.com/pmcelhaney/counterfact/blob/main/docs/usage.md#generated-code-"
20
- >How does this work?</a
21
- >
22
- </div>
23
- {{#each routes as |route|}}
24
- <a slot="get-{{escape_route route}}" href="vscode://file/{{../basePath}}/routes/{{.}}.ts">Edit in VSCode</a>
25
- <a slot="put-{{escape_route route}}" href="vscode://file/{{../basePath}}/routes/{{.}}.ts">Edit in VSCode</a>
26
- <a slot="post-{{escape_route route}}" href="vscode://file/{{../basePath}}/routes/{{.}}.ts">Edit in VSCode</a>
27
- <a slot="delete-{{escape_route route}}" href="vscode://file/{{../basePath}}/routes/{{.}}.ts">Edit in VSCode</a>
28
- <a slot="patch-{{escape_route route}}" href="vscode://file/{{../basePath}}/routes/{{.}}.ts">Edit in VSCode</a>
29
-
30
- {{/each}}
31
- </rapi-doc>
32
-
33
-
34
-
35
- </body>
36
- </html>
@@ -1,23 +0,0 @@
1
- import nodePath from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import createDebug from "debug";
4
- import Handlebars from "handlebars";
5
- import { readFile } from "../util/read-file.js";
6
- const __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
7
- const debug = createDebug("counterfact:server:page-middleware");
8
- Handlebars.registerHelper("escape_route", (route) => route.replaceAll(/[^\w/]/gu, "-"));
9
- export function pageMiddleware(pathname, templateName, locals) {
10
- return async (ctx, next) => {
11
- const pathToHandlebarsTemplate = nodePath
12
- .join(__dirname, `../client/${templateName}.html.hbs`)
13
- .replaceAll("\\", "/");
14
- const render = Handlebars.compile(await readFile(pathToHandlebarsTemplate));
15
- if (ctx.URL.pathname === pathname) {
16
- debug("rendering page: %s", pathname);
17
- debug("locals: %o", locals);
18
- ctx.body = render(locals);
19
- return;
20
- }
21
- await next();
22
- };
23
- }
@@ -1,63 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import fs from "node:fs/promises";
3
- import nodePath from "node:path";
4
- import createDebug from "debug";
5
- import { ensureDirectoryExists } from "../util/ensure-directory-exists.js";
6
- import { OperationCoder } from "./operation-coder.js";
7
- import { pruneRoutes } from "./prune.js";
8
- import { Repository } from "./repository.js";
9
- import { Specification } from "./specification.js";
10
- const debug = createDebug("counterfact:typescript-generator:generate");
11
- async function buildCacheDirectory(destination) {
12
- const gitignorePath = nodePath.join(destination, ".gitignore");
13
- const cacheReadmePath = nodePath.join(destination, ".cache", "README.md");
14
- debug("ensuring the directory containing .gitgnore exists");
15
- await ensureDirectoryExists(gitignorePath);
16
- debug("creating the .gitignore file if it doesn't already exist");
17
- if (!existsSync(gitignorePath)) {
18
- await fs.writeFile(gitignorePath, ".cache\n", "utf8");
19
- }
20
- debug("creating the .cache/README.md file");
21
- ensureDirectoryExists(cacheReadmePath);
22
- await fs.writeFile(cacheReadmePath, "This directory contains compiled JS files from the paths directory. Do not edit these files directly.\n", "utf8");
23
- }
24
- async function getPathsFromSpecification(specification) {
25
- try {
26
- return specification.getRequirement("#/paths") ?? new Set();
27
- }
28
- catch (error) {
29
- process.stderr.write(`Could not find #/paths in the specification.\n${error}\n`);
30
- return undefined;
31
- }
32
- }
33
- export async function generate(source, destination, generateOptions, repository = new Repository()) {
34
- debug("generating code from %s to %s", source, destination);
35
- debug("initializing the .cache directory");
36
- await buildCacheDirectory(destination);
37
- debug("done initializing the .cache directory");
38
- debug("creating specification from %s", source);
39
- const specification = await Specification.fromFile(source);
40
- debug("created specification: $o", specification);
41
- debug("reading the #/paths from the specification");
42
- const paths = await getPathsFromSpecification(specification);
43
- debug("got %i paths", paths?.map?.length ?? 0);
44
- if (generateOptions.prune && generateOptions.routes) {
45
- debug("pruning defunct route files");
46
- await pruneRoutes(destination, paths.map((_v, key) => key));
47
- debug("done pruning");
48
- }
49
- const securityRequirement = specification.getRequirement("#/components/securitySchemes");
50
- const securitySchemes = Object.values(securityRequirement?.data ?? {});
51
- paths.forEach((pathDefinition, key) => {
52
- debug("processing path %s", key);
53
- const path = key === "/" ? "/index" : key;
54
- pathDefinition.forEach((operation, requestMethod) => {
55
- repository
56
- .get(`routes${path}.ts`)
57
- .export(new OperationCoder(operation, requestMethod, securitySchemes));
58
- });
59
- });
60
- debug("telling the repository to write the files to %s", destination);
61
- await repository.writeFiles(destination, generateOptions);
62
- debug("finished writing the files");
63
- }