counterfact 0.10.0 → 0.10.2
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/.eslintrc.cjs +1 -8
- package/CHANGELOG.md +15 -0
- package/package.json +1 -2
- package/src/counterfact.js +8 -0
- package/src/start.js +4 -1
- package/src/typescript-generator/coder.js +5 -1
- package/src/typescript-generator/context-coder.js +18 -12
- package/src/typescript-generator/operation-coder.js +0 -14
- package/src/typescript-generator/response-type-coder.js +17 -3
- package/templates/response-builder-factory.ts +142 -0
- package/test/typescript-generator/__snapshots__/end-to-end.test.js.snap +168 -168
- package/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap +2 -2
- package/test/typescript-generator/coder.test.js +11 -0
- package/src/counterfact.d.ts +0 -144
- package/src/typescript-generator/tools-coder.js +0 -30
- package/templates/typescript/.devcontainer/Dockerfile +0 -14
- package/templates/typescript/.devcontainer/base.Dockerfile +0 -17
- package/templates/typescript/.devcontainer/devcontainer.json +0 -47
- package/templates/typescript/counterfact/context/context.ts +0 -18
- package/templates/typescript/counterfact/public/index.html +0 -17
- package/templates/typescript/counterfact/server.ts +0 -56
- package/templates/typescript/eslintrc.yaml +0 -13
- package/templates/typescript/package.json +0 -28
- package/templates/typescript/tsconfig.json +0 -13
- package/test/typescript-generator/__snapshots__/tools.coder.test.js.snap +0 -10
- package/test/typescript-generator/tools.coder.test.js +0 -30
package/.eslintrc.cjs
CHANGED
|
@@ -36,14 +36,7 @@ const rules = {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
module.exports = {
|
|
39
|
-
ignorePatterns: [
|
|
40
|
-
"/node_modules/",
|
|
41
|
-
"/coverage/",
|
|
42
|
-
"/reports/",
|
|
43
|
-
"/demo-ts",
|
|
44
|
-
"/templates/",
|
|
45
|
-
"/out/",
|
|
46
|
-
],
|
|
39
|
+
ignorePatterns: ["/node_modules/", "/coverage/", "/reports/", "/out/"],
|
|
47
40
|
|
|
48
41
|
extends: ["hardcore", "hardcore/ts", "hardcore/node"],
|
|
49
42
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# counterfact
|
|
2
2
|
|
|
3
|
+
## 0.10.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d8cbc41: fix swagger-ui when the openapi doc has a host property
|
|
8
|
+
- d8cbc41: ensure a $context.ts file is created in every directory
|
|
9
|
+
- 15270ed: remove dead code in operation-coder.js
|
|
10
|
+
- c7a928f: encode non-alphanumeric name as valid variable names
|
|
11
|
+
|
|
12
|
+
## 0.10.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 02efcd5: fix so that Counterfact no longer depends on itself
|
|
17
|
+
|
|
3
18
|
## 0.10.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "counterfact",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "a library for building a fake REST API for testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/counterfact.js",
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"@types/json-schema": "^7.0.11",
|
|
61
61
|
"chokidar": "^3.5.3",
|
|
62
62
|
"commander": "^9.4.0",
|
|
63
|
-
"counterfact": "*",
|
|
64
63
|
"fs-extra": "^10.1.0",
|
|
65
64
|
"js-yaml": "^4.1.0",
|
|
66
65
|
"json-schema-faker": "^0.5.0-rcv.44",
|
package/src/counterfact.js
CHANGED
|
@@ -14,6 +14,9 @@ import { ModuleLoader } from "./module-loader.js";
|
|
|
14
14
|
import { Transpiler } from "./transpiler.js";
|
|
15
15
|
import { ContextRegistry } from "./context-registry.js";
|
|
16
16
|
|
|
17
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
18
|
+
const __dirname = nodePath.dirname(new URL(import.meta.url).pathname);
|
|
19
|
+
|
|
17
20
|
async function loadOpenApiDocument(source) {
|
|
18
21
|
try {
|
|
19
22
|
return $RefParser.dereference(await yaml.load(await readFile(source)));
|
|
@@ -35,6 +38,11 @@ export async function counterfact(
|
|
|
35
38
|
nodePath.join(os.tmpdir(), "counterfact-")
|
|
36
39
|
)}/`;
|
|
37
40
|
|
|
41
|
+
fs.copyFile(
|
|
42
|
+
nodePath.join(__dirname, "../templates/response-builder-factory.ts"),
|
|
43
|
+
nodePath.join(basePath, "response-builder-factory.ts")
|
|
44
|
+
);
|
|
45
|
+
|
|
38
46
|
try {
|
|
39
47
|
await fs.writeFile(
|
|
40
48
|
nodePath.join(modulesPath, "package.json"),
|
package/src/start.js
CHANGED
|
@@ -25,7 +25,10 @@ export async function start({
|
|
|
25
25
|
|
|
26
26
|
openApiDocument.servers ??= [];
|
|
27
27
|
|
|
28
|
-
openApiDocument.servers.unshift({
|
|
28
|
+
openApiDocument.servers.unshift({
|
|
29
|
+
description: "Counterfact",
|
|
30
|
+
url: `//localhost:${port}`,
|
|
31
|
+
});
|
|
29
32
|
|
|
30
33
|
// eslint-disable-next-line require-atomic-updates
|
|
31
34
|
ctx.body = yaml.dump(openApiDocument);
|
|
@@ -27,7 +27,11 @@ export class Coder {
|
|
|
27
27
|
return new this.constructor(requirement);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
*names(
|
|
30
|
+
*names(rawName = this.requirement.url.split("/").at(-1)) {
|
|
31
|
+
const name = rawName
|
|
32
|
+
.replace(/^\d/u, (digit) => `_${digit}`)
|
|
33
|
+
.replace(/[^\w$]/gu, "_");
|
|
34
|
+
|
|
31
35
|
yield name;
|
|
32
36
|
|
|
33
37
|
let index = 1;
|
|
@@ -3,31 +3,37 @@ import nodePath from "node:path";
|
|
|
3
3
|
import { Coder } from "./coder.js";
|
|
4
4
|
|
|
5
5
|
export class ContextCoder extends Coder {
|
|
6
|
-
|
|
7
|
-
return this.requirement.url
|
|
6
|
+
pathString() {
|
|
7
|
+
return this.requirement.url
|
|
8
|
+
.split("/")
|
|
9
|
+
.at(-2)
|
|
10
|
+
.replaceAll("~1", "/")
|
|
11
|
+
.replaceAll("~0", "~");
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
names() {
|
|
11
15
|
return super.names("Context");
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
write() {
|
|
15
|
-
if (
|
|
18
|
+
write(script) {
|
|
19
|
+
if (script.path === "paths/$context.ts") {
|
|
16
20
|
return "{}";
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
const parentPath = nodePath.normalize(
|
|
24
|
+
nodePath.join(script.path, "../../$context.ts")
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
script.repository.get(parentPath).exportDefault(this);
|
|
28
|
+
|
|
19
29
|
return { raw: 'export { default } from "../$context.js"' };
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
modulePath() {
|
|
23
|
-
|
|
24
|
-
.split("/")
|
|
25
|
-
.at(-2)
|
|
26
|
-
.replaceAll("~1", "/");
|
|
27
|
-
|
|
28
|
-
return `${nodePath.join(
|
|
33
|
+
return nodePath.join(
|
|
29
34
|
"paths",
|
|
30
|
-
nodePath.dirname(pathString)
|
|
31
|
-
|
|
35
|
+
nodePath.dirname(this.pathString()),
|
|
36
|
+
"$context.ts"
|
|
37
|
+
);
|
|
32
38
|
}
|
|
33
39
|
}
|
|
@@ -16,20 +16,6 @@ export class OperationCoder extends Coder {
|
|
|
16
16
|
write() {
|
|
17
17
|
const responses = this.requirement.get("responses");
|
|
18
18
|
|
|
19
|
-
const requestProperties = this.requirement.data.parameters
|
|
20
|
-
? Array.from(
|
|
21
|
-
new Set(
|
|
22
|
-
this.requirement.data.parameters.map((parameter) => parameter.in)
|
|
23
|
-
)
|
|
24
|
-
)
|
|
25
|
-
: [];
|
|
26
|
-
|
|
27
|
-
if (this.requestMethod() !== "GET") {
|
|
28
|
-
requestProperties.push("body");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
requestProperties.push("context", "tools");
|
|
32
|
-
|
|
33
19
|
const [firstStatusCode] = responses.map(([statusCode]) => statusCode);
|
|
34
20
|
const [firstResponse] = responses.map(([, response]) => response.data);
|
|
35
21
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import nodePath from "node:path";
|
|
2
|
+
|
|
1
3
|
import { Coder } from "./coder.js";
|
|
2
4
|
import { SchemaTypeCoder } from "./schema-type-coder.js";
|
|
3
5
|
|
|
@@ -75,9 +77,21 @@ export class ResponseTypeCoder extends Coder {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
write(script) {
|
|
78
|
-
script.
|
|
79
|
-
|
|
80
|
+
const basePath = script.path
|
|
81
|
+
.split("/")
|
|
82
|
+
.slice(0, -1)
|
|
83
|
+
.map(() => "..")
|
|
84
|
+
.join("/");
|
|
85
|
+
|
|
86
|
+
script.importExternalType(
|
|
87
|
+
"ResponseBuilderFactory",
|
|
88
|
+
nodePath.join(basePath, "response-builder-factory.js")
|
|
89
|
+
);
|
|
90
|
+
script.importExternalType(
|
|
91
|
+
"HttpStatusCode",
|
|
92
|
+
nodePath.join(basePath, "response-builder-factory.js")
|
|
93
|
+
);
|
|
80
94
|
|
|
81
|
-
return `
|
|
95
|
+
return `ResponseBuilderFactory<${this.buildResponseObjectType(script)}>`;
|
|
82
96
|
}
|
|
83
97
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
interface OpenApiHeader {
|
|
2
|
+
schema: unknown;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface OpenApiContent {
|
|
6
|
+
schema: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type OmitValueWhenNever<Base> = Pick<
|
|
10
|
+
Base,
|
|
11
|
+
{
|
|
12
|
+
[Key in keyof Base]: [Base[Key]] extends [never] ? never : Key;
|
|
13
|
+
}[keyof Base]
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
type MediaType = `${string}/${string}`;
|
|
17
|
+
|
|
18
|
+
interface OpenApiResponse {
|
|
19
|
+
headers: { [key: string]: OpenApiHeader };
|
|
20
|
+
content: { [key: MediaType]: OpenApiContent };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface OpenApiResponses {
|
|
24
|
+
[key: string]: OpenApiResponse;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type IfHasKey<SomeObject, Key, Yes, No> = Key extends keyof SomeObject
|
|
28
|
+
? Yes
|
|
29
|
+
: No;
|
|
30
|
+
|
|
31
|
+
type MaybeShortcut<
|
|
32
|
+
ContentType extends MediaType,
|
|
33
|
+
Response extends OpenApiResponse
|
|
34
|
+
> = IfHasKey<
|
|
35
|
+
Response["content"],
|
|
36
|
+
ContentType,
|
|
37
|
+
(body: Response["content"][ContentType]["schema"]) => ResponseBuilder<{
|
|
38
|
+
headers: Response["headers"];
|
|
39
|
+
content: Omit<Response["content"], ContentType>;
|
|
40
|
+
}>,
|
|
41
|
+
never
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
type MatchFunction<Response extends OpenApiResponse> = <
|
|
45
|
+
ContentType extends MediaType & keyof Response["content"]
|
|
46
|
+
>(
|
|
47
|
+
contentType: ContentType,
|
|
48
|
+
body: Response["content"][ContentType]["schema"]
|
|
49
|
+
) => ResponseBuilder<{
|
|
50
|
+
headers: Response["headers"];
|
|
51
|
+
content: Omit<Response["content"], ContentType>;
|
|
52
|
+
}>;
|
|
53
|
+
|
|
54
|
+
type HeaderFunction<Response extends OpenApiResponse> = <
|
|
55
|
+
Header extends string & keyof Response["headers"]
|
|
56
|
+
>(
|
|
57
|
+
header: Header,
|
|
58
|
+
value: Response["headers"][Header]["schema"]
|
|
59
|
+
) => ResponseBuilder<{
|
|
60
|
+
content: Response["content"];
|
|
61
|
+
headers: Omit<Response["headers"], Header>;
|
|
62
|
+
}>;
|
|
63
|
+
|
|
64
|
+
type ResponseBuilder<Response extends OpenApiResponse> = [
|
|
65
|
+
keyof Response["content"]
|
|
66
|
+
] extends [never]
|
|
67
|
+
? // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
|
68
|
+
void
|
|
69
|
+
: OmitValueWhenNever<{
|
|
70
|
+
header: [keyof Response["headers"]] extends [never]
|
|
71
|
+
? never
|
|
72
|
+
: HeaderFunction<Response>;
|
|
73
|
+
match: [keyof Response["content"]] extends [never]
|
|
74
|
+
? never
|
|
75
|
+
: MatchFunction<Response>;
|
|
76
|
+
text: MaybeShortcut<"text/plain", Response>;
|
|
77
|
+
json: MaybeShortcut<"application/json", Response>;
|
|
78
|
+
html: MaybeShortcut<"text/html", Response>;
|
|
79
|
+
random: [keyof Response["content"]] extends [never] ? never : () => void;
|
|
80
|
+
}>;
|
|
81
|
+
|
|
82
|
+
export type ResponseBuilderFactory<Responses extends OpenApiResponses> = {
|
|
83
|
+
[StatusCode in keyof Responses]: ResponseBuilder<Responses[StatusCode]>;
|
|
84
|
+
} & { [key: string]: ResponseBuilder<Responses["default"]> };
|
|
85
|
+
|
|
86
|
+
export type HttpStatusCode =
|
|
87
|
+
| 100
|
|
88
|
+
| 101
|
|
89
|
+
| 102
|
|
90
|
+
| 200
|
|
91
|
+
| 201
|
|
92
|
+
| 202
|
|
93
|
+
| 203
|
|
94
|
+
| 204
|
|
95
|
+
| 205
|
|
96
|
+
| 206
|
|
97
|
+
| 207
|
|
98
|
+
| 226
|
|
99
|
+
| 300
|
|
100
|
+
| 301
|
|
101
|
+
| 302
|
|
102
|
+
| 303
|
|
103
|
+
| 304
|
|
104
|
+
| 305
|
|
105
|
+
| 307
|
|
106
|
+
| 308
|
|
107
|
+
| 400
|
|
108
|
+
| 401
|
|
109
|
+
| 402
|
|
110
|
+
| 403
|
|
111
|
+
| 404
|
|
112
|
+
| 405
|
|
113
|
+
| 406
|
|
114
|
+
| 407
|
|
115
|
+
| 408
|
|
116
|
+
| 409
|
|
117
|
+
| 410
|
|
118
|
+
| 411
|
|
119
|
+
| 412
|
|
120
|
+
| 413
|
|
121
|
+
| 414
|
|
122
|
+
| 415
|
|
123
|
+
| 416
|
|
124
|
+
| 417
|
|
125
|
+
| 418
|
|
126
|
+
| 422
|
|
127
|
+
| 423
|
|
128
|
+
| 424
|
|
129
|
+
| 426
|
|
130
|
+
| 428
|
|
131
|
+
| 429
|
|
132
|
+
| 431
|
|
133
|
+
| 451
|
|
134
|
+
| 500
|
|
135
|
+
| 501
|
|
136
|
+
| 502
|
|
137
|
+
| 503
|
|
138
|
+
| 504
|
|
139
|
+
| 505
|
|
140
|
+
| 506
|
|
141
|
+
| 507
|
|
142
|
+
| 511;
|