zenko 0.1.0
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/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/cli.cjs +898 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +875 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +729 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +702 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +73 -0
package/dist/cli.cjs
ADDED
@@ -0,0 +1,898 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
"use strict";
|
3
|
+
var __create = Object.create;
|
4
|
+
var __defProp = Object.defineProperty;
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
11
|
+
for (let key of __getOwnPropNames(from))
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
14
|
+
}
|
15
|
+
return to;
|
16
|
+
};
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
23
|
+
mod
|
24
|
+
));
|
25
|
+
|
26
|
+
// src/cli.ts
|
27
|
+
var fs = __toESM(require("fs"), 1);
|
28
|
+
var path = __toESM(require("path"), 1);
|
29
|
+
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/http-status.ts
|
162
|
+
var statusNameMap = {
|
163
|
+
"400": "badRequest",
|
164
|
+
"401": "unauthorized",
|
165
|
+
"402": "paymentRequired",
|
166
|
+
"403": "forbidden",
|
167
|
+
"404": "notFound",
|
168
|
+
"405": "methodNotAllowed",
|
169
|
+
"406": "notAcceptable",
|
170
|
+
"407": "proxyAuthenticationRequired",
|
171
|
+
"408": "requestTimeout",
|
172
|
+
"409": "conflict",
|
173
|
+
"410": "gone",
|
174
|
+
"411": "lengthRequired",
|
175
|
+
"412": "preconditionFailed",
|
176
|
+
"413": "payloadTooLarge",
|
177
|
+
"414": "uriTooLong",
|
178
|
+
"415": "unsupportedMediaType",
|
179
|
+
"416": "rangeNotSatisfiable",
|
180
|
+
"417": "expectationFailed",
|
181
|
+
"418": "imATeapot",
|
182
|
+
"421": "misdirectedRequest",
|
183
|
+
"422": "unprocessableEntity",
|
184
|
+
"423": "locked",
|
185
|
+
"424": "failedDependency",
|
186
|
+
"425": "tooEarly",
|
187
|
+
"426": "upgradeRequired",
|
188
|
+
"428": "preconditionRequired",
|
189
|
+
"429": "tooManyRequests",
|
190
|
+
"431": "requestHeaderFieldsTooLarge",
|
191
|
+
"451": "unavailableForLegalReasons",
|
192
|
+
"500": "internalServerError",
|
193
|
+
"501": "notImplemented",
|
194
|
+
"502": "badGateway",
|
195
|
+
"503": "serviceUnavailable",
|
196
|
+
"504": "gatewayTimeout",
|
197
|
+
"505": "httpVersionNotSupported",
|
198
|
+
"506": "variantAlsoNegotiates",
|
199
|
+
"507": "insufficientStorage",
|
200
|
+
"508": "loopDetected",
|
201
|
+
"510": "notExtended",
|
202
|
+
"511": "networkAuthenticationRequired"
|
203
|
+
};
|
204
|
+
function mapStatusToIdentifier(status) {
|
205
|
+
if (status === "default") return "defaultError";
|
206
|
+
const trimmed = status.trim();
|
207
|
+
const mapped = statusNameMap[trimmed];
|
208
|
+
if (mapped) return mapped;
|
209
|
+
if (/^\d{3}$/.test(trimmed)) {
|
210
|
+
return `status${trimmed}`;
|
211
|
+
}
|
212
|
+
const sanitized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
213
|
+
if (!sanitized) return "unknownError";
|
214
|
+
const parts = sanitized.split(/\s+/);
|
215
|
+
const [first, ...rest] = parts;
|
216
|
+
const candidate = first + rest.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
217
|
+
if (!candidate) return "unknownError";
|
218
|
+
return /^[a-zA-Z_$]/.test(candidate) ? candidate : `status${candidate.charAt(0).toUpperCase()}${candidate.slice(1)}`;
|
219
|
+
}
|
220
|
+
function getStatusCategory(status) {
|
221
|
+
if (status === "default") return "default";
|
222
|
+
const code = Number(status);
|
223
|
+
if (!Number.isInteger(code)) return "unknown";
|
224
|
+
if (code >= 400 && code <= 499) return "client";
|
225
|
+
if (code >= 500 && code <= 599) return "server";
|
226
|
+
return "unknown";
|
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/zenko.ts
|
236
|
+
function generate(spec, options = {}) {
|
237
|
+
const output = [];
|
238
|
+
const generatedTypes = /* @__PURE__ */ new Set();
|
239
|
+
const { strictDates = false, strictNumeric = false } = options;
|
240
|
+
const schemaOptions = {
|
241
|
+
strictDates,
|
242
|
+
strictNumeric
|
243
|
+
};
|
244
|
+
output.push('import { z } from "zod";');
|
245
|
+
output.push("");
|
246
|
+
if (spec.components?.schemas) {
|
247
|
+
output.push("// Generated Zod Schemas");
|
248
|
+
output.push("");
|
249
|
+
const sortedSchemas = topologicalSort(spec.components.schemas);
|
250
|
+
for (const name of sortedSchemas) {
|
251
|
+
const schema = spec.components.schemas[name];
|
252
|
+
output.push(
|
253
|
+
generateZodSchema(name, schema, generatedTypes, schemaOptions)
|
254
|
+
);
|
255
|
+
output.push("");
|
256
|
+
output.push(`export type ${name} = z.infer<typeof ${name}>;`);
|
257
|
+
output.push("");
|
258
|
+
}
|
259
|
+
}
|
260
|
+
const operations = parseOperations(spec);
|
261
|
+
output.push("// Path Functions");
|
262
|
+
output.push("export const paths = {");
|
263
|
+
for (const op of operations) {
|
264
|
+
if (op.pathParams.length === 0) {
|
265
|
+
output.push(` ${op.operationId}: () => "${op.path}",`);
|
266
|
+
} else {
|
267
|
+
const paramNames = op.pathParams.map((p) => p.name).join(", ");
|
268
|
+
const paramTypes = op.pathParams.map((p) => `${p.name}: string`).join(", ");
|
269
|
+
const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
|
270
|
+
output.push(
|
271
|
+
` ${op.operationId}: ({ ${paramNames} }: { ${paramTypes} }) => \`${pathWithParams}\`,`
|
272
|
+
);
|
273
|
+
}
|
274
|
+
}
|
275
|
+
output.push("} as const;");
|
276
|
+
output.push("");
|
277
|
+
output.push("// Header Functions");
|
278
|
+
output.push("export const headers = {");
|
279
|
+
for (const op of operations) {
|
280
|
+
if (!op.requestHeaders || op.requestHeaders.length === 0) {
|
281
|
+
output.push(` ${op.operationId}: () => ({}),`);
|
282
|
+
continue;
|
283
|
+
}
|
284
|
+
const typeEntries = op.requestHeaders.map(
|
285
|
+
(header) => `${formatPropertyName(header.name)}${header.required ? "" : "?"}: ${mapHeaderType(
|
286
|
+
header
|
287
|
+
)}`
|
288
|
+
).join(", ");
|
289
|
+
const requiredHeaders = op.requestHeaders.filter(
|
290
|
+
(header) => header.required
|
291
|
+
);
|
292
|
+
const optionalHeaders = op.requestHeaders.filter(
|
293
|
+
(header) => !header.required
|
294
|
+
);
|
295
|
+
const hasRequired = requiredHeaders.length > 0;
|
296
|
+
const signature = hasRequired ? `(params: { ${typeEntries} })` : `(params: { ${typeEntries} } = {})`;
|
297
|
+
if (optionalHeaders.length === 0) {
|
298
|
+
output.push(` ${op.operationId}: ${signature} => ({`);
|
299
|
+
for (const header of requiredHeaders) {
|
300
|
+
const propertyKey = formatPropertyName(header.name);
|
301
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
302
|
+
output.push(` ${propertyKey}: ${accessor},`);
|
303
|
+
}
|
304
|
+
output.push(" }),");
|
305
|
+
continue;
|
306
|
+
}
|
307
|
+
if (!hasRequired && optionalHeaders.length === 1 && optionalHeaders[0]) {
|
308
|
+
const header = optionalHeaders[0];
|
309
|
+
const propertyKey = formatPropertyName(header.name);
|
310
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
311
|
+
output.push(` ${op.operationId}: ${signature} =>`);
|
312
|
+
output.push(
|
313
|
+
` ${accessor} !== undefined ? { ${propertyKey}: ${accessor} } : {},`
|
314
|
+
);
|
315
|
+
continue;
|
316
|
+
}
|
317
|
+
const valueTypes = Array.from(
|
318
|
+
new Set(optionalHeaders.map((header) => mapHeaderType(header)))
|
319
|
+
).join(" | ");
|
320
|
+
output.push(` ${op.operationId}: ${signature} => {`);
|
321
|
+
if (hasRequired) {
|
322
|
+
output.push(" const headers = {");
|
323
|
+
for (const header of requiredHeaders) {
|
324
|
+
const propertyKey = formatPropertyName(header.name);
|
325
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
326
|
+
output.push(` ${propertyKey}: ${accessor},`);
|
327
|
+
}
|
328
|
+
output.push(" }");
|
329
|
+
} else {
|
330
|
+
output.push(` const headers: Record<string, ${valueTypes}> = {}`);
|
331
|
+
}
|
332
|
+
for (const header of optionalHeaders) {
|
333
|
+
const propertyKey = formatPropertyName(header.name);
|
334
|
+
const accessor = isValidJSIdentifier(header.name) ? `params.${header.name}` : `params[${propertyKey}]`;
|
335
|
+
const assignment = isValidJSIdentifier(header.name) ? `headers.${header.name}` : `headers[${propertyKey}]`;
|
336
|
+
output.push(` if (${accessor} !== undefined) {`);
|
337
|
+
output.push(` ${assignment} = ${accessor}`);
|
338
|
+
output.push(" }");
|
339
|
+
}
|
340
|
+
output.push(" return headers");
|
341
|
+
output.push(" },");
|
342
|
+
}
|
343
|
+
output.push("} as const;");
|
344
|
+
output.push("");
|
345
|
+
output.push("// Operation Objects");
|
346
|
+
for (const op of operations) {
|
347
|
+
output.push(`export const ${op.operationId} = {`);
|
348
|
+
output.push(` path: paths.${op.operationId},`);
|
349
|
+
appendOperationField(output, "request", op.requestType);
|
350
|
+
appendOperationField(output, "response", op.responseType);
|
351
|
+
if (op.requestHeaders && op.requestHeaders.length > 0) {
|
352
|
+
output.push(` headers: headers.${op.operationId},`);
|
353
|
+
}
|
354
|
+
if (op.errors && hasAnyErrors(op.errors)) {
|
355
|
+
output.push(" errors: {");
|
356
|
+
appendErrorGroup(output, "clientErrors", op.errors.clientErrors);
|
357
|
+
appendErrorGroup(output, "serverErrors", op.errors.serverErrors);
|
358
|
+
appendErrorGroup(output, "defaultErrors", op.errors.defaultErrors);
|
359
|
+
appendErrorGroup(output, "otherErrors", op.errors.otherErrors);
|
360
|
+
output.push(" },");
|
361
|
+
}
|
362
|
+
output.push("} as const;");
|
363
|
+
output.push("");
|
364
|
+
}
|
365
|
+
return output.join("\n");
|
366
|
+
}
|
367
|
+
function appendOperationField(buffer, key, value) {
|
368
|
+
if (!value) return;
|
369
|
+
buffer.push(` ${key}: ${value},`);
|
370
|
+
}
|
371
|
+
function appendErrorGroup(buffer, label, errors) {
|
372
|
+
if (!errors || Object.keys(errors).length === 0) return;
|
373
|
+
buffer.push(` ${label}: {`);
|
374
|
+
for (const [name, typeName] of Object.entries(errors)) {
|
375
|
+
buffer.push(` ${formatPropertyName(name)}: ${typeName},`);
|
376
|
+
}
|
377
|
+
buffer.push(" },");
|
378
|
+
}
|
379
|
+
function hasAnyErrors(group) {
|
380
|
+
return [
|
381
|
+
group.clientErrors,
|
382
|
+
group.serverErrors,
|
383
|
+
group.defaultErrors,
|
384
|
+
group.otherErrors
|
385
|
+
].some((bucket) => bucket && Object.keys(bucket).length > 0);
|
386
|
+
}
|
387
|
+
function parseOperations(spec) {
|
388
|
+
const operations = [];
|
389
|
+
for (const [path2, pathItem] of Object.entries(spec.paths)) {
|
390
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
391
|
+
if (!operation.operationId) continue;
|
392
|
+
const pathParams = extractPathParams(path2);
|
393
|
+
const requestType = getRequestType(operation);
|
394
|
+
const { successResponse, errors } = getResponseTypes(
|
395
|
+
operation,
|
396
|
+
operation.operationId
|
397
|
+
);
|
398
|
+
const resolvedParameters = collectParameters(pathItem, operation, spec);
|
399
|
+
const requestHeaders = getRequestHeaders(resolvedParameters);
|
400
|
+
operations.push({
|
401
|
+
operationId: operation.operationId,
|
402
|
+
path: path2,
|
403
|
+
method: method.toLowerCase(),
|
404
|
+
pathParams,
|
405
|
+
requestType,
|
406
|
+
responseType: successResponse,
|
407
|
+
requestHeaders,
|
408
|
+
errors
|
409
|
+
});
|
410
|
+
}
|
411
|
+
}
|
412
|
+
return operations;
|
413
|
+
}
|
414
|
+
function collectParameters(pathItem, operation, spec) {
|
415
|
+
const parametersMap = /* @__PURE__ */ new Map();
|
416
|
+
const addParameters = (params) => {
|
417
|
+
if (!Array.isArray(params)) return;
|
418
|
+
for (const param of params) {
|
419
|
+
const resolved = resolveParameter(param, spec);
|
420
|
+
if (!resolved) continue;
|
421
|
+
const key = `${resolved.in}:${resolved.name}`;
|
422
|
+
parametersMap.set(key, resolved);
|
423
|
+
}
|
424
|
+
};
|
425
|
+
addParameters(pathItem.parameters);
|
426
|
+
addParameters(operation.parameters);
|
427
|
+
return Array.from(parametersMap.values());
|
428
|
+
}
|
429
|
+
function resolveParameter(parameter, spec) {
|
430
|
+
if (!parameter) return void 0;
|
431
|
+
if (parameter.$ref) {
|
432
|
+
const refName = extractRefName(parameter.$ref);
|
433
|
+
const resolved = spec.components?.parameters?.[refName];
|
434
|
+
if (!resolved) return void 0;
|
435
|
+
const { $ref, ...overrides } = parameter;
|
436
|
+
return {
|
437
|
+
...resolved,
|
438
|
+
...overrides
|
439
|
+
};
|
440
|
+
}
|
441
|
+
return parameter;
|
442
|
+
}
|
443
|
+
function extractPathParams(path2) {
|
444
|
+
const params = [];
|
445
|
+
const matches = path2.match(/{([^}]+)}/g);
|
446
|
+
if (matches) {
|
447
|
+
for (const match of matches) {
|
448
|
+
const paramName = match.slice(1, -1);
|
449
|
+
params.push({
|
450
|
+
name: paramName,
|
451
|
+
type: "string"
|
452
|
+
// OpenAPI path params are always strings
|
453
|
+
});
|
454
|
+
}
|
455
|
+
}
|
456
|
+
return params;
|
457
|
+
}
|
458
|
+
function getRequestType(operation) {
|
459
|
+
const requestBody = operation.requestBody?.content?.["application/json"]?.schema;
|
460
|
+
if (!requestBody) return void 0;
|
461
|
+
if (requestBody.$ref) {
|
462
|
+
return extractRefName(requestBody.$ref);
|
463
|
+
}
|
464
|
+
const typeName = `${capitalize(operation.operationId)}Request`;
|
465
|
+
return typeName;
|
466
|
+
}
|
467
|
+
function getResponseTypes(operation, operationId) {
|
468
|
+
const responses = operation.responses ?? {};
|
469
|
+
const successCodes = /* @__PURE__ */ new Map();
|
470
|
+
const errorEntries = [];
|
471
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
472
|
+
const resolvedSchema = response?.content?.["application/json"]?.schema;
|
473
|
+
if (!resolvedSchema) continue;
|
474
|
+
if (isErrorStatus(statusCode)) {
|
475
|
+
errorEntries.push({ code: statusCode, schema: resolvedSchema });
|
476
|
+
continue;
|
477
|
+
}
|
478
|
+
if (/^2\d\d$/.test(statusCode) || statusCode === "default") {
|
479
|
+
successCodes.set(statusCode, resolvedSchema);
|
480
|
+
}
|
481
|
+
}
|
482
|
+
const successResponse = selectSuccessResponse(successCodes, operationId);
|
483
|
+
const errors = buildErrorGroups(errorEntries, operationId);
|
484
|
+
return { successResponse, errors };
|
485
|
+
}
|
486
|
+
function selectSuccessResponse(responses, operationId) {
|
487
|
+
if (responses.size === 0) return void 0;
|
488
|
+
const preferredOrder = ["200", "201", "204"];
|
489
|
+
for (const code of preferredOrder) {
|
490
|
+
const schema = responses.get(code);
|
491
|
+
if (schema) {
|
492
|
+
return resolveResponseType(
|
493
|
+
schema,
|
494
|
+
`${capitalize(operationId)}Response${code}`
|
495
|
+
);
|
496
|
+
}
|
497
|
+
}
|
498
|
+
const [firstCode, firstSchema] = responses.entries().next().value ?? [];
|
499
|
+
if (!firstSchema) return void 0;
|
500
|
+
return resolveResponseType(
|
501
|
+
firstSchema,
|
502
|
+
`${capitalize(operationId)}Response${firstCode ?? "Default"}`
|
503
|
+
);
|
504
|
+
}
|
505
|
+
function buildErrorGroups(errors = [], operationId) {
|
506
|
+
if (!errors.length) return void 0;
|
507
|
+
const group = {};
|
508
|
+
for (const { code, schema } of errors) {
|
509
|
+
const category = getStatusCategory(code);
|
510
|
+
const identifier = mapStatusToIdentifier(code);
|
511
|
+
const typeName = resolveResponseType(
|
512
|
+
schema,
|
513
|
+
`${capitalize(operationId)}${capitalize(identifier)}`
|
514
|
+
);
|
515
|
+
switch (category) {
|
516
|
+
case "client":
|
517
|
+
group.clientErrors ??= {};
|
518
|
+
group.clientErrors[identifier] = typeName;
|
519
|
+
break;
|
520
|
+
case "server":
|
521
|
+
group.serverErrors ??= {};
|
522
|
+
group.serverErrors[identifier] = typeName;
|
523
|
+
break;
|
524
|
+
case "default":
|
525
|
+
group.defaultErrors ??= {};
|
526
|
+
group.defaultErrors[identifier] = typeName;
|
527
|
+
break;
|
528
|
+
default:
|
529
|
+
group.otherErrors ??= {};
|
530
|
+
group.otherErrors[identifier] = typeName;
|
531
|
+
break;
|
532
|
+
}
|
533
|
+
}
|
534
|
+
return group;
|
535
|
+
}
|
536
|
+
function resolveResponseType(schema, fallbackName) {
|
537
|
+
if (schema.$ref) {
|
538
|
+
return extractRefName(schema.$ref);
|
539
|
+
}
|
540
|
+
return fallbackName;
|
541
|
+
}
|
542
|
+
function getRequestHeaders(parameters) {
|
543
|
+
const headers = [];
|
544
|
+
for (const param of parameters ?? []) {
|
545
|
+
if (param.in === "header") {
|
546
|
+
headers.push({
|
547
|
+
name: param.name,
|
548
|
+
description: param.description,
|
549
|
+
schema: param.schema,
|
550
|
+
required: param.required
|
551
|
+
});
|
552
|
+
}
|
553
|
+
}
|
554
|
+
return headers;
|
555
|
+
}
|
556
|
+
function mapHeaderType(header) {
|
557
|
+
const schemaType = header.schema?.type;
|
558
|
+
switch (schemaType) {
|
559
|
+
case "integer":
|
560
|
+
case "number":
|
561
|
+
return "number";
|
562
|
+
case "boolean":
|
563
|
+
return "boolean";
|
564
|
+
default:
|
565
|
+
return "string";
|
566
|
+
}
|
567
|
+
}
|
568
|
+
function generateZodSchema(name, schema, generatedTypes, options) {
|
569
|
+
if (generatedTypes.has(name)) return "";
|
570
|
+
generatedTypes.add(name);
|
571
|
+
if (schema.enum) {
|
572
|
+
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
573
|
+
return `export const ${name} = z.enum([${enumValues}]);`;
|
574
|
+
}
|
575
|
+
if (schema.type === "object" || schema.properties) {
|
576
|
+
const properties = [];
|
577
|
+
for (const [propName, propSchema] of Object.entries(
|
578
|
+
schema.properties || {}
|
579
|
+
)) {
|
580
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
581
|
+
const zodType = getZodTypeFromSchema(propSchema, options);
|
582
|
+
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
583
|
+
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
584
|
+
}
|
585
|
+
return `export const ${name} = z.object({
|
586
|
+
${properties.join("\n")}
|
587
|
+
});`;
|
588
|
+
}
|
589
|
+
if (schema.type === "array") {
|
590
|
+
const itemSchema = schema.items ?? { type: "unknown" };
|
591
|
+
const itemType = getZodTypeFromSchema(itemSchema, options);
|
592
|
+
const builder = applyStrictArrayBounds(
|
593
|
+
schema,
|
594
|
+
`z.array(${itemType})`,
|
595
|
+
itemSchema,
|
596
|
+
options.strictNumeric
|
597
|
+
);
|
598
|
+
return `export const ${name} = ${builder};`;
|
599
|
+
}
|
600
|
+
return `export const ${name} = ${getZodTypeFromSchema(schema, options)};`;
|
601
|
+
}
|
602
|
+
function getZodTypeFromSchema(schema, options) {
|
603
|
+
if (schema.$ref) {
|
604
|
+
return extractRefName(schema.$ref);
|
605
|
+
}
|
606
|
+
if (schema.enum) {
|
607
|
+
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
608
|
+
return `z.enum([${enumValues}])`;
|
609
|
+
}
|
610
|
+
switch (schema.type) {
|
611
|
+
case "string":
|
612
|
+
return buildString(schema, options);
|
613
|
+
case "boolean":
|
614
|
+
return "z.boolean()";
|
615
|
+
case "array":
|
616
|
+
return `z.array(${getZodTypeFromSchema(
|
617
|
+
schema.items ?? { type: "unknown" },
|
618
|
+
options
|
619
|
+
)})`;
|
620
|
+
case "null":
|
621
|
+
return "z.null()";
|
622
|
+
case "number":
|
623
|
+
return buildNumber(schema, options);
|
624
|
+
case "integer":
|
625
|
+
return buildInteger(schema, options);
|
626
|
+
default:
|
627
|
+
return "z.unknown()";
|
628
|
+
}
|
629
|
+
}
|
630
|
+
function buildString(schema, options) {
|
631
|
+
if (options.strictDates) {
|
632
|
+
switch (schema.format) {
|
633
|
+
case "date-time":
|
634
|
+
return "z.string().datetime()";
|
635
|
+
case "date":
|
636
|
+
return "z.string().date()";
|
637
|
+
case "time":
|
638
|
+
return "z.string().time()";
|
639
|
+
case "duration":
|
640
|
+
return "z.string().duration()";
|
641
|
+
}
|
642
|
+
}
|
643
|
+
let builder = "z.string()";
|
644
|
+
if (options.strictNumeric) {
|
645
|
+
if (typeof schema.minLength === "number") {
|
646
|
+
builder += `.min(${schema.minLength})`;
|
647
|
+
}
|
648
|
+
if (typeof schema.maxLength === "number") {
|
649
|
+
builder += `.max(${schema.maxLength})`;
|
650
|
+
}
|
651
|
+
if (schema.pattern) {
|
652
|
+
builder += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`;
|
653
|
+
}
|
654
|
+
}
|
655
|
+
switch (schema.format) {
|
656
|
+
case "uuid":
|
657
|
+
return `${builder}.uuid()`;
|
658
|
+
case "email":
|
659
|
+
return `${builder}.email()`;
|
660
|
+
case "uri":
|
661
|
+
case "url":
|
662
|
+
return `${builder}.url()`;
|
663
|
+
case "ipv4":
|
664
|
+
return `${builder}.ip({ version: "v4" })`;
|
665
|
+
case "ipv6":
|
666
|
+
return `${builder}.ip({ version: "v6" })`;
|
667
|
+
default:
|
668
|
+
return builder;
|
669
|
+
}
|
670
|
+
}
|
671
|
+
function buildNumber(schema, options) {
|
672
|
+
let builder = "z.number()";
|
673
|
+
if (options.strictNumeric) {
|
674
|
+
builder = applyNumericBounds(schema, builder);
|
675
|
+
if (typeof schema.multipleOf === "number" && schema.multipleOf !== 0) {
|
676
|
+
builder += `.refine((value) => Math.abs(value / ${schema.multipleOf} - Math.round(value / ${schema.multipleOf})) < Number.EPSILON, { message: "Must be a multiple of ${schema.multipleOf}" })`;
|
677
|
+
}
|
678
|
+
}
|
679
|
+
return builder;
|
680
|
+
}
|
681
|
+
function buildInteger(schema, options) {
|
682
|
+
let builder = buildNumber(schema, options);
|
683
|
+
builder += ".int()";
|
684
|
+
return builder;
|
685
|
+
}
|
686
|
+
function applyStrictArrayBounds(schema, builder, itemSchema, enforceBounds) {
|
687
|
+
if (!enforceBounds) {
|
688
|
+
return builder;
|
689
|
+
}
|
690
|
+
if (typeof schema.minItems === "number") {
|
691
|
+
builder += `.min(${schema.minItems})`;
|
692
|
+
}
|
693
|
+
if (typeof schema.maxItems === "number") {
|
694
|
+
builder += `.max(${schema.maxItems})`;
|
695
|
+
}
|
696
|
+
if (schema.uniqueItems && isPrimitiveLike(itemSchema)) {
|
697
|
+
builder += '.refine((items) => new Set(items).size === items.length, { message: "Items must be unique" })';
|
698
|
+
}
|
699
|
+
return builder;
|
700
|
+
}
|
701
|
+
function isPrimitiveLike(schema) {
|
702
|
+
if (schema?.$ref) return false;
|
703
|
+
const primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean"]);
|
704
|
+
return primitiveTypes.has(schema?.type);
|
705
|
+
}
|
706
|
+
function applyNumericBounds(schema, builder) {
|
707
|
+
if (typeof schema.minimum === "number") {
|
708
|
+
if (schema.exclusiveMinimum === true) {
|
709
|
+
builder += `.gt(${schema.minimum})`;
|
710
|
+
} else {
|
711
|
+
builder += `.min(${schema.minimum})`;
|
712
|
+
}
|
713
|
+
} else if (typeof schema.exclusiveMinimum === "number") {
|
714
|
+
builder += `.gt(${schema.exclusiveMinimum})`;
|
715
|
+
}
|
716
|
+
if (typeof schema.maximum === "number") {
|
717
|
+
if (schema.exclusiveMaximum === true) {
|
718
|
+
builder += `.lt(${schema.maximum})`;
|
719
|
+
} else {
|
720
|
+
builder += `.max(${schema.maximum})`;
|
721
|
+
}
|
722
|
+
} else if (typeof schema.exclusiveMaximum === "number") {
|
723
|
+
builder += `.lt(${schema.exclusiveMaximum})`;
|
724
|
+
}
|
725
|
+
return builder;
|
726
|
+
}
|
727
|
+
function capitalize(str) {
|
728
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
729
|
+
}
|
730
|
+
|
731
|
+
// src/cli.ts
|
732
|
+
async function main() {
|
733
|
+
const args = process.argv.slice(2);
|
734
|
+
const parsed = parseArgs(args);
|
735
|
+
if (parsed.showHelp || !parsed.configPath && parsed.positional.length === 0) {
|
736
|
+
printHelp();
|
737
|
+
process.exit(parsed.showHelp ? 0 : 1);
|
738
|
+
return;
|
739
|
+
}
|
740
|
+
try {
|
741
|
+
if (parsed.configPath) {
|
742
|
+
await runFromConfig(parsed);
|
743
|
+
} else {
|
744
|
+
if (parsed.positional.length !== 2) {
|
745
|
+
printHelp();
|
746
|
+
process.exit(1);
|
747
|
+
return;
|
748
|
+
}
|
749
|
+
const [inputFile, outputFile] = parsed.positional;
|
750
|
+
if (!inputFile || !outputFile) {
|
751
|
+
printHelp();
|
752
|
+
process.exit(1);
|
753
|
+
return;
|
754
|
+
}
|
755
|
+
await generateSingle({
|
756
|
+
inputFile,
|
757
|
+
outputFile,
|
758
|
+
strictDates: parsed.strictDates,
|
759
|
+
strictNumeric: parsed.strictNumeric
|
760
|
+
});
|
761
|
+
}
|
762
|
+
} catch (error) {
|
763
|
+
console.error("\u274C Error:", error);
|
764
|
+
process.exit(1);
|
765
|
+
}
|
766
|
+
}
|
767
|
+
function parseArgs(args) {
|
768
|
+
const parsed = {
|
769
|
+
showHelp: false,
|
770
|
+
strictDates: false,
|
771
|
+
strictNumeric: false,
|
772
|
+
positional: []
|
773
|
+
};
|
774
|
+
for (let index = 0; index < args.length; index += 1) {
|
775
|
+
const arg = args[index];
|
776
|
+
if (arg === "-h" || arg === "--help") {
|
777
|
+
parsed.showHelp = true;
|
778
|
+
continue;
|
779
|
+
}
|
780
|
+
if (arg === "--strict-dates") {
|
781
|
+
parsed.strictDates = true;
|
782
|
+
continue;
|
783
|
+
}
|
784
|
+
if (arg === "--strict-numeric") {
|
785
|
+
parsed.strictNumeric = true;
|
786
|
+
continue;
|
787
|
+
}
|
788
|
+
if (arg === "--config" || arg === "-c") {
|
789
|
+
const next = args[index + 1];
|
790
|
+
if (!next) {
|
791
|
+
throw new Error("--config flag requires a file path");
|
792
|
+
}
|
793
|
+
parsed.configPath = next;
|
794
|
+
index += 1;
|
795
|
+
continue;
|
796
|
+
}
|
797
|
+
parsed.positional.push(arg);
|
798
|
+
}
|
799
|
+
return parsed;
|
800
|
+
}
|
801
|
+
function printHelp() {
|
802
|
+
console.log("Usage:");
|
803
|
+
console.log(" zenko <input-file> <output-file> [options]");
|
804
|
+
console.log(" zenko --config <config-file> [options]");
|
805
|
+
console.log("");
|
806
|
+
console.log("Options:");
|
807
|
+
console.log(" -h, --help Show this help message");
|
808
|
+
console.log(
|
809
|
+
" --strict-dates Use ISO datetime parsing (can be set per config entry)"
|
810
|
+
);
|
811
|
+
console.log(
|
812
|
+
" --strict-numeric Preserve numeric min/max bounds (can be set per config entry)"
|
813
|
+
);
|
814
|
+
console.log(
|
815
|
+
" -c, --config Path to config file (JSON, YAML, or JS module)"
|
816
|
+
);
|
817
|
+
console.log("");
|
818
|
+
console.log("Config file format:");
|
819
|
+
console.log(
|
820
|
+
' {"schemas": [{ input, output, strictDates?, strictNumeric? }] }'
|
821
|
+
);
|
822
|
+
}
|
823
|
+
async function runFromConfig(parsed) {
|
824
|
+
const configPath = parsed.configPath;
|
825
|
+
const resolvedConfigPath = path.resolve(configPath);
|
826
|
+
const config = await loadConfig(resolvedConfigPath);
|
827
|
+
validateConfig(config);
|
828
|
+
const baseDir = path.dirname(resolvedConfigPath);
|
829
|
+
for (const entry of config.schemas) {
|
830
|
+
const inputFile = resolvePath(entry.input, baseDir);
|
831
|
+
const outputFile = resolvePath(entry.output, baseDir);
|
832
|
+
await generateSingle({
|
833
|
+
inputFile,
|
834
|
+
outputFile,
|
835
|
+
strictDates: entry.strictDates ?? parsed.strictDates,
|
836
|
+
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric
|
837
|
+
});
|
838
|
+
}
|
839
|
+
}
|
840
|
+
async function loadConfig(filePath) {
|
841
|
+
const extension = path.extname(filePath).toLowerCase();
|
842
|
+
if (extension === ".json") {
|
843
|
+
const content = fs.readFileSync(filePath, "utf8");
|
844
|
+
return JSON.parse(content);
|
845
|
+
}
|
846
|
+
if (extension === ".yaml" || extension === ".yml") {
|
847
|
+
const content = fs.readFileSync(filePath, "utf8");
|
848
|
+
return (0, import_js_yaml.load)(content);
|
849
|
+
}
|
850
|
+
const fileUrl = (0, import_url.pathToFileURL)(filePath).href;
|
851
|
+
const module2 = await import(fileUrl);
|
852
|
+
return module2.default ?? module2.config ?? module2;
|
853
|
+
}
|
854
|
+
function validateConfig(config) {
|
855
|
+
if (!config || typeof config !== "object") {
|
856
|
+
throw new Error("Config file must export an object");
|
857
|
+
}
|
858
|
+
if (!Array.isArray(config.schemas)) {
|
859
|
+
throw new Error("Config file must contain a 'schemas' array");
|
860
|
+
}
|
861
|
+
for (const entry of config.schemas) {
|
862
|
+
if (!entry || typeof entry !== "object") {
|
863
|
+
throw new Error("Each schema entry must be an object");
|
864
|
+
}
|
865
|
+
if (typeof entry.input !== "string" || typeof entry.output !== "string") {
|
866
|
+
throw new Error("Each schema entry requires 'input' and 'output' paths");
|
867
|
+
}
|
868
|
+
}
|
869
|
+
}
|
870
|
+
function resolvePath(filePath, baseDir) {
|
871
|
+
return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
|
872
|
+
}
|
873
|
+
async function generateSingle(options) {
|
874
|
+
const { inputFile, outputFile, strictDates, strictNumeric } = options;
|
875
|
+
const resolvedInput = path.resolve(inputFile);
|
876
|
+
const resolvedOutput = path.resolve(outputFile);
|
877
|
+
const spec = readSpec(resolvedInput);
|
878
|
+
const output = generate(spec, { strictDates, strictNumeric });
|
879
|
+
fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
880
|
+
fs.writeFileSync(resolvedOutput, output);
|
881
|
+
console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
|
882
|
+
console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths).length} paths`);
|
883
|
+
}
|
884
|
+
function readSpec(filePath) {
|
885
|
+
if (!fs.existsSync(filePath)) {
|
886
|
+
throw new Error(`Input file not found: ${filePath}`);
|
887
|
+
}
|
888
|
+
const content = fs.readFileSync(filePath, "utf8");
|
889
|
+
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
890
|
+
return (0, import_js_yaml.load)(content);
|
891
|
+
}
|
892
|
+
return JSON.parse(content);
|
893
|
+
}
|
894
|
+
main().catch((error) => {
|
895
|
+
console.error("\u274C Error:", error);
|
896
|
+
process.exit(1);
|
897
|
+
});
|
898
|
+
//# sourceMappingURL=cli.cjs.map
|