zenko 0.1.0 → 0.1.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/README.md +60 -12
- package/dist/cli.cjs +245 -22
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +245 -22
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +227 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.mjs +227 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -8
package/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/RawToast/zenko/actions/workflows/ci.yml)
|
4
4
|
|
5
|
-
A TypeScript generator for OpenAPI specifications that creates **Zod schemas**, **type-safe path functions**, and **operation objects**.
|
5
|
+
A work in progress TypeScript generator for OpenAPI specifications that creates **Zod schemas**, **type-safe path functions**, and **operation objects**.
|
6
6
|
|
7
7
|
Unlike most OpenAPI generators, Zenko does not create a client. Instead you are free to use your own fetch wrapper or library of choice.
|
8
8
|
|
@@ -11,6 +11,7 @@ Unlike most OpenAPI generators, Zenko does not create a client. Instead you are
|
|
11
11
|
- 🔧 **Zod Schema Generation** - Generates runtime-validated Zod schemas from OpenAPI schemas
|
12
12
|
- 🛣️ **Type-safe Path Functions** - Creates functions to build API paths with proper TypeScript types
|
13
13
|
- 📋 **Operation Objects** - Generates objects containing path functions, request validation, and response types
|
14
|
+
- 🧰 **Operation Type Helpers** - Import `PathFn`, `HeaderFn`, `OperationDefinition`, and `OperationErrors` to power reusable clients
|
14
15
|
- 🔄 **Dependency Resolution** - Automatically resolves schema dependencies with topological sorting
|
15
16
|
- ⚡ **CLI & Programmatic API** - Use via command line or import as a library
|
16
17
|
|
@@ -66,10 +67,14 @@ bunx zenko input.yaml output.ts
|
|
66
67
|
|
67
68
|
### Config File
|
68
69
|
|
69
|
-
The config file
|
70
|
+
The config file controls generation for multiple specs and can also configure type helper emission.
|
70
71
|
|
71
72
|
```json
|
72
73
|
{
|
74
|
+
"types": {
|
75
|
+
"emit": true,
|
76
|
+
"helpers": "package"
|
77
|
+
},
|
73
78
|
"schemas": [
|
74
79
|
{
|
75
80
|
"input": "my-api.yaml",
|
@@ -79,12 +84,22 @@ The config file is a JSON file that contains an array of schema objects. Each sc
|
|
79
84
|
"input": "my-strict-api.yaml",
|
80
85
|
"output": "my-strict-api.gen.ts",
|
81
86
|
"strictDates": true,
|
82
|
-
"strictNumeric": true
|
87
|
+
"strictNumeric": true,
|
88
|
+
"types": {
|
89
|
+
"helpers": "inline"
|
90
|
+
}
|
83
91
|
}
|
84
92
|
]
|
85
93
|
}
|
86
94
|
```
|
87
95
|
|
96
|
+
#### Type Helper Modes
|
97
|
+
|
98
|
+
- `helpers: "package"` (default) imports helpers from `zenko`
|
99
|
+
- `helpers: "inline"` writes the helper definitions into each generated file
|
100
|
+
- `helpers: "file"` imports from a custom module (`helpersOutput` path)
|
101
|
+
- `emit: false` disables per-operation type aliases entirely
|
102
|
+
|
88
103
|
### Programmatic Usage
|
89
104
|
|
90
105
|
```typescript
|
@@ -157,7 +172,7 @@ export const paths = {
|
|
157
172
|
} as const
|
158
173
|
```
|
159
174
|
|
160
|
-
### 3. Operation Objects
|
175
|
+
### 3. Operation Objects & Types
|
161
176
|
|
162
177
|
```typescript
|
163
178
|
// Operation Objects
|
@@ -171,12 +186,28 @@ export const getUserById = {
|
|
171
186
|
path: paths.getUserById,
|
172
187
|
response: UserResponse,
|
173
188
|
} as const
|
189
|
+
|
190
|
+
// Operation Types
|
191
|
+
import type { OperationDefinition, OperationErrors } from "zenko"
|
192
|
+
|
193
|
+
export type AuthenticateUserOperation = OperationDefinition<
|
194
|
+
typeof paths.authenticateUser,
|
195
|
+
typeof AuthenticateRequest,
|
196
|
+
typeof AuthenticateResponse,
|
197
|
+
undefined,
|
198
|
+
OperationErrors
|
199
|
+
>
|
174
200
|
```
|
175
201
|
|
176
202
|
## Example Usage in Your App
|
177
203
|
|
178
204
|
```typescript
|
179
|
-
import {
|
205
|
+
import {
|
206
|
+
paths,
|
207
|
+
authenticateUser,
|
208
|
+
type AuthenticateUserOperation,
|
209
|
+
AuthenticateRequest,
|
210
|
+
} from "./generated-types"
|
180
211
|
|
181
212
|
// Type-safe path building
|
182
213
|
const userPath = paths.getUserById({ userId: "123" })
|
@@ -201,6 +232,30 @@ if (validation.success) {
|
|
201
232
|
}
|
202
233
|
```
|
203
234
|
|
235
|
+
### Building a Generic Client
|
236
|
+
|
237
|
+
```typescript
|
238
|
+
import type { OperationDefinition } from "zenko"
|
239
|
+
|
240
|
+
async function runOperation<
|
241
|
+
T extends OperationDefinition<PathFn<any[]>, any, any>,
|
242
|
+
>(
|
243
|
+
operation: T,
|
244
|
+
config: { baseUrl: string; init?: RequestInit }
|
245
|
+
): Promise<
|
246
|
+
T["response"] extends undefined
|
247
|
+
? void
|
248
|
+
: T["response"] extends (...args: any[]) => infer U
|
249
|
+
? U
|
250
|
+
: T["response"]
|
251
|
+
> {
|
252
|
+
const url = `${config.baseUrl}${operation.path()}`
|
253
|
+
const res = await fetch(url, config.init)
|
254
|
+
if (!res.ok) throw new Error(`Request failed: ${res.status}`)
|
255
|
+
return (await res.json()) as any
|
256
|
+
}
|
257
|
+
```
|
258
|
+
|
204
259
|
## Key Improvements
|
205
260
|
|
206
261
|
### Dependency Resolution
|
@@ -234,10 +289,3 @@ zenko src/resources/petstore.yaml output.ts
|
|
234
289
|
# Format code
|
235
290
|
bun run format
|
236
291
|
```
|
237
|
-
|
238
|
-
## Architecture
|
239
|
-
|
240
|
-
- **`src/zenko.ts`** - Main OpenAPI → TypeScript generator
|
241
|
-
- **`src/cli.ts`** - Command-line interface
|
242
|
-
- **`dist/`** - Bundled outputs (CJS + ESM)
|
243
|
-
- **Topological Sort** - Ensures proper dependency ordering for schema generation
|
package/dist/cli.cjs
CHANGED
@@ -237,11 +237,13 @@ function generate(spec, options = {}) {
|
|
237
237
|
const output = [];
|
238
238
|
const generatedTypes = /* @__PURE__ */ new Set();
|
239
239
|
const { strictDates = false, strictNumeric = false } = options;
|
240
|
+
const typesConfig = normalizeTypesConfig(options.types);
|
240
241
|
const schemaOptions = {
|
241
242
|
strictDates,
|
242
243
|
strictNumeric
|
243
244
|
};
|
244
245
|
output.push('import { z } from "zod";');
|
246
|
+
appendHelperTypesImport(output, typesConfig);
|
245
247
|
output.push("");
|
246
248
|
if (spec.components?.schemas) {
|
247
249
|
output.push("// Generated Zod Schemas");
|
@@ -261,16 +263,79 @@ function generate(spec, options = {}) {
|
|
261
263
|
output.push("// Path Functions");
|
262
264
|
output.push("export const paths = {");
|
263
265
|
for (const op of operations) {
|
264
|
-
|
266
|
+
const pathParamNames = op.pathParams.map((p) => p.name);
|
267
|
+
const hasPathParams = pathParamNames.length > 0;
|
268
|
+
const hasQueryParams = op.queryParams.length > 0;
|
269
|
+
if (!hasPathParams && !hasQueryParams) {
|
265
270
|
output.push(` ${op.operationId}: () => "${op.path}",`);
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
271
|
+
continue;
|
272
|
+
}
|
273
|
+
const allParamNames = [
|
274
|
+
...pathParamNames,
|
275
|
+
...op.queryParams.map((p) => p.name)
|
276
|
+
];
|
277
|
+
const signaturePieces = [];
|
278
|
+
for (const param of op.pathParams) {
|
279
|
+
signaturePieces.push(`${param.name}: string`);
|
280
|
+
}
|
281
|
+
for (const param of op.queryParams) {
|
282
|
+
signaturePieces.push(
|
283
|
+
`${param.name}${param.required ? "" : "?"}: ${mapQueryType(param)}`
|
284
|
+
);
|
285
|
+
}
|
286
|
+
const signatureParams = signaturePieces.join(", ");
|
287
|
+
const needsDefaultObject = !hasPathParams && hasQueryParams && op.queryParams.every((param) => !param.required);
|
288
|
+
const signatureArgs = allParamNames.length ? `{ ${allParamNames.join(", ")} }` : "{}";
|
289
|
+
const signature = `${signatureArgs}: { ${signatureParams} }${needsDefaultObject ? " = {}" : ""}`;
|
290
|
+
const pathWithParams = op.path.replace(/{([^}]+)}/g, "${$1}");
|
291
|
+
if (!hasQueryParams) {
|
270
292
|
output.push(
|
271
|
-
` ${op.operationId}: (
|
293
|
+
` ${op.operationId}: (${signature}) => \`${pathWithParams}\`,`
|
272
294
|
);
|
295
|
+
continue;
|
296
|
+
}
|
297
|
+
output.push(` ${op.operationId}: (${signature}) => {`);
|
298
|
+
output.push(" const params = new URLSearchParams()");
|
299
|
+
for (const param of op.queryParams) {
|
300
|
+
const propertyKey = formatPropertyName(param.name);
|
301
|
+
const accessor = isValidJSIdentifier(param.name) ? param.name : propertyKey;
|
302
|
+
const schema = param.schema ?? {};
|
303
|
+
if (schema?.type === "array") {
|
304
|
+
const itemValueExpression = convertQueryParamValue(
|
305
|
+
schema.items ?? {},
|
306
|
+
"value"
|
307
|
+
);
|
308
|
+
if (param.required) {
|
309
|
+
output.push(` for (const value of ${accessor}) {`);
|
310
|
+
output.push(
|
311
|
+
` params.append("${param.name}", ${itemValueExpression})`
|
312
|
+
);
|
313
|
+
output.push(" }");
|
314
|
+
} else {
|
315
|
+
output.push(` if (${accessor} !== undefined) {`);
|
316
|
+
output.push(` for (const value of ${accessor}) {`);
|
317
|
+
output.push(
|
318
|
+
` params.append("${param.name}", ${itemValueExpression})`
|
319
|
+
);
|
320
|
+
output.push(" }");
|
321
|
+
output.push(" }");
|
322
|
+
}
|
323
|
+
continue;
|
324
|
+
}
|
325
|
+
const valueExpression = convertQueryParamValue(schema, accessor);
|
326
|
+
if (param.required) {
|
327
|
+
output.push(` params.set("${param.name}", ${valueExpression})`);
|
328
|
+
} else {
|
329
|
+
output.push(` if (${accessor} !== undefined) {`);
|
330
|
+
output.push(` params.set("${param.name}", ${valueExpression})`);
|
331
|
+
output.push(" }");
|
332
|
+
}
|
273
333
|
}
|
334
|
+
output.push(" const _searchParams = params.toString()");
|
335
|
+
output.push(
|
336
|
+
` return \`${pathWithParams}\${_searchParams ? \`?\${_searchParams}\` : ""}\``
|
337
|
+
);
|
338
|
+
output.push(" },");
|
274
339
|
}
|
275
340
|
output.push("} as const;");
|
276
341
|
output.push("");
|
@@ -362,6 +427,7 @@ function generate(spec, options = {}) {
|
|
362
427
|
output.push("} as const;");
|
363
428
|
output.push("");
|
364
429
|
}
|
430
|
+
generateOperationTypes(output, operations, typesConfig);
|
365
431
|
return output.join("\n");
|
366
432
|
}
|
367
433
|
function appendOperationField(buffer, key, value) {
|
@@ -397,11 +463,13 @@ function parseOperations(spec) {
|
|
397
463
|
);
|
398
464
|
const resolvedParameters = collectParameters(pathItem, operation, spec);
|
399
465
|
const requestHeaders = getRequestHeaders(resolvedParameters);
|
466
|
+
const queryParams = getQueryParams(resolvedParameters);
|
400
467
|
operations.push({
|
401
468
|
operationId: operation.operationId,
|
402
469
|
path: path2,
|
403
470
|
method: method.toLowerCase(),
|
404
471
|
pathParams,
|
472
|
+
queryParams,
|
405
473
|
requestType,
|
406
474
|
responseType: successResponse,
|
407
475
|
requestHeaders,
|
@@ -411,6 +479,91 @@ function parseOperations(spec) {
|
|
411
479
|
}
|
412
480
|
return operations;
|
413
481
|
}
|
482
|
+
function normalizeTypesConfig(config) {
|
483
|
+
return {
|
484
|
+
emit: config?.emit ?? true,
|
485
|
+
helpers: config?.helpers ?? "package",
|
486
|
+
helpersOutput: config?.helpersOutput ?? "./zenko-types"
|
487
|
+
};
|
488
|
+
}
|
489
|
+
function appendHelperTypesImport(buffer, config) {
|
490
|
+
if (!config.emit) return;
|
491
|
+
switch (config.helpers) {
|
492
|
+
case "package":
|
493
|
+
buffer.push(
|
494
|
+
'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
|
495
|
+
);
|
496
|
+
return;
|
497
|
+
case "file":
|
498
|
+
buffer.push(
|
499
|
+
`import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
|
500
|
+
);
|
501
|
+
return;
|
502
|
+
case "inline":
|
503
|
+
buffer.push(
|
504
|
+
"type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string;"
|
505
|
+
);
|
506
|
+
buffer.push(
|
507
|
+
"type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult;"
|
508
|
+
);
|
509
|
+
buffer.push(
|
510
|
+
"type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
|
511
|
+
);
|
512
|
+
buffer.push(" clientErrors?: Record<string, TClient>;");
|
513
|
+
buffer.push(" serverErrors?: Record<string, TServer>;");
|
514
|
+
buffer.push(" defaultErrors?: Record<string, TDefault>;");
|
515
|
+
buffer.push(" otherErrors?: Record<string, TOther>;");
|
516
|
+
buffer.push("};");
|
517
|
+
buffer.push(
|
518
|
+
"type OperationDefinition<TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends HeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
519
|
+
);
|
520
|
+
buffer.push(" path: TPath;");
|
521
|
+
buffer.push(" request?: TRequest;");
|
522
|
+
buffer.push(" response?: TResponse;");
|
523
|
+
buffer.push(" headers?: THeaders;");
|
524
|
+
buffer.push(" errors?: TErrors;");
|
525
|
+
buffer.push("};");
|
526
|
+
return;
|
527
|
+
}
|
528
|
+
}
|
529
|
+
function generateOperationTypes(buffer, operations, config) {
|
530
|
+
if (!config.emit) return;
|
531
|
+
buffer.push("// Operation Types");
|
532
|
+
for (const op of operations) {
|
533
|
+
const headerType = op.requestHeaders?.length ? `typeof headers.${op.operationId}` : "undefined";
|
534
|
+
const requestType = op.requestType ?? "undefined";
|
535
|
+
const responseType = op.responseType ?? "undefined";
|
536
|
+
const errorsType = buildOperationErrorsType(op.errors);
|
537
|
+
buffer.push(
|
538
|
+
`export type ${capitalize(op.operationId)}Operation = OperationDefinition<`
|
539
|
+
);
|
540
|
+
buffer.push(` typeof paths.${op.operationId},`);
|
541
|
+
buffer.push(` ${requestType},`);
|
542
|
+
buffer.push(` ${responseType},`);
|
543
|
+
buffer.push(` ${headerType},`);
|
544
|
+
buffer.push(` ${errorsType}`);
|
545
|
+
buffer.push(`>;`);
|
546
|
+
buffer.push("");
|
547
|
+
}
|
548
|
+
}
|
549
|
+
function buildOperationErrorsType(errors) {
|
550
|
+
if (!errors || !hasAnyErrors(errors)) {
|
551
|
+
return "OperationErrors";
|
552
|
+
}
|
553
|
+
const client = buildErrorBucket(errors.clientErrors);
|
554
|
+
const server = buildErrorBucket(errors.serverErrors);
|
555
|
+
const fallback = buildErrorBucket(errors.defaultErrors);
|
556
|
+
const other = buildErrorBucket(errors.otherErrors);
|
557
|
+
return `OperationErrors<${client}, ${server}, ${fallback}, ${other}>`;
|
558
|
+
}
|
559
|
+
function buildErrorBucket(bucket) {
|
560
|
+
if (!bucket || Object.keys(bucket).length === 0) {
|
561
|
+
return "unknown";
|
562
|
+
}
|
563
|
+
const entries = Object.entries(bucket);
|
564
|
+
const typeEntries = entries.map(([name, type]) => `${formatPropertyName(name)}: ${type}`).join("; ");
|
565
|
+
return `{ ${typeEntries} }`;
|
566
|
+
}
|
414
567
|
function collectParameters(pathItem, operation, spec) {
|
415
568
|
const parametersMap = /* @__PURE__ */ new Map();
|
416
569
|
const addParameters = (params) => {
|
@@ -553,6 +706,20 @@ function getRequestHeaders(parameters) {
|
|
553
706
|
}
|
554
707
|
return headers;
|
555
708
|
}
|
709
|
+
function getQueryParams(parameters) {
|
710
|
+
const queryParams = [];
|
711
|
+
for (const param of parameters ?? []) {
|
712
|
+
if (param.in === "query") {
|
713
|
+
queryParams.push({
|
714
|
+
name: param.name,
|
715
|
+
description: param.description,
|
716
|
+
schema: param.schema,
|
717
|
+
required: param.required
|
718
|
+
});
|
719
|
+
}
|
720
|
+
}
|
721
|
+
return queryParams;
|
722
|
+
}
|
556
723
|
function mapHeaderType(header) {
|
557
724
|
const schemaType = header.schema?.type;
|
558
725
|
switch (schemaType) {
|
@@ -565,6 +732,39 @@ function mapHeaderType(header) {
|
|
565
732
|
return "string";
|
566
733
|
}
|
567
734
|
}
|
735
|
+
function mapQueryType(param) {
|
736
|
+
return mapQuerySchemaType(param.schema);
|
737
|
+
}
|
738
|
+
function mapQuerySchemaType(schema) {
|
739
|
+
if (!schema) return "string";
|
740
|
+
if (schema.type === "array") {
|
741
|
+
const itemType = mapQuerySchemaType(schema.items);
|
742
|
+
return `Array<${itemType}>`;
|
743
|
+
}
|
744
|
+
switch (schema.type) {
|
745
|
+
case "integer":
|
746
|
+
case "number":
|
747
|
+
return "number";
|
748
|
+
case "boolean":
|
749
|
+
return "boolean";
|
750
|
+
default:
|
751
|
+
return "string";
|
752
|
+
}
|
753
|
+
}
|
754
|
+
function convertQueryParamValue(schema, accessor) {
|
755
|
+
if (!schema) {
|
756
|
+
return `String(${accessor})`;
|
757
|
+
}
|
758
|
+
switch (schema.type) {
|
759
|
+
case "integer":
|
760
|
+
case "number":
|
761
|
+
return `String(${accessor})`;
|
762
|
+
case "boolean":
|
763
|
+
return `${accessor} ? "true" : "false"`;
|
764
|
+
default:
|
765
|
+
return `String(${accessor})`;
|
766
|
+
}
|
767
|
+
}
|
568
768
|
function generateZodSchema(name, schema, generatedTypes, options) {
|
569
769
|
if (generatedTypes.has(name)) return "";
|
570
770
|
generatedTypes.add(name);
|
@@ -573,18 +773,7 @@ function generateZodSchema(name, schema, generatedTypes, options) {
|
|
573
773
|
return `export const ${name} = z.enum([${enumValues}]);`;
|
574
774
|
}
|
575
775
|
if (schema.type === "object" || schema.properties) {
|
576
|
-
const
|
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
|
-
});`;
|
776
|
+
return `export const ${name} = ${buildZodObject(schema, options)};`;
|
588
777
|
}
|
589
778
|
if (schema.type === "array") {
|
590
779
|
const itemSchema = schema.items ?? { type: "unknown" };
|
@@ -607,6 +796,9 @@ function getZodTypeFromSchema(schema, options) {
|
|
607
796
|
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
608
797
|
return `z.enum([${enumValues}])`;
|
609
798
|
}
|
799
|
+
if (schema.type === "object" || schema.properties) {
|
800
|
+
return buildZodObject(schema, options);
|
801
|
+
}
|
610
802
|
switch (schema.type) {
|
611
803
|
case "string":
|
612
804
|
return buildString(schema, options);
|
@@ -627,6 +819,23 @@ function getZodTypeFromSchema(schema, options) {
|
|
627
819
|
return "z.unknown()";
|
628
820
|
}
|
629
821
|
}
|
822
|
+
function buildZodObject(schema, options) {
|
823
|
+
const properties = [];
|
824
|
+
for (const [propName, propSchema] of Object.entries(
|
825
|
+
schema.properties || {}
|
826
|
+
)) {
|
827
|
+
const isRequired = schema.required?.includes(propName) ?? false;
|
828
|
+
const zodType = getZodTypeFromSchema(propSchema, options);
|
829
|
+
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
830
|
+
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
831
|
+
}
|
832
|
+
if (properties.length === 0) {
|
833
|
+
return "z.object({})";
|
834
|
+
}
|
835
|
+
return `z.object({
|
836
|
+
${properties.join("\n")}
|
837
|
+
})`;
|
838
|
+
}
|
630
839
|
function buildString(schema, options) {
|
631
840
|
if (options.strictDates) {
|
632
841
|
switch (schema.format) {
|
@@ -817,7 +1026,7 @@ function printHelp() {
|
|
817
1026
|
console.log("");
|
818
1027
|
console.log("Config file format:");
|
819
1028
|
console.log(
|
820
|
-
' {"schemas": [{ input, output, strictDates?, strictNumeric? }] }'
|
1029
|
+
' {"types"?: { emit?, helpers?, helpersOutput? }, "schemas": [{ input, output, strictDates?, strictNumeric?, types? }] }'
|
821
1030
|
);
|
822
1031
|
}
|
823
1032
|
async function runFromConfig(parsed) {
|
@@ -826,14 +1035,17 @@ async function runFromConfig(parsed) {
|
|
826
1035
|
const config = await loadConfig(resolvedConfigPath);
|
827
1036
|
validateConfig(config);
|
828
1037
|
const baseDir = path.dirname(resolvedConfigPath);
|
1038
|
+
const baseTypesConfig = config.types;
|
829
1039
|
for (const entry of config.schemas) {
|
830
1040
|
const inputFile = resolvePath(entry.input, baseDir);
|
831
1041
|
const outputFile = resolvePath(entry.output, baseDir);
|
1042
|
+
const typesConfig = resolveTypesConfig(baseTypesConfig, entry.types);
|
832
1043
|
await generateSingle({
|
833
1044
|
inputFile,
|
834
1045
|
outputFile,
|
835
1046
|
strictDates: entry.strictDates ?? parsed.strictDates,
|
836
|
-
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric
|
1047
|
+
strictNumeric: entry.strictNumeric ?? parsed.strictNumeric,
|
1048
|
+
typesConfig
|
837
1049
|
});
|
838
1050
|
}
|
839
1051
|
}
|
@@ -870,12 +1082,23 @@ function validateConfig(config) {
|
|
870
1082
|
function resolvePath(filePath, baseDir) {
|
871
1083
|
return path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
|
872
1084
|
}
|
1085
|
+
function resolveTypesConfig(baseConfig, entryConfig) {
|
1086
|
+
if (!baseConfig && !entryConfig) return void 0;
|
1087
|
+
return {
|
1088
|
+
...baseConfig,
|
1089
|
+
...entryConfig
|
1090
|
+
};
|
1091
|
+
}
|
873
1092
|
async function generateSingle(options) {
|
874
|
-
const { inputFile, outputFile, strictDates, strictNumeric } = options;
|
1093
|
+
const { inputFile, outputFile, strictDates, strictNumeric, typesConfig } = options;
|
875
1094
|
const resolvedInput = path.resolve(inputFile);
|
876
1095
|
const resolvedOutput = path.resolve(outputFile);
|
877
1096
|
const spec = readSpec(resolvedInput);
|
878
|
-
const output = generate(spec, {
|
1097
|
+
const output = generate(spec, {
|
1098
|
+
strictDates,
|
1099
|
+
strictNumeric,
|
1100
|
+
types: typesConfig
|
1101
|
+
});
|
879
1102
|
fs.mkdirSync(path.dirname(resolvedOutput), { recursive: true });
|
880
1103
|
fs.writeFileSync(resolvedOutput, output);
|
881
1104
|
console.log(`\u2705 Generated TypeScript types in ${resolvedOutput}`);
|