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