counterfact 2.5.0 → 2.5.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.
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import createDebug from "debug";
4
- import { OperationTypeCoder } from "../typescript-generator/operation-type-coder.js";
4
+ import { OperationTypeCoder, } from "../typescript-generator/operation-type-coder.js";
5
5
  import { Specification } from "../typescript-generator/specification.js";
6
6
  const debug = createDebug("counterfact:migrate:update-route-types");
7
7
  const HTTP_METHODS = [
@@ -25,8 +25,8 @@ function openApiPathToFilePath(openApiPath) {
25
25
  }
26
26
  /**
27
27
  * Builds a mapping of route file paths to their operation type names per method
28
- * @param {Specification} specification - The OpenAPI specification
29
- * @returns {Promise<Map<string, Map<string, string>>>} - Map of filePath -> Map of method -> typeName
28
+ * @param specification - The OpenAPI specification
29
+ * @returns Map of filePath -> Map of method -> typeName
30
30
  */
31
31
  async function buildTypeNameMapping(specification) {
32
32
  debug("building type name mapping from specification");
@@ -68,8 +68,7 @@ async function buildTypeNameMapping(specification) {
68
68
  }
69
69
  /**
70
70
  * Checks if a route file needs migration by looking for old-style HTTP_ imports
71
- * @param {string} content - The file content
72
- * @returns {boolean}
71
+ * @param content - The file content
73
72
  */
74
73
  function needsMigration(content) {
75
74
  const methodAlternation = HTTP_METHODS.map((method) => method.toUpperCase()).join("|");
@@ -78,9 +77,9 @@ function needsMigration(content) {
78
77
  }
79
78
  /**
80
79
  * Updates a single route file with the correct type names
81
- * @param {string} filePath - Absolute path to the route file
82
- * @param {Map<string, string>} methodToTypeName - Map of HTTP method to type name
83
- * @returns {Promise<boolean>} - True if file was updated
80
+ * @param filePath - Absolute path to the route file
81
+ * @param methodToTypeName - Map of HTTP method to type name
82
+ * @returns True if file was updated
84
83
  */
85
84
  async function updateRouteFile(filePath, methodToTypeName) {
86
85
  debug("processing route file: %s", filePath);
@@ -97,7 +96,7 @@ async function updateRouteFile(filePath, methodToTypeName) {
97
96
  const importRegex = /import\s+type\s+\{(?<types>[^}]+)\}\s+from\s+["'][^"']+["'];?/gu;
98
97
  let importMatch;
99
98
  while ((importMatch = importRegex.exec(content)) !== null) {
100
- const importedTypes = importMatch.groups.types
99
+ const importedTypes = (importMatch.groups?.["types"] ?? "")
101
100
  .split(",")
102
101
  .map((t) => t.trim())
103
102
  .filter((t) => t.length > 0);
@@ -105,7 +104,7 @@ async function updateRouteFile(filePath, methodToTypeName) {
105
104
  // Check if this is an HTTP_ type
106
105
  const httpMethodMatch = importedType.match(new RegExp(`^HTTP_(?<method>${HTTP_METHODS.join("|")})$`, "u"));
107
106
  if (httpMethodMatch) {
108
- const method = httpMethodMatch.groups.method;
107
+ const method = httpMethodMatch.groups?.["method"] ?? "";
109
108
  const newTypeName = methodToTypeName.get(method);
110
109
  if (newTypeName && newTypeName !== importedType) {
111
110
  replacements.set(importedType, newTypeName);
@@ -127,7 +126,7 @@ async function updateRouteFile(filePath, methodToTypeName) {
127
126
  // Match the method from the old type name
128
127
  const methodMatch = oldName.match(new RegExp(`^HTTP_(?<method>${HTTP_METHODS.join("|")})$`, "u"));
129
128
  if (methodMatch) {
130
- const method = methodMatch.groups.method;
129
+ const method = methodMatch.groups?.["method"] ?? "";
131
130
  const exportPattern = new RegExp(`(export\\s+const\\s+${method}\\s*:\\s*)${oldName}(\\b)`, "g");
132
131
  content = content.replace(exportPattern, `$1${newName}$2`);
133
132
  }
@@ -141,10 +140,10 @@ async function updateRouteFile(filePath, methodToTypeName) {
141
140
  }
142
141
  /**
143
142
  * Recursively processes route files in a directory
144
- * @param {string} routesDir - Path to routes directory
145
- * @param {string} currentPath - Current subdirectory being processed
146
- * @param {Map<string, Map<string, string>>} mapping - Type name mapping
147
- * @returns {Promise<number>} - Number of files updated
143
+ * @param routesDir - Path to routes directory
144
+ * @param currentPath - Current subdirectory being processed
145
+ * @param mapping - Type name mapping
146
+ * @returns Number of files updated
148
147
  */
149
148
  async function processRouteDirectory(routesDir, currentPath, mapping) {
150
149
  let updatedCount = 0;
@@ -183,8 +182,7 @@ async function processRouteDirectory(routesDir, currentPath, mapping) {
183
182
  }
184
183
  /**
185
184
  * Checks if any route files need migration
186
- * @param {string} routesDir - Path to routes directory
187
- * @returns {Promise<boolean>}
185
+ * @param routesDir - Path to routes directory
188
186
  */
189
187
  async function checkIfMigrationNeeded(routesDir) {
190
188
  try {
@@ -213,9 +211,9 @@ async function checkIfMigrationNeeded(routesDir) {
213
211
  }
214
212
  /**
215
213
  * Main migration function - updates route type imports to use new naming convention
216
- * @param {string} basePath - Base path where routes and types are located
217
- * @param {string} openApiPath - Path or URL to OpenAPI specification
218
- * @returns {Promise<boolean>} - True if migration was performed
214
+ * @param basePath - Base path where routes and types are located
215
+ * @param openApiPath - Path or URL to OpenAPI specification
216
+ * @returns True if migration was performed
219
217
  */
220
218
  export async function updateRouteTypes(basePath, openApiPath) {
221
219
  debug("starting route type migration for base path: %s", basePath);
@@ -1,14 +1,15 @@
1
1
  export class Coder {
2
+ requirement;
2
3
  constructor(requirement) {
3
4
  this.requirement = requirement;
4
5
  }
5
6
  get id() {
6
7
  if (this.requirement.isReference) {
7
- return `${this.constructor.name}@${this.requirement.$ref}`;
8
+ return `${this.constructor.name}@${this.requirement.data["$ref"]}`;
8
9
  }
9
10
  return `${this.constructor.name}@${this.requirement.url}`;
10
11
  }
11
- beforeExport() {
12
+ beforeExport(_path) {
12
13
  return "";
13
14
  }
14
15
  write(script) {
@@ -17,7 +18,7 @@ export class Coder {
17
18
  }
18
19
  return this.writeCode(script);
19
20
  }
20
- writeCode() {
21
+ writeCode(_script) {
21
22
  throw new Error("write() is abstract and should be overwritten by a subclass");
22
23
  }
23
24
  async delegate() {
@@ -39,7 +40,7 @@ export class Coder {
39
40
  yield name + index;
40
41
  }
41
42
  }
42
- typeDeclaration() {
43
+ typeDeclaration(_namespace, _script) {
43
44
  return "";
44
45
  }
45
46
  modulePath() {
@@ -27,7 +27,7 @@ async function getPathsFromSpecification(specification) {
27
27
  }
28
28
  catch (error) {
29
29
  process.stderr.write(`Could not find #/paths in the specification.\n${error}\n`);
30
- return new Set();
30
+ return undefined;
31
31
  }
32
32
  }
33
33
  export async function generate(source, destination, generateOptions, repository = new Repository()) {
@@ -40,10 +40,10 @@ export async function generate(source, destination, generateOptions, repository
40
40
  debug("created specification: $o", specification);
41
41
  debug("reading the #/paths from the specification");
42
42
  const paths = await getPathsFromSpecification(specification);
43
- debug("got %i paths", paths.size);
43
+ debug("got %i paths", paths?.map?.length ?? 0);
44
44
  if (generateOptions.prune && generateOptions.routes) {
45
45
  debug("pruning defunct route files");
46
- await pruneRoutes(destination, paths.keys());
46
+ await pruneRoutes(destination, paths.map((_v, key) => key));
47
47
  debug("done pruning");
48
48
  }
49
49
  const securityRequirement = specification.getRequirement("#/components/securitySchemes");
@@ -1,8 +1,10 @@
1
1
  import nodePath from "node:path";
2
2
  import { Coder } from "./coder.js";
3
- import { OperationTypeCoder } from "./operation-type-coder.js";
3
+ import { OperationTypeCoder, } from "./operation-type-coder.js";
4
4
  export class OperationCoder extends Coder {
5
- constructor(requirement, requestMethod, securitySchemes = {}) {
5
+ requestMethod;
6
+ securitySchemes;
7
+ constructor(requirement, requestMethod, securitySchemes = []) {
6
8
  super(requirement);
7
9
  if (requestMethod === undefined) {
8
10
  throw new Error("requestMethod is required");
@@ -15,9 +17,10 @@ export class OperationCoder extends Coder {
15
17
  }
16
18
  write() {
17
19
  const responses = this.requirement.get("responses");
18
- const [firstStatusCode] = responses.map((response, statusCode) => statusCode);
20
+ const [firstStatusCode] = responses.map((_response, statusCode) => statusCode);
19
21
  const [firstResponse] = responses.map((response) => response.data);
20
- if (!("content" in firstResponse || "schema" in firstResponse)) {
22
+ if (firstResponse === undefined ||
23
+ !("content" in firstResponse || "schema" in firstResponse)) {
21
24
  return `async ($) => {
22
25
  return $.response[${firstStatusCode === "default" ? 200 : firstStatusCode}];
23
26
  }`;
@@ -26,7 +29,7 @@ export class OperationCoder extends Coder {
26
29
  return $.response[${firstStatusCode === "default" ? 200 : firstStatusCode}].random();
27
30
  }`;
28
31
  }
29
- typeDeclaration(namespace, script) {
32
+ typeDeclaration(_namespace, script) {
30
33
  const operationTypeCoder = new OperationTypeCoder(this.requirement, this.requestMethod, this.securitySchemes);
31
34
  return script.importType(operationTypeCoder);
32
35
  }
@@ -1,11 +1,11 @@
1
1
  import nodePath from "node:path";
2
2
  import { CONTEXT_FILE_TOKEN } from "./context-file-token.js";
3
+ import { ParameterExportTypeCoder } from "./parameter-export-type-coder.js";
3
4
  import { ParametersTypeCoder } from "./parameters-type-coder.js";
4
5
  import { READ_ONLY_COMMENTS } from "./read-only-comments.js";
5
6
  import { ResponsesTypeCoder } from "./responses-type-coder.js";
6
7
  import { SchemaTypeCoder } from "./schema-type-coder.js";
7
8
  import { TypeCoder } from "./type-coder.js";
8
- import { ParameterExportTypeCoder } from "./parameter-export-type-coder.js";
9
9
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words
10
10
  const RESERVED_WORDS = new Set([
11
11
  "break",
@@ -72,6 +72,8 @@ function sanitizeIdentifier(value) {
72
72
  return result || "_";
73
73
  }
74
74
  export class OperationTypeCoder extends TypeCoder {
75
+ requestMethod;
76
+ securitySchemes;
75
77
  constructor(requirement, requestMethod, securitySchemes = []) {
76
78
  super(requirement);
77
79
  if (requestMethod === undefined) {
@@ -114,15 +116,18 @@ export class OperationTypeCoder extends TypeCoder {
114
116
  }`);
115
117
  }
116
118
  if (response.has("schema")) {
117
- const produces = this.requirement?.get("produces")?.data ??
118
- this.requirement.specification.rootRequirement.get("produces").data;
119
- return produces
120
- .map((contentType) => `{
119
+ const producesReq = this.requirement?.get("produces") ??
120
+ this.requirement.specification?.rootRequirement?.get("produces");
121
+ const produces = producesReq?.data;
122
+ if (produces) {
123
+ return produces
124
+ .map((contentType) => `{
121
125
  status: ${status},
122
126
  contentType?: "${contentType}",
123
127
  body?: ${new SchemaTypeCoder(response.get("schema")).write(script)}
124
128
  }`)
125
- .join(" | ");
129
+ .join(" | ");
130
+ }
126
131
  }
127
132
  return `{
128
133
  status: ${status}
@@ -157,17 +162,18 @@ export class OperationTypeCoder extends TypeCoder {
157
162
  const pathType = new ParametersTypeCoder(parameters, "path").write(script);
158
163
  const headersType = new ParametersTypeCoder(parameters, "header").write(script);
159
164
  const cookieType = new ParametersTypeCoder(parameters, "cookie").write(script);
160
- const bodyRequirement = this.requirement.get("consumes") ||
161
- this.requirement.specification?.rootRequirement?.get("consumes")
165
+ const bodyRequirement = (this.requirement.get("consumes") ??
166
+ this.requirement.specification?.rootRequirement?.get("consumes"))
162
167
  ? parameters
163
- ?.find((parameter) => ["body", "formData"].includes(parameter.get("in").data))
168
+ ?.find((parameter) => ["body", "formData"].includes(parameter.get("in")?.data))
164
169
  ?.get("schema")
165
170
  : this.requirement.select("requestBody/content/application~1json/schema");
166
171
  const bodyType = bodyRequirement === undefined
167
172
  ? "never"
168
173
  : new SchemaTypeCoder(bodyRequirement).write(script);
169
- const responseType = new ResponsesTypeCoder(this.requirement.get("responses"), this.requirement.get("produces")?.data ??
170
- this.requirement.specification?.rootRequirement?.get("produces")?.data).write(script);
174
+ const responseType = new ResponsesTypeCoder(this.requirement.get("responses"), (this.requirement.get("produces")?.data ??
175
+ this.requirement.specification?.rootRequirement?.get("produces")
176
+ ?.data)).write(script);
171
177
  const proxyType = "(url: string) => COUNTERFACT_RESPONSE";
172
178
  const delayType = "(milliseconds: number, maxMilliseconds?: number) => Promise<void>";
173
179
  // Get the base name for this operation and export parameter types
@@ -1,6 +1,9 @@
1
1
  import { TypeCoder } from "./type-coder.js";
2
- // Helper class for exporting parameter types
3
2
  export class ParameterExportTypeCoder extends TypeCoder {
3
+ _typeName;
4
+ _typeCode;
5
+ _parameterKind;
6
+ _modulePath;
4
7
  constructor(requirement, typeName, typeCode, parameterKind) {
5
8
  super(requirement);
6
9
  this._typeName = typeName;
@@ -2,6 +2,7 @@ import nodePath from "node:path";
2
2
  import { SchemaTypeCoder } from "./schema-type-coder.js";
3
3
  import { TypeCoder } from "./type-coder.js";
4
4
  export class ParametersTypeCoder extends TypeCoder {
5
+ placement;
5
6
  constructor(requirement, placement) {
6
7
  super(requirement);
7
8
  this.placement = placement;
@@ -5,9 +5,9 @@ const debug = createDebug("counterfact:typescript-generator:prune");
5
5
  /**
6
6
  * Collects all .ts route files in a directory recursively.
7
7
  * Context files (_.context.ts) are excluded.
8
- * @param {string} routesDir - Path to routes directory
9
- * @param {string} currentPath - Current subdirectory being processed (relative to routesDir)
10
- * @returns {Promise<string[]>} - Array of relative paths (using forward slashes)
8
+ * @param routesDir - Path to routes directory
9
+ * @param currentPath - Current subdirectory being processed (relative to routesDir)
10
+ * @returns Array of relative paths (using forward slashes)
11
11
  */
12
12
  async function collectRouteFiles(routesDir, currentPath = "") {
13
13
  const files = [];
@@ -37,8 +37,8 @@ async function collectRouteFiles(routesDir, currentPath = "") {
37
37
  }
38
38
  /**
39
39
  * Recursively removes empty directories under rootDir, but not rootDir itself.
40
- * @param {string} dir - Directory to check
41
- * @param {string} rootDir - Root directory that should never be removed
40
+ * @param dir - Directory to check
41
+ * @param rootDir - Root directory that should never be removed
42
42
  */
43
43
  async function removeEmptyDirectories(dir, rootDir) {
44
44
  let entries;
@@ -65,8 +65,7 @@ async function removeEmptyDirectories(dir, rootDir) {
65
65
  /**
66
66
  * Converts an OpenAPI path to the expected route file path (relative to routesDir).
67
67
  * e.g. "/pet/{id}" -> "pet/{id}.ts", "/" -> "index.ts"
68
- * @param {string} openApiPath
69
- * @returns {string}
68
+ * @param openApiPath - The OpenAPI path string
70
69
  */
71
70
  function openApiPathToRouteFile(openApiPath) {
72
71
  const filePath = openApiPath === "/" ? "index" : openApiPath.slice(1);
@@ -75,9 +74,9 @@ function openApiPathToRouteFile(openApiPath) {
75
74
  /**
76
75
  * Prunes route files that no longer correspond to any path in the OpenAPI spec.
77
76
  * Context files (_.context.ts) are never pruned.
78
- * @param {string} destination - Base destination directory (contains the routes/ sub-directory)
79
- * @param {Iterable<string>} openApiPaths - Iterable of OpenAPI path strings (e.g. "/pet/{id}")
80
- * @returns {Promise<number>} - Number of files removed
77
+ * @param destination - Base destination directory (contains the routes/ sub-directory)
78
+ * @param openApiPaths - Iterable of OpenAPI path strings (e.g. "/pet/{id}")
79
+ * @returns Number of files removed
81
80
  */
82
81
  export async function pruneRoutes(destination, openApiPaths) {
83
82
  const routesDir = nodePath.join(destination, "routes");
@@ -11,6 +11,7 @@ const debug = createDebug("counterfact:server:repository");
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url)).replaceAll("\\", "/");
12
12
  debug("dirname is %s", __dirname);
13
13
  export class Repository {
14
+ scripts;
14
15
  constructor() {
15
16
  this.scripts = new Map();
16
17
  }
@@ -37,7 +38,6 @@ export class Repository {
37
38
  if (!existsSync(sourcePath)) {
38
39
  return false;
39
40
  }
40
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
41
41
  return fs.cp(sourcePath, destinationPath, { recursive: true });
42
42
  }
43
43
  async writeFiles(destination, { routes, types }) {
@@ -1,14 +1,17 @@
1
1
  export class Requirement {
2
+ data;
3
+ url;
4
+ specification;
2
5
  constructor(data, url = "", specification = undefined) {
3
6
  this.data = data;
4
7
  this.url = url;
5
8
  this.specification = specification;
6
9
  }
7
10
  get isReference() {
8
- return this.data?.$ref !== undefined;
11
+ return this.data["$ref"] !== undefined;
9
12
  }
10
13
  reference() {
11
- return this.specification.getRequirement(this.data.$ref);
14
+ return this.specification.getRequirement(this.data["$ref"]);
12
15
  }
13
16
  has(item) {
14
17
  if (this.isReference) {
@@ -20,19 +23,21 @@ export class Requirement {
20
23
  if (this.isReference) {
21
24
  return this.reference().get(item);
22
25
  }
23
- if (!this.has(item)) {
26
+ const key = String(item);
27
+ if (!this.has(key)) {
24
28
  return undefined;
25
29
  }
26
- return new Requirement(this.data[item], `${this.url}/${this.escapeJsonPointer(item)}`, this.specification);
30
+ return new Requirement(this.data[key], `${this.url}/${this.escapeJsonPointer(key)}`, this.specification);
27
31
  }
28
32
  select(path) {
29
33
  const parts = path
30
34
  .split("/")
31
- .map(this.unescapeJsonPointer)
35
+ .map((p) => this.unescapeJsonPointer(p))
32
36
  // Unescape URL encoded characters (e.g. %20 -> " ")
33
37
  // Technically we should not be unescaping, but it came up in https://github.com/pmcelhaney/counterfact/issues/1083
34
38
  // and I can't think of a reason anyone would intentionally put a % in a key name.
35
39
  .map(unescape);
40
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
36
41
  let result = this;
37
42
  for (const part of parts) {
38
43
  result = result.get(part);
@@ -2,16 +2,19 @@ import { printObject } from "./printers.js";
2
2
  import { SchemaTypeCoder } from "./schema-type-coder.js";
3
3
  import { TypeCoder } from "./type-coder.js";
4
4
  export class ResponseTypeCoder extends TypeCoder {
5
+ openApi2MediaTypes;
5
6
  constructor(requirement, openApi2MediaTypes = []) {
6
7
  super(requirement);
7
8
  this.openApi2MediaTypes = openApi2MediaTypes;
8
9
  }
9
10
  names() {
10
- return super.names(this.requirement.data.$ref.split("/").at(-1));
11
+ return super.names(this.requirement.data["$ref"].split("/").at(-1));
11
12
  }
12
13
  buildContentObjectType(script, response) {
13
14
  if (response.has("content")) {
14
- return response.get("content").map((content, mediaType) => [
15
+ return response
16
+ .get("content")
17
+ .map((content, mediaType) => [
15
18
  mediaType,
16
19
  `{
17
20
  schema: ${content.has("schema") ? new SchemaTypeCoder(content.get("schema")).write(script) : "unknown"}
@@ -46,8 +49,10 @@ export class ResponseTypeCoder extends TypeCoder {
46
49
  return printObject(this.buildHeaders(script, response));
47
50
  }
48
51
  printRequiredHeaders(response) {
49
- const requiredHeaders = (response.get("headers") ?? [])
50
- .map((value, name) => ({ name, required: value.data.required }))
52
+ const requiredHeaders = (response.get("headers")?.map((value, name) => ({
53
+ name,
54
+ required: value.data.required,
55
+ })) ?? [])
51
56
  .filter(({ required }) => required)
52
57
  .map(({ name }) => `"${name}"`);
53
58
  return requiredHeaders.length === 0 ? "never" : requiredHeaders.join(" | ");
@@ -72,7 +77,7 @@ export class ResponseTypeCoder extends TypeCoder {
72
77
  return printObject(exampleNames.map((name) => [name, "unknown"]));
73
78
  }
74
79
  modulePath() {
75
- return `types/${this.requirement.data.$ref}.ts`;
80
+ return `types/${this.requirement.data["$ref"]}.ts`;
76
81
  }
77
82
  writeCode(script) {
78
83
  return `{
@@ -2,6 +2,7 @@ import { printObjectWithoutQuotes } from "./printers.js";
2
2
  import { ResponseTypeCoder } from "./response-type-coder.js";
3
3
  import { TypeCoder } from "./type-coder.js";
4
4
  export class ResponsesTypeCoder extends TypeCoder {
5
+ openApi2MediaTypes;
5
6
  constructor(requirement, openApi2MediaTypes = []) {
6
7
  super(requirement);
7
8
  this.openApi2MediaTypes = openApi2MediaTypes;
@@ -2,13 +2,13 @@ import { Coder } from "./coder.js";
2
2
  function scrubSchema(schema) {
3
3
  // remove properties that are not valid in JSON Schema 6 and not useful anyway
4
4
  const cleaned = { ...schema };
5
- delete cleaned.example;
6
- delete cleaned.xml;
5
+ delete cleaned["example"];
6
+ delete cleaned["xml"];
7
7
  return cleaned;
8
8
  }
9
9
  export class SchemaCoder extends Coder {
10
10
  names() {
11
- return super.names(`${this.requirement.data.$ref.split("/").at(-1)}Schema`);
11
+ return super.names(`${this.requirement.data["$ref"].split("/").at(-1)}Schema`);
12
12
  }
13
13
  objectSchema(script) {
14
14
  const { properties, required } = this.requirement.data;
@@ -30,11 +30,11 @@ export class SchemaCoder extends Coder {
30
30
  items: ${new SchemaCoder(this.requirement.get("items")).write(script)}
31
31
  }`;
32
32
  }
33
- typeDeclaration(namespace, script) {
33
+ typeDeclaration(_namespace, script) {
34
34
  return script.importExternalType("JSONSchema6", "json-schema");
35
35
  }
36
36
  modulePath() {
37
- return `types/${this.requirement.data.$ref}.ts`;
37
+ return `types/${this.requirement.data["$ref"]}.ts`;
38
38
  }
39
39
  writeCode(script) {
40
40
  const { type } = this.requirement.data;
@@ -1,7 +1,7 @@
1
1
  import { TypeCoder } from "./type-coder.js";
2
2
  export class SchemaTypeCoder extends TypeCoder {
3
3
  names() {
4
- return super.names(this.requirement.data.$ref.split("/").at(-1));
4
+ return super.names(this.requirement.data["$ref"]?.split("/").at(-1));
5
5
  }
6
6
  additionalPropertiesType(script) {
7
7
  const { additionalProperties, properties } = this.requirement.data;
@@ -16,13 +16,15 @@ export class SchemaTypeCoder extends TypeCoder {
16
16
  }
17
17
  objectSchema(script) {
18
18
  const { data } = this.requirement;
19
- const properties = Object.keys(data.properties ?? {}).map((name) => {
19
+ const typedData = data;
20
+ const properties = Object.keys(typedData.properties ?? {}).map((name) => {
20
21
  const property = this.requirement.get("properties").get(name);
21
- const isRequired = data.required?.includes(name) || property.data.required === true;
22
+ const propertyData = property.data;
23
+ const isRequired = typedData.required?.includes(name) || propertyData.required === true;
22
24
  const optionalFlag = isRequired ? "" : "?";
23
25
  return `"${name}"${optionalFlag}: ${new SchemaTypeCoder(property).write(script)}`;
24
26
  });
25
- if (data.additionalProperties) {
27
+ if (typedData.additionalProperties) {
26
28
  properties.push(`[key: string]: ${this.additionalPropertiesType(script)}`);
27
29
  }
28
30
  return `{${properties.join(",")}}`;
@@ -37,11 +39,13 @@ export class SchemaTypeCoder extends TypeCoder {
37
39
  if (value === null) {
38
40
  return "null";
39
41
  }
40
- return value;
42
+ return String(value);
41
43
  }
42
44
  writeType(script, type) {
43
45
  if (Array.isArray(type)) {
44
- return type.map((item) => this.writeType(script, item)).join(" | ");
46
+ return type
47
+ .map((item) => this.writeType(script, item))
48
+ .join(" | ");
45
49
  }
46
50
  if (typeof type !== "string") {
47
51
  return "unknown";
@@ -57,7 +61,7 @@ export class SchemaTypeCoder extends TypeCoder {
57
61
  }
58
62
  return type ?? "unknown";
59
63
  }
60
- writeGroup(script, { allOf, anyOf, oneOf }) {
64
+ writeGroup(script, { allOf, anyOf, oneOf, }) {
61
65
  function matchingKey() {
62
66
  if (allOf) {
63
67
  return "allOf";
@@ -67,19 +71,20 @@ export class SchemaTypeCoder extends TypeCoder {
67
71
  }
68
72
  return "oneOf";
69
73
  }
70
- const types = (allOf ?? anyOf ?? oneOf).map((item, index) => new SchemaTypeCoder(this.requirement.get(matchingKey()).get(index)).write(script));
74
+ const key = matchingKey();
75
+ const items = (allOf ?? anyOf ?? oneOf);
76
+ const types = items.map((_item, index) => new SchemaTypeCoder(this.requirement.get(key).get(index)).write(script));
71
77
  return types.join(allOf ? " & " : " | ");
72
78
  }
73
- writeEnum(script, requirement) {
79
+ writeEnum(_script, requirement) {
74
80
  return requirement.data
75
81
  .map((item) => this.writePrimitive(item))
76
82
  .join(" | ");
77
83
  }
78
84
  modulePath() {
79
- return `types/${this.requirement.data.$ref.replace(/^#\//u, "")}.ts`;
85
+ return `types/${this.requirement.data["$ref"].replace(/^#\//u, "")}.ts`;
80
86
  }
81
87
  writeCode(script) {
82
- // script.comments = READ_ONLY_COMMENTS;
83
88
  const { allOf, anyOf, oneOf, type, format } = this.requirement.data;
84
89
  if (allOf ?? anyOf ?? oneOf) {
85
90
  return this.writeGroup(script, { allOf, anyOf, oneOf });
@@ -4,6 +4,14 @@ import { format } from "prettier";
4
4
  import { escapePathForWindows } from "../util/windows-escape.js";
5
5
  const debug = createDebugger("counterfact:typescript-generator:script");
6
6
  export class Script {
7
+ repository;
8
+ comments;
9
+ exports;
10
+ imports;
11
+ externalImport;
12
+ cache;
13
+ typeCache;
14
+ path;
7
15
  constructor(repository, path) {
8
16
  this.repository = repository;
9
17
  this.comments = [];
@@ -54,6 +62,7 @@ export class Script {
54
62
  .catch((error) => {
55
63
  exportStatement.code = `{/* error creating export "${name}" for ${this.path}: ${error.stack} */}`;
56
64
  exportStatement.error = error;
65
+ return undefined;
57
66
  })
58
67
  .finally(() => {
59
68
  exportStatement.done = true;
@@ -126,15 +135,17 @@ export class Script {
126
135
  }
127
136
  exportStatements() {
128
137
  return Array.from(this.exports.values(), ({ beforeExport, code, isDefault, isType, name, typeDeclaration }) => {
129
- if (code.raw) {
138
+ if (typeof code === "object" && code !== null && "raw" in code) {
130
139
  return code.raw;
131
140
  }
132
141
  if (isDefault) {
133
142
  return `${beforeExport}export default ${code};`;
134
143
  }
135
144
  const keyword = isType ? "type" : "const";
136
- const typeAnnotation = typeDeclaration.length === 0 ? "" : `:${typeDeclaration}`;
137
- return `${beforeExport}export ${keyword} ${name}${typeAnnotation} = ${code};`;
145
+ const typeAnnotation = (typeDeclaration ?? "").length === 0
146
+ ? ""
147
+ : `:${typeDeclaration ?? ""}`;
148
+ return `${beforeExport}export ${keyword} ${name ?? ""}${typeAnnotation} = ${code};`;
138
149
  });
139
150
  }
140
151
  contents() {
@@ -1,12 +1,15 @@
1
+ import { bundle } from "@apidevtools/json-schema-ref-parser";
1
2
  import createDebug from "debug";
2
3
  import { Requirement } from "./requirement.js";
3
- import { bundle } from "@apidevtools/json-schema-ref-parser";
4
4
  const debug = createDebug("counterfact:typescript-generator:specification");
5
5
  export class Specification {
6
+ cache;
7
+ rootRequirement;
6
8
  constructor(rootRequirement) {
7
9
  this.cache = new Map();
8
- this.rootUrl = rootRequirement;
9
- this.rootRequirement = rootRequirement;
10
+ if (rootRequirement) {
11
+ this.rootRequirement = rootRequirement;
12
+ }
10
13
  }
11
14
  static async fromFile(urlOrPath) {
12
15
  const specification = new Specification();
@@ -18,6 +21,6 @@ export class Specification {
18
21
  return this.rootRequirement.select(url.slice(2));
19
22
  }
20
23
  async load(urlOrPath) {
21
- this.rootRequirement = new Requirement(await bundle(urlOrPath), urlOrPath, this);
24
+ this.rootRequirement = new Requirement((await bundle(urlOrPath)), urlOrPath, this);
22
25
  }
23
26
  }
@@ -3,7 +3,7 @@ import nodePath from "node:path";
3
3
  export function ensureDirectoryExists(filePath) {
4
4
  const directory = nodePath.dirname(filePath);
5
5
  try {
6
- fs.accessSync(directory, fs.fsConstants.W_OK);
6
+ fs.accessSync(directory, fs.constants.W_OK);
7
7
  }
8
8
  catch {
9
9
  // with the async option, await doesn't seem to wait for the directory to be created
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "2.5.0",
3
+ "version": "2.5.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",