zenko 0.1.8 → 0.1.10-beta.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.
package/dist/cli.cjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -24,1340 +24,76 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli.ts
27
- var fs = __toESM(require("fs"), 1);
27
+ var path2 = __toESM(require("path"), 1);
28
+ var import_promises = require("fs/promises");
29
+ var import_core = require("@zenko/core");
30
+
31
+ // src/loader.ts
28
32
  var path = __toESM(require("path"), 1);
29
33
  var import_url = require("url");
30
- var import_js_yaml = require("js-yaml");
31
-
32
- // src/utils/topological-sort.ts
33
- function topologicalSort(schemas) {
34
- const visited = /* @__PURE__ */ new Set();
35
- const visiting = /* @__PURE__ */ new Set();
36
- const result = [];
37
- const visit = (name) => {
38
- if (visited.has(name)) return;
39
- if (visiting.has(name)) {
40
- return;
41
- }
42
- visiting.add(name);
43
- const schema = schemas[name];
44
- const dependencies = extractDependencies(schema);
45
- for (const dep of dependencies) {
46
- if (schemas[dep]) {
47
- visit(dep);
48
- }
49
- }
50
- visiting.delete(name);
51
- visited.add(name);
52
- result.push(name);
53
- };
54
- for (const name of Object.keys(schemas)) {
55
- visit(name);
56
- }
57
- return result;
58
- }
59
- function extractDependencies(schema) {
60
- const dependencies = [];
61
- const traverse = (obj) => {
62
- if (typeof obj !== "object" || obj === null) return;
63
- if (obj.$ref && typeof obj.$ref === "string") {
64
- const refName = extractRefName(obj.$ref);
65
- dependencies.push(refName);
66
- return;
67
- }
68
- if (Array.isArray(obj)) {
69
- obj.forEach(traverse);
70
- } else {
71
- Object.values(obj).forEach(traverse);
72
- }
73
- };
74
- traverse(schema);
75
- return [...new Set(dependencies)];
76
- }
77
- function extractRefName(ref) {
78
- return ref.split("/").pop() || "Unknown";
79
- }
80
-
81
- // src/utils/property-name.ts
82
- function isValidJSIdentifier(name) {
83
- if (!name) return false;
84
- const firstChar = name.at(0);
85
- if (firstChar === void 0) return false;
86
- if (!/[a-zA-Z_$]/.test(firstChar)) return false;
87
- if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) return false;
88
- const reservedWords = /* @__PURE__ */ new Set([
89
- "abstract",
90
- "arguments",
91
- "await",
92
- "boolean",
93
- "break",
94
- "byte",
95
- "case",
96
- "catch",
97
- "char",
98
- "class",
99
- "const",
100
- "continue",
101
- "debugger",
102
- "default",
103
- "delete",
104
- "do",
105
- "double",
106
- "else",
107
- "enum",
108
- "eval",
109
- "export",
110
- "extends",
111
- "false",
112
- "final",
113
- "finally",
114
- "float",
115
- "for",
116
- "function",
117
- "goto",
118
- "if",
119
- "implements",
120
- "import",
121
- "in",
122
- "instanceof",
123
- "int",
124
- "interface",
125
- "let",
126
- "long",
127
- "native",
128
- "new",
129
- "null",
130
- "package",
131
- "private",
132
- "protected",
133
- "public",
134
- "return",
135
- "short",
136
- "static",
137
- "super",
138
- "switch",
139
- "synchronized",
140
- "this",
141
- "throw",
142
- "throws",
143
- "transient",
144
- "true",
145
- "try",
146
- "typeof",
147
- "var",
148
- "void",
149
- "volatile",
150
- "while",
151
- "with",
152
- "yield",
153
- "async"
154
- ]);
155
- return !reservedWords.has(name);
156
- }
157
- function formatPropertyName(name) {
158
- return isValidJSIdentifier(name) ? name : `"${name}"`;
159
- }
160
-
161
- // src/utils/string-utils.ts
162
- function toCamelCase(str) {
163
- return str.replace(/-([a-zA-Z])/g, (_, letter) => letter.toUpperCase()).replace(/-+$/, "");
164
- }
165
- function capitalize(str) {
166
- return str.charAt(0).toUpperCase() + str.slice(1);
167
- }
168
-
169
- // src/utils/http-status.ts
170
- var statusNameMap = {
171
- "400": "badRequest",
172
- "401": "unauthorized",
173
- "402": "paymentRequired",
174
- "403": "forbidden",
175
- "404": "notFound",
176
- "405": "methodNotAllowed",
177
- "406": "notAcceptable",
178
- "407": "proxyAuthenticationRequired",
179
- "408": "requestTimeout",
180
- "409": "conflict",
181
- "410": "gone",
182
- "411": "lengthRequired",
183
- "412": "preconditionFailed",
184
- "413": "payloadTooLarge",
185
- "414": "uriTooLong",
186
- "415": "unsupportedMediaType",
187
- "416": "rangeNotSatisfiable",
188
- "417": "expectationFailed",
189
- "418": "imATeapot",
190
- "421": "misdirectedRequest",
191
- "422": "unprocessableEntity",
192
- "423": "locked",
193
- "424": "failedDependency",
194
- "425": "tooEarly",
195
- "426": "upgradeRequired",
196
- "428": "preconditionRequired",
197
- "429": "tooManyRequests",
198
- "431": "requestHeaderFieldsTooLarge",
199
- "451": "unavailableForLegalReasons",
200
- "500": "internalServerError",
201
- "501": "notImplemented",
202
- "502": "badGateway",
203
- "503": "serviceUnavailable",
204
- "504": "gatewayTimeout",
205
- "505": "httpVersionNotSupported",
206
- "506": "variantAlsoNegotiates",
207
- "507": "insufficientStorage",
208
- "508": "loopDetected",
209
- "510": "notExtended",
210
- "511": "networkAuthenticationRequired"
211
- };
212
- function mapStatusToIdentifier(status) {
213
- if (status === "default") return "defaultError";
214
- const trimmed = status.trim();
215
- const mapped = statusNameMap[trimmed];
216
- if (mapped) return mapped;
217
- if (/^\d{3}$/.test(trimmed)) {
218
- return `status${trimmed}`;
219
- }
220
- const sanitized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
221
- if (!sanitized) return "unknownError";
222
- const parts = sanitized.split(/\s+/);
223
- const [first, ...rest] = parts;
224
- const candidate = first + rest.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
225
- if (!candidate) return "unknownError";
226
- return /^[a-zA-Z_$]/.test(candidate) ? candidate : `status${candidate.charAt(0).toUpperCase()}${candidate.slice(1)}`;
227
- }
228
- function isErrorStatus(status) {
229
- if (status === "default") return true;
230
- const code = Number(status);
231
- if (!Number.isInteger(code)) return false;
232
- return code >= 400;
233
- }
234
-
235
- // src/utils/tree-shaking.ts
236
- function analyzeZenkoUsage(operations) {
237
- const usage = {
238
- usesHeaderFn: false,
239
- usesOperationDefinition: false,
240
- usesOperationErrors: false
241
- };
242
- if (operations.length > 0) {
243
- usage.usesOperationDefinition = true;
244
- }
245
- for (const op of operations) {
246
- if (op.errors && hasAnyErrors(op.errors)) {
247
- usage.usesOperationErrors = true;
248
- }
249
- }
250
- if (operations.length > 0 && !usage.usesOperationErrors) {
251
- const hasDefaultErrors = operations.some(
252
- (op) => !op.errors || !hasAnyErrors(op.errors)
253
- );
254
- if (hasDefaultErrors) {
255
- usage.usesOperationErrors = true;
256
- }
257
- }
258
- return usage;
259
- }
260
- function generateZenkoImport(usage, mode, helpersOutput) {
261
- const types = [];
262
- if (usage.usesHeaderFn) types.push("HeaderFn");
263
- if (usage.usesOperationDefinition) types.push("OperationDefinition");
264
- if (usage.usesOperationErrors) types.push("OperationErrors");
265
- if (types.length === 0) {
266
- return "";
34
+ var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
35
+ var JSON_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
36
+ async function readFileText(filePath) {
37
+ const file = Bun.file(filePath);
38
+ if (!await file.exists()) {
39
+ throw new Error(`File not found: ${filePath}`);
267
40
  }
268
- const importSource = mode === "package" ? '"zenko"' : `"${helpersOutput}"`;
269
- return `import type { ${types.join(", ")} } from ${importSource};`;
41
+ return await file.text();
270
42
  }
271
- function hasAnyErrors(errors) {
272
- return Boolean(errors && Object.keys(errors).length > 0);
273
- }
274
-
275
- // src/utils/collect-inline-types.ts
276
- function collectInlineRequestTypes(operations, spec) {
277
- const requestTypesToGenerate = /* @__PURE__ */ new Map();
278
- const operationLookup = /* @__PURE__ */ new Map();
279
- for (const [, pathItem] of Object.entries(spec.paths || {})) {
280
- for (const [, operation] of Object.entries(pathItem)) {
281
- const op = operation;
282
- if (op.operationId) {
283
- operationLookup.set(op.operationId, op);
284
- }
285
- }
286
- }
287
- for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
288
- for (const [, operation] of Object.entries(pathItem)) {
289
- const op = operation;
290
- if (op.operationId) {
291
- operationLookup.set(op.operationId, op);
292
- }
293
- }
294
- }
295
- for (const op of operations) {
296
- const operation = operationLookup.get(op.operationId);
297
- if (!operation) continue;
298
- const requestBody = operation.requestBody;
299
- if (requestBody && requestBody.content) {
300
- const content = requestBody.content;
301
- const jsonContent = content["application/json"];
302
- if (jsonContent && jsonContent.schema) {
303
- const schema = jsonContent.schema;
304
- const typeName = `${capitalize(toCamelCase(op.operationId))}Request`;
305
- if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
306
- requestTypesToGenerate.set(typeName, schema);
307
- }
308
- }
309
- }
310
- }
311
- return requestTypesToGenerate;
312
- }
313
- function collectInlineResponseTypes(operations, spec) {
314
- const responseTypesToGenerate = /* @__PURE__ */ new Map();
315
- const operationLookup = /* @__PURE__ */ new Map();
316
- for (const [, pathItem] of Object.entries(spec.paths || {})) {
317
- for (const [, operation] of Object.entries(pathItem)) {
318
- const op = operation;
319
- if (op.operationId) {
320
- operationLookup.set(op.operationId, op);
321
- }
322
- }
323
- }
324
- for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
325
- for (const [, operation] of Object.entries(pathItem)) {
326
- const op = operation;
327
- if (op.operationId) {
328
- operationLookup.set(op.operationId, op);
329
- }
330
- }
331
- }
332
- for (const op of operations) {
333
- const operation = operationLookup.get(op.operationId);
334
- if (!operation) continue;
335
- const responses = operation.responses || {};
336
- for (const [statusCode, response] of Object.entries(responses)) {
337
- if (/^2\d\d$/.test(statusCode) && response.content) {
338
- const content = response.content;
339
- const jsonContent = content["application/json"];
340
- if (jsonContent && jsonContent.schema) {
341
- const schema = jsonContent.schema;
342
- const typeName = `${capitalize(toCamelCase(op.operationId))}Response`;
343
- if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
344
- responseTypesToGenerate.set(typeName, schema);
345
- }
346
- }
347
- }
348
- }
349
- }
350
- return responseTypesToGenerate;
351
- }
352
-
353
- // src/utils/generate-helper-file.ts
354
- function generateHelperFile() {
355
- const output = [];
356
- output.push("// Generated helper types for Zenko");
357
- output.push(
358
- "// This file provides type definitions for operation objects and path functions"
359
- );
360
- output.push("");
361
- output.push(
362
- "export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
363
- );
364
- output.push("");
365
- output.push(
366
- 'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
367
- );
368
- output.push("");
369
- output.push(
370
- "export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
371
- );
372
- output.push("");
373
- output.push(
374
- "export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
375
- );
376
- output.push("");
377
- output.push(
378
- "export type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
379
- );
380
- output.push("");
381
- output.push(
382
- "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> = {"
383
- );
384
- output.push(" method: TMethod");
385
- output.push(" path: TPath");
386
- output.push(" request?: TRequest");
387
- output.push(" response?: TResponse");
388
- output.push(" headers?: THeaders");
389
- output.push(" errors?: TErrors");
390
- output.push("}");
391
- output.push("");
392
- return output.join("\n");
393
- }
394
-
395
- // src/zenko.ts
396
- function generateWithMetadata(spec, options = {}) {
397
- const output = [];
398
- const generatedTypes = /* @__PURE__ */ new Set();
399
- const { strictDates = false, strictNumeric = false } = options;
400
- const typesConfig = normalizeTypesConfig(options.types);
401
- const schemaOptions = {
402
- strictDates,
403
- strictNumeric
404
- };
405
- output.push('import { z } from "zod";');
406
- const nameMap = /* @__PURE__ */ new Map();
407
- if (spec.components?.schemas) {
408
- for (const name of Object.keys(spec.components.schemas)) {
409
- nameMap.set(name, toCamelCase(name));
410
- }
411
- }
412
- const operations = parseOperations(spec, nameMap);
413
- appendHelperTypesImport(output, typesConfig, operations);
414
- output.push("");
415
- if (spec.components?.schemas) {
416
- output.push("// Generated Zod Schemas");
417
- output.push("");
418
- const sortedSchemas = topologicalSort(spec.components.schemas);
419
- for (const name of sortedSchemas) {
420
- const schema = spec.components.schemas[name];
421
- const sanitizedName = nameMap.get(name);
422
- output.push(
423
- generateZodSchema(
424
- sanitizedName,
425
- schema,
426
- generatedTypes,
427
- schemaOptions,
428
- nameMap
429
- )
430
- );
431
- output.push("");
432
- output.push(
433
- `export type ${sanitizedName} = z.infer<typeof ${sanitizedName}>;`
434
- );
435
- output.push("");
436
- }
437
- }
438
- output.push("// Path Functions");
439
- output.push("export const paths = {");
440
- for (const op of operations) {
441
- const pathParamNames = op.pathParams.map((p) => p.name);
442
- const hasPathParams = pathParamNames.length > 0;
443
- const hasQueryParams = op.queryParams.length > 0;
444
- const camelCaseOperationId = toCamelCase(op.operationId);
445
- if (!hasPathParams && !hasQueryParams) {
446
- output.push(
447
- ` ${formatPropertyName(camelCaseOperationId)}: () => "${op.path}",`
448
- );
449
- continue;
450
- }
451
- const alias = (n) => {
452
- if (isValidJSIdentifier(n)) return n;
453
- let aliased = toCamelCase(n);
454
- if (!isValidJSIdentifier(aliased)) {
455
- aliased = `_${aliased}`;
456
- }
457
- return aliased;
458
- };
459
- const destructPieces = [];
460
- const typePieces = [];
461
- for (const param of op.pathParams) {
462
- destructPieces.push(
463
- isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
464
- );
465
- typePieces.push(`${formatPropertyName(param.name)}: string`);
466
- }
467
- for (const param of op.queryParams) {
468
- destructPieces.push(
469
- isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
470
- );
471
- typePieces.push(
472
- `${formatPropertyName(param.name)}${param.required ? "" : "?"}: ${mapQueryType(param)}`
473
- );
474
- }
475
- const needsDefaultObject = !hasPathParams && hasQueryParams && op.queryParams.every((param) => !param.required);
476
- const signatureArgs = destructPieces.length ? `{ ${destructPieces.join(", ")} }` : "{}";
477
- const signature = `${signatureArgs}: { ${typePieces.join(", ")} }${needsDefaultObject ? " = {}" : ""}`;
478
- const pathWithParams = op.path.replace(
479
- /{([^}]+)}/g,
480
- (_m, n) => `\${${alias(n)}}`
481
- );
482
- if (!hasQueryParams) {
483
- output.push(
484
- ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
485
- );
486
- continue;
487
- }
488
- output.push(
489
- ` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => {`
490
- );
491
- output.push(" const params = new URLSearchParams()");
492
- for (const param of op.queryParams) {
493
- const accessor = isValidJSIdentifier(param.name) ? param.name : alias(toCamelCase(param.name));
494
- const schema = param.schema ?? {};
495
- if (schema?.type === "array") {
496
- const itemValueExpression = convertQueryParamValue(
497
- schema.items ?? {},
498
- "value"
499
- );
500
- if (param.required) {
501
- output.push(` for (const value of ${accessor}) {`);
502
- output.push(
503
- ` params.append("${param.name}", ${itemValueExpression})`
504
- );
505
- output.push(" }");
506
- } else {
507
- output.push(` if (${accessor} !== undefined) {`);
508
- output.push(` for (const value of ${accessor}) {`);
509
- output.push(
510
- ` params.append("${param.name}", ${itemValueExpression})`
511
- );
512
- output.push(" }");
513
- output.push(" }");
514
- }
515
- continue;
516
- }
517
- const valueExpression = convertQueryParamValue(schema, accessor);
518
- if (param.required) {
519
- output.push(` params.set("${param.name}", ${valueExpression})`);
520
- } else {
521
- output.push(` if (${accessor} !== undefined) {`);
522
- output.push(` params.set("${param.name}", ${valueExpression})`);
523
- output.push(" }");
524
- }
525
- }
526
- output.push(" const _searchParams = params.toString()");
527
- output.push(
528
- ` return \`${pathWithParams}\${_searchParams ? \`?\${_searchParams}\` : ""}\``
529
- );
530
- output.push(" },");
531
- }
532
- output.push("} as const;");
533
- output.push("");
534
- output.push("// Header Schemas");
535
- output.push("export const headerSchemas = {");
536
- for (const op of operations) {
537
- const camelCaseOperationId = toCamelCase(op.operationId);
538
- if (!op.requestHeaders || op.requestHeaders.length === 0) {
539
- output.push(
540
- ` ${formatPropertyName(camelCaseOperationId)}: z.object({}),`
541
- );
542
- continue;
543
- }
544
- const schemaFields = op.requestHeaders.map((header) => {
545
- const zodType = mapHeaderToZodType(header);
546
- const optional = header.required ? "" : ".optional()";
547
- return ` ${formatPropertyName(header.name)}: ${zodType}${optional},`;
548
- }).join("\n");
549
- output.push(` ${formatPropertyName(camelCaseOperationId)}: z.object({`);
550
- output.push(schemaFields);
551
- output.push(" }),");
552
- }
553
- output.push("} as const;");
554
- output.push("");
555
- output.push("// Header Functions");
556
- output.push("export const headers = {");
557
- for (const op of operations) {
558
- const camelCaseOperationId = toCamelCase(op.operationId);
559
- if (!op.requestHeaders || op.requestHeaders.length === 0) {
560
- output.push(
561
- ` ${formatPropertyName(camelCaseOperationId)}: () => ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse({}),`
562
- );
563
- continue;
564
- }
565
- output.push(
566
- ` ${formatPropertyName(camelCaseOperationId)}: (params: z.input<${isValidJSIdentifier(camelCaseOperationId) ? `typeof headerSchemas.${camelCaseOperationId}` : `(typeof headerSchemas)[${formatPropertyName(camelCaseOperationId)}]`}>) => {`
567
- );
568
- output.push(
569
- ` return ${isValidJSIdentifier(camelCaseOperationId) ? `headerSchemas.${camelCaseOperationId}` : `headerSchemas[${formatPropertyName(camelCaseOperationId)}]`}.parse(params)`
570
- );
571
- output.push(" },");
572
- }
573
- output.push("} as const;");
574
- output.push("");
575
- generateRequestTypes(output, operations, spec, nameMap, schemaOptions);
576
- generateResponseTypes(output, operations, spec, nameMap, schemaOptions);
577
- generateOperationTypes(output, operations, typesConfig);
578
- output.push("// Operation Objects");
579
- for (const op of operations) {
580
- const camelCaseOperationId = toCamelCase(op.operationId);
581
- const typeAnnotation = typesConfig.emit ? `: ${capitalize(camelCaseOperationId)}Operation` : "";
582
- output.push(`export const ${camelCaseOperationId}${typeAnnotation} = {`);
583
- output.push(` method: "${op.method}",`);
584
- output.push(` path: paths.${camelCaseOperationId},`);
585
- appendOperationField(output, "request", op.requestType);
586
- appendOperationField(output, "response", op.responseType);
587
- if (op.requestHeaders && op.requestHeaders.length > 0) {
588
- output.push(` headers: headers.${camelCaseOperationId},`);
589
- }
590
- if (op.errors && hasAnyErrors2(op.errors)) {
591
- appendErrorGroup(output, "errors", op.errors);
592
- }
593
- output.push("} as const;");
594
- output.push("");
595
- }
596
- const result = {
597
- output: output.join("\n")
598
- };
599
- if (typesConfig.emit && typesConfig.helpers === "file" && typesConfig.helpersOutput) {
600
- result.helperFile = {
601
- path: typesConfig.helpersOutput,
602
- content: generateHelperFile()
603
- };
43
+ async function loadConfig(filePath) {
44
+ const extension = path.extname(filePath).toLowerCase();
45
+ if (JSON_EXTENSIONS.has(extension)) {
46
+ const content = await readFileText(filePath);
47
+ return JSON.parse(content);
604
48
  }
605
- return result;
606
- }
607
- function generateRequestTypes(output, operations, spec, nameMap, schemaOptions) {
608
- const requestTypesToGenerate = collectInlineRequestTypes(operations, spec);
609
- if (requestTypesToGenerate.size > 0) {
610
- output.push("// Generated Request Types");
611
- output.push("");
612
- for (const [typeName, schema] of requestTypesToGenerate) {
613
- const generatedSchema = generateZodSchema(
614
- typeName,
615
- schema,
616
- /* @__PURE__ */ new Set(),
617
- schemaOptions,
618
- nameMap
619
- );
620
- output.push(generatedSchema);
621
- output.push("");
622
- output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
623
- output.push("");
624
- }
49
+ if (YAML_EXTENSIONS.has(extension)) {
50
+ const content = await readFileText(filePath);
51
+ return Bun.YAML.parse(content);
625
52
  }
53
+ const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
54
+ const module2 = await import(fileUrl);
55
+ return module2.default ?? module2.config ?? module2;
626
56
  }
627
- function generateResponseTypes(output, operations, spec, nameMap, schemaOptions) {
628
- const responseTypesToGenerate = collectInlineResponseTypes(operations, spec);
629
- if (responseTypesToGenerate.size > 0) {
630
- output.push("// Generated Response Types");
631
- output.push("");
632
- for (const [typeName, schema] of responseTypesToGenerate) {
633
- const generatedSchema = generateZodSchema(
634
- typeName,
635
- schema,
636
- /* @__PURE__ */ new Set(),
637
- schemaOptions,
638
- nameMap
57
+ async function loadSpec(filePath) {
58
+ const extension = path.extname(filePath).toLowerCase();
59
+ const content = await readFileText(filePath);
60
+ if (YAML_EXTENSIONS.has(extension)) {
61
+ const parsed = Bun.YAML.parse(content);
62
+ if (!parsed || typeof parsed !== "object") {
63
+ throw new Error(`YAML spec did not resolve to an object: ${filePath}`);
64
+ }
65
+ if (Array.isArray(parsed)) {
66
+ throw new Error(
67
+ `YAML spec produced multiple documents; provide a single document in ${filePath}`
639
68
  );
640
- output.push(generatedSchema);
641
- output.push("");
642
- output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
643
- output.push("");
644
- }
645
- }
646
- }
647
- function appendOperationField(buffer, key, value) {
648
- if (!value) return;
649
- buffer.push(` ${key}: ${value},`);
650
- }
651
- function appendErrorGroup(buffer, label, errors) {
652
- if (!errors || Object.keys(errors).length === 0) return;
653
- buffer.push(` ${label}: {`);
654
- for (const [name, typeName] of Object.entries(errors)) {
655
- buffer.push(` ${formatPropertyName(name)}: ${typeName},`);
656
- }
657
- buffer.push(" },");
658
- }
659
- function hasAnyErrors2(group) {
660
- return Boolean(group && Object.keys(group).length > 0);
661
- }
662
- function isRequestMethod(method) {
663
- switch (method) {
664
- case "get":
665
- case "put":
666
- case "post":
667
- case "delete":
668
- case "options":
669
- case "head":
670
- case "patch":
671
- case "trace":
672
- return true;
673
- default:
674
- return false;
675
- }
676
- }
677
- var CONTENT_TYPE_MAP = {
678
- "application/json": "unknown",
679
- // Will use schema when available
680
- "text/csv": "string",
681
- "text/plain": "string",
682
- // Binary/ambiguous types default to unknown for cross-platform compatibility
683
- "application/octet-stream": "unknown",
684
- "application/pdf": "unknown"
685
- };
686
- function findContentType(content) {
687
- const contentTypes = Object.keys(content);
688
- if (contentTypes.includes("application/json")) {
689
- return "application/json";
690
- }
691
- for (const contentType of contentTypes) {
692
- if (contentType in CONTENT_TYPE_MAP) {
693
- return contentType;
694
- }
695
- }
696
- return contentTypes[0] || "";
697
- }
698
- function inferResponseType(contentType, statusCode) {
699
- if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
700
- return "undefined";
701
- }
702
- if (contentType in CONTENT_TYPE_MAP) {
703
- return CONTENT_TYPE_MAP[contentType];
704
- }
705
- return "unknown";
706
- }
707
- function parseOperations(spec, nameMap) {
708
- const operations = [];
709
- if (spec.paths) {
710
- for (const [path2, pathItem] of Object.entries(spec.paths)) {
711
- for (const [method, operation] of Object.entries(pathItem)) {
712
- const normalizedMethod = method.toLowerCase();
713
- if (!isRequestMethod(normalizedMethod)) continue;
714
- if (!operation.operationId) continue;
715
- const pathParams = extractPathParams(path2);
716
- const requestType = getRequestType(operation);
717
- const { successResponse, errors } = getResponseTypes(
718
- operation,
719
- operation.operationId,
720
- nameMap
721
- );
722
- const resolvedParameters = collectParameters(pathItem, operation, spec);
723
- const requestHeaders = getRequestHeaders(resolvedParameters);
724
- const queryParams = getQueryParams(resolvedParameters);
725
- operations.push({
726
- operationId: operation.operationId,
727
- path: path2,
728
- method: normalizedMethod,
729
- pathParams,
730
- queryParams,
731
- requestType,
732
- responseType: successResponse,
733
- requestHeaders,
734
- errors
735
- });
736
- }
737
69
  }
70
+ return parsed;
738
71
  }
739
- if (spec.webhooks) {
740
- for (const [webhookName, webhookItem] of Object.entries(spec.webhooks)) {
741
- for (const [method, operation] of Object.entries(webhookItem)) {
742
- const normalizedMethod = method.toLowerCase();
743
- if (!isRequestMethod(normalizedMethod)) continue;
744
- if (!operation.operationId) continue;
745
- const path2 = webhookName;
746
- const pathParams = extractPathParams(path2);
747
- const requestType = getRequestType(operation);
748
- const { successResponse, errors } = getResponseTypes(
749
- operation,
750
- operation.operationId
751
- );
752
- const resolvedParameters = collectParameters(
753
- webhookItem,
754
- operation,
755
- spec
756
- );
757
- const requestHeaders = getRequestHeaders(resolvedParameters);
758
- const queryParams = getQueryParams(resolvedParameters);
759
- operations.push({
760
- operationId: operation.operationId,
761
- path: path2,
762
- method: normalizedMethod,
763
- pathParams,
764
- queryParams,
765
- requestType,
766
- responseType: successResponse,
767
- requestHeaders,
768
- errors
769
- });
770
- }
771
- }
72
+ if (JSON_EXTENSIONS.has(extension)) {
73
+ return JSON.parse(content);
772
74
  }
773
- return operations;
75
+ throw new Error(
76
+ `Unsupported specification format for ${filePath}. Expected .yaml, .yml, or .json`
77
+ );
774
78
  }
775
- function normalizeTypesConfig(config) {
79
+ function normalizeGenerationOptions(entry, baseDir, defaults) {
80
+ const resolvedInput = path.isAbsolute(entry.input) ? entry.input : path.join(baseDir, entry.input);
81
+ const resolvedOutput = path.isAbsolute(entry.output) ? entry.output : path.join(baseDir, entry.output);
776
82
  return {
777
- emit: config?.emit ?? true,
778
- helpers: config?.helpers ?? "package",
779
- helpersOutput: config?.helpersOutput ?? "./zenko-types",
780
- treeShake: config?.treeShake ?? true
83
+ resolvedInput,
84
+ resolvedOutput,
85
+ strictDates: entry.strictDates ?? defaults.strictDates,
86
+ strictNumeric: entry.strictNumeric ?? defaults.strictNumeric,
87
+ types: mergeTypesConfig(defaults.types, entry.types),
88
+ operationIds: entry.operationIds ?? defaults.operationIds
781
89
  };
782
90
  }
783
- function appendHelperTypesImport(buffer, config, operations) {
784
- if (!config.emit) return;
785
- switch (config.helpers) {
786
- case "package":
787
- if (config.treeShake) {
788
- const usage = analyzeZenkoUsage(operations);
789
- const importStatement = generateZenkoImport(usage, "package");
790
- if (importStatement) {
791
- buffer.push(importStatement);
792
- }
793
- } else {
794
- buffer.push(
795
- 'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
796
- );
797
- }
798
- return;
799
- case "file":
800
- if (config.treeShake) {
801
- const usage = analyzeZenkoUsage(operations);
802
- const importStatement = generateZenkoImport(
803
- usage,
804
- "file",
805
- config.helpersOutput
806
- );
807
- if (importStatement) {
808
- buffer.push(importStatement);
809
- }
810
- } else {
811
- buffer.push(
812
- `import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
813
- );
814
- }
815
- return;
816
- case "inline":
817
- buffer.push(
818
- "type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
819
- );
820
- buffer.push(
821
- 'type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";'
822
- );
823
- buffer.push(
824
- "type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
825
- );
826
- buffer.push(
827
- "type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
828
- );
829
- buffer.push(
830
- "type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
831
- );
832
- buffer.push(
833
- "type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
834
- );
835
- buffer.push(" method: TMethod");
836
- buffer.push(" path: TPath");
837
- buffer.push(" request?: TRequest");
838
- buffer.push(" response?: TResponse");
839
- buffer.push(" headers?: THeaders");
840
- buffer.push(" errors?: TErrors");
841
- buffer.push("}");
842
- buffer.push("");
843
- }
844
- }
845
- function generateOperationTypes(buffer, operations, config) {
846
- if (!config.emit) return;
847
- buffer.push("// Operation Types");
848
- for (const op of operations) {
849
- const camelCaseOperationId = toCamelCase(op.operationId);
850
- const headerType = op.requestHeaders?.length ? isValidJSIdentifier(camelCaseOperationId) ? `typeof headers.${camelCaseOperationId}` : `(typeof headers)[${formatPropertyName(camelCaseOperationId)}]` : "undefined";
851
- const requestType = wrapTypeReference(op.requestType);
852
- const responseType = wrapTypeReference(op.responseType);
853
- const errorsType = buildOperationErrorsType(op.errors);
854
- buffer.push(
855
- `export type ${capitalize(camelCaseOperationId)}Operation = OperationDefinition<`
856
- );
857
- buffer.push(` "${op.method}",`);
858
- buffer.push(
859
- ` ${isValidJSIdentifier(camelCaseOperationId) ? `typeof paths.${camelCaseOperationId}` : `(typeof paths)[${formatPropertyName(camelCaseOperationId)}]`},`
860
- );
861
- buffer.push(` ${requestType},`);
862
- buffer.push(` ${responseType},`);
863
- buffer.push(` ${headerType},`);
864
- buffer.push(` ${errorsType}`);
865
- buffer.push(`>;`);
866
- buffer.push("");
867
- }
868
- }
869
- function buildOperationErrorsType(errors) {
870
- if (!errors || !hasAnyErrors2(errors)) {
871
- return "OperationErrors";
872
- }
873
- const errorBucket = buildErrorBucket(errors);
874
- return `OperationErrors<${errorBucket}>`;
875
- }
876
- function buildErrorBucket(bucket) {
877
- if (!bucket || Object.keys(bucket).length === 0) {
878
- return "unknown";
879
- }
880
- const entries = Object.entries(bucket);
881
- const accessibleEntries = entries.map(([name, type]) => {
882
- const propertyKey = formatPropertyName(name);
883
- const valueType = wrapErrorValueType(type);
884
- return `${propertyKey}: ${valueType}`;
885
- });
886
- return `{ ${accessibleEntries.join("; ")} }`;
887
- }
888
- var TYPE_KEYWORDS = /* @__PURE__ */ new Set([
889
- "any",
890
- "unknown",
891
- "never",
892
- "void",
893
- "null",
894
- "undefined",
895
- "string",
896
- "number",
897
- "boolean",
898
- "bigint",
899
- "symbol"
900
- ]);
901
- var IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
902
- function wrapTypeReference(typeName) {
903
- if (!typeName) return "undefined";
904
- const normalized = typeName.trim();
905
- if (normalized === "undefined") return "undefined";
906
- if (TYPE_KEYWORDS.has(normalized)) return normalized;
907
- if (normalized.startsWith("typeof ")) return normalized;
908
- const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
909
- if (arrayMatch) {
910
- return `z.ZodArray<${wrapTypeReference(arrayMatch[1])}>`;
911
- }
912
- if (IDENTIFIER_PATTERN.test(normalized)) {
913
- return `typeof ${normalized}`;
914
- }
915
- return normalized;
916
- }
917
- function wrapErrorValueType(typeName) {
918
- if (!typeName) return "unknown";
919
- const normalized = typeName.trim();
920
- if (TYPE_KEYWORDS.has(normalized)) return normalized;
921
- if (normalized.startsWith("typeof ")) return normalized;
922
- const arrayMatch = normalized.match(/^z\.array\((.+)\)$/);
923
- if (arrayMatch) {
924
- return `z.ZodArray<${wrapErrorValueType(arrayMatch[1])}>`;
925
- }
926
- if (IDENTIFIER_PATTERN.test(normalized)) {
927
- return `typeof ${normalized}`;
928
- }
929
- return normalized;
930
- }
931
- function collectParameters(pathItem, operation, spec) {
932
- const parametersMap = /* @__PURE__ */ new Map();
933
- const addParameters = (params) => {
934
- if (!Array.isArray(params)) return;
935
- for (const param of params) {
936
- const resolved = resolveParameter(param, spec);
937
- if (!resolved) continue;
938
- const key = `${resolved.in}:${resolved.name}`;
939
- parametersMap.set(key, resolved);
940
- }
91
+ function mergeTypesConfig(baseConfig, entryConfig) {
92
+ if (!baseConfig && !entryConfig) return void 0;
93
+ return {
94
+ ...baseConfig,
95
+ ...entryConfig
941
96
  };
942
- addParameters(pathItem.parameters);
943
- addParameters(operation.parameters);
944
- return Array.from(parametersMap.values());
945
- }
946
- function resolveParameter(parameter, spec) {
947
- if (!parameter) return void 0;
948
- if (parameter.$ref) {
949
- const refName = extractRefName(parameter.$ref);
950
- const resolved = spec.components?.parameters?.[refName];
951
- if (!resolved) return void 0;
952
- const { $ref, ...overrides } = parameter;
953
- return {
954
- ...resolved,
955
- ...overrides
956
- };
957
- }
958
- return parameter;
959
- }
960
- function extractPathParams(path2) {
961
- const params = [];
962
- const matches = path2.match(/{([^}]+)}/g);
963
- if (matches) {
964
- for (const match of matches) {
965
- const paramName = match.slice(1, -1);
966
- params.push({
967
- name: paramName,
968
- type: "string"
969
- // OpenAPI path params are always strings
970
- });
971
- }
972
- }
973
- return params;
974
- }
975
- function getRequestType(operation) {
976
- const requestBody = operation.requestBody?.content?.["application/json"]?.schema;
977
- if (!requestBody) return void 0;
978
- if (requestBody.$ref) {
979
- return extractRefName(requestBody.$ref);
980
- }
981
- const typeName = `${capitalize(toCamelCase(operation.operationId))}Request`;
982
- return typeName;
983
- }
984
- function getResponseTypes(operation, operationId, nameMap) {
985
- const responses = operation.responses ?? {};
986
- const successCodes = /* @__PURE__ */ new Map();
987
- const errorEntries = [];
988
- for (const [statusCode, response] of Object.entries(responses)) {
989
- const content = response?.content;
990
- if (!content || Object.keys(content).length === 0) {
991
- if (statusCode === "204" || /^3\d\d$/.test(statusCode)) {
992
- successCodes.set(statusCode, "undefined");
993
- } else if (isErrorStatus(statusCode)) {
994
- errorEntries.push({
995
- code: statusCode,
996
- schema: "undefined"
997
- });
998
- }
999
- continue;
1000
- }
1001
- const contentType = findContentType(content);
1002
- const resolvedSchema = content[contentType]?.schema;
1003
- if (!resolvedSchema) {
1004
- const inferredType = inferResponseType(contentType, statusCode);
1005
- if (inferredType) {
1006
- if (isErrorStatus(statusCode)) {
1007
- errorEntries.push({
1008
- code: statusCode,
1009
- schema: inferredType
1010
- });
1011
- } else if (/^2\d\d$/.test(statusCode)) {
1012
- successCodes.set(statusCode, inferredType);
1013
- }
1014
- }
1015
- continue;
1016
- }
1017
- if (isErrorStatus(statusCode)) {
1018
- errorEntries.push({ code: statusCode, schema: resolvedSchema });
1019
- continue;
1020
- }
1021
- if (/^2\d\d$/.test(statusCode)) {
1022
- successCodes.set(statusCode, resolvedSchema);
1023
- }
1024
- }
1025
- const successResponse = selectSuccessResponse(
1026
- successCodes,
1027
- operationId,
1028
- nameMap
1029
- );
1030
- const errors = buildErrorGroups(errorEntries, operationId, nameMap);
1031
- return { successResponse, errors };
1032
- }
1033
- function selectSuccessResponse(responses, operationId, nameMap) {
1034
- if (responses.size === 0) return void 0;
1035
- const preferredOrder = ["200", "201", "204"];
1036
- for (const code of preferredOrder) {
1037
- const schema = responses.get(code);
1038
- if (schema) {
1039
- if (typeof schema === "string") {
1040
- return schema;
1041
- }
1042
- return resolveResponseType(
1043
- schema,
1044
- `${capitalize(toCamelCase(operationId))}Response`,
1045
- nameMap
1046
- );
1047
- }
1048
- }
1049
- const [, firstSchema] = responses.entries().next().value ?? [];
1050
- if (!firstSchema) return void 0;
1051
- if (typeof firstSchema === "string") {
1052
- return firstSchema;
1053
- }
1054
- return resolveResponseType(
1055
- firstSchema,
1056
- `${capitalize(toCamelCase(operationId))}Response`,
1057
- nameMap
1058
- );
1059
- }
1060
- function buildErrorGroups(errors = [], operationId, nameMap) {
1061
- if (!errors.length) return void 0;
1062
- const group = {};
1063
- for (const { code, schema } of errors) {
1064
- const identifier = mapStatusToIdentifier(code);
1065
- const typeName = resolveResponseType(
1066
- schema,
1067
- `${capitalize(toCamelCase(operationId))}${capitalize(identifier)}`,
1068
- nameMap
1069
- );
1070
- group[identifier] = typeName;
1071
- }
1072
- return group;
1073
- }
1074
- function resolveResponseType(schema, fallbackName, nameMap) {
1075
- if (typeof schema === "string") {
1076
- return schema;
1077
- }
1078
- if (schema.$ref) {
1079
- const refName = extractRefName(schema.$ref);
1080
- return nameMap?.get(refName) || refName;
1081
- }
1082
- if (schema.type === "array" && schema.items?.$ref) {
1083
- const itemRef = extractRefName(schema.items.$ref);
1084
- const sanitizedItemRef = nameMap?.get(itemRef) || itemRef;
1085
- return `z.array(${sanitizedItemRef})`;
1086
- }
1087
- if (schema.allOf && Array.isArray(schema.allOf)) {
1088
- return fallbackName;
1089
- }
1090
- return fallbackName;
1091
- }
1092
- function getRequestHeaders(parameters) {
1093
- const headers = [];
1094
- for (const param of parameters ?? []) {
1095
- if (param.in === "header") {
1096
- headers.push({
1097
- name: param.name,
1098
- description: param.description,
1099
- schema: param.schema,
1100
- required: param.required
1101
- });
1102
- }
1103
- }
1104
- return headers;
1105
- }
1106
- function getQueryParams(parameters) {
1107
- const queryParams = [];
1108
- for (const param of parameters ?? []) {
1109
- if (param.in === "query") {
1110
- queryParams.push({
1111
- name: param.name,
1112
- description: param.description,
1113
- schema: param.schema,
1114
- required: param.required
1115
- });
1116
- }
1117
- }
1118
- return queryParams;
1119
- }
1120
- function mapHeaderToZodType(header) {
1121
- const schema = header.schema ?? {};
1122
- const schemaType = schema.type;
1123
- switch (schemaType) {
1124
- case "integer":
1125
- case "number":
1126
- return "z.coerce.number()";
1127
- case "boolean":
1128
- return "z.coerce.boolean()";
1129
- case "array": {
1130
- const items = schema.items ?? { type: "string" };
1131
- const itemType = items.type === "integer" || items.type === "number" ? "z.coerce.number()" : items.type === "boolean" ? "z.coerce.boolean()" : "z.string()";
1132
- return `z.array(${itemType})`;
1133
- }
1134
- default:
1135
- return "z.string()";
1136
- }
1137
- }
1138
- function mapQueryType(param) {
1139
- return mapQuerySchemaType(param.schema);
1140
- }
1141
- function mapQuerySchemaType(schema) {
1142
- if (!schema) return "string";
1143
- if (schema.type === "array") {
1144
- const itemType = mapQuerySchemaType(schema.items);
1145
- return `Array<${itemType}>`;
1146
- }
1147
- switch (schema.type) {
1148
- case "integer":
1149
- case "number":
1150
- return "number";
1151
- case "boolean":
1152
- return "boolean";
1153
- default:
1154
- return "string";
1155
- }
1156
- }
1157
- function convertQueryParamValue(schema, accessor) {
1158
- if (!schema) {
1159
- return `String(${accessor})`;
1160
- }
1161
- switch (schema.type) {
1162
- case "integer":
1163
- case "number":
1164
- return `String(${accessor})`;
1165
- case "boolean":
1166
- return `${accessor} ? "true" : "false"`;
1167
- default:
1168
- return `String(${accessor})`;
1169
- }
1170
- }
1171
- function generateZodSchema(name, schema, generatedTypes, options, nameMap) {
1172
- if (generatedTypes.has(name)) return "";
1173
- generatedTypes.add(name);
1174
- if (schema.enum) {
1175
- const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
1176
- return `export const ${name} = z.enum([${enumValues}]);`;
1177
- }
1178
- if (schema.allOf && Array.isArray(schema.allOf)) {
1179
- const allOfParts = schema.allOf.map(
1180
- (part) => getZodTypeFromSchema(part, options, nameMap)
1181
- );
1182
- if (allOfParts.length === 0) return `export const ${name} = z.object({});`;
1183
- if (allOfParts.length === 1)
1184
- return `export const ${name} = ${allOfParts[0]};`;
1185
- const first = allOfParts[0];
1186
- const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
1187
- return `export const ${name} = ${first}${rest};`;
1188
- }
1189
- if (schema.type === "object" || schema.properties) {
1190
- return `export const ${name} = ${buildZodObject(schema, options, nameMap)};`;
1191
- }
1192
- if (schema.type === "array") {
1193
- const itemSchema = schema.items ?? { type: "unknown" };
1194
- const itemType = getZodTypeFromSchema(itemSchema, options, nameMap);
1195
- const builder = applyStrictArrayBounds(
1196
- schema,
1197
- `z.array(${itemType})`,
1198
- itemSchema,
1199
- options.strictNumeric
1200
- );
1201
- return `export const ${name} = ${builder};`;
1202
- }
1203
- return `export const ${name} = ${getZodTypeFromSchema(schema, options, nameMap)};`;
1204
- }
1205
- function getZodTypeFromSchema(schema, options, nameMap) {
1206
- if (schema.$ref) {
1207
- const refName = extractRefName(schema.$ref);
1208
- return nameMap?.get(refName) || refName;
1209
- }
1210
- if (schema.enum) {
1211
- const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
1212
- return `z.enum([${enumValues}])`;
1213
- }
1214
- if (schema.allOf && Array.isArray(schema.allOf)) {
1215
- const allOfParts = schema.allOf.map(
1216
- (part) => getZodTypeFromSchema(part, options, nameMap)
1217
- );
1218
- if (allOfParts.length === 0) return "z.object({})";
1219
- if (allOfParts.length === 1) return allOfParts[0];
1220
- const first = allOfParts[0];
1221
- const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
1222
- return `${first}${rest}`;
1223
- }
1224
- if (schema.type === "object" || schema.properties || schema.allOf || schema.oneOf || schema.anyOf) {
1225
- return buildZodObject(schema, options, nameMap);
1226
- }
1227
- switch (schema.type) {
1228
- case "string":
1229
- return buildString(schema, options);
1230
- case "boolean":
1231
- return "z.boolean()";
1232
- case "array":
1233
- return `z.array(${getZodTypeFromSchema(
1234
- schema.items ?? { type: "unknown" },
1235
- options,
1236
- nameMap
1237
- )})`;
1238
- case "null":
1239
- return "z.null()";
1240
- case "number":
1241
- return buildNumber(schema, options);
1242
- case "integer":
1243
- return buildInteger(schema, options);
1244
- default:
1245
- return "z.unknown()";
1246
- }
1247
- }
1248
- function buildZodObject(schema, options, nameMap) {
1249
- const properties = [];
1250
- for (const [propName, propSchema] of Object.entries(
1251
- schema.properties || {}
1252
- )) {
1253
- const isRequired = schema.required?.includes(propName) ?? false;
1254
- const zodType = getZodTypeFromSchema(propSchema, options, nameMap);
1255
- const finalType = isRequired ? zodType : `${zodType}.optional()`;
1256
- properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
1257
- }
1258
- if (properties.length === 0) {
1259
- return "z.object({})";
1260
- }
1261
- return `z.object({
1262
- ${properties.join("\n")}
1263
- })`;
1264
- }
1265
- function buildString(schema, options) {
1266
- if (options.strictDates) {
1267
- switch (schema.format) {
1268
- case "date-time":
1269
- return "z.string().datetime()";
1270
- case "date":
1271
- return "z.string().date()";
1272
- case "time":
1273
- return "z.string().time()";
1274
- case "duration":
1275
- return "z.string().duration()";
1276
- }
1277
- }
1278
- let builder = "z.string()";
1279
- if (options.strictNumeric) {
1280
- if (typeof schema.minLength === "number") {
1281
- builder += `.min(${schema.minLength})`;
1282
- }
1283
- if (typeof schema.maxLength === "number") {
1284
- builder += `.max(${schema.maxLength})`;
1285
- }
1286
- if (schema.pattern) {
1287
- builder += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`;
1288
- }
1289
- }
1290
- switch (schema.format) {
1291
- case "uuid":
1292
- return `${builder}.uuid()`;
1293
- case "email":
1294
- return `${builder}.email()`;
1295
- case "uri":
1296
- case "url":
1297
- return `${builder}.url()`;
1298
- case "ipv4":
1299
- return `${builder}.ip({ version: "v4" })`;
1300
- case "ipv6":
1301
- return `${builder}.ip({ version: "v6" })`;
1302
- default:
1303
- return builder;
1304
- }
1305
- }
1306
- function buildNumber(schema, options) {
1307
- let builder = "z.number()";
1308
- if (options.strictNumeric) {
1309
- builder = applyNumericBounds(schema, builder);
1310
- if (typeof schema.multipleOf === "number" && schema.multipleOf !== 0) {
1311
- builder += `.refine((value) => Math.abs(value / ${schema.multipleOf} - Math.round(value / ${schema.multipleOf})) < Number.EPSILON, { message: "Must be a multiple of ${schema.multipleOf}" })`;
1312
- }
1313
- }
1314
- return builder;
1315
- }
1316
- function buildInteger(schema, options) {
1317
- let builder = buildNumber(schema, options);
1318
- builder += ".int()";
1319
- return builder;
1320
- }
1321
- function applyStrictArrayBounds(schema, builder, itemSchema, enforceBounds) {
1322
- if (!enforceBounds) {
1323
- return builder;
1324
- }
1325
- if (typeof schema.minItems === "number") {
1326
- builder += `.min(${schema.minItems})`;
1327
- }
1328
- if (typeof schema.maxItems === "number") {
1329
- builder += `.max(${schema.maxItems})`;
1330
- }
1331
- if (schema.uniqueItems && isPrimitiveLike(itemSchema)) {
1332
- builder += '.refine((items) => new Set(items).size === items.length, { message: "Items must be unique" })';
1333
- }
1334
- return builder;
1335
- }
1336
- function isPrimitiveLike(schema) {
1337
- if (schema?.$ref) return false;
1338
- const primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
1339
- return primitiveTypes.has(schema?.type);
1340
- }
1341
- function applyNumericBounds(schema, builder) {
1342
- if (typeof schema.minimum === "number") {
1343
- if (schema.exclusiveMinimum === true) {
1344
- builder += `.gt(${schema.minimum})`;
1345
- } else {
1346
- builder += `.min(${schema.minimum})`;
1347
- }
1348
- } else if (typeof schema.exclusiveMinimum === "number") {
1349
- builder += `.gt(${schema.exclusiveMinimum})`;
1350
- }
1351
- if (typeof schema.maximum === "number") {
1352
- if (schema.exclusiveMaximum === true) {
1353
- builder += `.lt(${schema.maximum})`;
1354
- } else {
1355
- builder += `.max(${schema.maximum})`;
1356
- }
1357
- } else if (typeof schema.exclusiveMaximum === "number") {
1358
- builder += `.lt(${schema.exclusiveMaximum})`;
1359
- }
1360
- return builder;
1361
97
  }
1362
98
 
1363
99
  // src/cli.ts
@@ -1385,8 +121,8 @@ async function main() {
1385
121
  return;
1386
122
  }
1387
123
  await generateSingle({
1388
- inputFile,
1389
- outputFile,
124
+ resolvedInput: path2.resolve(inputFile),
125
+ resolvedOutput: path2.resolve(outputFile),
1390
126
  strictDates: parsed.strictDates,
1391
127
  strictNumeric: parsed.strictNumeric
1392
128
  });
@@ -1449,42 +185,26 @@ function printHelp() {
1449
185
  console.log("");
1450
186
  console.log("Config file format:");
1451
187
  console.log(
1452
- ' {"types"?: { emit?, helpers?, helpersOutput? }, "schemas": [{ input, output, strictDates?, strictNumeric?, types? }] }'
188
+ ' {"types"?: { emit?, helpers?, helpersOutput?, optionalType?, treeShake? }, "schemas": [{ input, output, strictDates?, strictNumeric?, types? }] }'
1453
189
  );
1454
190
  }
1455
191
  async function runFromConfig(parsed) {
1456
192
  const configPath = parsed.configPath;
1457
- const resolvedConfigPath = path.resolve(configPath);
1458
- const config = await loadConfig(resolvedConfigPath);
1459
- validateConfig(config);
1460
- const baseDir = path.dirname(resolvedConfigPath);
1461
- const baseTypesConfig = config.types;
193
+ const resolvedConfigPath = path2.resolve(configPath);
194
+ const configDocument = await loadConfig(resolvedConfigPath);
195
+ validateConfig(configDocument);
196
+ const config = configDocument;
197
+ const baseDir = path2.dirname(resolvedConfigPath);
198
+ const defaults = {
199
+ strictDates: parsed.strictDates,
200
+ strictNumeric: parsed.strictNumeric,
201
+ types: config.types,
202
+ operationIds: void 0
203
+ };
1462
204
  for (const entry of config.schemas) {
1463
- const inputFile = resolvePath(entry.input, baseDir);
1464
- const outputFile = resolvePath(entry.output, baseDir);
1465
- const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
1466
- await generateSingle({
1467
- inputFile,
1468
- outputFile,
1469
- strictDates: entry.strictDates ?? parsed.strictDates,
1470
- strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
1471
- typesConfig
1472
- });
1473
- }
1474
- }
1475
- async function loadConfig(filePath) {
1476
- const extension = path.extname(filePath).toLowerCase();
1477
- if (extension === ".json") {
1478
- const content = fs.readFileSync(filePath, "utf8");
1479
- return JSON.parse(content);
205
+ const options = normalizeGenerationOptions(entry, baseDir, defaults);
206
+ await generateSingle(options);
1480
207
  }
1481
- if (extension === ".yaml" || extension === ".yml") {
1482
- const content = fs.readFileSync(filePath, "utf8");
1483
- return (0, import_js_yaml.load)(content);
1484
- }
1485
- const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
1486
- const module2 = await import(fileUrl);
1487
- return module2.default ?? module2.config ?? module2;
1488
208
  }
1489
209
  function validateConfig(config) {
1490
210
  if (!config || typeof config !== "object") {
@@ -1502,60 +222,44 @@ function validateConfig(config) {
1502
222
  }
1503
223
  }
1504
224
  }
1505
- function resolvePath(filePath, baseDir) {
1506
- return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
1507
- }
1508
- function resolveTypesConfig(baseConfig, entryConfig) {
1509
- if (!baseConfig && !entryConfig) return void 0;
1510
- return {
1511
- ...baseConfig,
1512
- ...entryConfig
1513
- };
1514
- }
1515
225
  async function generateSingle(options) {
1516
- const { inputFile, outputFile, strictDates, strictNumeric, typesConfig } = options;
1517
- const resolvedInput = path.resolve(inputFile);
1518
- const resolvedOutput = path.resolve(outputFile);
1519
- const spec = readSpec(resolvedInput);
1520
- const result = generateWithMetadata(spec, {
226
+ const {
227
+ resolvedInput,
228
+ resolvedOutput,
229
+ strictDates = false,
230
+ strictNumeric = false,
231
+ types,
232
+ operationIds
233
+ } = options;
234
+ const spec = await loadSpec(resolvedInput);
235
+ const result = (0, import_core.generateFromDocument)(spec, {
1521
236
  strictDates,
1522
237
  strictNumeric,
1523
- types: typesConfig
238
+ types,
239
+ operationIds
1524
240
  });
1525
- fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
1526
- fs.writeFileSync(resolvedOutput, result.output);
241
+ await (0, import_promises.mkdir)(path2.dirname(resolvedOutput), { recursive: true });
242
+ await Bun.write(resolvedOutput, result.output);
1527
243
  console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
1528
244
  console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths || {}).length} paths`);
1529
245
  if (spec.webhooks) {
1530
246
  console.log(`\u{1FA9D} Processed ${Object.keys(spec.webhooks).length} webhooks`);
1531
247
  }
1532
248
  if (result.helperFile) {
1533
- const helperPath = path.isAbsolute(result.helperFile.path) ? result.helperFile.path : path.resolve(path.dirname(resolvedOutput), result.helperFile.path);
1534
- const absoluteResolvedOutput = path.resolve(resolvedOutput);
1535
- const absoluteHelperPath = path.resolve(helperPath);
249
+ const helperPath = path2.isAbsolute(result.helperFile.path) ? result.helperFile.path : path2.resolve(path2.dirname(resolvedOutput), result.helperFile.path);
250
+ const absoluteResolvedOutput = path2.resolve(resolvedOutput);
251
+ const absoluteHelperPath = path2.resolve(helperPath);
1536
252
  if (absoluteResolvedOutput === absoluteHelperPath) {
1537
253
  console.warn(
1538
254
  `\u26A0\uFE0F Skipping helper file generation: would overwrite main output at ${absoluteResolvedOutput}`
1539
255
  );
1540
256
  return;
1541
257
  }
1542
- fs.mkdirSync(path.dirname(helperPath), { recursive: true });
1543
- fs.writeFileSync(helperPath, result.helperFile.content, {
1544
- encoding: "utf8"
1545
- });
258
+ await (0, import_promises.mkdir)(path2.dirname(helperPath), { recursive: true });
259
+ await Bun.write(helperPath, result.helperFile.content);
1546
260
  console.log(`\u{1F4E6} Generated helper types in ${helperPath}`);
1547
261
  }
1548
262
  }
1549
- function readSpec(filePath) {
1550
- if (!fs.existsSync(filePath)) {
1551
- throw new Error(`Input file not found: ${filePath}`);
1552
- }
1553
- const content = fs.readFileSync(filePath, "utf8");
1554
- if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
1555
- return (0, import_js_yaml.load)(content);
1556
- }
1557
- return JSON.parse(content);
1558
- }
1559
263
  main().catch((error) => {
1560
264
  console.error("\u274C Error:", error);
1561
265
  process.exit(1);