zenko 0.1.9 → 0.1.10-beta.2
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/README.md +11 -21
- package/dist/cli.cjs +337 -95
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.mjs +337 -95
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +231 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -47
- package/dist/index.d.ts +1 -47
- package/dist/index.mjs +228 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -4
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
import { pathToFileURL } from "url";
|
|
7
|
-
import { load } from "js-yaml";
|
|
4
|
+
import * as path2 from "path";
|
|
5
|
+
import { mkdir } from "fs/promises";
|
|
8
6
|
|
|
9
|
-
// src/utils/topological-sort.ts
|
|
7
|
+
// ../zenko-core/src/utils/topological-sort.ts
|
|
10
8
|
function topologicalSort(schemas) {
|
|
11
9
|
const visited = /* @__PURE__ */ new Set();
|
|
12
10
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -55,7 +53,7 @@ function extractRefName(ref) {
|
|
|
55
53
|
return ref.split("/").pop() || "Unknown";
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
// src/utils/property-name.ts
|
|
56
|
+
// ../zenko-core/src/utils/property-name.ts
|
|
59
57
|
function isValidJSIdentifier(name) {
|
|
60
58
|
if (!name) return false;
|
|
61
59
|
const firstChar = name.at(0);
|
|
@@ -135,7 +133,7 @@ function formatPropertyName(name) {
|
|
|
135
133
|
return isValidJSIdentifier(name) ? name : `"${name}"`;
|
|
136
134
|
}
|
|
137
135
|
|
|
138
|
-
// src/utils/string-utils.ts
|
|
136
|
+
// ../zenko-core/src/utils/string-utils.ts
|
|
139
137
|
function toCamelCase(str) {
|
|
140
138
|
return str.replace(/-([a-zA-Z])/g, (_, letter) => letter.toUpperCase()).replace(/-+$/, "");
|
|
141
139
|
}
|
|
@@ -143,7 +141,7 @@ function capitalize(str) {
|
|
|
143
141
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
// src/utils/http-status.ts
|
|
144
|
+
// ../zenko-core/src/utils/http-status.ts
|
|
147
145
|
var statusNameMap = {
|
|
148
146
|
"400": "badRequest",
|
|
149
147
|
"401": "unauthorized",
|
|
@@ -209,7 +207,7 @@ function isErrorStatus(status) {
|
|
|
209
207
|
return code >= 400;
|
|
210
208
|
}
|
|
211
209
|
|
|
212
|
-
// src/utils/tree-shaking.ts
|
|
210
|
+
// ../zenko-core/src/utils/tree-shaking.ts
|
|
213
211
|
function analyzeZenkoUsage(operations) {
|
|
214
212
|
const usage = {
|
|
215
213
|
usesHeaderFn: false,
|
|
@@ -249,7 +247,7 @@ function hasAnyErrors(errors) {
|
|
|
249
247
|
return Boolean(errors && Object.keys(errors).length > 0);
|
|
250
248
|
}
|
|
251
249
|
|
|
252
|
-
// src/utils/collect-inline-types.ts
|
|
250
|
+
// ../zenko-core/src/utils/collect-inline-types.ts
|
|
253
251
|
function collectInlineRequestTypes(operations, spec) {
|
|
254
252
|
const requestTypesToGenerate = /* @__PURE__ */ new Map();
|
|
255
253
|
const operationLookup = /* @__PURE__ */ new Map();
|
|
@@ -326,8 +324,186 @@ function collectInlineResponseTypes(operations, spec) {
|
|
|
326
324
|
}
|
|
327
325
|
return responseTypesToGenerate;
|
|
328
326
|
}
|
|
327
|
+
function collectInlineErrorTypes(operations, spec) {
|
|
328
|
+
const errorTypesToGenerate = /* @__PURE__ */ new Map();
|
|
329
|
+
const operationLookup = /* @__PURE__ */ new Map();
|
|
330
|
+
for (const [, pathItem] of Object.entries(spec.paths || {})) {
|
|
331
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
332
|
+
const op = operation;
|
|
333
|
+
if (op.operationId) {
|
|
334
|
+
operationLookup.set(op.operationId, op);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
|
|
339
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
340
|
+
const op = operation;
|
|
341
|
+
if (op.operationId) {
|
|
342
|
+
operationLookup.set(op.operationId, op);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (const op of operations) {
|
|
347
|
+
const operation = operationLookup.get(op.operationId);
|
|
348
|
+
if (!operation) continue;
|
|
349
|
+
const responses = operation.responses || {};
|
|
350
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
351
|
+
if (isErrorStatus(statusCode) && response.content) {
|
|
352
|
+
const content = response.content;
|
|
353
|
+
const jsonContent = content["application/json"];
|
|
354
|
+
if (jsonContent && jsonContent.schema) {
|
|
355
|
+
const schema = jsonContent.schema;
|
|
356
|
+
const identifier = mapStatusToIdentifier(statusCode);
|
|
357
|
+
const typeName = `${capitalize(toCamelCase(op.operationId))}${capitalize(identifier)}`;
|
|
358
|
+
if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
|
|
359
|
+
errorTypesToGenerate.set(typeName, schema);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return errorTypesToGenerate;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ../zenko-core/src/utils/collect-referenced-schemas.ts
|
|
369
|
+
function findContentType(content) {
|
|
370
|
+
const contentTypes = Object.keys(content);
|
|
371
|
+
if (contentTypes.includes("application/json")) {
|
|
372
|
+
return "application/json";
|
|
373
|
+
}
|
|
374
|
+
const CONTENT_TYPE_MAP2 = {
|
|
375
|
+
"application/json": "unknown",
|
|
376
|
+
"text/csv": "string",
|
|
377
|
+
"text/plain": "string",
|
|
378
|
+
"application/octet-stream": "unknown",
|
|
379
|
+
"application/pdf": "unknown"
|
|
380
|
+
};
|
|
381
|
+
for (const contentType of contentTypes) {
|
|
382
|
+
if (contentType in CONTENT_TYPE_MAP2) {
|
|
383
|
+
return contentType;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return contentTypes[0] || "";
|
|
387
|
+
}
|
|
388
|
+
function resolveParameter(parameter, spec) {
|
|
389
|
+
if (!parameter) return void 0;
|
|
390
|
+
if (parameter.$ref) {
|
|
391
|
+
const refName = extractRefName(parameter.$ref);
|
|
392
|
+
const resolved = spec.components?.parameters?.[refName];
|
|
393
|
+
if (!resolved) return void 0;
|
|
394
|
+
const { $ref, ...overrides } = parameter;
|
|
395
|
+
return {
|
|
396
|
+
...resolved,
|
|
397
|
+
...overrides
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return parameter;
|
|
401
|
+
}
|
|
402
|
+
function collectReferencedSchemas(operations, spec) {
|
|
403
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
404
|
+
const operationLookup = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const [, pathItem] of Object.entries(spec.paths || {})) {
|
|
406
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
407
|
+
const op = operation;
|
|
408
|
+
if (op.operationId) {
|
|
409
|
+
operationLookup.set(op.operationId, op);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
|
|
414
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
415
|
+
const op = operation;
|
|
416
|
+
if (op.operationId) {
|
|
417
|
+
operationLookup.set(op.operationId, op);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
for (const op of operations) {
|
|
422
|
+
const rawOperation = operationLookup.get(op.operationId);
|
|
423
|
+
if (!rawOperation) continue;
|
|
424
|
+
const requestBody = rawOperation.requestBody?.content?.["application/json"]?.schema;
|
|
425
|
+
if (requestBody?.$ref) {
|
|
426
|
+
const refName = extractRefName(requestBody.$ref);
|
|
427
|
+
referenced.add(refName);
|
|
428
|
+
} else if (requestBody) {
|
|
429
|
+
const deps = extractDependencies(requestBody);
|
|
430
|
+
for (const dep of deps) {
|
|
431
|
+
if (spec.components?.schemas?.[dep]) {
|
|
432
|
+
referenced.add(dep);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const responses = rawOperation.responses || {};
|
|
437
|
+
for (const [, response] of Object.entries(responses)) {
|
|
438
|
+
const content = response?.content;
|
|
439
|
+
if (!content) continue;
|
|
440
|
+
const contentType = findContentType(content);
|
|
441
|
+
const responseSchema = content[contentType]?.schema;
|
|
442
|
+
if (responseSchema?.$ref) {
|
|
443
|
+
const refName = extractRefName(responseSchema.$ref);
|
|
444
|
+
referenced.add(refName);
|
|
445
|
+
} else if (responseSchema) {
|
|
446
|
+
const deps = extractDependencies(responseSchema);
|
|
447
|
+
for (const dep of deps) {
|
|
448
|
+
if (spec.components?.schemas?.[dep]) {
|
|
449
|
+
referenced.add(dep);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const param of op.queryParams) {
|
|
455
|
+
if (param.schema?.$ref) {
|
|
456
|
+
const refName = extractRefName(param.schema.$ref);
|
|
457
|
+
referenced.add(refName);
|
|
458
|
+
}
|
|
459
|
+
if (param.schema?.items?.$ref) {
|
|
460
|
+
const refName = extractRefName(param.schema.items.$ref);
|
|
461
|
+
referenced.add(refName);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const header of op.requestHeaders || []) {
|
|
465
|
+
if (header.schema?.$ref) {
|
|
466
|
+
const refName = extractRefName(header.schema.$ref);
|
|
467
|
+
referenced.add(refName);
|
|
468
|
+
}
|
|
469
|
+
if (header.schema?.items?.$ref) {
|
|
470
|
+
const refName = extractRefName(header.schema.items.$ref);
|
|
471
|
+
referenced.add(refName);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const parameters = rawOperation.parameters || [];
|
|
475
|
+
for (const param of parameters) {
|
|
476
|
+
const resolvedParam = resolveParameter(param, spec);
|
|
477
|
+
if (resolvedParam?.schema?.$ref) {
|
|
478
|
+
const refName = extractRefName(resolvedParam.schema.$ref);
|
|
479
|
+
referenced.add(refName);
|
|
480
|
+
}
|
|
481
|
+
if (resolvedParam?.schema?.items?.$ref) {
|
|
482
|
+
const refName = extractRefName(resolvedParam.schema.items.$ref);
|
|
483
|
+
referenced.add(refName);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const visited = /* @__PURE__ */ new Set();
|
|
488
|
+
const toVisit = Array.from(referenced);
|
|
489
|
+
while (toVisit.length > 0) {
|
|
490
|
+
const schemaName = toVisit.pop();
|
|
491
|
+
if (visited.has(schemaName)) continue;
|
|
492
|
+
visited.add(schemaName);
|
|
493
|
+
const schema = spec.components?.schemas?.[schemaName];
|
|
494
|
+
if (!schema) continue;
|
|
495
|
+
const dependencies = extractDependencies(schema);
|
|
496
|
+
for (const dep of dependencies) {
|
|
497
|
+
if (spec.components?.schemas?.[dep] && !visited.has(dep)) {
|
|
498
|
+
referenced.add(dep);
|
|
499
|
+
toVisit.push(dep);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return referenced;
|
|
504
|
+
}
|
|
329
505
|
|
|
330
|
-
// src/utils/generate-helper-file.ts
|
|
506
|
+
// ../zenko-core/src/utils/generate-helper-file.ts
|
|
331
507
|
function generateHelperFile() {
|
|
332
508
|
const output = [];
|
|
333
509
|
output.push("// Generated helper types for Zenko");
|
|
@@ -369,11 +545,11 @@ function generateHelperFile() {
|
|
|
369
545
|
return output.join("\n");
|
|
370
546
|
}
|
|
371
547
|
|
|
372
|
-
// src/zenko.ts
|
|
548
|
+
// ../zenko-core/src/zenko.ts
|
|
373
549
|
function generateWithMetadata(spec, options = {}) {
|
|
374
550
|
const output = [];
|
|
375
551
|
const generatedTypes = /* @__PURE__ */ new Set();
|
|
376
|
-
const { strictDates = false, strictNumeric = false } = options;
|
|
552
|
+
const { strictDates = false, strictNumeric = false, operationIds } = options;
|
|
377
553
|
const typesConfig = normalizeTypesConfig(options.types);
|
|
378
554
|
const schemaOptions = {
|
|
379
555
|
strictDates,
|
|
@@ -387,13 +563,26 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
387
563
|
nameMap.set(name, toCamelCase(name));
|
|
388
564
|
}
|
|
389
565
|
}
|
|
390
|
-
|
|
566
|
+
let operations = parseOperations(spec, nameMap);
|
|
567
|
+
if (operationIds && operationIds.length > 0) {
|
|
568
|
+
const selectedIds = new Set(operationIds);
|
|
569
|
+
operations = operations.filter((op) => selectedIds.has(op.operationId));
|
|
570
|
+
}
|
|
391
571
|
appendHelperTypesImport(output, typesConfig, operations);
|
|
392
572
|
output.push("");
|
|
393
573
|
if (spec.components?.schemas) {
|
|
394
574
|
output.push("// Generated Zod Schemas");
|
|
395
575
|
output.push("");
|
|
396
|
-
|
|
576
|
+
let schemasToGenerate;
|
|
577
|
+
if (operationIds && operationIds.length > 0) {
|
|
578
|
+
const referencedSchemas = collectReferencedSchemas(operations, spec);
|
|
579
|
+
schemasToGenerate = Array.from(referencedSchemas);
|
|
580
|
+
} else {
|
|
581
|
+
schemasToGenerate = Object.keys(spec.components.schemas);
|
|
582
|
+
}
|
|
583
|
+
const sortedSchemas = topologicalSort(spec.components.schemas).filter(
|
|
584
|
+
(name) => schemasToGenerate.includes(name)
|
|
585
|
+
);
|
|
397
586
|
for (const name of sortedSchemas) {
|
|
398
587
|
const schema = spec.components.schemas[name];
|
|
399
588
|
const sanitizedName = nameMap.get(name);
|
|
@@ -582,6 +771,9 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
582
771
|
}
|
|
583
772
|
return result;
|
|
584
773
|
}
|
|
774
|
+
function generateFromDocument(spec, options = {}) {
|
|
775
|
+
return generateWithMetadata(spec, options);
|
|
776
|
+
}
|
|
585
777
|
function generateRequestTypes(output, operations, spec, nameMap, schemaOptions) {
|
|
586
778
|
const requestTypesToGenerate = collectInlineRequestTypes(operations, spec);
|
|
587
779
|
if (requestTypesToGenerate.size > 0) {
|
|
@@ -604,7 +796,8 @@ function generateRequestTypes(output, operations, spec, nameMap, schemaOptions)
|
|
|
604
796
|
}
|
|
605
797
|
function generateResponseTypes(output, operations, spec, nameMap, schemaOptions) {
|
|
606
798
|
const responseTypesToGenerate = collectInlineResponseTypes(operations, spec);
|
|
607
|
-
|
|
799
|
+
const errorTypesToGenerate = collectInlineErrorTypes(operations, spec);
|
|
800
|
+
if (responseTypesToGenerate.size > 0 || errorTypesToGenerate.size > 0) {
|
|
608
801
|
output.push("// Generated Response Types");
|
|
609
802
|
output.push("");
|
|
610
803
|
for (const [typeName, schema] of responseTypesToGenerate) {
|
|
@@ -620,6 +813,19 @@ function generateResponseTypes(output, operations, spec, nameMap, schemaOptions)
|
|
|
620
813
|
output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
|
|
621
814
|
output.push("");
|
|
622
815
|
}
|
|
816
|
+
for (const [typeName, schema] of errorTypesToGenerate) {
|
|
817
|
+
const generatedSchema = generateZodSchema(
|
|
818
|
+
typeName,
|
|
819
|
+
schema,
|
|
820
|
+
/* @__PURE__ */ new Set(),
|
|
821
|
+
schemaOptions,
|
|
822
|
+
nameMap
|
|
823
|
+
);
|
|
824
|
+
output.push(generatedSchema);
|
|
825
|
+
output.push("");
|
|
826
|
+
output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
|
|
827
|
+
output.push("");
|
|
828
|
+
}
|
|
623
829
|
}
|
|
624
830
|
}
|
|
625
831
|
function appendOperationField(buffer, key, value) {
|
|
@@ -661,7 +867,7 @@ var CONTENT_TYPE_MAP = {
|
|
|
661
867
|
"application/octet-stream": "unknown",
|
|
662
868
|
"application/pdf": "unknown"
|
|
663
869
|
};
|
|
664
|
-
function
|
|
870
|
+
function findContentType2(content) {
|
|
665
871
|
const contentTypes = Object.keys(content);
|
|
666
872
|
if (contentTypes.includes("application/json")) {
|
|
667
873
|
return "application/json";
|
|
@@ -685,12 +891,12 @@ function inferResponseType(contentType, statusCode) {
|
|
|
685
891
|
function parseOperations(spec, nameMap) {
|
|
686
892
|
const operations = [];
|
|
687
893
|
if (spec.paths) {
|
|
688
|
-
for (const [
|
|
894
|
+
for (const [path3, pathItem] of Object.entries(spec.paths)) {
|
|
689
895
|
for (const [method, operation] of Object.entries(pathItem)) {
|
|
690
896
|
const normalizedMethod = method.toLowerCase();
|
|
691
897
|
if (!isRequestMethod(normalizedMethod)) continue;
|
|
692
898
|
if (!operation.operationId) continue;
|
|
693
|
-
const pathParams = extractPathParams(
|
|
899
|
+
const pathParams = extractPathParams(path3);
|
|
694
900
|
const requestType = getRequestType(operation);
|
|
695
901
|
const { successResponse, errors } = getResponseTypes(
|
|
696
902
|
operation,
|
|
@@ -702,7 +908,7 @@ function parseOperations(spec, nameMap) {
|
|
|
702
908
|
const queryParams = getQueryParams(resolvedParameters);
|
|
703
909
|
operations.push({
|
|
704
910
|
operationId: operation.operationId,
|
|
705
|
-
path:
|
|
911
|
+
path: path3,
|
|
706
912
|
method: normalizedMethod,
|
|
707
913
|
pathParams,
|
|
708
914
|
queryParams,
|
|
@@ -720,8 +926,8 @@ function parseOperations(spec, nameMap) {
|
|
|
720
926
|
const normalizedMethod = method.toLowerCase();
|
|
721
927
|
if (!isRequestMethod(normalizedMethod)) continue;
|
|
722
928
|
if (!operation.operationId) continue;
|
|
723
|
-
const
|
|
724
|
-
const pathParams = extractPathParams(
|
|
929
|
+
const path3 = webhookName;
|
|
930
|
+
const pathParams = extractPathParams(path3);
|
|
725
931
|
const requestType = getRequestType(operation);
|
|
726
932
|
const { successResponse, errors } = getResponseTypes(
|
|
727
933
|
operation,
|
|
@@ -736,7 +942,7 @@ function parseOperations(spec, nameMap) {
|
|
|
736
942
|
const queryParams = getQueryParams(resolvedParameters);
|
|
737
943
|
operations.push({
|
|
738
944
|
operationId: operation.operationId,
|
|
739
|
-
path:
|
|
945
|
+
path: path3,
|
|
740
946
|
method: normalizedMethod,
|
|
741
947
|
pathParams,
|
|
742
948
|
queryParams,
|
|
@@ -912,7 +1118,7 @@ function collectParameters(pathItem, operation, spec) {
|
|
|
912
1118
|
const addParameters = (params) => {
|
|
913
1119
|
if (!Array.isArray(params)) return;
|
|
914
1120
|
for (const param of params) {
|
|
915
|
-
const resolved =
|
|
1121
|
+
const resolved = resolveParameter2(param, spec);
|
|
916
1122
|
if (!resolved) continue;
|
|
917
1123
|
const key = `${resolved.in}:${resolved.name}`;
|
|
918
1124
|
parametersMap.set(key, resolved);
|
|
@@ -922,7 +1128,7 @@ function collectParameters(pathItem, operation, spec) {
|
|
|
922
1128
|
addParameters(operation.parameters);
|
|
923
1129
|
return Array.from(parametersMap.values());
|
|
924
1130
|
}
|
|
925
|
-
function
|
|
1131
|
+
function resolveParameter2(parameter, spec) {
|
|
926
1132
|
if (!parameter) return void 0;
|
|
927
1133
|
if (parameter.$ref) {
|
|
928
1134
|
const refName = extractRefName(parameter.$ref);
|
|
@@ -936,9 +1142,9 @@ function resolveParameter(parameter, spec) {
|
|
|
936
1142
|
}
|
|
937
1143
|
return parameter;
|
|
938
1144
|
}
|
|
939
|
-
function extractPathParams(
|
|
1145
|
+
function extractPathParams(path3) {
|
|
940
1146
|
const params = [];
|
|
941
|
-
const matches =
|
|
1147
|
+
const matches = path3.match(/{([^}]+)}/g);
|
|
942
1148
|
if (matches) {
|
|
943
1149
|
for (const match of matches) {
|
|
944
1150
|
const paramName = match.slice(1, -1);
|
|
@@ -977,7 +1183,7 @@ function getResponseTypes(operation, operationId, nameMap) {
|
|
|
977
1183
|
}
|
|
978
1184
|
continue;
|
|
979
1185
|
}
|
|
980
|
-
const contentType =
|
|
1186
|
+
const contentType = findContentType2(content);
|
|
981
1187
|
const resolvedSchema = content[contentType]?.schema;
|
|
982
1188
|
if (!resolvedSchema) {
|
|
983
1189
|
const inferredType = inferResponseType(contentType, statusCode);
|
|
@@ -1349,6 +1555,74 @@ function applyNumericBounds(schema, builder) {
|
|
|
1349
1555
|
return builder;
|
|
1350
1556
|
}
|
|
1351
1557
|
|
|
1558
|
+
// src/loader.ts
|
|
1559
|
+
import * as path from "path";
|
|
1560
|
+
import { pathToFileURL } from "url";
|
|
1561
|
+
var YAML_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
1562
|
+
var JSON_EXTENSIONS = /* @__PURE__ */ new Set([".json"]);
|
|
1563
|
+
async function readFileText(filePath) {
|
|
1564
|
+
const file = Bun.file(filePath);
|
|
1565
|
+
if (!await file.exists()) {
|
|
1566
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1567
|
+
}
|
|
1568
|
+
return await file.text();
|
|
1569
|
+
}
|
|
1570
|
+
async function loadConfig(filePath) {
|
|
1571
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
1572
|
+
if (JSON_EXTENSIONS.has(extension)) {
|
|
1573
|
+
const content = await readFileText(filePath);
|
|
1574
|
+
return JSON.parse(content);
|
|
1575
|
+
}
|
|
1576
|
+
if (YAML_EXTENSIONS.has(extension)) {
|
|
1577
|
+
const content = await readFileText(filePath);
|
|
1578
|
+
return Bun.YAML.parse(content);
|
|
1579
|
+
}
|
|
1580
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
1581
|
+
const module = await import(fileUrl);
|
|
1582
|
+
return module.default ?? module.config ?? module;
|
|
1583
|
+
}
|
|
1584
|
+
async function loadSpec(filePath) {
|
|
1585
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
1586
|
+
const content = await readFileText(filePath);
|
|
1587
|
+
if (YAML_EXTENSIONS.has(extension)) {
|
|
1588
|
+
const parsed = Bun.YAML.parse(content);
|
|
1589
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1590
|
+
throw new Error(`YAML spec did not resolve to an object: ${filePath}`);
|
|
1591
|
+
}
|
|
1592
|
+
if (Array.isArray(parsed)) {
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`YAML spec produced multiple documents; provide a single document in ${filePath}`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
return parsed;
|
|
1598
|
+
}
|
|
1599
|
+
if (JSON_EXTENSIONS.has(extension)) {
|
|
1600
|
+
return JSON.parse(content);
|
|
1601
|
+
}
|
|
1602
|
+
throw new Error(
|
|
1603
|
+
`Unsupported specification format for ${filePath}. Expected .yaml, .yml, or .json`
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
function normalizeGenerationOptions(entry, baseDir, defaults) {
|
|
1607
|
+
const resolvedInput = path.isAbsolute(entry.input) ? entry.input : path.join(baseDir, entry.input);
|
|
1608
|
+
const resolvedOutput = path.isAbsolute(entry.output) ? entry.output : path.join(baseDir, entry.output);
|
|
1609
|
+
return {
|
|
1610
|
+
resolvedInput,
|
|
1611
|
+
resolvedOutput,
|
|
1612
|
+
strictDates: entry.strictDates ?? defaults.strictDates,
|
|
1613
|
+
strictNumeric: entry.strictNumeric ?? defaults.strictNumeric,
|
|
1614
|
+
types: mergeTypesConfig(defaults.types, entry.types),
|
|
1615
|
+
operationIds: entry.operationIds ?? defaults.operationIds
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
function mergeTypesConfig(baseConfig, entryConfig) {
|
|
1619
|
+
if (!baseConfig && !entryConfig) return void 0;
|
|
1620
|
+
return {
|
|
1621
|
+
...baseConfig,
|
|
1622
|
+
...entryConfig
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1352
1626
|
// src/cli.ts
|
|
1353
1627
|
async function main() {
|
|
1354
1628
|
const args = process.argv.slice(2);
|
|
@@ -1374,8 +1648,8 @@ async function main() {
|
|
|
1374
1648
|
return;
|
|
1375
1649
|
}
|
|
1376
1650
|
await generateSingle({
|
|
1377
|
-
inputFile,
|
|
1378
|
-
outputFile,
|
|
1651
|
+
resolvedInput: path2.resolve(inputFile),
|
|
1652
|
+
resolvedOutput: path2.resolve(outputFile),
|
|
1379
1653
|
strictDates: parsed.strictDates,
|
|
1380
1654
|
strictNumeric: parsed.strictNumeric
|
|
1381
1655
|
});
|
|
@@ -1443,38 +1717,22 @@ function printHelp() {
|
|
|
1443
1717
|
}
|
|
1444
1718
|
async function runFromConfig(parsed) {
|
|
1445
1719
|
const configPath = parsed.configPath;
|
|
1446
|
-
const resolvedConfigPath =
|
|
1447
|
-
const
|
|
1448
|
-
validateConfig(
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1720
|
+
const resolvedConfigPath = path2.resolve(configPath);
|
|
1721
|
+
const configDocument = await loadConfig(resolvedConfigPath);
|
|
1722
|
+
validateConfig(configDocument);
|
|
1723
|
+
const config = configDocument;
|
|
1724
|
+
const baseDir = path2.dirname(resolvedConfigPath);
|
|
1725
|
+
const defaults = {
|
|
1726
|
+
strictDates: parsed.strictDates,
|
|
1727
|
+
strictNumeric: parsed.strictNumeric,
|
|
1728
|
+
types: config.types,
|
|
1729
|
+
operationIds: void 0
|
|
1730
|
+
};
|
|
1451
1731
|
for (const entry of config.schemas) {
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
|
|
1455
|
-
await generateSingle({
|
|
1456
|
-
inputFile,
|
|
1457
|
-
outputFile,
|
|
1458
|
-
strictDates: entry.strictDates ?? parsed.strictDates,
|
|
1459
|
-
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
|
|
1460
|
-
typesConfig
|
|
1461
|
-
});
|
|
1732
|
+
const options = normalizeGenerationOptions(entry, baseDir, defaults);
|
|
1733
|
+
await generateSingle(options);
|
|
1462
1734
|
}
|
|
1463
1735
|
}
|
|
1464
|
-
async function loadConfig(filePath) {
|
|
1465
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
1466
|
-
if (extension === ".json") {
|
|
1467
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
1468
|
-
return JSON.parse(content);
|
|
1469
|
-
}
|
|
1470
|
-
if (extension === ".yaml" || extension === ".yml") {
|
|
1471
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
1472
|
-
return load(content);
|
|
1473
|
-
}
|
|
1474
|
-
const fileUrl = pathToFileURL(filePath).href;
|
|
1475
|
-
const module = await import(fileUrl);
|
|
1476
|
-
return module.default ?? module.config ?? module;
|
|
1477
|
-
}
|
|
1478
1736
|
function validateConfig(config) {
|
|
1479
1737
|
if (!config || typeof config !== "object") {
|
|
1480
1738
|
throw new Error("Config file must export an object");
|
|
@@ -1491,60 +1749,44 @@ function validateConfig(config) {
|
|
|
1491
1749
|
}
|
|
1492
1750
|
}
|
|
1493
1751
|
}
|
|
1494
|
-
function resolvePath(filePath, baseDir) {
|
|
1495
|
-
return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
|
|
1496
|
-
}
|
|
1497
|
-
function resolveTypesConfig(baseConfig, entryConfig) {
|
|
1498
|
-
if (!baseConfig && !entryConfig) return void 0;
|
|
1499
|
-
return {
|
|
1500
|
-
...baseConfig,
|
|
1501
|
-
...entryConfig
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
1752
|
async function generateSingle(options) {
|
|
1505
|
-
const {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1753
|
+
const {
|
|
1754
|
+
resolvedInput,
|
|
1755
|
+
resolvedOutput,
|
|
1756
|
+
strictDates = false,
|
|
1757
|
+
strictNumeric = false,
|
|
1758
|
+
types,
|
|
1759
|
+
operationIds
|
|
1760
|
+
} = options;
|
|
1761
|
+
const spec = await loadSpec(resolvedInput);
|
|
1762
|
+
const result = generateFromDocument(spec, {
|
|
1510
1763
|
strictDates,
|
|
1511
1764
|
strictNumeric,
|
|
1512
|
-
types
|
|
1765
|
+
types,
|
|
1766
|
+
operationIds
|
|
1513
1767
|
});
|
|
1514
|
-
|
|
1515
|
-
|
|
1768
|
+
await mkdir(path2.dirname(resolvedOutput), { recursive: true });
|
|
1769
|
+
await Bun.write(resolvedOutput, result.output);
|
|
1516
1770
|
console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
|
|
1517
1771
|
console.log(`\u{1F4C4} Processed ${Object.keys(spec.paths || {}).length} paths`);
|
|
1518
1772
|
if (spec.webhooks) {
|
|
1519
1773
|
console.log(`\u{1FA9D} Processed ${Object.keys(spec.webhooks).length} webhooks`);
|
|
1520
1774
|
}
|
|
1521
1775
|
if (result.helperFile) {
|
|
1522
|
-
const helperPath =
|
|
1523
|
-
const absoluteResolvedOutput =
|
|
1524
|
-
const absoluteHelperPath =
|
|
1776
|
+
const helperPath = path2.isAbsolute(result.helperFile.path) ? result.helperFile.path : path2.resolve(path2.dirname(resolvedOutput), result.helperFile.path);
|
|
1777
|
+
const absoluteResolvedOutput = path2.resolve(resolvedOutput);
|
|
1778
|
+
const absoluteHelperPath = path2.resolve(helperPath);
|
|
1525
1779
|
if (absoluteResolvedOutput === absoluteHelperPath) {
|
|
1526
1780
|
console.warn(
|
|
1527
1781
|
`\u26A0\uFE0F Skipping helper file generation: would overwrite main output at ${absoluteResolvedOutput}`
|
|
1528
1782
|
);
|
|
1529
1783
|
return;
|
|
1530
1784
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
encoding: "utf8"
|
|
1534
|
-
});
|
|
1785
|
+
await mkdir(path2.dirname(helperPath), { recursive: true });
|
|
1786
|
+
await Bun.write(helperPath, result.helperFile.content);
|
|
1535
1787
|
console.log(`\u{1F4E6} Generated helper types in ${helperPath}`);
|
|
1536
1788
|
}
|
|
1537
1789
|
}
|
|
1538
|
-
function readSpec(filePath) {
|
|
1539
|
-
if (!fs.existsSync(filePath)) {
|
|
1540
|
-
throw new Error(`Input file not found: ${filePath}`);
|
|
1541
|
-
}
|
|
1542
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
1543
|
-
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
|
1544
|
-
return load(content);
|
|
1545
|
-
}
|
|
1546
|
-
return JSON.parse(content);
|
|
1547
|
-
}
|
|
1548
1790
|
main().catch((error) => {
|
|
1549
1791
|
console.error("\u274C Error:", error);
|
|
1550
1792
|
process.exit(1);
|