zenko 0.1.3 → 0.1.5

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/dist/cli.mjs CHANGED
@@ -210,7 +210,7 @@ function isErrorStatus(status) {
210
210
  }
211
211
 
212
212
  // src/zenko.ts
213
- function generate(spec, options = {}) {
213
+ function generateWithMetadata(spec, options = {}) {
214
214
  const output = [];
215
215
  const generatedTypes = /* @__PURE__ */ new Set();
216
216
  const { strictDates = false, strictNumeric = false } = options;
@@ -243,8 +243,11 @@ function generate(spec, options = {}) {
243
243
  const pathParamNames = op.pathParams.map((p) => p.name);
244
244
  const hasPathParams = pathParamNames.length > 0;
245
245
  const hasQueryParams = op.queryParams.length > 0;
246
+ const camelCaseOperationId = toCamelCase(op.operationId);
246
247
  if (!hasPathParams && !hasQueryParams) {
247
- output.push(` ${op.operationId}: () => "${op.path}",`);
248
+ output.push(
249
+ ` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
250
+ );
248
251
  continue;
249
252
  }
250
253
  const allParamNames = [
@@ -267,11 +270,13 @@ function generate(spec, options = {}) {
267
270
  const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
268
271
  if (!hasQueryParams) {
269
272
  output.push(
270
- ` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
273
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
271
274
  );
272
275
  continue;
273
276
  }
274
- output.push(` ${op.operationId}: (${signature}) => {`);
277
+ output.push(
278
+ ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
279
+ );
275
280
  output.push(" const params = new URLSearchParams()");
276
281
  for (const param of op.queryParams) {
277
282
  const propertyKey = formatPropertyName(param.name);
@@ -316,83 +321,57 @@ function generate(spec, options = {}) {
316
321
  }
317
322
  output.push("} as const;");
318
323
  output.push("");
319
- output.push("// Header Functions");
320
- output.push("export const headers = {");
324
+ output.push("// Header Schemas");
325
+ output.push("export const headerSchemas = {");
321
326
  for (const op of operations) {
327
+ const camelCaseOperationId = toCamelCase(op.operationId);
322
328
  if (!op.requestHeaders || op.requestHeaders.length === 0) {
323
- output.push(` ${op.operationId}: () => ({}),`);
324
- continue;
325
- }
326
- const typeEntries = op.requestHeaders.map(
327
- (header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
328
- header
329
- )}`
330
- ).join(", ");
331
- const requiredHeaders = op.requestHeaders.filter(
332
- (header) => header.required
333
- );
334
- const optionalHeaders = op.requestHeaders.filter(
335
- (header) => !header.required
336
- );
337
- const hasRequired = requiredHeaders.length > 0;
338
- const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
339
- if (optionalHeaders.length === 0) {
340
- output.push(` ${op.operationId}: ${signature} => ({`);
341
- for (const header of requiredHeaders) {
342
- const propertyKey = formatPropertyName(header.name);
343
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
344
- output.push(` ${propertyKey}: ${accessor},`);
345
- }
346
- output.push(" }),");
329
+ output.push(
330
+ ` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
331
+ );
347
332
  continue;
348
333
  }
349
- if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
350
- const header = optionalHeaders[0];
351
- const propertyKey = formatPropertyName(header.name);
352
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
353
- output.push(` ${op.operationId}: ${signature} =>`);
334
+ const schemaFields = op.requestHeaders.map((header) => {
335
+ const zodType = mapHeaderToZodType(header);
336
+ const optional = header.required ? "" : ".optional()";
337
+ return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
338
+ }).join("\n");
339
+ output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
340
+ output.push(schemaFields);
341
+ output.push(" }),");
342
+ }
343
+ output.push("} as const;");
344
+ output.push("");
345
+ output.push("// Header Functions");
346
+ output.push("export const headers = {");
347
+ for (const op of operations) {
348
+ const camelCaseOperationId = toCamelCase(op.operationId);
349
+ if (!op.requestHeaders || op.requestHeaders.length === 0) {
354
350
  output.push(
355
- ` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
351
+ ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
356
352
  );
357
353
  continue;
358
354
  }
359
- const valueTypes = Array.from(
360
- new Set(optionalHeaders.map((header) => mapHeaderType(header)))
361
- ).join(" | ");
362
- output.push(` ${op.operationId}: ${signature} => {`);
363
- if (hasRequired) {
364
- output.push(" const headers = {");
365
- for (const header of requiredHeaders) {
366
- const propertyKey = formatPropertyName(header.name);
367
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
368
- output.push(` ${propertyKey}: ${accessor},`);
369
- }
370
- output.push(" }");
371
- } else {
372
- output.push(` const headers: Record<string, ${valueTypes}> = {}`);
373
- }
374
- for (const header of optionalHeaders) {
375
- const propertyKey = formatPropertyName(header.name);
376
- const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
377
- const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
378
- output.push(` if (${accessor} !== undefined) {`);
379
- output.push(` ${assignment} = ${accessor}`);
380
- output.push(" }");
381
- }
382
- output.push(" return headers");
355
+ output.push(
356
+ ` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
357
+ );
358
+ output.push(
359
+ ` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
360
+ );
383
361
  output.push(" },");
384
362
  }
385
363
  output.push("} as const;");
386
364
  output.push("");
387
365
  output.push("// Operation Objects");
388
366
  for (const op of operations) {
389
- output.push(`export const ${op.operationId} = {`);
367
+ const camelCaseOperationId = toCamelCase(op.operationId);
368
+ output.push(`export const ${camelCaseOperationId} = {`);
390
369
  output.push(` method: "${op.method}",`);
391
- output.push(` path: paths.${op.operationId},`);
370
+ output.push(` path: paths.${camelCaseOperationId},`);
392
371
  appendOperationField(output, "request", op.requestType);
393
372
  appendOperationField(output, "response", op.responseType);
394
373
  if (op.requestHeaders && op.requestHeaders.length > 0) {
395
- output.push(` headers: headers.${op.operationId},`);
374
+ output.push(` headers: headers.${camelCaseOperationId},`);
396
375
  }
397
376
  if (op.errors && hasAnyErrors(op.errors)) {
398
377
  output.push(" errors: {");
@@ -406,7 +385,16 @@ function generate(spec, options = {}) {
406
385
  output.push("");
407
386
  }
408
387
  generateOperationTypes(output, operations, typesConfig);
409
- return output.join("\n");
388
+ const result = {
389
+ output: output.join("\n")
390
+ };
391
+ if (typesConfig.emit && typesConfig.helpers === "file" && typesConfig.helpersOutput) {
392
+ result.helperFile = {
393
+ path: typesConfig.helpersOutput,
394
+ content: generateHelperFile()
395
+ };
396
+ }
397
+ return result;
410
398
  }
411
399
  function appendOperationField(buffer, key, value) {
412
400
  if (!value) return;
@@ -443,6 +431,36 @@ function isRequestMethod(method) {
443
431
  return false;
444
432
  }
445
433
  }
434
+ var CONTENT_TYPE_MAP = {
435
+ "application/json": "unknown",
436
+ // Will use schema when available
437
+ "text/csv": "string",
438
+ "text/plain": "string",
439
+ // Binary/ambiguous types default to unknown for cross-platform compatibility
440
+ "application/octet-stream": "unknown",
441
+ "application/pdf": "unknown"
442
+ };
443
+ function findContentType(content) {
444
+ const contentTypes = Object.keys(content);
445
+ if (contentTypes.includes("application/json")) {
446
+ return "application/json";
447
+ }
448
+ for (const contentType of contentTypes) {
449
+ if (contentType in CONTENT_TYPE_MAP) {
450
+ return contentType;
451
+ }
452
+ }
453
+ return contentTypes[0] || "";
454
+ }
455
+ function inferResponseType(contentType, statusCode) {
456
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
457
+ return "undefined";
458
+ }
459
+ if (contentType in CONTENT_TYPE_MAP) {
460
+ return CONTENT_TYPE_MAP[contentType];
461
+ }
462
+ return "unknown";
463
+ }
446
464
  function parseOperations(spec) {
447
465
  const operations = [];
448
466
  for (const [path2, pathItem] of Object.entries(spec.paths)) {
@@ -504,6 +522,9 @@ function appendHelperTypesImport(buffer, config) {
504
522
  buffer.push(
505
523
  "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
506
524
  );
525
+ buffer.push(
526
+ "type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
527
+ );
507
528
  buffer.push(
508
529
  "type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
509
530
  );
@@ -513,7 +534,7 @@ function appendHelperTypesImport(buffer, config) {
513
534
  buffer.push(" otherErrors?: TOther;");
514
535
  buffer.push("};");
515
536
  buffer.push(
516
- "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
537
+ "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
517
538
  );
518
539
  buffer.push(" method: TMethod;");
519
540
  buffer.push(" path: TPath;");
@@ -529,15 +550,18 @@ function generateOperationTypes(buffer, operations, config) {
529
550
  if (!config.emit) return;
530
551
  buffer.push("// Operation Types");
531
552
  for (const op of operations) {
532
- const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
553
+ const camelCaseOperationId = toCamelCase(op.operationId);
554
+ const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
533
555
  const requestType = wrapTypeReference(op.requestType);
534
556
  const responseType = wrapTypeReference(op.responseType);
535
557
  const errorsType = buildOperationErrorsType(op.errors);
536
558
  buffer.push(
537
- `export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
559
+ `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
538
560
  );
539
561
  buffer.push(` "${op.method}",`);
540
- buffer.push(` typeof paths.${op.operationId},`);
562
+ buffer.push(
563
+ ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
564
+ );
541
565
  buffer.push(` ${requestType},`);
542
566
  buffer.push(` ${responseType},`);
543
567
  buffer.push(` ${headerType},`);
@@ -581,26 +605,35 @@ var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
581
605
  "bigint",
582
606
  "symbol"
583
607
  ]);
608
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
584
609
  function wrapTypeReference(typeName) {
585
610
  if (!typeName) return "undefined";
586
- if (typeName === "undefined") return "undefined";
587
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
588
- if (typeName.startsWith("typeof ")) return typeName;
589
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
590
- if (identifierPattern.test(typeName)) {
591
- return `typeof ${typeName}`;
611
+ const normalized = typeName.trim();
612
+ if (normalized === "undefined") return "undefined";
613
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
614
+ if (normalized.startsWith("typeof ")) return normalized;
615
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
616
+ if (arrayMatch) {
617
+ return `z.ZodArray<${wrapTypeReference(arrayMatch[1])}>`;
592
618
  }
593
- return typeName;
619
+ if (IDENTIFIER_PATTERN.test(normalized)) {
620
+ return `typeof ${normalized}`;
621
+ }
622
+ return normalized;
594
623
  }
595
624
  function wrapErrorValueType(typeName) {
596
625
  if (!typeName) return "unknown";
597
- if (TYPE_KEYWORDS.has(typeName)) return typeName;
598
- if (typeName.startsWith("typeof ")) return typeName;
599
- const identifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
600
- if (identifierPattern.test(typeName)) {
601
- return `typeof ${typeName}`;
626
+ const normalized = typeName.trim();
627
+ if (TYPE_KEYWORDS.has(normalized)) return normalized;
628
+ if (normalized.startsWith("typeof ")) return normalized;
629
+ const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
630
+ if (arrayMatch) {
631
+ return `z.ZodArray<${wrapErrorValueType(arrayMatch[1])}>`;
602
632
  }
603
- return typeName;
633
+ if (IDENTIFIER_PATTERN.test(normalized)) {
634
+ return `typeof ${normalized}`;
635
+ }
636
+ return normalized;
604
637
  }
605
638
  function collectParameters(pathItem, operation, spec) {
606
639
  const parametersMap = /* @__PURE__ */ new Map();
@@ -660,13 +693,39 @@ function getResponseTypes(operation, operationId) {
660
693
  const successCodes = /* @__PURE__ */ new Map();
661
694
  const errorEntries = [];
662
695
  for (const [statusCode, response] of Object.entries(responses)) {
663
- const resolvedSchema = response?.content?.["application/json"]?.schema;
664
- if (!resolvedSchema) continue;
696
+ const content = response?.content;
697
+ if (!content || Object.keys(content).length === 0) {
698
+ if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
699
+ successCodes.set(statusCode, "undefined");
700
+ } else if (isErrorStatus(statusCode)) {
701
+ errorEntries.push({
702
+ code: statusCode,
703
+ schema: "undefined"
704
+ });
705
+ }
706
+ continue;
707
+ }
708
+ const contentType = findContentType(content);
709
+ const resolvedSchema = content[contentType]?.schema;
710
+ if (!resolvedSchema) {
711
+ const inferredType = inferResponseType(contentType, statusCode);
712
+ if (inferredType) {
713
+ if (isErrorStatus(statusCode)) {
714
+ errorEntries.push({
715
+ code: statusCode,
716
+ schema: inferredType
717
+ });
718
+ } else if (/^2\d\d$/.test(statusCode)) {
719
+ successCodes.set(statusCode, inferredType);
720
+ }
721
+ }
722
+ continue;
723
+ }
665
724
  if (isErrorStatus(statusCode)) {
666
725
  errorEntries.push({ code: statusCode, schema: resolvedSchema });
667
726
  continue;
668
727
  }
669
- if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
728
+ if (/^2\d\d$/.test(statusCode)) {
670
729
  successCodes.set(statusCode, resolvedSchema);
671
730
  }
672
731
  }
@@ -680,6 +739,9 @@ function selectSuccessResponse(responses, operationId) {
680
739
  for (const code of preferredOrder) {
681
740
  const schema = responses.get(code);
682
741
  if (schema) {
742
+ if (typeof schema === "string") {
743
+ return schema;
744
+ }
683
745
  return resolveResponseType(
684
746
  schema,
685
747
  `${capitalize(operationId)}Response${code}`
@@ -688,6 +750,9 @@ function selectSuccessResponse(responses, operationId) {
688
750
  }
689
751
  const [firstCode, firstSchema] = responses.entries().next().value ?? [];
690
752
  if (!firstSchema) return void 0;
753
+ if (typeof firstSchema === "string") {
754
+ return firstSchema;
755
+ }
691
756
  return resolveResponseType(
692
757
  firstSchema,
693
758
  `${capitalize(operationId)}Response${firstCode ?? "Default"}`
@@ -725,9 +790,16 @@ function buildErrorGroups(errors = [], operationId) {
725
790
  return group;
726
791
  }
727
792
  function resolveResponseType(schema, fallbackName) {
793
+ if (typeof schema === "string") {
794
+ return schema;
795
+ }
728
796
  if (schema.$ref) {
729
797
  return extractRefName(schema.$ref);
730
798
  }
799
+ if (schema.type === "array" && schema.items?.$ref) {
800
+ const itemRef = extractRefName(schema.items.$ref);
801
+ return `z.array(${itemRef})`;
802
+ }
731
803
  return fallbackName;
732
804
  }
733
805
  function getRequestHeaders(parameters) {
@@ -758,16 +830,22 @@ function getQueryParams(parameters) {
758
830
  }
759
831
  return queryParams;
760
832
  }
761
- function mapHeaderType(header) {
762
- const schemaType = header.schema?.type;
833
+ function mapHeaderToZodType(header) {
834
+ const schema = header.schema ?? {};
835
+ const schemaType = schema.type;
763
836
  switch (schemaType) {
764
837
  case "integer":
765
838
  case "number":
766
- return "number";
839
+ return "z.coerce.number()";
767
840
  case "boolean":
768
- return "boolean";
841
+ return "z.coerce.boolean()";
842
+ case "array": {
843
+ const items = schema.items ?? { type: "string" };
844
+ const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
845
+ return `z.array(${itemType})`;
846
+ }
769
847
  default:
770
- return "string";
848
+ return "z.string()";
771
849
  }
772
850
  }
773
851
  function mapQueryType(param) {
@@ -971,6 +1049,54 @@ function applyNumericBounds(schema, builder) {
971
1049
  }
972
1050
  return builder;
973
1051
  }
1052
+ function generateHelperFile() {
1053
+ const output = [];
1054
+ output.push("// Generated helper types for Zenko");
1055
+ output.push(
1056
+ "// This file provides type definitions for operation objects and path functions"
1057
+ );
1058
+ output.push("");
1059
+ output.push(
1060
+ "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
1061
+ );
1062
+ output.push("");
1063
+ output.push(
1064
+ 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
1065
+ );
1066
+ output.push("");
1067
+ output.push(
1068
+ "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
1069
+ );
1070
+ output.push("");
1071
+ output.push(
1072
+ "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
1073
+ );
1074
+ output.push("");
1075
+ output.push(
1076
+ "export type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
1077
+ );
1078
+ output.push(" clientErrors?: TClient");
1079
+ output.push(" serverErrors?: TServer");
1080
+ output.push(" defaultErrors?: TDefault");
1081
+ output.push(" otherErrors?: TOther");
1082
+ output.push("}");
1083
+ output.push("");
1084
+ output.push(
1085
+ "export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
1086
+ );
1087
+ output.push(" method: TMethod");
1088
+ output.push(" path: TPath");
1089
+ output.push(" request?: TRequest");
1090
+ output.push(" response?: TResponse");
1091
+ output.push(" headers?: THeaders");
1092
+ output.push(" errors?: TErrors");
1093
+ output.push("}");
1094
+ output.push("");
1095
+ return output.join("\n");
1096
+ }
1097
+ function toCamelCase(str) {
1098
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
1099
+ }
974
1100
  function capitalize(str) {
975
1101
  return str.charAt(0).toUpperCase() + str.slice(1);
976
1102
  }
@@ -1132,15 +1258,31 @@ async function generateSingle(options) {
1132
1258
  const resolvedInput = path.resolve(inputFile);
1133
1259
  const resolvedOutput = path.resolve(outputFile);
1134
1260
  const spec = readSpec(resolvedInput);
1135
- const output = generate(spec, {
1261
+ const result = generateWithMetadata(spec, {
1136
1262
  strictDates,
1137
1263
  strictNumeric,
1138
1264
  types: typesConfig
1139
1265
  });
1140
1266
  fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
1141
- fs.writeFileSync(resolvedOutput, output);
1267
+ fs.writeFileSync(resolvedOutput, result.output);
1142
1268
  console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
1143
1269
  console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths).length} paths`);
1270
+ if (result.helperFile) {
1271
+ const helperPath = path.isAbsolute(result.helperFile.path) ? result.helperFile.path : path.resolve(path.dirname(resolvedOutput), result.helperFile.path);
1272
+ const absoluteResolvedOutput = path.resolve(resolvedOutput);
1273
+ const absoluteHelperPath = path.resolve(helperPath);
1274
+ if (absoluteResolvedOutput === absoluteHelperPath) {
1275
+ console.warn(
1276
+ `\u26A0\uFE0F Skipping helper file generation: would overwrite main output at ${absoluteResolvedOutput}`
1277
+ );
1278
+ return;
1279
+ }
1280
+ fs.mkdirSync(path.dirname(helperPath), { recursive: true });
1281
+ fs.writeFileSync(helperPath, result.helperFile.content, {
1282
+ encoding: "utf8"
1283
+ });
1284
+ console.log(`\u{1F4E6} Generated helper types in ${helperPath}`);
1285
+ }
1144
1286
  }
1145
1287
  function readSpec(filePath) {
1146
1288
  if (!fs.existsSync(filePath)) {