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 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.0",
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",
@@ -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({ url: "/" });
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(name = this.requirement.url.split("/").at(-1)) {
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
- isRoot() {
7
- return this.requirement.url.split("/").at(-2).split("~1").length === 2;
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 (this.isRoot()) {
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
- const pathString = this.requirement.url
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
- )}/$context.ts`;
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.importExternalType("ResponseBuilderBuilder", "counterfact");
79
- script.importExternalType("HttpStatusCode", "counterfact");
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 `ResponseBuilderBuilder<${this.buildResponseObjectType(script)}>`;
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;