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.
- package/README.md +5 -160
- package/bin/README.md +39 -14
- package/bin/counterfact.js +18 -539
- package/bin/ts-loader.mjs +1 -0
- package/dist/api-runner.js +202 -0
- package/dist/app.js +102 -114
- package/dist/cli/banner.js +81 -0
- package/dist/cli/check-for-updates.js +45 -0
- package/dist/cli/run.js +304 -0
- package/dist/cli/telemetry.js +50 -0
- package/dist/migrate/paths-to-routes.js +1 -0
- package/dist/migrate/update-route-types.js +3 -3
- package/dist/msw.js +78 -0
- package/dist/repl/raw-http-client.js +22 -1
- package/dist/repl/repl.js +250 -63
- package/dist/repl/route-builder.js +68 -0
- package/dist/server/constants.js +8 -0
- package/dist/server/context-registry.js +54 -1
- package/dist/server/determine-module-kind.js +14 -0
- package/dist/server/dispatcher.js +46 -0
- package/dist/server/file-discovery.js +21 -9
- package/dist/server/is-proxy-enabled-for-path.js +12 -0
- package/dist/server/json-to-xml.js +10 -0
- package/dist/server/load-openapi-document.js +4 -11
- package/dist/server/module-dependency-graph.js +25 -0
- package/dist/server/module-loader.js +52 -21
- package/dist/server/module-tree.js +36 -0
- package/dist/server/openapi-document.js +69 -0
- package/dist/server/registry.js +89 -0
- package/dist/server/response-builder.js +15 -0
- package/dist/server/scenario-registry.js +26 -0
- package/dist/server/tools.js +27 -0
- package/dist/server/transpiler.js +24 -9
- package/dist/server/{admin-api-middleware.js → web-server/admin-api-middleware.js} +19 -9
- package/dist/server/web-server/create-koa-app.js +68 -0
- package/dist/server/web-server/openapi-middleware.js +34 -0
- package/dist/server/{koa-middleware.js → web-server/routes-middleware.js} +26 -6
- package/dist/typescript-generator/code-generator.js +118 -4
- package/dist/typescript-generator/coder.js +76 -0
- package/dist/typescript-generator/operation-coder.js +12 -4
- package/dist/typescript-generator/operation-type-coder.js +39 -4
- package/dist/typescript-generator/parameters-type-coder.js +2 -4
- package/dist/typescript-generator/prune.js +3 -1
- package/dist/typescript-generator/repository.js +77 -20
- package/dist/typescript-generator/requirement.js +69 -0
- package/dist/typescript-generator/{generate.js → scenario-file-generator.js} +99 -81
- package/dist/typescript-generator/script.js +70 -7
- package/dist/typescript-generator/specification.js +27 -0
- package/dist/util/ensure-directory-exists.js +8 -0
- package/dist/util/forward-slash-path.js +63 -0
- package/dist/util/load-config-file.js +2 -2
- package/dist/util/read-file.js +27 -2
- package/dist/util/runtime-can-execute-erasable-ts.js +12 -0
- package/dist/util/windows-escape.js +18 -0
- package/package.json +5 -4
- package/dist/server/create-koa-app.js +0 -42
- 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,
|
|
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(
|
|
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
|
|
20
|
+
content = await readFile(configPath);
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
23
23
|
if (typeof error === "object" &&
|
package/dist/util/read-file.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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/
|
|
24
|
+
"url": "git+https://github.com/counterfact/api-simulator.git"
|
|
25
25
|
},
|
|
26
|
-
"bugs": "https://github.com/
|
|
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
|
|
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
|
-
}
|