vite-plugin-openapi-codegen 0.0.3 → 1.0.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 +48 -13
- package/dist/index.d.mts +1 -4
- package/dist/index.mjs +51 -72
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Generate typed API clients and path builders from an OpenAPI document during Vite builds.
|
|
4
4
|
|
|
5
|
-
The plugin reads your OpenAPI spec, runs `openapi-typescript`, and emits
|
|
5
|
+
The plugin reads your OpenAPI spec, runs `openapi-typescript`, and emits three files into your target directory:
|
|
6
6
|
|
|
7
7
|
- `api-types.d.ts` for raw OpenAPI-derived types
|
|
8
|
-
- `types.ts` for schema aliases
|
|
9
8
|
- `api.ts` for path builder functions
|
|
10
9
|
- `client.ts` for typed request helpers
|
|
11
10
|
|
|
@@ -42,7 +41,39 @@ When you run `vp dev` or `vp build`, the plugin generates:
|
|
|
42
41
|
```text
|
|
43
42
|
src/generated/
|
|
44
43
|
api-types.d.ts
|
|
45
|
-
|
|
44
|
+
api.ts
|
|
45
|
+
client.ts
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Real Example Project
|
|
49
|
+
|
|
50
|
+
This repository includes a real minimal Vite project under `example/` that demonstrates
|
|
51
|
+
automatic generation from `vite.config.ts`.
|
|
52
|
+
|
|
53
|
+
Key files:
|
|
54
|
+
|
|
55
|
+
- `example/vite.config.ts` wires the plugin into Vite
|
|
56
|
+
- `example/openapi.json` is the input spec
|
|
57
|
+
- `example/src/http.ts` provides the runtime symbols used by generated clients
|
|
58
|
+
- `example/src/generated/*` is generated during build/dev and is gitignored
|
|
59
|
+
|
|
60
|
+
Run the example build:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
vp build example --config ./example/vite.config.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run the example dev server:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
vp example --config ./example/vite.config.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
After either command, generated files are written to:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
example/src/generated/
|
|
76
|
+
api-types.d.ts
|
|
46
77
|
api.ts
|
|
47
78
|
client.ts
|
|
48
79
|
```
|
|
@@ -105,7 +136,9 @@ export default defineConfig({
|
|
|
105
136
|
Given a spec path like `/api/users/{user_id}`, the plugin generates a path builder:
|
|
106
137
|
|
|
107
138
|
```ts
|
|
108
|
-
|
|
139
|
+
import type { components } from "./api-types";
|
|
140
|
+
|
|
141
|
+
export function getUser(params: components["schemas"]["UserPath"]): string {
|
|
109
142
|
return `users/${params.user_id}`;
|
|
110
143
|
}
|
|
111
144
|
```
|
|
@@ -113,9 +146,11 @@ export function getUser(params: UserPath): string {
|
|
|
113
146
|
And a typed client helper:
|
|
114
147
|
|
|
115
148
|
```ts
|
|
149
|
+
import type { components } from "./api-types";
|
|
150
|
+
|
|
116
151
|
export interface GetUserOptions {
|
|
117
152
|
query?: never;
|
|
118
|
-
path: UserPath;
|
|
153
|
+
path: components["schemas"]["UserPath"];
|
|
119
154
|
body?: never;
|
|
120
155
|
signal?: AbortSignal;
|
|
121
156
|
}
|
|
@@ -123,8 +158,8 @@ export interface GetUserOptions {
|
|
|
123
158
|
export function getUser(
|
|
124
159
|
options: GetUserOptions,
|
|
125
160
|
requestOptions: RuntimeRequestOptions = {},
|
|
126
|
-
): Promise<UserResponse> {
|
|
127
|
-
return requestJson<UserResponse>(buildGetUserPath(options.path), {
|
|
161
|
+
): Promise<components["schemas"]["UserResponse"]> {
|
|
162
|
+
return requestJson<components["schemas"]["UserResponse"]>(buildGetUserPath(options.path), {
|
|
128
163
|
...requestOptions,
|
|
129
164
|
method: "GET",
|
|
130
165
|
signal: options.signal,
|
|
@@ -155,7 +190,6 @@ interface Options {
|
|
|
155
190
|
requestOptionsType?: string;
|
|
156
191
|
omitKeys?: string[];
|
|
157
192
|
};
|
|
158
|
-
legacyAliases?: Record<string, string>;
|
|
159
193
|
}
|
|
160
194
|
```
|
|
161
195
|
|
|
@@ -179,10 +213,6 @@ Controls whether the `pathPrefix` is removed from generated path builders. The d
|
|
|
179
213
|
|
|
180
214
|
Overrides the runtime import path and symbol names used by generated clients.
|
|
181
215
|
|
|
182
|
-
### `legacyAliases`
|
|
183
|
-
|
|
184
|
-
Adds extra type aliases to `types.ts` so you can preserve older type names during migrations.
|
|
185
|
-
|
|
186
216
|
## Programmatic Usage
|
|
187
217
|
|
|
188
218
|
If you want to generate artifacts outside the Vite lifecycle, use `renderGeneratedArtifacts`:
|
|
@@ -200,7 +230,6 @@ const files = renderGeneratedArtifacts(spec, {
|
|
|
200
230
|
|
|
201
231
|
console.log(files.api);
|
|
202
232
|
console.log(files.client);
|
|
203
|
-
console.log(files.types);
|
|
204
233
|
```
|
|
205
234
|
|
|
206
235
|
## Development
|
|
@@ -211,3 +240,9 @@ vp test
|
|
|
211
240
|
vp check
|
|
212
241
|
vp pack
|
|
213
242
|
```
|
|
243
|
+
|
|
244
|
+
To validate the real example project as part of local development:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
vp build example --config ./example/vite.config.ts
|
|
248
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -75,15 +75,12 @@ interface Options {
|
|
|
75
75
|
stripPrefix?: boolean;
|
|
76
76
|
/** HTTP client configuration */
|
|
77
77
|
httpClient?: HttpClientConfig;
|
|
78
|
-
/** Legacy type aliases for types.ts: { OldName: 'NewName' } */
|
|
79
|
-
legacyAliases?: Record<string, string>;
|
|
80
78
|
}
|
|
81
79
|
interface GeneratedArtifacts {
|
|
82
80
|
api: string;
|
|
83
81
|
client: string;
|
|
84
|
-
types: string;
|
|
85
82
|
}
|
|
86
|
-
declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "
|
|
83
|
+
declare function renderGeneratedArtifacts(spec: OpenAPISpec, options: Pick<Options, "httpClient" | "pathPrefix" | "stripPrefix">, preCollectedOperations?: OperationEntry[]): GeneratedArtifacts;
|
|
87
84
|
declare function openapiCodegen(options: Options): Plugin;
|
|
88
85
|
//#endregion
|
|
89
86
|
export { type HttpClientConfig, type OpenAPISpec, type OperationEntry, type Options, openapiCodegen, renderGeneratedArtifacts };
|
package/dist/index.mjs
CHANGED
|
@@ -1,45 +1,33 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import * as ts from "typescript";
|
|
4
4
|
//#region src/ast.ts
|
|
5
5
|
const AST_PRINTER = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
6
|
-
function renderTypesSource(schemaNames, legacyAliases, generatedHeader) {
|
|
7
|
-
const statements = [createTypeOnlyImport(["components"], "./api-types")];
|
|
8
|
-
for (const name of schemaNames) statements.push(ts.factory.createTypeAliasDeclaration([createExportModifier()], ts.factory.createIdentifier(name), void 0, createIndexedAccessTypeNode("components", ["schemas", name])));
|
|
9
|
-
if (legacyAliases && Object.keys(legacyAliases).length > 0) Object.entries(legacyAliases).forEach(([alias, target], index) => {
|
|
10
|
-
const statement = ts.factory.createTypeAliasDeclaration([createExportModifier()], ts.factory.createIdentifier(alias), void 0, createTypeNodeFromText(target));
|
|
11
|
-
statements.push(index === 0 ? createGeneratedBannerComment(statement, "Legacy aliases") : statement);
|
|
12
|
-
});
|
|
13
|
-
return printGeneratedFile(statements, generatedHeader);
|
|
14
|
-
}
|
|
15
6
|
function renderApiSource(entries, generatedHeader) {
|
|
16
7
|
const statements = [];
|
|
17
8
|
const seenGroups = /* @__PURE__ */ new Set();
|
|
18
|
-
let needsOperationsImport = false;
|
|
19
|
-
const typeAliasImports = /* @__PURE__ */ new Set();
|
|
20
9
|
for (const entry of entries) {
|
|
21
10
|
const parameters = entry.pathTypeExpr == null ? [] : [ts.factory.createParameterDeclaration(void 0, void 0, ts.factory.createIdentifier("params"), void 0, createTypeNodeFromText(entry.pathTypeExpr))];
|
|
22
|
-
if (entry.pathTypeExpr) {
|
|
23
|
-
if (entry.pathTypeExpr.includes("operations[")) needsOperationsImport = true;
|
|
24
|
-
else if (/^[A-Z]\w*$/.test(entry.pathTypeExpr)) typeAliasImports.add(entry.pathTypeExpr);
|
|
25
|
-
}
|
|
26
11
|
const declaration = ts.factory.createFunctionDeclaration([createExportModifier()], void 0, ts.factory.createIdentifier(entry.funcName), void 0, parameters, ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ts.factory.createBlock([ts.factory.createReturnStatement(entry.pathTypeExpr == null ? ts.factory.createStringLiteral(entry.strippedPath) : createPathExpression(entry.strippedPath))], true));
|
|
27
12
|
statements.push(seenGroups.has(entry.group) ? declaration : createGeneratedBannerComment(declaration, capitalize$1(entry.group)));
|
|
28
13
|
seenGroups.add(entry.group);
|
|
29
14
|
}
|
|
30
15
|
const sourceStatements = [];
|
|
31
|
-
|
|
32
|
-
if (
|
|
16
|
+
const apiTypeImports = collectApiTypeImports(entries.map((entry) => entry.pathTypeExpr));
|
|
17
|
+
if (apiTypeImports.length > 0) sourceStatements.push(createTypeOnlyImport(apiTypeImports, "./api-types"));
|
|
33
18
|
sourceStatements.push(...statements);
|
|
34
19
|
return printGeneratedFile(sourceStatements, generatedHeader);
|
|
35
20
|
}
|
|
36
21
|
function renderClientSource(model, generatedHeader, httpClient) {
|
|
37
|
-
const statements = [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
22
|
+
const statements = [createTypeOnlyImport([httpClient.requestOptionsType], httpClient.module), createValueImport([{ name: httpClient.jsonFunction }, { name: httpClient.voidFunction }], httpClient.module)];
|
|
23
|
+
const apiTypeImports = collectApiTypeImports(model.operations.flatMap((operation) => [
|
|
24
|
+
operation.bodyChannel.typeExpr,
|
|
25
|
+
operation.pathChannel.typeExpr,
|
|
26
|
+
operation.queryChannel.typeExpr,
|
|
27
|
+
operation.responseTypeExpr,
|
|
28
|
+
operation.returnTypeExpr
|
|
29
|
+
]));
|
|
30
|
+
if (apiTypeImports.length > 0) statements.push(createTypeOnlyImport(apiTypeImports, "./api-types"));
|
|
43
31
|
statements.push(createValueImport(model.operations.map((operation) => ({
|
|
44
32
|
alias: operation.builderAlias,
|
|
45
33
|
name: operation.funcName
|
|
@@ -80,13 +68,14 @@ function createExportModifier() {
|
|
|
80
68
|
function createGeneratedBannerComment(node, text) {
|
|
81
69
|
return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, ` ${text}`, true);
|
|
82
70
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
71
|
+
function collectApiTypeImports(typeExprs) {
|
|
72
|
+
const names = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const typeExpr of typeExprs) {
|
|
74
|
+
if (!typeExpr) continue;
|
|
75
|
+
if (typeExpr.includes("components[")) names.add("components");
|
|
76
|
+
if (typeExpr.includes("operations[")) names.add("operations");
|
|
77
|
+
}
|
|
78
|
+
return [...names].sort();
|
|
90
79
|
}
|
|
91
80
|
function createPathExpression(strippedPath) {
|
|
92
81
|
const matches = [...strippedPath.matchAll(/\{(\w+)\}/g)];
|
|
@@ -151,12 +140,10 @@ function buildClientRenderModelFromOperations(operations, spec, requestFunctionN
|
|
|
151
140
|
void: "requestVoid"
|
|
152
141
|
}) {
|
|
153
142
|
const context = buildNormalizationContext(spec);
|
|
154
|
-
const
|
|
155
|
-
const normalized = operations.map((entry) => normalizeOperation(entry, context, typeImports, requestFunctionNames));
|
|
143
|
+
const normalized = operations.map((entry) => normalizeOperation(entry, context, requestFunctionNames));
|
|
156
144
|
return {
|
|
157
145
|
operations: normalized,
|
|
158
|
-
needsSearchParamsHelper: normalized.some((operation) => operation.queryChannel.present)
|
|
159
|
-
typeImports: [...typeImports].sort()
|
|
146
|
+
needsSearchParamsHelper: normalized.some((operation) => operation.queryChannel.present)
|
|
160
147
|
};
|
|
161
148
|
}
|
|
162
149
|
function collectOperations(spec, pathPrefix = "/api/", stripPrefix = true) {
|
|
@@ -275,24 +262,21 @@ function getTemplateParameterNames(apiPath) {
|
|
|
275
262
|
const matches = apiPath.match(/\{(\w+)\}/g) ?? [];
|
|
276
263
|
return new Set(matches.map((match) => match.slice(1, -1)));
|
|
277
264
|
}
|
|
278
|
-
function resolveParameterTypeExpression(entry, context, location
|
|
265
|
+
function resolveParameterTypeExpression(entry, context, location) {
|
|
279
266
|
const effectiveParameters = getEffectiveParametersByLocation(entry, location);
|
|
280
267
|
if (effectiveParameters.length === 0) return "never";
|
|
281
|
-
const
|
|
282
|
-
if (
|
|
283
|
-
typeImports.add(alias);
|
|
284
|
-
return alias;
|
|
285
|
-
}
|
|
268
|
+
const schemaTypeExpr = resolveAlias(context, effectiveParameters.map((parameter) => parameter.name), entry.operation.tags?.[0]);
|
|
269
|
+
if (schemaTypeExpr) return schemaTypeExpr;
|
|
286
270
|
if (hasSameParameterNames(getParametersByLocation(entry.operation, location), effectiveParameters)) return `operations['${entry.operationId}']['parameters']['${location}']`;
|
|
287
271
|
return renderInlineParameterObject(effectiveParameters);
|
|
288
272
|
}
|
|
289
|
-
function normalizeOperation(entry, context,
|
|
273
|
+
function normalizeOperation(entry, context, requestFunctionNames) {
|
|
290
274
|
const successResponse = getSuccessResponseInfo(entry.operation);
|
|
291
275
|
const builderAlias = getBuilderAlias(entry.funcName);
|
|
292
|
-
const pathChannel = normalizeParameterChannel(entry, context, "path"
|
|
293
|
-
const queryChannel = normalizeParameterChannel(entry, context, "query"
|
|
294
|
-
const bodyChannel = normalizeBodyChannel(entry, context
|
|
295
|
-
const responseTypeExpr = resolveResponseTypeExpression(entry, context,
|
|
276
|
+
const pathChannel = normalizeParameterChannel(entry, context, "path");
|
|
277
|
+
const queryChannel = normalizeParameterChannel(entry, context, "query");
|
|
278
|
+
const bodyChannel = normalizeBodyChannel(entry, context);
|
|
279
|
+
const responseTypeExpr = resolveResponseTypeExpression(entry, context, successResponse);
|
|
296
280
|
return {
|
|
297
281
|
bodyChannel,
|
|
298
282
|
builderAlias,
|
|
@@ -306,7 +290,7 @@ function normalizeOperation(entry, context, typeImports, requestFunctionNames) {
|
|
|
306
290
|
returnTypeExpr: responseTypeExpr ? `Promise<${responseTypeExpr}>` : "Promise<void>"
|
|
307
291
|
};
|
|
308
292
|
}
|
|
309
|
-
function normalizeParameterChannel(entry, context, location
|
|
293
|
+
function normalizeParameterChannel(entry, context, location) {
|
|
310
294
|
const parameters = getEffectiveParametersByLocation(entry, location);
|
|
311
295
|
if (parameters.length === 0) return {
|
|
312
296
|
present: false,
|
|
@@ -316,11 +300,11 @@ function normalizeParameterChannel(entry, context, location, typeImports) {
|
|
|
316
300
|
return {
|
|
317
301
|
present: true,
|
|
318
302
|
required: hasRequiredChannel(parameters),
|
|
319
|
-
typeExpr: resolveParameterTypeExpression(entry, context, location
|
|
303
|
+
typeExpr: resolveParameterTypeExpression(entry, context, location)
|
|
320
304
|
};
|
|
321
305
|
}
|
|
322
|
-
function normalizeBodyChannel(entry, context
|
|
323
|
-
const typeExpr = resolveRequestBodyTypeExpression(entry, context
|
|
306
|
+
function normalizeBodyChannel(entry, context) {
|
|
307
|
+
const typeExpr = resolveRequestBodyTypeExpression(entry, context);
|
|
324
308
|
if (!typeExpr) return {
|
|
325
309
|
present: false,
|
|
326
310
|
required: false,
|
|
@@ -332,18 +316,18 @@ function normalizeBodyChannel(entry, context, typeImports) {
|
|
|
332
316
|
typeExpr
|
|
333
317
|
};
|
|
334
318
|
}
|
|
335
|
-
function resolveRequestBodyTypeExpression(entry, context
|
|
319
|
+
function resolveRequestBodyTypeExpression(entry, context) {
|
|
336
320
|
const jsonBody = getJsonRequestBody(entry.operation);
|
|
337
321
|
if (!jsonBody) return null;
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
322
|
+
const schemaTypeExpr = resolveSchemaTypeExpression(context, jsonBody.schema);
|
|
323
|
+
if (schemaTypeExpr) return schemaTypeExpr;
|
|
340
324
|
return `operations['${entry.operationId}']['requestBody']['content']['application/json']`;
|
|
341
325
|
}
|
|
342
|
-
function resolveResponseTypeExpression(entry, context,
|
|
326
|
+
function resolveResponseTypeExpression(entry, context, successResponse) {
|
|
343
327
|
if (!successResponse.hasJsonBody) return null;
|
|
344
328
|
const jsonContent = (entry.operation.responses?.[successResponse.statusKey])?.content?.["application/json"];
|
|
345
|
-
const
|
|
346
|
-
if (
|
|
329
|
+
const schemaTypeExpr = resolveSchemaTypeExpression(context, jsonContent?.schema);
|
|
330
|
+
if (schemaTypeExpr) return schemaTypeExpr;
|
|
347
331
|
return `operations['${entry.operationId}']['responses'][${formatStatusKey(successResponse.statusKey)}]['content']['application/json']`;
|
|
348
332
|
}
|
|
349
333
|
function hasSameParameterNames(left, right) {
|
|
@@ -356,22 +340,24 @@ function resolveAlias(context, parameterNames, tag) {
|
|
|
356
340
|
const key = [...parameterNames].sort().join(",");
|
|
357
341
|
const candidates = context.schemaAliasIndex.get(key);
|
|
358
342
|
if (!candidates || candidates.length === 0) return;
|
|
359
|
-
if (candidates.length === 1) return candidates[0];
|
|
343
|
+
if (candidates.length === 1) return createSchemaTypeExpression(candidates[0]);
|
|
360
344
|
if (tag) {
|
|
361
345
|
const singularTag = tag.replace(/s$/, "");
|
|
362
346
|
const prefix = `${singularTag[0]?.toUpperCase() ?? ""}${singularTag.slice(1)}`;
|
|
363
347
|
const match = candidates.find((candidate) => candidate.startsWith(prefix));
|
|
364
|
-
if (match) return match;
|
|
348
|
+
if (match) return createSchemaTypeExpression(match);
|
|
365
349
|
}
|
|
366
|
-
return candidates[0];
|
|
350
|
+
return createSchemaTypeExpression(candidates[0]);
|
|
367
351
|
}
|
|
368
|
-
function
|
|
352
|
+
function resolveSchemaTypeExpression(context, schema) {
|
|
369
353
|
const ref = readSchemaRef(schema);
|
|
370
354
|
if (!ref) return;
|
|
371
355
|
const schemaName = ref.split("/").pop();
|
|
372
356
|
if (!schemaName || !context.schemaNames.has(schemaName)) return;
|
|
373
|
-
|
|
374
|
-
|
|
357
|
+
return createSchemaTypeExpression(schemaName);
|
|
358
|
+
}
|
|
359
|
+
function createSchemaTypeExpression(schemaName) {
|
|
360
|
+
return `components['schemas']['${schemaName}']`;
|
|
375
361
|
}
|
|
376
362
|
function readSchemaRef(schema) {
|
|
377
363
|
if (!schema || typeof schema !== "object") return;
|
|
@@ -426,11 +412,6 @@ async function generateApiTypes(inputPath, outputDir) {
|
|
|
426
412
|
const contents = astToString(await openapiTS(new URL(`file://${inputPath}`)));
|
|
427
413
|
writeFileSync(resolve(outputDir, "api-types.d.ts"), `${GENERATED_HEADER.join("\n")}\n\n${contents}`);
|
|
428
414
|
}
|
|
429
|
-
function renderTypesBarrel(spec, legacyAliases) {
|
|
430
|
-
const schemaNames = Object.keys(spec.components?.schemas ?? {}).sort();
|
|
431
|
-
if (schemaNames.length === 0) throw new Error("No schemas found in openapi.json");
|
|
432
|
-
return renderTypesSource(schemaNames, legacyAliases, GENERATED_HEADER);
|
|
433
|
-
}
|
|
434
415
|
function createApiEntries(normalizedOps) {
|
|
435
416
|
return normalizedOps.map((op) => ({
|
|
436
417
|
funcName: op.entry.funcName,
|
|
@@ -455,8 +436,7 @@ function createClientRenderModel(model) {
|
|
|
455
436
|
requestFunction: operation.requestFunction,
|
|
456
437
|
responseTypeExpr: operation.responseTypeExpr,
|
|
457
438
|
returnTypeExpr: operation.returnTypeExpr
|
|
458
|
-
}))
|
|
459
|
-
typeImports: model.typeImports
|
|
439
|
+
}))
|
|
460
440
|
};
|
|
461
441
|
}
|
|
462
442
|
function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
@@ -470,19 +450,18 @@ function renderGeneratedArtifacts(spec, options, preCollectedOperations) {
|
|
|
470
450
|
if (clientModel.operations.length === 0) throw new Error(`No paths matching prefix "${pathPrefix}" found in openapi.json`);
|
|
471
451
|
return {
|
|
472
452
|
api: renderApiSource(createApiEntries(clientModel.operations), GENERATED_HEADER),
|
|
473
|
-
client: renderClientSource(createClientRenderModel(clientModel), GENERATED_HEADER, httpClient)
|
|
474
|
-
types: renderTypesBarrel(spec, options.legacyAliases)
|
|
453
|
+
client: renderClientSource(createClientRenderModel(clientModel), GENERATED_HEADER, httpClient)
|
|
475
454
|
};
|
|
476
455
|
}
|
|
477
456
|
async function generate(root, options) {
|
|
478
457
|
const inputPath = resolve(root, options.input);
|
|
479
458
|
const outputDir = resolve(root, options.output);
|
|
459
|
+
mkdirSync(outputDir, { recursive: true });
|
|
480
460
|
const spec = JSON.parse(readFileSync(inputPath, "utf-8"));
|
|
481
461
|
const operations = collectOperations(spec, options.pathPrefix ?? "/api/", options.stripPrefix ?? true);
|
|
482
462
|
const artifacts = renderGeneratedArtifacts(spec, options, operations);
|
|
483
463
|
warnOnParameterLocationMismatch(operations);
|
|
484
464
|
await generateApiTypes(inputPath, outputDir);
|
|
485
|
-
writeFileSync(resolve(outputDir, "types.ts"), artifacts.types);
|
|
486
465
|
writeFileSync(resolve(outputDir, "api.ts"), artifacts.api);
|
|
487
466
|
writeFileSync(resolve(outputDir, "client.ts"), artifacts.client);
|
|
488
467
|
}
|