@moccona/apicodegen 0.0.3 → 0.0.8
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 +320 -25
- package/bin/cli.cjs +24 -0
- package/npm/index.cjs +2097 -2382
- package/npm/index.cjs.map +1 -1
- package/npm/index.d.cts +603 -408
- package/npm/index.d.cts.map +1 -0
- package/npm/index.d.mts +731 -0
- package/npm/index.d.mts.map +1 -0
- package/npm/index.mjs +2078 -0
- package/npm/index.mjs.map +1 -0
- package/npm/vite/index.d.mts +39 -0
- package/npm/vite/index.d.mts.map +1 -0
- package/npm/vite/index.mjs +1990 -0
- package/npm/vite/index.mjs.map +1 -0
- package/package.json +42 -25
- package/bin/cli.js +0 -2248
- package/npm/index.d.ts +0 -536
- package/npm/index.js +0 -2367
- package/npm/index.js.map +0 -1
- package/npm/vite/index.d.ts +0 -29
- package/npm/vite/index.js +0 -2276
- package/npm/vite/index.js.map +0 -1
package/npm/index.mjs
ADDED
|
@@ -0,0 +1,2078 @@
|
|
|
1
|
+
import { Agent, request } from "undici";
|
|
2
|
+
import { NodeFlags, SyntaxKind, addSyntheticLeadingComment, createPrinter, factory } from "typescript";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { format } from "prettier";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "fs-extra";
|
|
7
|
+
import { createScopedLogger } from "@moccona/logger";
|
|
8
|
+
//#region src/core/base/Adaptor.ts
|
|
9
|
+
/**
|
|
10
|
+
* Base adapter for tool
|
|
11
|
+
* This abstract class serves as the foundation for implementing adapters for different code generation tools
|
|
12
|
+
*/
|
|
13
|
+
var Adapter = class {};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/core/constants/keywords.ts
|
|
16
|
+
const typescriptKeywords = new Set([
|
|
17
|
+
"break",
|
|
18
|
+
"case",
|
|
19
|
+
"catch",
|
|
20
|
+
"class",
|
|
21
|
+
"const",
|
|
22
|
+
"continue",
|
|
23
|
+
"debugger",
|
|
24
|
+
"default",
|
|
25
|
+
"delete",
|
|
26
|
+
"do",
|
|
27
|
+
"else",
|
|
28
|
+
"enum",
|
|
29
|
+
"export",
|
|
30
|
+
"extends",
|
|
31
|
+
"false",
|
|
32
|
+
"finally",
|
|
33
|
+
"for",
|
|
34
|
+
"function",
|
|
35
|
+
"if",
|
|
36
|
+
"import",
|
|
37
|
+
"in",
|
|
38
|
+
"instanceof",
|
|
39
|
+
"new",
|
|
40
|
+
"null",
|
|
41
|
+
"return",
|
|
42
|
+
"super",
|
|
43
|
+
"switch",
|
|
44
|
+
"this",
|
|
45
|
+
"throw",
|
|
46
|
+
"true",
|
|
47
|
+
"try",
|
|
48
|
+
"typeof",
|
|
49
|
+
"var",
|
|
50
|
+
"void",
|
|
51
|
+
"while",
|
|
52
|
+
"with",
|
|
53
|
+
"as",
|
|
54
|
+
"implements",
|
|
55
|
+
"interface",
|
|
56
|
+
"let",
|
|
57
|
+
"package",
|
|
58
|
+
"private",
|
|
59
|
+
"protected",
|
|
60
|
+
"public",
|
|
61
|
+
"static",
|
|
62
|
+
"yield",
|
|
63
|
+
"abstract",
|
|
64
|
+
"any",
|
|
65
|
+
"async",
|
|
66
|
+
"await",
|
|
67
|
+
"constructor",
|
|
68
|
+
"declare",
|
|
69
|
+
"from",
|
|
70
|
+
"get",
|
|
71
|
+
"is",
|
|
72
|
+
"module",
|
|
73
|
+
"namespace",
|
|
74
|
+
"never",
|
|
75
|
+
"require",
|
|
76
|
+
"set",
|
|
77
|
+
"type",
|
|
78
|
+
"unknown",
|
|
79
|
+
"readonly",
|
|
80
|
+
"of",
|
|
81
|
+
"asserts",
|
|
82
|
+
"infer",
|
|
83
|
+
"keyof",
|
|
84
|
+
"boolean",
|
|
85
|
+
"number",
|
|
86
|
+
"string",
|
|
87
|
+
"symbol",
|
|
88
|
+
"object",
|
|
89
|
+
"undefined",
|
|
90
|
+
"bigint"
|
|
91
|
+
]);
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/core/interface.ts
|
|
94
|
+
let SchemaType = /* @__PURE__ */ function(SchemaType) {
|
|
95
|
+
SchemaType["schemas"] = "schemas";
|
|
96
|
+
SchemaType["parameters"] = "parameters";
|
|
97
|
+
SchemaType["responses"] = "responses";
|
|
98
|
+
SchemaType["requestBodies"] = "requestBodies";
|
|
99
|
+
return SchemaType;
|
|
100
|
+
}({});
|
|
101
|
+
let NonArraySchemaType = /* @__PURE__ */ function(NonArraySchemaType) {
|
|
102
|
+
NonArraySchemaType["object"] = "object";
|
|
103
|
+
NonArraySchemaType["string"] = "string";
|
|
104
|
+
NonArraySchemaType["number"] = "number";
|
|
105
|
+
NonArraySchemaType["boolean"] = "boolean";
|
|
106
|
+
NonArraySchemaType["integer"] = "integer";
|
|
107
|
+
NonArraySchemaType["enum"] = "enum";
|
|
108
|
+
NonArraySchemaType["file"] = "file";
|
|
109
|
+
return NonArraySchemaType;
|
|
110
|
+
}({});
|
|
111
|
+
let ArraySchemaType = /* @__PURE__ */ function(ArraySchemaType) {
|
|
112
|
+
ArraySchemaType["array"] = "array";
|
|
113
|
+
return ArraySchemaType;
|
|
114
|
+
}({});
|
|
115
|
+
let SchemaFormatType = /* @__PURE__ */ function(SchemaFormatType) {
|
|
116
|
+
SchemaFormatType["string"] = "string";
|
|
117
|
+
SchemaFormatType["number"] = "number";
|
|
118
|
+
SchemaFormatType["boolean"] = "boolean";
|
|
119
|
+
SchemaFormatType["file"] = "file";
|
|
120
|
+
SchemaFormatType["binary"] = "binary";
|
|
121
|
+
SchemaFormatType["blob"] = "blob";
|
|
122
|
+
return SchemaFormatType;
|
|
123
|
+
}({});
|
|
124
|
+
let ParameterIn = /* @__PURE__ */ function(ParameterIn) {
|
|
125
|
+
ParameterIn["header"] = "header";
|
|
126
|
+
ParameterIn["body"] = "body";
|
|
127
|
+
ParameterIn["query"] = "query";
|
|
128
|
+
ParameterIn["cookie"] = "cookie";
|
|
129
|
+
ParameterIn["path"] = "path";
|
|
130
|
+
ParameterIn["formData"] = "formData";
|
|
131
|
+
return ParameterIn;
|
|
132
|
+
}({});
|
|
133
|
+
let MediaTypes = /* @__PURE__ */ function(MediaTypes) {
|
|
134
|
+
MediaTypes["JSON"] = "application/json";
|
|
135
|
+
MediaTypes["TEXT"] = "text";
|
|
136
|
+
MediaTypes["IMAGE"] = "image";
|
|
137
|
+
MediaTypes["AUDIO"] = "audio";
|
|
138
|
+
MediaTypes["VIDEO"] = "video";
|
|
139
|
+
return MediaTypes;
|
|
140
|
+
}({});
|
|
141
|
+
let HttpMethods = /* @__PURE__ */ function(HttpMethods) {
|
|
142
|
+
HttpMethods["GET"] = "get";
|
|
143
|
+
HttpMethods["PUT"] = "put";
|
|
144
|
+
HttpMethods["POST"] = "post";
|
|
145
|
+
HttpMethods["DELETE"] = "delete";
|
|
146
|
+
HttpMethods["OPTIONS"] = "options";
|
|
147
|
+
HttpMethods["HEAD"] = "head";
|
|
148
|
+
HttpMethods["PATCH"] = "patch";
|
|
149
|
+
HttpMethods["TRACE"] = "trace";
|
|
150
|
+
return HttpMethods;
|
|
151
|
+
}({});
|
|
152
|
+
let Adaptors = /* @__PURE__ */ function(Adaptors) {
|
|
153
|
+
Adaptors["fetch"] = "fetch";
|
|
154
|
+
Adaptors["axios"] = "axios";
|
|
155
|
+
return Adaptors;
|
|
156
|
+
}({});
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/core/base/Base.ts
|
|
159
|
+
/**
|
|
160
|
+
* @file Base class implementation
|
|
161
|
+
* @author wp.l
|
|
162
|
+
* @description Base utility class providing common methods for code generation and API handling
|
|
163
|
+
*/
|
|
164
|
+
/**
|
|
165
|
+
* Represents success HTTP status codes.
|
|
166
|
+
* Each key is a string representation of a success HTTP status code.
|
|
167
|
+
*/
|
|
168
|
+
const SuccessHttpStatusCode = {
|
|
169
|
+
"200": "200",
|
|
170
|
+
"201": "201",
|
|
171
|
+
"202": "202",
|
|
172
|
+
"203": "203",
|
|
173
|
+
"204": "204",
|
|
174
|
+
"205": "205",
|
|
175
|
+
"206": "206",
|
|
176
|
+
"207": "207",
|
|
177
|
+
"208": "208",
|
|
178
|
+
"226": "226"
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Base abstract class providing common utility methods.
|
|
182
|
+
*/
|
|
183
|
+
var Base = class Base {
|
|
184
|
+
constructor() {
|
|
185
|
+
if (new.target === Base) throw new Error("Cannot instantiate abstract class");
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Converts a reference string to a meaningful name.
|
|
189
|
+
* @param ref - The reference string to process.
|
|
190
|
+
* @param [doc] - Optional document reference for context.
|
|
191
|
+
* @returns - The processed name.
|
|
192
|
+
*/
|
|
193
|
+
static ref2name(ref, doc) {
|
|
194
|
+
const paths = ref.replace(/^#/, "").split("/").filter(Boolean);
|
|
195
|
+
if (!doc) return paths.slice(-1)[0];
|
|
196
|
+
let temporary = doc;
|
|
197
|
+
let lastPath = "";
|
|
198
|
+
for (const path of paths) {
|
|
199
|
+
const adjustedPath = path.replaceAll("~1", "/");
|
|
200
|
+
temporary = temporary[adjustedPath];
|
|
201
|
+
lastPath = adjustedPath;
|
|
202
|
+
}
|
|
203
|
+
if (!temporary) return "unknown";
|
|
204
|
+
return temporary.$ref ? Base.ref2name(temporary.$ref, doc) : lastPath;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Converts an API path to a function name.
|
|
208
|
+
* @param path - The API endpoint path.
|
|
209
|
+
* @param [method] - The HTTP method (e.g., GET, POST).
|
|
210
|
+
* @param [operationId] - Unique identifier for the operation.
|
|
211
|
+
* @returns - The generated function name.
|
|
212
|
+
*/
|
|
213
|
+
static pathToFnName(path, method, _operationId = "") {
|
|
214
|
+
return Base.normalize(Base.camelCase(Base.normalize(path))) + (method ? Base.capitalize(Base.upperCamelCase(`using_${method}`)) : "");
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Normalizes a string by replacing special characters and avoiding TypeScript keywords.
|
|
218
|
+
* @param text - Input text to normalize.
|
|
219
|
+
* @returns - The normalized string.
|
|
220
|
+
*/
|
|
221
|
+
static normalize(text) {
|
|
222
|
+
if (typescriptKeywords.has(text)) text += "_";
|
|
223
|
+
return text.replace(/[/\-_{}():\s`,*<>$#.]/gm, "_").replace(/^\d./gm, "").replaceAll("...", "");
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Capitalizes the first character of a string.
|
|
227
|
+
* @param text - Input string.
|
|
228
|
+
* @returns - Capitalized string.
|
|
229
|
+
*/
|
|
230
|
+
static capitalize(text) {
|
|
231
|
+
text = text.trim();
|
|
232
|
+
return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Converts a string to camelCase.
|
|
236
|
+
* @param text - Input string.
|
|
237
|
+
* @returns - CamelCase string.
|
|
238
|
+
*/
|
|
239
|
+
static camelCase(text) {
|
|
240
|
+
text = text.trim();
|
|
241
|
+
return text.split("_").filter(Boolean).map((t, index) => index === 0 ? t : Base.capitalize(t)).join("");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Converts a string to UpperCamelCase.
|
|
245
|
+
* @param text - Input string.
|
|
246
|
+
* @returns - UpperCamelCase string.
|
|
247
|
+
*/
|
|
248
|
+
static upperCamelCase(text) {
|
|
249
|
+
return Base.normalize(text).replaceAll("...", "").split("_").filter(Boolean).map(Base.capitalize).join("");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Fetches documentation from a given URL.
|
|
253
|
+
* @param url - The URL to fetch the documentation from.
|
|
254
|
+
* @param requestInit - Additional request parameters.
|
|
255
|
+
* @returns - A promise resolving to the fetched documentation data.
|
|
256
|
+
*/
|
|
257
|
+
static async fetchDoc(url, requestInit = {}) {
|
|
258
|
+
const { body, statusCode } = await request(url, {
|
|
259
|
+
method: "GET",
|
|
260
|
+
dispatcher: new Agent({ connect: { rejectUnauthorized: false } }),
|
|
261
|
+
...requestInit
|
|
262
|
+
});
|
|
263
|
+
if (statusCode >= 400) throw new Error(`Failed to fetch OpenAPI documentation from ${url}: HTTP ${statusCode}`);
|
|
264
|
+
try {
|
|
265
|
+
return body.json();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
throw new Error(`Failed to parse JSON response from ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Determines the media type from a given media type string.
|
|
272
|
+
* @param mediaType - The media type string to evaluate.
|
|
273
|
+
* @returns - The matched MediaTypes or null.
|
|
274
|
+
*/
|
|
275
|
+
static getMediaType(mediaType) {
|
|
276
|
+
return Object.values(MediaTypes).find((type) => mediaType.includes(type));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Checks if a schema is a valid enum type that isn't boolean.
|
|
280
|
+
* @param a - The schema object to evaluate.
|
|
281
|
+
* @returns - True if the schema is a valid non-boolean enum.
|
|
282
|
+
*/
|
|
283
|
+
static isValidEnumType(a) {
|
|
284
|
+
return a.type !== "boolean" && !Base.isBooleanEnum(a);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Checks if a schema represents a boolean enum.
|
|
288
|
+
* @param a - The schema object to evaluate.
|
|
289
|
+
* @returns - True if the schema is a boolean enum.
|
|
290
|
+
*/
|
|
291
|
+
static isBooleanEnum(a) {
|
|
292
|
+
return a.type === "boolean" || !!a.enum?.some((member) => typeof member === "boolean");
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Checks if two enum schemas are identical.
|
|
296
|
+
* @param a - First enum schema to compare.
|
|
297
|
+
* @param b - Second enum schema to compare.
|
|
298
|
+
* @returns - True if the enums are identical.
|
|
299
|
+
*/
|
|
300
|
+
static isSameEnum(a, b) {
|
|
301
|
+
return a.enum.length === b.enum.length && a.enum.sort().every((v, index) => v === b.enum.sort()[index]);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Filters out duplicate enum schemas from an array.
|
|
305
|
+
* @param enums - Array of enum schemas to process.
|
|
306
|
+
* @returns - Array of unique enum schemas.
|
|
307
|
+
*/
|
|
308
|
+
static uniqueEnums(enums) {
|
|
309
|
+
const enumMap = /* @__PURE__ */ new Map();
|
|
310
|
+
for (const e of enums) {
|
|
311
|
+
const existing = enumMap.get(e.name);
|
|
312
|
+
if (existing) for (const value of e.enum) existing.add(value);
|
|
313
|
+
else enumMap.set(e.name, new Set(e.enum));
|
|
314
|
+
}
|
|
315
|
+
return Array.from(enumMap.entries()).map(([name, values]) => ({
|
|
316
|
+
name,
|
|
317
|
+
enum: Array.from(values)
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Finds the first occurrence of a matching enum schema in an array.
|
|
322
|
+
* @param a - The enum schema to find.
|
|
323
|
+
* @param enums - Array of enum schemas to search.
|
|
324
|
+
* @returns - The found schema or undefined.
|
|
325
|
+
*/
|
|
326
|
+
static findSameSchema(a, enums) {
|
|
327
|
+
return enums.find((b) => Base.isSameEnum(b, a));
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Checks if an object is a reference object.
|
|
331
|
+
* @param schema - The object to check.
|
|
332
|
+
* @returns - True if the object is a reference.
|
|
333
|
+
*/
|
|
334
|
+
static isRef(schema) {
|
|
335
|
+
return typeof schema === "object" && schema !== null && "$ref" in schema && typeof schema.$ref === "string";
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region src/core/base/Provider.ts
|
|
340
|
+
/**
|
|
341
|
+
* Abstract Provider Class.
|
|
342
|
+
*
|
|
343
|
+
* The Provider class is designed to be extended by specific implementations (e.g., OpenAPI 2 provider, OpenAPI 3 provider).
|
|
344
|
+
* It handles the initialization of the provider and the parsing of documentation into structured data.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
*
|
|
348
|
+
* ```ts
|
|
349
|
+
* /// Example of how this class might be used by a subclass:
|
|
350
|
+
* class OpenAPIProvider extends Provider {
|
|
351
|
+
* /// Implement the parse method to handle OpenAPI-specific documentation parsing.
|
|
352
|
+
* parse(doc: unknown): ProviderInitResult {
|
|
353
|
+
* /// Implementation details...
|
|
354
|
+
* }
|
|
355
|
+
* }
|
|
356
|
+
*
|
|
357
|
+
* /// Initializing a provider with configuration and documentation data:
|
|
358
|
+
* const initOptions: ProviderInitOptions = {
|
|
359
|
+
* docURL: "https://example.com/api/swagger.json",
|
|
360
|
+
* baseURL: "https://api.example.com",
|
|
361
|
+
* output: "./generated",
|
|
362
|
+
* requestOptions: {
|
|
363
|
+
* headers: { "Content-Type": "application/json" },
|
|
364
|
+
* },
|
|
365
|
+
* importClientSource: "generated/client",
|
|
366
|
+
* };
|
|
367
|
+
*
|
|
368
|
+
* const docData = fetchSwaggerDoc();
|
|
369
|
+
* const provider = new OpenAPIProvider(initOptions, docData);
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
var Provider = class {
|
|
373
|
+
/** collection of enum schemas */
|
|
374
|
+
enums = [];
|
|
375
|
+
/** collection of schemas indexed by name */
|
|
376
|
+
schemas = {};
|
|
377
|
+
/** collection of parameters indexed by name */
|
|
378
|
+
parameters = {};
|
|
379
|
+
/** collection of API responses indexed by name */
|
|
380
|
+
responses = {};
|
|
381
|
+
/** collection of request bodies indexed by name */
|
|
382
|
+
requestBodies = {};
|
|
383
|
+
/** collection of API endpoints (operations) indexed by path */
|
|
384
|
+
apis = {};
|
|
385
|
+
/** URL for fetching API documentation */
|
|
386
|
+
docURL;
|
|
387
|
+
/** base URL for API endpoints */
|
|
388
|
+
baseURL;
|
|
389
|
+
/** output directory for generated code */
|
|
390
|
+
output;
|
|
391
|
+
/** request options for API documentation fetch */
|
|
392
|
+
requestOptions;
|
|
393
|
+
/** source path for imported client */
|
|
394
|
+
importClientSource;
|
|
395
|
+
/**
|
|
396
|
+
* Provider Constructor.
|
|
397
|
+
* @param {ProviderInitOptions} initOptions - Initial configuration for the provider.
|
|
398
|
+
* @param {unknown} doc - Raw API documentation data to be parsed.
|
|
399
|
+
*/
|
|
400
|
+
constructor(initOptions, doc) {
|
|
401
|
+
this.docURL = initOptions.docURL;
|
|
402
|
+
this.baseURL = initOptions.baseURL ?? "";
|
|
403
|
+
this.output = initOptions.output ?? ".";
|
|
404
|
+
this.requestOptions = initOptions.requestOptions ?? {};
|
|
405
|
+
this.importClientSource = initOptions.importClientSource ?? "";
|
|
406
|
+
const { enums, schemas, requestBodies, responses, parameters, apis } = this.parse(doc);
|
|
407
|
+
this.enums = enums;
|
|
408
|
+
this.schemas = schemas;
|
|
409
|
+
this.responses = responses;
|
|
410
|
+
this.parameters = parameters;
|
|
411
|
+
this.requestBodies = requestBodies;
|
|
412
|
+
this.apis = apis;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/core/generator/index.ts
|
|
417
|
+
var Generator = class Generator {
|
|
418
|
+
/**
|
|
419
|
+
* Converts an array of TypeScript statements into a formatted string of code.
|
|
420
|
+
*
|
|
421
|
+
* @param statements - The array of TypeScript statement nodes.
|
|
422
|
+
* @returns Formatted code as a string.
|
|
423
|
+
* @throws {Error} If no valid statements are provided.
|
|
424
|
+
*/
|
|
425
|
+
static toCode(statements) {
|
|
426
|
+
if (statements.length === 0) return "// No api declaration found.";
|
|
427
|
+
const sourceFile = factory.createSourceFile(statements, factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None);
|
|
428
|
+
return createPrinter().printFile(sourceFile);
|
|
429
|
+
}
|
|
430
|
+
static async write(code, filepath) {
|
|
431
|
+
try {
|
|
432
|
+
await writeFile(filepath, code);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error(error);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Converts a path string with parameters into a TypeScript template expression.
|
|
439
|
+
* Handles query parameters and path placeholders.
|
|
440
|
+
*
|
|
441
|
+
* @param path - The base path string containing placeholders.
|
|
442
|
+
* @param parameters - Array of parameter objects defining the parameters.
|
|
443
|
+
* @param basePath - Optional base path to prepend (default: "").
|
|
444
|
+
* @returns A TypeScript template expressi
|
|
445
|
+
*/
|
|
446
|
+
static toUrlTemplate(path, parameters, basePath = "") {
|
|
447
|
+
const queryParameters = parameters.filter((p) => p.in === "query");
|
|
448
|
+
if (queryParameters.length > 0) {
|
|
449
|
+
const queryString = queryParameters.map((qp, index) => `${index === 0 ? "?" : "&"}${encodeURIComponent(qp.name)}={${Base.camelCase(Base.normalize(qp.name))}}`).join("");
|
|
450
|
+
path += queryString;
|
|
451
|
+
}
|
|
452
|
+
const pathSegments = path.replaceAll("{", "${").split("$").filter(Boolean);
|
|
453
|
+
if (pathSegments.length === 1) return factory.createNoSubstitutionTemplateLiteral(basePath + path);
|
|
454
|
+
return factory.createTemplateExpression(factory.createTemplateHead(basePath + pathSegments[0]), pathSegments.slice(1).map((segment, index) => {
|
|
455
|
+
const match = /^{(.+)}(.+)?/gm.exec(segment);
|
|
456
|
+
const isLastSegment = index === pathSegments.length - 2;
|
|
457
|
+
if (!match) throw new Error(`Invalid path segment: ${segment}`);
|
|
458
|
+
return factory.createTemplateSpan(factory.createIdentifier(match[1]), !isLastSegment ? factory.createTemplateMiddle(match[2]) : factory.createTemplateTail(match[2] || ""));
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Adds synthetic comments to a TypeScript AST node.
|
|
463
|
+
*
|
|
464
|
+
* @param node - The target AST node.
|
|
465
|
+
* @param comments - Array of comment objects to add.
|
|
466
|
+
*/
|
|
467
|
+
static addComments(node, comments) {
|
|
468
|
+
if (!Array.isArray(comments) || comments.filter(Boolean).length === 0) return;
|
|
469
|
+
const formatComment = (comment) => {
|
|
470
|
+
return comment.tag ? ` @${comment.tag} ${comment.comment ?? ""}` : ` ${comment.comment}`;
|
|
471
|
+
};
|
|
472
|
+
const formattedComments = "*\n" + comments.map(formatComment).join("\n").trim() + "\n";
|
|
473
|
+
addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, formattedComments, true);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Checks if a schema represents a binary type.
|
|
477
|
+
*
|
|
478
|
+
* @param schema - The schema object to check.
|
|
479
|
+
* @returns true if the schema is a binary type, false otherwise.
|
|
480
|
+
*/
|
|
481
|
+
static isBinarySchema(schema) {
|
|
482
|
+
if (schema.type === "array") {
|
|
483
|
+
const arraySchema = schema;
|
|
484
|
+
return Generator.isBinarySchema(arraySchema.items);
|
|
485
|
+
}
|
|
486
|
+
const nonArraySchema = schema;
|
|
487
|
+
return nonArraySchema.format === "blob" || nonArraySchema.format === "binary" || nonArraySchema.type === "file";
|
|
488
|
+
}
|
|
489
|
+
static toRequestBodyTypeNode(schema) {
|
|
490
|
+
return factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier("req"), void 0, Generator.toTypeNode(schema));
|
|
491
|
+
}
|
|
492
|
+
static toTypeNode(schema) {
|
|
493
|
+
const { type, ref } = schema;
|
|
494
|
+
if (ref) {
|
|
495
|
+
const identify = Base.ref2name(ref);
|
|
496
|
+
return factory.createTypeReferenceNode(factory.createIdentifier(identify === "unknown" ? identify : Base.upperCamelCase(identify)));
|
|
497
|
+
}
|
|
498
|
+
switch (type) {
|
|
499
|
+
case "array": {
|
|
500
|
+
const { items } = schema;
|
|
501
|
+
return factory.createArrayTypeNode(Generator.toTypeNode(items));
|
|
502
|
+
}
|
|
503
|
+
case "object": {
|
|
504
|
+
const propsCount = Object.keys(schema.properties ?? {}).length;
|
|
505
|
+
if (!schema.properties || propsCount === 0) return factory.createTypeReferenceNode(factory.createIdentifier("Record"), [factory.createToken(SyntaxKind.StringKeyword), factory.createToken(SyntaxKind.UnknownKeyword)]);
|
|
506
|
+
const props = Object.keys(schema.properties);
|
|
507
|
+
return factory.createTypeLiteralNode(props.map((propKey) => {
|
|
508
|
+
const propSchema = schema.properties[propKey];
|
|
509
|
+
return factory.createPropertySignature(void 0, factory.createStringLiteral(propKey), schema.required || schema.ref || Generator.isBinarySchema(schema) ? void 0 : factory.createToken(SyntaxKind.QuestionToken), Generator.toTypeNode(propSchema));
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
case "integer":
|
|
513
|
+
case "number":
|
|
514
|
+
if (schema.enum) return factory.createUnionTypeNode(schema.enum.map((e) => factory.createLiteralTypeNode(factory.createNumericLiteral(e))));
|
|
515
|
+
return factory.createToken(SyntaxKind.NumberKeyword);
|
|
516
|
+
case "boolean": return factory.createToken(SyntaxKind.BooleanKeyword);
|
|
517
|
+
case "file": return factory.createTypeReferenceNode(factory.createIdentifier("Blob"));
|
|
518
|
+
default: {
|
|
519
|
+
const { format, oneOf, allOf, anyOf, type, enum: enum_ } = schema;
|
|
520
|
+
switch (format) {
|
|
521
|
+
case "number": return factory.createToken(SyntaxKind.NumberKeyword);
|
|
522
|
+
case "string": return factory.createToken(SyntaxKind.StringKeyword);
|
|
523
|
+
case "boolean": return factory.createToken(SyntaxKind.BooleanKeyword);
|
|
524
|
+
case "blob":
|
|
525
|
+
case "binary": return factory.createTypeReferenceNode(factory.createIdentifier("Blob"));
|
|
526
|
+
default:
|
|
527
|
+
}
|
|
528
|
+
if (enum_) return factory.createUnionTypeNode(enum_.map((e) => factory.createLiteralTypeNode(factory.createStringLiteral(e))));
|
|
529
|
+
if (type === "string") return factory.createToken(SyntaxKind.StringKeyword);
|
|
530
|
+
if (oneOf) return factory.createUnionTypeNode(oneOf.map((schema) => Generator.toTypeNode(schema)));
|
|
531
|
+
if (anyOf) return factory.createUnionTypeNode(anyOf.map((schema) => Generator.toTypeNode(schema)));
|
|
532
|
+
if (allOf) return factory.createIntersectionTypeNode(allOf.map((schema) => Generator.toTypeNode(schema)));
|
|
533
|
+
if (type && typeof type === "string") return factory.createTypeReferenceNode(type !== "unknown" && type !== "null" ? factory.createIdentifier(Base.upperCamelCase(type)) : type);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return factory.createToken(SyntaxKind.UnknownKeyword);
|
|
537
|
+
}
|
|
538
|
+
static toDeclarationNodes(parameters) {
|
|
539
|
+
const objectElements = [];
|
|
540
|
+
const typeObjectElements = [];
|
|
541
|
+
const refParameters = [];
|
|
542
|
+
for (const parameter of parameters) if (parameter.ref) {
|
|
543
|
+
const refName = Base.ref2name(parameter.ref);
|
|
544
|
+
refParameters.push(factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier(Base.camelCase(Base.normalize(refName))), void 0, factory.createTypeReferenceNode(factory.createIdentifier(Base.upperCamelCase(Base.normalize(refName)))), void 0));
|
|
545
|
+
} else {
|
|
546
|
+
const { name, schema, required } = parameter;
|
|
547
|
+
objectElements.push(factory.createBindingElement(void 0, void 0, factory.createIdentifier(Base.camelCase(Base.normalize(name)))));
|
|
548
|
+
typeObjectElements.push(factory.createPropertySignature([], factory.createIdentifier(Base.camelCase(Base.normalize(name))), required ? void 0 : factory.createToken(SyntaxKind.QuestionToken), !schema ? factory.createToken(SyntaxKind.UnknownKeyword) : Generator.toTypeNode(schema)));
|
|
549
|
+
}
|
|
550
|
+
if (objectElements.length > 0) return [factory.createParameterDeclaration(void 0, void 0, factory.createObjectBindingPattern(objectElements), void 0, factory.createTypeLiteralNode(typeObjectElements), void 0), ...refParameters];
|
|
551
|
+
return refParameters;
|
|
552
|
+
}
|
|
553
|
+
static toFormDataStatement(parameters, requestBody) {
|
|
554
|
+
const statements = [];
|
|
555
|
+
const fdDeclaration = factory.createVariableStatement(void 0, factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier("fd"), void 0, void 0, factory.createNewExpression(factory.createIdentifier("FormData"), void 0, []))], NodeFlags.Const));
|
|
556
|
+
statements.push(fdDeclaration);
|
|
557
|
+
parameters.forEach((parameter) => {
|
|
558
|
+
statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(parameter.name), factory.createToken(SyntaxKind.AmpersandAmpersandToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(parameter.name), factory.createIdentifier(parameter.name)]))));
|
|
559
|
+
});
|
|
560
|
+
if (requestBody && requestBody.type === "object" && requestBody.properties && Object.keys(requestBody.properties).length !== 0) Object.keys(requestBody.properties).forEach((key) => {
|
|
561
|
+
const schemaByKey = requestBody.properties[key];
|
|
562
|
+
if (schemaByKey.type === "array" && Generator.isBinarySchema(schemaByKey)) statements.push(factory.createForOfStatement(void 0, factory.createVariableDeclarationList([factory.createVariableDeclaration("file")], NodeFlags.Const), factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)), factory.createBlock([factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), [], [
|
|
563
|
+
factory.createStringLiteral(key),
|
|
564
|
+
factory.createIdentifier("file"),
|
|
565
|
+
factory.createPropertyAccessExpression(factory.createAsExpression(factory.createIdentifier("file"), factory.createTypeReferenceNode(factory.createIdentifier("File"), void 0)), factory.createIdentifier("name"))
|
|
566
|
+
]))])));
|
|
567
|
+
else if (schemaByKey.required) statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(key), schemaByKey.type === "string" ? factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)) : factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key))])])));
|
|
568
|
+
else statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)), factory.createToken(SyntaxKind.AmpersandAmpersandToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("fd"), factory.createIdentifier("append")), void 0, [factory.createStringLiteral(key), schemaByKey.type === "string" ? factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key)) : factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createElementAccessExpression(factory.createIdentifier("req"), factory.createStringLiteral(key))])]))));
|
|
569
|
+
});
|
|
570
|
+
return statements;
|
|
571
|
+
}
|
|
572
|
+
static bodyBlock(uri, method, parameters, requestBody, response, adapter) {
|
|
573
|
+
const isFormDataRequest = requestBody && ["multipart/form-data", "application/x-www-form-urlencoded"].includes(requestBody.type);
|
|
574
|
+
const shouldParseResponseToJSON = "application/json" === response?.type;
|
|
575
|
+
const isRequestBodyBinary = requestBody?.schema && requestBody.schema.type === "array" && Generator.isBinarySchema(requestBody.schema);
|
|
576
|
+
const parametersShouldPutInFormData = parameters.filter((p) => p.in === "formData" || p.schema && Generator.isBinarySchema(p.schema));
|
|
577
|
+
const parametersShouldNotPutInFormData = parameters.filter((p) => !parametersShouldPutInFormData.includes(p));
|
|
578
|
+
const isRequestBodyContainsBinary = requestBody?.schema && "properties" in requestBody.schema && Object.values(requestBody.schema?.properties ?? {}).some((p) => Generator.isBinarySchema(p));
|
|
579
|
+
const hasBinaryInParameters = parameters.some((p) => p?.schema && Generator.isBinarySchema(p.schema));
|
|
580
|
+
const shouldPutParametersOrBodyInFormData = isFormDataRequest || isRequestBodyBinary || hasBinaryInParameters || isRequestBodyContainsBinary || parametersShouldPutInFormData.length > 0;
|
|
581
|
+
return factory.createBlock([...shouldPutParametersOrBodyInFormData ? Generator.toFormDataStatement(parametersShouldPutInFormData, requestBody?.schema) : [], ...adapter.client(uri, method, parametersShouldNotPutInFormData, requestBody, response, adapter, shouldPutParametersOrBodyInFormData, shouldParseResponseToJSON)]);
|
|
582
|
+
}
|
|
583
|
+
static schemaToStatemets(parsedDoc, adaptor, options) {
|
|
584
|
+
const statements = [];
|
|
585
|
+
const { apis, schemas = {}, enums } = parsedDoc;
|
|
586
|
+
const enumNames = [];
|
|
587
|
+
for (const enumObject of enums) {
|
|
588
|
+
enumNames.push(Base.upperCamelCase(enumObject.name));
|
|
589
|
+
statements.push(factory.createEnumDeclaration([factory.createToken(SyntaxKind.ExportKeyword)], factory.createIdentifier(Base.upperCamelCase(enumObject.name)), enumObject.enum.map((member) => {
|
|
590
|
+
return factory.createEnumMember(factory.createStringLiteral(typeof member === "string" ? member : `${member}_`), typeof member === "string" ? factory.createStringLiteral(member) : factory.createNumericLiteral(member));
|
|
591
|
+
})));
|
|
592
|
+
}
|
|
593
|
+
for (const schemaKey in schemas) if (Object.hasOwn(schemas, schemaKey) && !enumNames.includes(Base.upperCamelCase(schemaKey))) {
|
|
594
|
+
const schema = schemas[schemaKey];
|
|
595
|
+
statements.push(factory.createTypeAliasDeclaration([factory.createModifier(SyntaxKind.ExportKeyword)], factory.createIdentifier(Base.upperCamelCase(schemaKey)), void 0, Generator.toTypeNode(schema)));
|
|
596
|
+
}
|
|
597
|
+
for (const uri in apis) {
|
|
598
|
+
const operations = apis[uri];
|
|
599
|
+
for (const operation of operations) {
|
|
600
|
+
const { method, operationId, requestBody = [], responses = [], summary, deprecated, description } = operation;
|
|
601
|
+
let { parameters = [] } = operation;
|
|
602
|
+
parameters = parameters.filter((p) => p.in !== "cookie");
|
|
603
|
+
if (requestBody.length === 0) requestBody.push({ type: "application/json" });
|
|
604
|
+
const shouldAddExtraMethodNameSuffix = requestBody.length > 1;
|
|
605
|
+
for (const req of requestBody) {
|
|
606
|
+
const statement = factory.createFunctionDeclaration([factory.createModifier(SyntaxKind.ExportKeyword), factory.createModifier(SyntaxKind.AsyncKeyword)], void 0, Base.pathToFnName(uri, method, operationId) + (shouldAddExtraMethodNameSuffix ? Base.capitalize(req.type.split("/")[1]) : ""), void 0, [...parameters.length > 0 ? Generator.toDeclarationNodes(parameters) : [], ...req?.schema ? [Generator.toRequestBodyTypeNode(req.schema)] : []].filter(Boolean), void 0, Generator.bodyBlock(options.baseURL + uri, method, parameters, req, responses[0], adaptor));
|
|
607
|
+
Generator.addComments(statement, [
|
|
608
|
+
description && { comment: description },
|
|
609
|
+
summary && { comment: summary },
|
|
610
|
+
deprecated && { tag: "deprecated" }
|
|
611
|
+
].filter(Boolean));
|
|
612
|
+
statements.push(statement);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return statements;
|
|
617
|
+
}
|
|
618
|
+
static async prettier(code) {
|
|
619
|
+
return await format(code, { parser: "typescript" });
|
|
620
|
+
}
|
|
621
|
+
static async genCode(schema, initOptions, adaptor) {
|
|
622
|
+
const { importClientSource } = initOptions;
|
|
623
|
+
const statements = Generator.schemaToStatemets(schema, adaptor, { baseURL: initOptions.baseURL ?? "" });
|
|
624
|
+
let code = Generator.toCode(statements);
|
|
625
|
+
if (importClientSource) code = importClientSource + "\n\n" + code;
|
|
626
|
+
return await Generator.prettier(code);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/core/client/axios.ts
|
|
631
|
+
/**
|
|
632
|
+
* Adapter class implementing support for generating code that makes use of the Axios HTTP client library.
|
|
633
|
+
* This class defines custom behavior and field mappings specific to the Axios client.
|
|
634
|
+
*/
|
|
635
|
+
var AxiosAdapter = class extends Adapter {
|
|
636
|
+
/**
|
|
637
|
+
* Name of the field used to specify the HTTP method in the request configuration.
|
|
638
|
+
*/
|
|
639
|
+
methodFieldName = "method";
|
|
640
|
+
/**
|
|
641
|
+
* Name of the field used to specify the request body (data) in the request configuration.
|
|
642
|
+
*/
|
|
643
|
+
bodyFieldName = "data";
|
|
644
|
+
/**
|
|
645
|
+
* Name of the field used to specify the request headers in the request configuration.
|
|
646
|
+
*/
|
|
647
|
+
headersFieldName = "headers";
|
|
648
|
+
/**
|
|
649
|
+
* Name of the field used to specify the query parameters in the request configuration.
|
|
650
|
+
*/
|
|
651
|
+
queryFieldName = "params";
|
|
652
|
+
/**
|
|
653
|
+
* The name of the client this adapter is configured for, which is 'axios' in this case.
|
|
654
|
+
*/
|
|
655
|
+
name = "axios";
|
|
656
|
+
/**
|
|
657
|
+
* Method that should generate and return the client-specific configuration statements.
|
|
658
|
+
*
|
|
659
|
+
* @returns {Statement[]} An array of TypeScript statements that define the client configuration.
|
|
660
|
+
*
|
|
661
|
+
* @throws {Error} Indicates that the method is not yet implemented and needs to be filled in.
|
|
662
|
+
*/
|
|
663
|
+
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData) {
|
|
664
|
+
const statements = [];
|
|
665
|
+
const inBody = parameters.filter((p) => !p.in || p.in === "body");
|
|
666
|
+
const inHeader = parameters.filter((p) => p.in === "header");
|
|
667
|
+
/**
|
|
668
|
+
* Creates the literal object expression for fetch options
|
|
669
|
+
* including method, headers, and body.
|
|
670
|
+
* @returns - The constructed fetch options object
|
|
671
|
+
*/
|
|
672
|
+
const toLiterlExpression = () => {
|
|
673
|
+
return factory.createObjectLiteralExpression([factory.createPropertyAssignment(factory.createIdentifier(adapter.methodFieldName), factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? factory.createPropertyAssignment(factory.createIdentifier(adapter.headersFieldName), factory.createObjectLiteralExpression(inHeader.map((p) => factory.createPropertyAssignment(factory.createStringLiteral(p.name), factory.createCallExpression(factory.createIdentifier("encodeURIComponent"), void 0, [factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createIdentifier(Base.camelCase(Base.normalize(p.name)))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? factory.createPropertyAssignment(factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? factory.createIdentifier("req") : factory.createIdentifier("req")) : []), true);
|
|
674
|
+
};
|
|
675
|
+
statements.push(factory.createReturnStatement(factory.createCallExpression(factory.createIdentifier(adapter.name), response?.schema ? [Generator.toTypeNode(response.schema)] : void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
|
|
676
|
+
return statements;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/core/client/fetch.ts
|
|
681
|
+
/**
|
|
682
|
+
* FetchAdapter is an adapter class that generates client-side fetch requests.
|
|
683
|
+
* It handles parameters, headers, and request bodies to construct proper fetch calls.
|
|
684
|
+
*/
|
|
685
|
+
var FetchAdapter = class extends Adapter {
|
|
686
|
+
methodFieldName = "method";
|
|
687
|
+
bodyFieldName = "body";
|
|
688
|
+
headersFieldName = "headers";
|
|
689
|
+
queryFieldName = "";
|
|
690
|
+
name = "fetch";
|
|
691
|
+
/**
|
|
692
|
+
* Generates client code for making API requests using the Fetch API.
|
|
693
|
+
* @param uri - The API endpoint URI
|
|
694
|
+
* @param method - The HTTP method (GET, POST, etc.)
|
|
695
|
+
* @param parameters - Array of parameters to include in the request
|
|
696
|
+
* @param requestBody - The request body media type definition
|
|
697
|
+
* @param response - The response media type definition
|
|
698
|
+
* @param adapter - The adapter instance
|
|
699
|
+
* @param shouldUseFormData - Flag to use FormData for the request body
|
|
700
|
+
* @param shouldUseJSONResponse - Flag to use JSON parsing for the response
|
|
701
|
+
* @return - An array of generated TypeScript statements
|
|
702
|
+
*/
|
|
703
|
+
client(uri, method, parameters, requestBody, response, adapter, shouldUseFormData, shouldUseJSONResponse) {
|
|
704
|
+
const statements = [];
|
|
705
|
+
const inBody = parameters.filter((p) => !p.in || p.in === "body");
|
|
706
|
+
const inHeader = parameters.filter((p) => p.in === "header");
|
|
707
|
+
/**
|
|
708
|
+
* Creates the literal object expression for fetch options
|
|
709
|
+
* including method, headers, and body.
|
|
710
|
+
* @returns - The constructed fetch options object
|
|
711
|
+
*/
|
|
712
|
+
const toLiterlExpression = () => {
|
|
713
|
+
return factory.createObjectLiteralExpression([factory.createPropertyAssignment(factory.createIdentifier(adapter.methodFieldName), factory.createStringLiteral(method.toUpperCase()))].concat(inHeader.length > 0 ? factory.createPropertyAssignment(factory.createIdentifier(adapter.headersFieldName), factory.createObjectLiteralExpression(inHeader.map((p) => factory.createPropertyAssignment(factory.createStringLiteral(p.name), factory.createCallExpression(factory.createIdentifier("encodeURIComponent"), void 0, [factory.createCallExpression(factory.createIdentifier("String"), void 0, [factory.createIdentifier(Base.camelCase(Base.normalize(p.name)))])]))))) : []).concat(shouldUseFormData || inBody.length > 0 || requestBody?.schema ? factory.createPropertyAssignment(factory.createIdentifier(adapter.bodyFieldName), shouldUseFormData ? factory.createIdentifier("fd") : inBody.length > 0 || requestBody?.schema && !Generator.isBinarySchema(requestBody.schema) ? factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("JSON"), factory.createIdentifier("stringify")), [], [requestBody ? factory.createIdentifier("req") : factory.createObjectLiteralExpression(inBody.map((b) => factory.createShorthandPropertyAssignment(factory.createIdentifier(b.name))), true)]) : factory.createIdentifier("req")) : []), true);
|
|
714
|
+
};
|
|
715
|
+
statements.push(factory.createReturnStatement(shouldUseJSONResponse ? factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()]), factory.createIdentifier("then")), void 0, [factory.createArrowFunction([factory.createModifier(SyntaxKind.AsyncKeyword)], [], [factory.createParameterDeclaration(void 0, void 0, factory.createIdentifier("response"))], void 0, factory.createToken(SyntaxKind.EqualsGreaterThanToken), response?.schema ? factory.createAsExpression(factory.createParenthesizedExpression(factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("response"), factory.createIdentifier("json")), void 0, []))), response?.schema ? Generator.toTypeNode(response.schema) : factory.createToken(SyntaxKind.UnknownKeyword)) : factory.createParenthesizedExpression(factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("response"), factory.createIdentifier("json")), void 0, []))))]) : factory.createCallExpression(factory.createIdentifier(adapter.name), void 0, [Generator.toUrlTemplate(uri, parameters), toLiterlExpression()])));
|
|
716
|
+
return statements;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/core/config.ts
|
|
721
|
+
/**
|
|
722
|
+
* Environment variable mappings
|
|
723
|
+
*/
|
|
724
|
+
const ENV_MAPPINGS = {
|
|
725
|
+
APICODEGEN_SPEC: "spec",
|
|
726
|
+
APICODEGEN_OUTPUT: "output",
|
|
727
|
+
APICODEGEN_BASE_URL: "baseURL",
|
|
728
|
+
APICODEGEN_ADAPTOR: "adaptor",
|
|
729
|
+
APICODEGEN_VERBOSE: "verbose",
|
|
730
|
+
APICODEGEN_WATCH: "watch",
|
|
731
|
+
APICODEGEN_TYPE_CHECK: "typeCheck"
|
|
732
|
+
};
|
|
733
|
+
/**
|
|
734
|
+
* Load config from environment variables
|
|
735
|
+
*/
|
|
736
|
+
function loadFromEnv() {
|
|
737
|
+
const config = {};
|
|
738
|
+
for (const [envKey, configKey] of Object.entries(ENV_MAPPINGS)) {
|
|
739
|
+
const value = process.env[envKey];
|
|
740
|
+
if (value !== void 0) switch (configKey) {
|
|
741
|
+
case "verbose":
|
|
742
|
+
case "watch":
|
|
743
|
+
case "typeCheck":
|
|
744
|
+
config[configKey] = value === "true" || value === "1";
|
|
745
|
+
break;
|
|
746
|
+
case "adaptor":
|
|
747
|
+
config[configKey] = value;
|
|
748
|
+
break;
|
|
749
|
+
default: config[configKey] = value;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return config;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Load config from a file
|
|
756
|
+
*/
|
|
757
|
+
async function loadFromFile(filePath) {
|
|
758
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
759
|
+
try {
|
|
760
|
+
if (ext === ".json" || ext === ".jsonc") {
|
|
761
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
762
|
+
return JSON.parse(content);
|
|
763
|
+
}
|
|
764
|
+
if (ext === ".js" || ext === ".cjs" || ext === ".mjs") {
|
|
765
|
+
const mod = await import(filePath);
|
|
766
|
+
return mod.default || mod;
|
|
767
|
+
}
|
|
768
|
+
if (ext === ".ts") {
|
|
769
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
770
|
+
try {
|
|
771
|
+
return JSON.parse(content);
|
|
772
|
+
} catch {
|
|
773
|
+
const jsonMatch = content.match(/export\s+default\s+(\{.+\})/s);
|
|
774
|
+
if (jsonMatch) return JSON.parse(jsonMatch[1]);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
778
|
+
return JSON.parse(content);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
throw new Error(`Failed to load config from ${filePath}: ${error}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Find config file in project root
|
|
785
|
+
*/
|
|
786
|
+
async function findConfigFile(cwd) {
|
|
787
|
+
for (const fileName of [
|
|
788
|
+
"apicodegen.config.json",
|
|
789
|
+
"apicodegen.config.js",
|
|
790
|
+
"apicodegen.config.mjs",
|
|
791
|
+
".apicodegenrc",
|
|
792
|
+
".apicodegenrc.json",
|
|
793
|
+
".apicodegenrc.js",
|
|
794
|
+
".apicodegenrc.mjs"
|
|
795
|
+
]) {
|
|
796
|
+
const filePath = path.join(cwd, fileName);
|
|
797
|
+
if (await fs.pathExists(filePath)) return filePath;
|
|
798
|
+
}
|
|
799
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
800
|
+
if (await fs.pathExists(packageJsonPath)) try {
|
|
801
|
+
const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
802
|
+
if (pkg.apicodegen && typeof pkg.apicodegen === "string") return path.resolve(cwd, pkg.apicodegen);
|
|
803
|
+
} catch {}
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Merge multiple config sources with priority
|
|
808
|
+
* Priority: defaults < env vars < config file < CLI args
|
|
809
|
+
*/
|
|
810
|
+
function mergeConfigs(base, ...sources) {
|
|
811
|
+
const result = { ...base };
|
|
812
|
+
for (const source of sources) {
|
|
813
|
+
if (!source) continue;
|
|
814
|
+
for (const [key, value] of Object.entries(source)) if (value !== void 0) result[key] = value;
|
|
815
|
+
}
|
|
816
|
+
return result;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Validate config has required fields
|
|
820
|
+
*/
|
|
821
|
+
function validateConfig(config) {
|
|
822
|
+
if (!config.spec) throw new Error("Missing required field: spec (OpenAPI spec file path or URL)");
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Load and resolve config from multiple sources
|
|
827
|
+
*/
|
|
828
|
+
async function loadConfig(options = {}) {
|
|
829
|
+
const cwd = options.cwd || process.cwd();
|
|
830
|
+
const cliOptions = options.cliOptions || {};
|
|
831
|
+
const envConfig = loadFromEnv();
|
|
832
|
+
let fileConfig = {};
|
|
833
|
+
let configFilePath;
|
|
834
|
+
if (options.configFile) {
|
|
835
|
+
configFilePath = path.resolve(cwd, options.configFile);
|
|
836
|
+
fileConfig = await loadFromFile(configFilePath);
|
|
837
|
+
} else {
|
|
838
|
+
const foundPath = await findConfigFile(cwd);
|
|
839
|
+
if (foundPath) {
|
|
840
|
+
configFilePath = foundPath;
|
|
841
|
+
fileConfig = await loadFromFile(foundPath);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
845
|
+
let inlineConfig = {};
|
|
846
|
+
if (await fs.pathExists(packageJsonPath)) try {
|
|
847
|
+
const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
848
|
+
if (pkg.apicodegen && typeof pkg.apicodegen === "object") inlineConfig = pkg.apicodegen;
|
|
849
|
+
} catch {}
|
|
850
|
+
const merged = mergeConfigs({
|
|
851
|
+
spec: "",
|
|
852
|
+
output: "./output.ts"
|
|
853
|
+
}, envConfig, inlineConfig, fileConfig, cliOptions);
|
|
854
|
+
validateConfig(merged);
|
|
855
|
+
const name = options.name || merged.baseURL || merged.spec;
|
|
856
|
+
return {
|
|
857
|
+
...merged,
|
|
858
|
+
configFilePath,
|
|
859
|
+
name
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Create CLI options from config for commander
|
|
864
|
+
*/
|
|
865
|
+
function configToCLIOptions(config) {
|
|
866
|
+
const options = {};
|
|
867
|
+
if (config.spec) options.spec = config.spec;
|
|
868
|
+
if (config.output) options.output = config.output;
|
|
869
|
+
if (config.adaptor) options.adaptor = config.adaptor;
|
|
870
|
+
if (config.baseURL) options.baseURL = config.baseURL;
|
|
871
|
+
if (config.verbose) options.verbose = config.verbose;
|
|
872
|
+
if (config.watch) options.watch = config.watch;
|
|
873
|
+
if (config.importClientSource) options.importClientSource = config.importClientSource;
|
|
874
|
+
return options;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Convert resolved config to provider options format
|
|
878
|
+
*/
|
|
879
|
+
function toProviderOptions(config) {
|
|
880
|
+
return {
|
|
881
|
+
docURL: config.spec,
|
|
882
|
+
output: config.output,
|
|
883
|
+
adaptor: config.adaptor,
|
|
884
|
+
baseURL: config.baseURL,
|
|
885
|
+
importClientSource: config.importClientSource,
|
|
886
|
+
verbose: config.verbose,
|
|
887
|
+
requestOptions: config.requestOptions
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/core/errors.ts
|
|
892
|
+
/**
|
|
893
|
+
* Error handling utilities for api-codegen
|
|
894
|
+
*/
|
|
895
|
+
const ErrorCodes = {
|
|
896
|
+
SPEC_NOT_FOUND: "E_SPEC_NOT_FOUND",
|
|
897
|
+
SPEC_FETCH_FAILED: "E_SPEC_FETCH_FAILED",
|
|
898
|
+
SPEC_PARSE_FAILED: "E_SPEC_PARSE_FAILED",
|
|
899
|
+
OUTPUT_DIR_MISSING: "E_OUTPUT_DIR_MISSING",
|
|
900
|
+
CONFIG_INVALID: "E_CONFIG_INVALID",
|
|
901
|
+
VALIDATION_FAILED: "E_VALIDATION_FAILED",
|
|
902
|
+
GENERATION_FAILED: "E_GENERATION_FAILED",
|
|
903
|
+
TYPE_CHECK_FAILED: "E_TYPE_CHECK_FAILED"
|
|
904
|
+
};
|
|
905
|
+
/**
|
|
906
|
+
* Custom error class for api-codegen with rich context
|
|
907
|
+
*/
|
|
908
|
+
var ApicodegenError = class ApicodegenError extends Error {
|
|
909
|
+
code;
|
|
910
|
+
location;
|
|
911
|
+
line;
|
|
912
|
+
column;
|
|
913
|
+
path;
|
|
914
|
+
suggestions;
|
|
915
|
+
cause;
|
|
916
|
+
constructor(context) {
|
|
917
|
+
super(context.message);
|
|
918
|
+
this.name = "ApicodegenError";
|
|
919
|
+
this.code = context.code;
|
|
920
|
+
this.location = context.location;
|
|
921
|
+
this.line = context.line;
|
|
922
|
+
this.column = context.column;
|
|
923
|
+
this.path = context.path;
|
|
924
|
+
this.suggestions = context.suggestions || [];
|
|
925
|
+
this.cause = context.cause;
|
|
926
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, ApicodegenError);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Convert error to formatted string for CLI output
|
|
930
|
+
*/
|
|
931
|
+
toString(verbose = false) {
|
|
932
|
+
const lines = [];
|
|
933
|
+
lines.push(`\x1b[1;31mError [${this.code}]\x1b[0m ${this.message}`);
|
|
934
|
+
if (this.location) lines.push(` \x1b[36m→ Location:\x1b[0m ${this.location}`);
|
|
935
|
+
if (this.path) lines.push(` \x1b[36m→ Path:\x1b[0m ${this.path}`);
|
|
936
|
+
if (this.line !== void 0) {
|
|
937
|
+
let lineInfo = ` \x1b[36m→ Line:\x1b[0m ${this.line}`;
|
|
938
|
+
if (this.column !== void 0) lineInfo += `, Column: ${this.column}`;
|
|
939
|
+
lines.push(lineInfo);
|
|
940
|
+
}
|
|
941
|
+
if (this.suggestions.length > 0) for (const suggestion of this.suggestions) lines.push(` \x1b[32m→ Suggestion:\x1b[0m ${suggestion}`);
|
|
942
|
+
if (verbose && this.cause) {
|
|
943
|
+
lines.push(`\n \x1b[90mOriginal Error:\x1b[0m ${this.cause.message}`);
|
|
944
|
+
if (this.stack) {
|
|
945
|
+
const stackLines = this.stack.split("\n").slice(1).join("\n");
|
|
946
|
+
lines.push(`\x1b[90m${stackLines}\x1b[0m`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return lines.join("\n");
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Convert to JSON-serializable object
|
|
953
|
+
*/
|
|
954
|
+
toJSON() {
|
|
955
|
+
return {
|
|
956
|
+
name: this.name,
|
|
957
|
+
code: this.code,
|
|
958
|
+
message: this.message,
|
|
959
|
+
location: this.location,
|
|
960
|
+
line: this.line,
|
|
961
|
+
column: this.column,
|
|
962
|
+
path: this.path,
|
|
963
|
+
suggestions: this.suggestions,
|
|
964
|
+
cause: this.cause?.message
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
/**
|
|
969
|
+
* ANSI color codes for terminal output
|
|
970
|
+
*/
|
|
971
|
+
const Colors = {
|
|
972
|
+
reset: "\x1B[0m",
|
|
973
|
+
bold: "\x1B[1m",
|
|
974
|
+
red: "\x1B[31m",
|
|
975
|
+
green: "\x1B[32m",
|
|
976
|
+
yellow: "\x1B[33m",
|
|
977
|
+
blue: "\x1B[34m",
|
|
978
|
+
cyan: "\x1B[36m",
|
|
979
|
+
gray: "\x1B[90m",
|
|
980
|
+
brightRed: "\x1B[91m",
|
|
981
|
+
brightGreen: "\x1B[92m"
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
984
|
+
* Format error for CLI output
|
|
985
|
+
*/
|
|
986
|
+
function formatError(error, verbose = false) {
|
|
987
|
+
if (error instanceof ApicodegenError) return error.toString(verbose);
|
|
988
|
+
if (error instanceof Error) return `${Colors.red}${Colors.bold}Error${Colors.reset}: ${error.message}${verbose && error.stack ? `\n\n${Colors.gray}${error.stack}${Colors.reset}` : ""}`;
|
|
989
|
+
return `${Colors.red}${Colors.bold}Error${Colors.reset}: ${String(error)}`;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Print error to console with formatting
|
|
993
|
+
*/
|
|
994
|
+
function printError(error, verbose = false, stream = process.stderr) {
|
|
995
|
+
stream.write(formatError(error, verbose));
|
|
996
|
+
stream.write("\n");
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Create error with common patterns
|
|
1000
|
+
*/
|
|
1001
|
+
const createErrors = {
|
|
1002
|
+
specNotFound(path, cause) {
|
|
1003
|
+
return new ApicodegenError({
|
|
1004
|
+
code: ErrorCodes.SPEC_NOT_FOUND,
|
|
1005
|
+
message: "OpenAPI spec file not found",
|
|
1006
|
+
location: path,
|
|
1007
|
+
suggestions: [
|
|
1008
|
+
"Check if the file exists using 'ls -la'",
|
|
1009
|
+
"Use --spec to provide the correct path",
|
|
1010
|
+
"For remote specs, ensure the URL is accessible"
|
|
1011
|
+
],
|
|
1012
|
+
cause
|
|
1013
|
+
});
|
|
1014
|
+
},
|
|
1015
|
+
specFetchFailed(url, statusCode, cause) {
|
|
1016
|
+
const message = statusCode ? `Failed to fetch OpenAPI spec (HTTP ${statusCode})` : "Failed to fetch OpenAPI spec from URL";
|
|
1017
|
+
return new ApicodegenError({
|
|
1018
|
+
code: ErrorCodes.SPEC_FETCH_FAILED,
|
|
1019
|
+
message,
|
|
1020
|
+
location: url,
|
|
1021
|
+
suggestions: [
|
|
1022
|
+
"Check if the URL is accessible in a browser",
|
|
1023
|
+
"Download the spec file locally and use the local path",
|
|
1024
|
+
"Verify CORS settings if fetching from a different origin"
|
|
1025
|
+
],
|
|
1026
|
+
cause
|
|
1027
|
+
});
|
|
1028
|
+
},
|
|
1029
|
+
specParseFailed(path, line, column, cause) {
|
|
1030
|
+
return new ApicodegenError({
|
|
1031
|
+
code: ErrorCodes.SPEC_PARSE_FAILED,
|
|
1032
|
+
message: "Failed to parse OpenAPI spec (invalid JSON or YAML)",
|
|
1033
|
+
location: path,
|
|
1034
|
+
line,
|
|
1035
|
+
column,
|
|
1036
|
+
suggestions: [
|
|
1037
|
+
"Validate JSON syntax using jsonlint.com",
|
|
1038
|
+
"For YAML specs, ensure proper indentation",
|
|
1039
|
+
"Check for trailing commas or unquoted special characters"
|
|
1040
|
+
],
|
|
1041
|
+
cause
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
outputDirMissing(path, cause) {
|
|
1045
|
+
return new ApicodegenError({
|
|
1046
|
+
code: ErrorCodes.OUTPUT_DIR_MISSING,
|
|
1047
|
+
message: "Output directory does not exist",
|
|
1048
|
+
location: path,
|
|
1049
|
+
suggestions: ["Create the directory: mkdir -p $(dirname <output>)", "Check if the path is correct"],
|
|
1050
|
+
cause
|
|
1051
|
+
});
|
|
1052
|
+
},
|
|
1053
|
+
configInvalid(path, cause) {
|
|
1054
|
+
return new ApicodegenError({
|
|
1055
|
+
code: ErrorCodes.CONFIG_INVALID,
|
|
1056
|
+
message: "Invalid configuration file",
|
|
1057
|
+
location: path,
|
|
1058
|
+
suggestions: ["Validate JSON syntax in the config file", "Check for required fields (spec, output)"],
|
|
1059
|
+
cause
|
|
1060
|
+
});
|
|
1061
|
+
},
|
|
1062
|
+
validationFailed(path, details, cause) {
|
|
1063
|
+
return new ApicodegenError({
|
|
1064
|
+
code: ErrorCodes.VALIDATION_FAILED,
|
|
1065
|
+
message: "OpenAPI spec validation failed",
|
|
1066
|
+
location: path,
|
|
1067
|
+
path: details,
|
|
1068
|
+
suggestions: [
|
|
1069
|
+
"Check OpenAPI spec structure at the specified path",
|
|
1070
|
+
"Ensure all required fields are present",
|
|
1071
|
+
"Validate using swagger.io editor"
|
|
1072
|
+
],
|
|
1073
|
+
cause
|
|
1074
|
+
});
|
|
1075
|
+
},
|
|
1076
|
+
generationFailed(cause) {
|
|
1077
|
+
return new ApicodegenError({
|
|
1078
|
+
code: ErrorCodes.GENERATION_FAILED,
|
|
1079
|
+
message: "Code generation failed",
|
|
1080
|
+
suggestions: [
|
|
1081
|
+
"Check for unsupported OpenAPI features",
|
|
1082
|
+
"Ensure spec follows OpenAPI 2.0, 3.0, or 3.1 specification",
|
|
1083
|
+
"Use --verbose for more details"
|
|
1084
|
+
],
|
|
1085
|
+
cause
|
|
1086
|
+
});
|
|
1087
|
+
},
|
|
1088
|
+
typeCheckFailed(path, _errors, cause) {
|
|
1089
|
+
return new ApicodegenError({
|
|
1090
|
+
code: ErrorCodes.TYPE_CHECK_FAILED,
|
|
1091
|
+
message: "TypeScript type check failed",
|
|
1092
|
+
location: path,
|
|
1093
|
+
suggestions: [
|
|
1094
|
+
"Review type errors above",
|
|
1095
|
+
"Check for schema inconsistencies",
|
|
1096
|
+
"Update generated types or fix source schema"
|
|
1097
|
+
],
|
|
1098
|
+
cause
|
|
1099
|
+
});
|
|
1100
|
+
},
|
|
1101
|
+
missingRequiredField(field, context) {
|
|
1102
|
+
return new ApicodegenError({
|
|
1103
|
+
code: ErrorCodes.VALIDATION_FAILED,
|
|
1104
|
+
message: `Missing required field: ${field}`,
|
|
1105
|
+
path: context,
|
|
1106
|
+
suggestions: [`Add the '${field}' field to your configuration`]
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
/**
|
|
1111
|
+
* Wrap unknown error in ApicodegenError if needed
|
|
1112
|
+
*/
|
|
1113
|
+
function wrapError(error, context) {
|
|
1114
|
+
if (error instanceof ApicodegenError) return error;
|
|
1115
|
+
if (error instanceof Error) return new ApicodegenError({
|
|
1116
|
+
code: context?.code || ErrorCodes.GENERATION_FAILED,
|
|
1117
|
+
message: context?.message || error.message,
|
|
1118
|
+
location: context?.location,
|
|
1119
|
+
suggestions: context?.suggestions,
|
|
1120
|
+
cause: error
|
|
1121
|
+
});
|
|
1122
|
+
return new ApicodegenError({
|
|
1123
|
+
code: context?.code || ErrorCodes.GENERATION_FAILED,
|
|
1124
|
+
message: String(error),
|
|
1125
|
+
suggestions: context?.suggestions
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Check if error is an ApicodegenError
|
|
1130
|
+
*/
|
|
1131
|
+
function isApicodegenError(error) {
|
|
1132
|
+
return error instanceof ApicodegenError;
|
|
1133
|
+
}
|
|
1134
|
+
//#endregion
|
|
1135
|
+
//#region src/openapi/V2.ts
|
|
1136
|
+
var V2 = class {
|
|
1137
|
+
doc;
|
|
1138
|
+
constructor(doc) {
|
|
1139
|
+
this.doc = doc;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Resolves a path $ref to the actual path object.
|
|
1143
|
+
*/
|
|
1144
|
+
resolvePathRef($ref) {
|
|
1145
|
+
const refName = Base.ref2name($ref, this.doc);
|
|
1146
|
+
return this.doc.paths?.[refName];
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Is array schema.
|
|
1150
|
+
*/
|
|
1151
|
+
isOpenAPIArraySchema(schema) {
|
|
1152
|
+
return typeof schema === "object" && schema.type === "array";
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* OpenAPI schema to base schema.
|
|
1156
|
+
*/
|
|
1157
|
+
getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
|
|
1158
|
+
let refName = "";
|
|
1159
|
+
if (Base.isRef(schema)) {
|
|
1160
|
+
refName = Base.upperCamelCase(Base.ref2name(schema.$ref));
|
|
1161
|
+
if (reserveRef) return { type: upLevelSchemaKey + refName };
|
|
1162
|
+
if (!this.doc.definitions) this.doc.definitions = {};
|
|
1163
|
+
schema = this.doc.definitions[Base.ref2name(schema.$ref, this.doc)];
|
|
1164
|
+
}
|
|
1165
|
+
return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Transform all OpenAPI schema to Base Schema
|
|
1169
|
+
*/
|
|
1170
|
+
toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
|
|
1171
|
+
if (!schema) return { type: "unknown" };
|
|
1172
|
+
if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
|
|
1173
|
+
if (this.isOpenAPIArraySchema(schema)) {
|
|
1174
|
+
const { type, description, items, required } = schema;
|
|
1175
|
+
return {
|
|
1176
|
+
type,
|
|
1177
|
+
required: !!required,
|
|
1178
|
+
description,
|
|
1179
|
+
items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
|
|
1180
|
+
};
|
|
1181
|
+
} else {
|
|
1182
|
+
const { required = [], allOf, anyOf, description, enum: enum_, format, oneOf, properties = {} } = schema;
|
|
1183
|
+
let { type } = schema;
|
|
1184
|
+
if (enum_ && type !== "boolean") {
|
|
1185
|
+
const enumObject = {
|
|
1186
|
+
name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
|
|
1187
|
+
enum: [...new Set(enum_)]
|
|
1188
|
+
};
|
|
1189
|
+
const sameObject = Base.findSameSchema(enumObject, enums);
|
|
1190
|
+
if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
|
|
1191
|
+
return {
|
|
1192
|
+
type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
|
|
1193
|
+
required,
|
|
1194
|
+
description
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
if (type === void 0 && Object.keys(properties).length > 0) type = "object";
|
|
1198
|
+
return {
|
|
1199
|
+
type,
|
|
1200
|
+
required,
|
|
1201
|
+
description,
|
|
1202
|
+
enum: enum_,
|
|
1203
|
+
format,
|
|
1204
|
+
allOf: allOf?.map((s) => Base.isRef(s) ? {
|
|
1205
|
+
...s,
|
|
1206
|
+
ref: s.$ref,
|
|
1207
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1208
|
+
} : this.toBaseSchema(s, enums)),
|
|
1209
|
+
anyOf: anyOf?.map((s) => Base.isRef(s) ? {
|
|
1210
|
+
...s,
|
|
1211
|
+
ref: s.$ref,
|
|
1212
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1213
|
+
} : this.toBaseSchema(s, enums)),
|
|
1214
|
+
oneOf: oneOf?.map((s) => Base.isRef(s) ? {
|
|
1215
|
+
...s,
|
|
1216
|
+
ref: s.$ref,
|
|
1217
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1218
|
+
} : this.toBaseSchema(s, enums)),
|
|
1219
|
+
properties: Object.keys(properties).reduce((acc, p) => {
|
|
1220
|
+
const propSchema = properties[p];
|
|
1221
|
+
return {
|
|
1222
|
+
...acc,
|
|
1223
|
+
[p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
|
|
1224
|
+
};
|
|
1225
|
+
}, {})
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* OpenAPI parameter to base parameter.
|
|
1231
|
+
*/
|
|
1232
|
+
getParameterByRef(parameter, enums = [], upLevelSchemaKey = "") {
|
|
1233
|
+
if (Base.isRef(parameter)) {
|
|
1234
|
+
const refName = Base.ref2name(parameter.$ref, this.doc);
|
|
1235
|
+
const resolved = this.doc.parameters?.[refName];
|
|
1236
|
+
if (!resolved) throw new Error(`Parameter reference not found: ${parameter.$ref}`);
|
|
1237
|
+
parameter = resolved;
|
|
1238
|
+
}
|
|
1239
|
+
const p = parameter;
|
|
1240
|
+
const { name, required, description, type, items, enum: enum_, properties, schema } = p;
|
|
1241
|
+
if (enum_) {
|
|
1242
|
+
const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
|
|
1243
|
+
const enumSchema = {
|
|
1244
|
+
name: type,
|
|
1245
|
+
enum: [...new Set(enum_)]
|
|
1246
|
+
};
|
|
1247
|
+
const sameEnum = Base.findSameSchema(enumSchema, enums);
|
|
1248
|
+
if (!sameEnum && Base.isValidEnumType({
|
|
1249
|
+
type,
|
|
1250
|
+
enum: enum_
|
|
1251
|
+
})) enums.push(enumSchema);
|
|
1252
|
+
return {
|
|
1253
|
+
name,
|
|
1254
|
+
required,
|
|
1255
|
+
description,
|
|
1256
|
+
in: p.in,
|
|
1257
|
+
schema: { type: sameEnum?.name ?? type }
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (items) return {
|
|
1261
|
+
name,
|
|
1262
|
+
required,
|
|
1263
|
+
description,
|
|
1264
|
+
in: p.in,
|
|
1265
|
+
schema: {
|
|
1266
|
+
type,
|
|
1267
|
+
items
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
if (schema && Base.isRef(schema)) return {
|
|
1271
|
+
name,
|
|
1272
|
+
required,
|
|
1273
|
+
description,
|
|
1274
|
+
in: p.in,
|
|
1275
|
+
schema: { type: Base.capitalize(Base.ref2name(schema.$ref)) }
|
|
1276
|
+
};
|
|
1277
|
+
return {
|
|
1278
|
+
name,
|
|
1279
|
+
required,
|
|
1280
|
+
description,
|
|
1281
|
+
in: p.in,
|
|
1282
|
+
schema: {
|
|
1283
|
+
type,
|
|
1284
|
+
properties
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* OpenAPI schema to base response
|
|
1290
|
+
*/
|
|
1291
|
+
getResponseByRef(schema) {
|
|
1292
|
+
if (Base.isRef(schema)) schema = this.doc.responses[Base.ref2name(schema.$ref, this.doc)];
|
|
1293
|
+
const { schema: responseSchema } = schema;
|
|
1294
|
+
return [{
|
|
1295
|
+
type: "application/json",
|
|
1296
|
+
schema: responseSchema && this.getSchemaByRef(responseSchema, true)
|
|
1297
|
+
}];
|
|
1298
|
+
}
|
|
1299
|
+
init() {
|
|
1300
|
+
const { definitions = {}, responses = {}, paths = {} } = this.doc;
|
|
1301
|
+
const enums = [];
|
|
1302
|
+
const definitions_ = Object.keys(definitions).reduce((acc, key) => {
|
|
1303
|
+
const schema = definitions[key];
|
|
1304
|
+
return {
|
|
1305
|
+
...acc,
|
|
1306
|
+
[key]: this.getSchemaByRef(schema, false, enums, key)
|
|
1307
|
+
};
|
|
1308
|
+
}, {});
|
|
1309
|
+
const responses_ = Object.keys(responses).reduce((acc, key) => {
|
|
1310
|
+
const response = responses[key];
|
|
1311
|
+
return {
|
|
1312
|
+
...acc,
|
|
1313
|
+
[key]: this.getResponseByRef(response)
|
|
1314
|
+
};
|
|
1315
|
+
}, {});
|
|
1316
|
+
const apis = Object.keys(paths).reduce((acc, path) => {
|
|
1317
|
+
let pathObject = paths[path] ?? {};
|
|
1318
|
+
if (pathObject.$ref) {
|
|
1319
|
+
const resolved = this.resolvePathRef(pathObject.$ref);
|
|
1320
|
+
if (resolved) pathObject = resolved;
|
|
1321
|
+
}
|
|
1322
|
+
const { parameters = [] } = pathObject;
|
|
1323
|
+
const methodApis = [];
|
|
1324
|
+
Object.values(HttpMethods).forEach((method) => {
|
|
1325
|
+
const methodObject = pathObject[method];
|
|
1326
|
+
if (methodObject) {
|
|
1327
|
+
const { deprecated, operationId, summary: summary_, description: description_, responses = {} } = methodObject;
|
|
1328
|
+
const { parameters: parameters_ = [] } = methodObject;
|
|
1329
|
+
const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
|
|
1330
|
+
const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
|
|
1331
|
+
if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
|
|
1332
|
+
const inBody = baseParameters.filter((p) => p.in === "body" || p.in === "formData");
|
|
1333
|
+
const notInBody = baseParameters.filter((p) => p.in !== "body" && p.in !== "formData");
|
|
1334
|
+
const httpCodes = Object.keys(responses);
|
|
1335
|
+
for (const code of httpCodes) if (code in responses) {
|
|
1336
|
+
const response = responses[code];
|
|
1337
|
+
const responseSchema = this.getResponseByRef(response);
|
|
1338
|
+
const inBodyOnlyHasBody = inBody && inBody.length === 1 && inBody[0].in === "body" && inBody[0].name === "body";
|
|
1339
|
+
methodApis.push({
|
|
1340
|
+
method,
|
|
1341
|
+
operationId,
|
|
1342
|
+
summary: summary_,
|
|
1343
|
+
deprecated,
|
|
1344
|
+
description: description_,
|
|
1345
|
+
parameters: uniqueParameterName.map((name) => notInBody.find((p) => p.name === name)).filter(Boolean),
|
|
1346
|
+
responses: responseSchema,
|
|
1347
|
+
requestBody: inBody.length > 0 ? inBodyOnlyHasBody ? [{
|
|
1348
|
+
type: "application/json",
|
|
1349
|
+
schema: inBody[0].schema
|
|
1350
|
+
}] : [{
|
|
1351
|
+
type: "application/json",
|
|
1352
|
+
schema: {
|
|
1353
|
+
type: "object",
|
|
1354
|
+
properties: inBody.reduce((a, p) => {
|
|
1355
|
+
return {
|
|
1356
|
+
...a,
|
|
1357
|
+
[p.name]: {
|
|
1358
|
+
type: p.schema?.type ?? "unknown",
|
|
1359
|
+
required: p.schema?.required,
|
|
1360
|
+
items: p.schema?.items,
|
|
1361
|
+
description: p.schema?.description
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
}, {})
|
|
1365
|
+
}
|
|
1366
|
+
}] : void 0
|
|
1367
|
+
});
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
return {
|
|
1373
|
+
...acc,
|
|
1374
|
+
[path]: methodApis
|
|
1375
|
+
};
|
|
1376
|
+
}, {});
|
|
1377
|
+
return {
|
|
1378
|
+
enums: Base.uniqueEnums(enums),
|
|
1379
|
+
schemas: definitions_,
|
|
1380
|
+
responses: responses_,
|
|
1381
|
+
parameters: {},
|
|
1382
|
+
requestBodies: {},
|
|
1383
|
+
apis
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region src/openapi/V3.ts
|
|
1389
|
+
var V3 = class {
|
|
1390
|
+
doc;
|
|
1391
|
+
constructor(doc) {
|
|
1392
|
+
this.doc = doc;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Resolves a path $ref to the actual path object.
|
|
1396
|
+
*/
|
|
1397
|
+
resolvePathRef($ref) {
|
|
1398
|
+
const refName = Base.ref2name($ref, this.doc);
|
|
1399
|
+
return this.doc.paths?.[refName];
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Is array schema.
|
|
1403
|
+
*/
|
|
1404
|
+
isOpenAPIArraySchema(schema) {
|
|
1405
|
+
return typeof schema === "object" && schema.type === "array";
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* OpenAPI schema to base schema.
|
|
1409
|
+
*/
|
|
1410
|
+
getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
|
|
1411
|
+
let refName = "";
|
|
1412
|
+
if (Base.isRef(schema)) {
|
|
1413
|
+
refName = Base.capitalize(Base.ref2name(schema.$ref));
|
|
1414
|
+
if (reserveRef) return { type: upLevelSchemaKey + refName };
|
|
1415
|
+
const resolvedSchema = this.doc.components?.schemas?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1416
|
+
if (!resolvedSchema) return { type: "unknown" };
|
|
1417
|
+
schema = resolvedSchema;
|
|
1418
|
+
}
|
|
1419
|
+
return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* OpenAPI parameter to base parameter.
|
|
1423
|
+
*/
|
|
1424
|
+
getParameterByRef(schema, enums = [], upLevelSchemaKey = "") {
|
|
1425
|
+
if (Base.isRef(schema)) {
|
|
1426
|
+
const resolvedSchema = this.doc.components?.parameters?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1427
|
+
if (!resolvedSchema) return {
|
|
1428
|
+
name: "unknown",
|
|
1429
|
+
in: "query"
|
|
1430
|
+
};
|
|
1431
|
+
schema = resolvedSchema;
|
|
1432
|
+
}
|
|
1433
|
+
const { name, required, deprecated, description, schema: parameterSchema } = schema;
|
|
1434
|
+
if (parameterSchema && !Base.isRef(parameterSchema) && parameterSchema.enum) {
|
|
1435
|
+
const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
|
|
1436
|
+
const enumSchema = {
|
|
1437
|
+
name: type,
|
|
1438
|
+
enum: [...new Set(parameterSchema.enum)]
|
|
1439
|
+
};
|
|
1440
|
+
const sameEnum = Base.findSameSchema(enumSchema, enums);
|
|
1441
|
+
if (!sameEnum && Base.isValidEnumType(parameterSchema)) enums.push(enumSchema);
|
|
1442
|
+
return {
|
|
1443
|
+
name,
|
|
1444
|
+
required,
|
|
1445
|
+
description,
|
|
1446
|
+
deprecated,
|
|
1447
|
+
in: schema.in,
|
|
1448
|
+
schema: { type: sameEnum?.name ?? type }
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
name,
|
|
1453
|
+
required,
|
|
1454
|
+
description,
|
|
1455
|
+
deprecated,
|
|
1456
|
+
in: schema.in,
|
|
1457
|
+
schema: schema.schema && this.getSchemaByRef(schema.schema, false, enums, upLevelSchemaKey + Base.capitalize(name))
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* OpenAPI schema to base response
|
|
1462
|
+
*/
|
|
1463
|
+
getResponseByRef(schema) {
|
|
1464
|
+
if (Base.isRef(schema)) {
|
|
1465
|
+
const resolvedSchema = this.doc.components?.responses?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1466
|
+
if (!resolvedSchema) return [];
|
|
1467
|
+
schema = resolvedSchema;
|
|
1468
|
+
}
|
|
1469
|
+
const { content = {} } = schema;
|
|
1470
|
+
return Object.keys(content).map((c) => ({
|
|
1471
|
+
type: c,
|
|
1472
|
+
schema: content[c].schema && this.getSchemaByRef(content[c].schema, true)
|
|
1473
|
+
}));
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* OpenAPI schema to requestBody.
|
|
1477
|
+
*/
|
|
1478
|
+
getRequestBodyByRef(schema, enums = []) {
|
|
1479
|
+
if (Base.isRef(schema)) {
|
|
1480
|
+
const resolvedSchema = this.doc.components?.requestBodies?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1481
|
+
if (!resolvedSchema) return [];
|
|
1482
|
+
schema = resolvedSchema;
|
|
1483
|
+
}
|
|
1484
|
+
const { content = {} } = schema;
|
|
1485
|
+
return Object.keys(content).map((c) => ({
|
|
1486
|
+
type: c,
|
|
1487
|
+
schema: content[c].schema && this.getSchemaByRef(content[c].schema, false, enums)
|
|
1488
|
+
}));
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Transform all OpenAPI schema to Base Schema
|
|
1492
|
+
*/
|
|
1493
|
+
toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
|
|
1494
|
+
if (!schema) return { type: "unknown" };
|
|
1495
|
+
if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
|
|
1496
|
+
if (this.isOpenAPIArraySchema(schema)) {
|
|
1497
|
+
const { type, description, items, required } = schema;
|
|
1498
|
+
return {
|
|
1499
|
+
type,
|
|
1500
|
+
required: !!required,
|
|
1501
|
+
description,
|
|
1502
|
+
items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
|
|
1503
|
+
};
|
|
1504
|
+
} else {
|
|
1505
|
+
const { required = [], allOf, anyOf, description, deprecated, enum: enum_, format, oneOf, properties = {} } = schema;
|
|
1506
|
+
let { type } = schema;
|
|
1507
|
+
if (enum_ && type !== "boolean") {
|
|
1508
|
+
const enumObject = {
|
|
1509
|
+
name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
|
|
1510
|
+
enum: [...new Set(enum_)]
|
|
1511
|
+
};
|
|
1512
|
+
const sameObject = Base.findSameSchema(enumObject, enums);
|
|
1513
|
+
if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
|
|
1514
|
+
return {
|
|
1515
|
+
type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
|
|
1516
|
+
required,
|
|
1517
|
+
description,
|
|
1518
|
+
deprecated
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
if (type === void 0 && Object.keys(properties).length > 0) type = "object";
|
|
1522
|
+
return {
|
|
1523
|
+
type,
|
|
1524
|
+
required,
|
|
1525
|
+
description,
|
|
1526
|
+
deprecated,
|
|
1527
|
+
enum: enum_,
|
|
1528
|
+
format,
|
|
1529
|
+
allOf: allOf?.map((s) => Base.isRef(s) ? {
|
|
1530
|
+
...s,
|
|
1531
|
+
ref: s.$ref,
|
|
1532
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1533
|
+
} : this.toBaseSchema(s, enums)),
|
|
1534
|
+
anyOf: anyOf?.map((s) => Base.isRef(s) ? {
|
|
1535
|
+
...s,
|
|
1536
|
+
ref: s.$ref,
|
|
1537
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1538
|
+
} : this.toBaseSchema(s, enums)),
|
|
1539
|
+
oneOf: oneOf?.map((s) => Base.isRef(s) ? {
|
|
1540
|
+
...s,
|
|
1541
|
+
ref: s.$ref,
|
|
1542
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1543
|
+
} : this.toBaseSchema(s, enums)),
|
|
1544
|
+
properties: Object.keys(properties).reduce((acc, p) => {
|
|
1545
|
+
const propSchema = properties[p];
|
|
1546
|
+
return {
|
|
1547
|
+
...acc,
|
|
1548
|
+
[p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
|
|
1549
|
+
};
|
|
1550
|
+
}, {})
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
init() {
|
|
1555
|
+
const { components = {}, paths = {} } = this.doc;
|
|
1556
|
+
const enums = [];
|
|
1557
|
+
const { requestBodies = {}, responses = {}, parameters = {}, schemas = {} } = components;
|
|
1558
|
+
const schemas_ = Object.keys(schemas).reduce((acc, key) => {
|
|
1559
|
+
const schema = schemas[key];
|
|
1560
|
+
return {
|
|
1561
|
+
...acc,
|
|
1562
|
+
[key]: this.getSchemaByRef(schema, false, enums, key)
|
|
1563
|
+
};
|
|
1564
|
+
}, {});
|
|
1565
|
+
const parameters_ = Object.keys(parameters).reduce((acc, key) => {
|
|
1566
|
+
const parameter = parameters[key];
|
|
1567
|
+
return {
|
|
1568
|
+
...acc,
|
|
1569
|
+
[key]: this.getParameterByRef(parameter, enums, key)
|
|
1570
|
+
};
|
|
1571
|
+
}, {});
|
|
1572
|
+
const responses_ = Object.keys(responses).reduce((acc, key) => {
|
|
1573
|
+
const response = responses[key];
|
|
1574
|
+
return {
|
|
1575
|
+
...acc,
|
|
1576
|
+
[key]: this.getResponseByRef(response)
|
|
1577
|
+
};
|
|
1578
|
+
}, {});
|
|
1579
|
+
const requestBodies_ = Object.keys(requestBodies).reduce((acc, key) => {
|
|
1580
|
+
const requestBody = requestBodies[key];
|
|
1581
|
+
return {
|
|
1582
|
+
...acc,
|
|
1583
|
+
[key]: this.getRequestBodyByRef(requestBody, enums)
|
|
1584
|
+
};
|
|
1585
|
+
}, {});
|
|
1586
|
+
const apis = Object.keys(paths).reduce((acc, path) => {
|
|
1587
|
+
let pathObject = paths[path] ?? {};
|
|
1588
|
+
if (pathObject.$ref) {
|
|
1589
|
+
const resolved = this.resolvePathRef(pathObject.$ref);
|
|
1590
|
+
if (resolved) pathObject = resolved;
|
|
1591
|
+
}
|
|
1592
|
+
const { parameters = [], description, summary } = pathObject;
|
|
1593
|
+
const methodApis = [];
|
|
1594
|
+
Object.values(HttpMethods).forEach((method) => {
|
|
1595
|
+
const methodObject = pathObject[method];
|
|
1596
|
+
if (methodObject) {
|
|
1597
|
+
const { deprecated, operationId, responses = {}, summary: summary_, description: description_, requestBody = { content: {} } } = methodObject;
|
|
1598
|
+
const { parameters: parameters_ = [] } = methodObject;
|
|
1599
|
+
const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
|
|
1600
|
+
const baseRequestBody = this.getRequestBodyByRef(requestBody, enums);
|
|
1601
|
+
const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
|
|
1602
|
+
if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
|
|
1603
|
+
const httpCodes = Object.keys(responses);
|
|
1604
|
+
for (const code of httpCodes) if (code in responses) {
|
|
1605
|
+
const response = responses[code];
|
|
1606
|
+
const responseSchema = this.getResponseByRef(response);
|
|
1607
|
+
methodApis.push({
|
|
1608
|
+
method,
|
|
1609
|
+
operationId,
|
|
1610
|
+
summary: summary_ ?? summary,
|
|
1611
|
+
description: description_ ?? description,
|
|
1612
|
+
deprecated,
|
|
1613
|
+
parameters: uniqueParameterName.map((name) => baseParameters.find((p) => p.name === name)).filter((p) => p !== void 0),
|
|
1614
|
+
responses: responseSchema,
|
|
1615
|
+
requestBody: baseRequestBody
|
|
1616
|
+
});
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
return {
|
|
1622
|
+
...acc,
|
|
1623
|
+
[path]: methodApis
|
|
1624
|
+
};
|
|
1625
|
+
}, {});
|
|
1626
|
+
return {
|
|
1627
|
+
enums: Base.uniqueEnums(enums),
|
|
1628
|
+
schemas: schemas_,
|
|
1629
|
+
responses: responses_,
|
|
1630
|
+
parameters: parameters_,
|
|
1631
|
+
requestBodies: requestBodies_,
|
|
1632
|
+
apis
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
//#endregion
|
|
1637
|
+
//#region src/openapi/V3_1.ts
|
|
1638
|
+
var V3_1 = class {
|
|
1639
|
+
doc;
|
|
1640
|
+
constructor(doc) {
|
|
1641
|
+
this.doc = doc;
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Resolves a path $ref to the actual path object.
|
|
1645
|
+
*/
|
|
1646
|
+
resolvePathRef($ref) {
|
|
1647
|
+
const refName = Base.ref2name($ref, this.doc);
|
|
1648
|
+
return this.doc.paths?.[refName];
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Is array schema.
|
|
1652
|
+
*/
|
|
1653
|
+
isOpenAPIArraySchema(schema) {
|
|
1654
|
+
return typeof schema === "object" && schema.type === "array";
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* OpenAPI schema to base schema.
|
|
1658
|
+
*/
|
|
1659
|
+
getSchemaByRef(schema, reserveRef = false, enums = [], upLevelSchemaKey = "") {
|
|
1660
|
+
let refName = "";
|
|
1661
|
+
if (Base.isRef(schema)) {
|
|
1662
|
+
refName = Base.upperCamelCase(Base.ref2name(schema.$ref));
|
|
1663
|
+
if (reserveRef) return { type: upLevelSchemaKey + refName };
|
|
1664
|
+
if (!this.doc.components) this.doc.components = { schemas: {} };
|
|
1665
|
+
const resolvedSchema = this.doc.components.schemas?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1666
|
+
if (!resolvedSchema) throw new Error(`Schema reference not found: ${refName}`);
|
|
1667
|
+
schema = resolvedSchema;
|
|
1668
|
+
}
|
|
1669
|
+
return this.toBaseSchema(schema, enums, "", upLevelSchemaKey + refName);
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* OpenAPI parameter to base parameter.
|
|
1673
|
+
*/
|
|
1674
|
+
getParameterByRef(schema, enums = [], upLevelSchemaKey = "") {
|
|
1675
|
+
if (Base.isRef(schema)) schema = this.doc.components?.parameters?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1676
|
+
const { name, required, deprecated, description, schema: parameterSchema } = schema;
|
|
1677
|
+
if (parameterSchema && !Base.isRef(parameterSchema) && parameterSchema.enum) {
|
|
1678
|
+
const type = Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(name));
|
|
1679
|
+
const enumSchema = {
|
|
1680
|
+
name: type,
|
|
1681
|
+
enum: [...new Set(parameterSchema.enum)]
|
|
1682
|
+
};
|
|
1683
|
+
const sameEnum = Base.findSameSchema(enumSchema, enums);
|
|
1684
|
+
if (!sameEnum && Base.isValidEnumType(parameterSchema)) enums.push(enumSchema);
|
|
1685
|
+
return {
|
|
1686
|
+
name,
|
|
1687
|
+
required,
|
|
1688
|
+
description,
|
|
1689
|
+
deprecated,
|
|
1690
|
+
in: schema.in,
|
|
1691
|
+
schema: { type: sameEnum?.name ?? type }
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
return {
|
|
1695
|
+
name,
|
|
1696
|
+
required,
|
|
1697
|
+
description,
|
|
1698
|
+
deprecated,
|
|
1699
|
+
in: schema.in,
|
|
1700
|
+
schema: schema.schema && this.getSchemaByRef(schema.schema, false, enums, upLevelSchemaKey + Base.capitalize(name))
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* OpenAPI schema to base response
|
|
1705
|
+
*/
|
|
1706
|
+
getResponseByRef(schema) {
|
|
1707
|
+
if (Base.isRef(schema)) schema = this.doc.components?.responses?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1708
|
+
const { content = {} } = schema;
|
|
1709
|
+
return Object.keys(content).map((c) => ({
|
|
1710
|
+
type: c,
|
|
1711
|
+
schema: content[c].schema && this.getSchemaByRef(content[c].schema, true)
|
|
1712
|
+
}));
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* OpenAPI schema to requestBody.
|
|
1716
|
+
*/
|
|
1717
|
+
getRequestBodyByRef(schema, enums = []) {
|
|
1718
|
+
if (Base.isRef(schema)) schema = this.doc.components?.requestBodies?.[Base.ref2name(schema.$ref, this.doc)];
|
|
1719
|
+
const { content = {} } = schema;
|
|
1720
|
+
return Object.keys(content).map((c) => ({
|
|
1721
|
+
type: c,
|
|
1722
|
+
schema: content[c].schema && this.getSchemaByRef(content[c].schema, true, enums)
|
|
1723
|
+
}));
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Transform all OpenAPI schema to Base Schema
|
|
1727
|
+
*/
|
|
1728
|
+
toBaseSchema(schema, enums = [], schemaKey = "", upLevelSchemaKey = "") {
|
|
1729
|
+
if (!schema) return { type: "unknown" };
|
|
1730
|
+
if (Base.isRef(schema)) return this.getSchemaByRef(schema, true);
|
|
1731
|
+
if (this.isOpenAPIArraySchema(schema)) {
|
|
1732
|
+
const { type, description, items, required } = schema;
|
|
1733
|
+
return {
|
|
1734
|
+
type,
|
|
1735
|
+
required: !!required,
|
|
1736
|
+
description,
|
|
1737
|
+
items: this.toBaseSchema(items, enums, schemaKey, upLevelSchemaKey)
|
|
1738
|
+
};
|
|
1739
|
+
} else {
|
|
1740
|
+
const { required = [], allOf, anyOf, description, deprecated, enum: enum_, format, oneOf, properties = {} } = schema;
|
|
1741
|
+
let { type } = schema;
|
|
1742
|
+
if (enum_ && type !== "boolean") {
|
|
1743
|
+
const enumObject = {
|
|
1744
|
+
name: Base.upperCamelCase(Base.normalize(upLevelSchemaKey)) + Base.upperCamelCase(Base.normalize(schemaKey)),
|
|
1745
|
+
enum: [...new Set(enum_)]
|
|
1746
|
+
};
|
|
1747
|
+
const sameObject = Base.findSameSchema(enumObject, enums);
|
|
1748
|
+
if (!sameObject && Base.isValidEnumType(schema)) enums.push(enumObject);
|
|
1749
|
+
return {
|
|
1750
|
+
type: sameObject ? sameObject.name : Base.isBooleanEnum(schema) ? "boolean" : enumObject.name,
|
|
1751
|
+
required,
|
|
1752
|
+
description,
|
|
1753
|
+
deprecated
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
if (type === void 0 && Object.keys(properties).length > 0) type = "object";
|
|
1757
|
+
return {
|
|
1758
|
+
type,
|
|
1759
|
+
required,
|
|
1760
|
+
description,
|
|
1761
|
+
deprecated,
|
|
1762
|
+
enum: enum_,
|
|
1763
|
+
format,
|
|
1764
|
+
allOf: allOf?.map((s) => Base.isRef(s) ? {
|
|
1765
|
+
...s,
|
|
1766
|
+
ref: s.$ref,
|
|
1767
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1768
|
+
} : this.toBaseSchema(s, enums)),
|
|
1769
|
+
anyOf: anyOf?.map((s) => Base.isRef(s) ? {
|
|
1770
|
+
...s,
|
|
1771
|
+
ref: s.$ref,
|
|
1772
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1773
|
+
} : this.toBaseSchema(s, enums)),
|
|
1774
|
+
oneOf: oneOf?.map((s) => Base.isRef(s) ? {
|
|
1775
|
+
...s,
|
|
1776
|
+
ref: s.$ref,
|
|
1777
|
+
type: Base.capitalize(Base.ref2name(s.$ref, this.doc))
|
|
1778
|
+
} : this.toBaseSchema(s, enums)),
|
|
1779
|
+
properties: Object.keys(properties).reduce((acc, p) => {
|
|
1780
|
+
const propSchema = properties[p];
|
|
1781
|
+
return {
|
|
1782
|
+
...acc,
|
|
1783
|
+
[p]: Base.isRef(propSchema) ? { type: Base.capitalize(Base.ref2name(propSchema.$ref, this.doc)) } : this.toBaseSchema(propSchema, enums, p, upLevelSchemaKey)
|
|
1784
|
+
};
|
|
1785
|
+
}, {})
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
init() {
|
|
1790
|
+
const { components = {}, paths = {} } = this.doc;
|
|
1791
|
+
const enums = [];
|
|
1792
|
+
const { requestBodies = {}, responses = {}, parameters = {}, schemas = {} } = components;
|
|
1793
|
+
const schemas_ = Object.keys(schemas).reduce((acc, key) => {
|
|
1794
|
+
const schema = schemas[key];
|
|
1795
|
+
return {
|
|
1796
|
+
...acc,
|
|
1797
|
+
[key]: this.getSchemaByRef(schema, false, enums, key)
|
|
1798
|
+
};
|
|
1799
|
+
}, {});
|
|
1800
|
+
const parameters_ = Object.keys(parameters).reduce((acc, key) => {
|
|
1801
|
+
const parameter = parameters[key];
|
|
1802
|
+
return {
|
|
1803
|
+
...acc,
|
|
1804
|
+
[key]: this.getParameterByRef(parameter, enums, key)
|
|
1805
|
+
};
|
|
1806
|
+
}, {});
|
|
1807
|
+
const responses_ = Object.keys(responses).reduce((acc, key) => {
|
|
1808
|
+
const response = responses[key];
|
|
1809
|
+
return {
|
|
1810
|
+
...acc,
|
|
1811
|
+
[key]: this.getResponseByRef(response)
|
|
1812
|
+
};
|
|
1813
|
+
}, {});
|
|
1814
|
+
const requestBodies_ = Object.keys(requestBodies).reduce((acc, key) => {
|
|
1815
|
+
const requestBody = requestBodies[key];
|
|
1816
|
+
return {
|
|
1817
|
+
...acc,
|
|
1818
|
+
[key]: this.getRequestBodyByRef(requestBody, enums)
|
|
1819
|
+
};
|
|
1820
|
+
}, {});
|
|
1821
|
+
const apis = Object.keys(paths).reduce((acc, path) => {
|
|
1822
|
+
let pathObject = paths[path] ?? {};
|
|
1823
|
+
if (pathObject.$ref) {
|
|
1824
|
+
const resolved = this.resolvePathRef(pathObject.$ref);
|
|
1825
|
+
if (resolved) pathObject = resolved;
|
|
1826
|
+
}
|
|
1827
|
+
const { parameters = [], description, summary } = pathObject;
|
|
1828
|
+
const methodApis = [];
|
|
1829
|
+
Object.values(HttpMethods).forEach((method) => {
|
|
1830
|
+
const methodObject = pathObject[method];
|
|
1831
|
+
if (methodObject) {
|
|
1832
|
+
const { deprecated, operationId, summary: summary_, description: description_, responses = {}, requestBody = { content: {} } } = methodObject;
|
|
1833
|
+
const { parameters: parameters_ = [] } = methodObject;
|
|
1834
|
+
const baseParameters = [...parameters, ...parameters_].map((parameter) => this.getParameterByRef(parameter, enums));
|
|
1835
|
+
const baseRequestBody = this.getRequestBodyByRef(requestBody, enums);
|
|
1836
|
+
const uniqueParameterName = [...new Set(baseParameters.map((p) => p.name))];
|
|
1837
|
+
if (Object.keys(responses).length === 0) Object.assign(responses, { 200: { description: "Successful response" } });
|
|
1838
|
+
const httpCodes = Object.keys(responses);
|
|
1839
|
+
for (const code of httpCodes) if (code in responses) {
|
|
1840
|
+
const response = responses[code];
|
|
1841
|
+
const responseSchema = this.getResponseByRef(response);
|
|
1842
|
+
methodApis.push({
|
|
1843
|
+
method,
|
|
1844
|
+
operationId,
|
|
1845
|
+
summary: summary_ ?? summary,
|
|
1846
|
+
description: description_ ?? description,
|
|
1847
|
+
deprecated,
|
|
1848
|
+
parameters: uniqueParameterName.map((name) => baseParameters.find((p) => p.name === name)).filter((p) => p !== void 0),
|
|
1849
|
+
responses: responseSchema,
|
|
1850
|
+
requestBody: baseRequestBody
|
|
1851
|
+
});
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
return {
|
|
1857
|
+
...acc,
|
|
1858
|
+
[path]: methodApis
|
|
1859
|
+
};
|
|
1860
|
+
}, {});
|
|
1861
|
+
return {
|
|
1862
|
+
enums: Base.uniqueEnums(enums),
|
|
1863
|
+
schemas: schemas_,
|
|
1864
|
+
responses: responses_,
|
|
1865
|
+
parameters: parameters_,
|
|
1866
|
+
requestBodies: requestBodies_,
|
|
1867
|
+
apis
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
//#endregion
|
|
1872
|
+
//#region src/openapi/index.ts
|
|
1873
|
+
const logger$1 = createScopedLogger("OpenAPI");
|
|
1874
|
+
let OpenAPIVersion = /* @__PURE__ */ function(OpenAPIVersion) {
|
|
1875
|
+
OpenAPIVersion["v2"] = "v2";
|
|
1876
|
+
OpenAPIVersion["v3"] = "v3";
|
|
1877
|
+
OpenAPIVersion["v3_1"] = "v3_1";
|
|
1878
|
+
OpenAPIVersion["unknown"] = "unknown";
|
|
1879
|
+
return OpenAPIVersion;
|
|
1880
|
+
}({});
|
|
1881
|
+
function getDocVersion(doc) {
|
|
1882
|
+
switch ((doc.openapi || doc.swagger).slice(0, 3)) {
|
|
1883
|
+
case "3.1": return "v3_1";
|
|
1884
|
+
case "3.0": return "v3";
|
|
1885
|
+
case "2.0": return "v2";
|
|
1886
|
+
default: return "unknown";
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
var OpenAPIProvider = class extends Provider {
|
|
1890
|
+
parse(doc) {
|
|
1891
|
+
const version = getDocVersion(doc);
|
|
1892
|
+
logger$1.debug(`openapi version ${version}`);
|
|
1893
|
+
switch (version) {
|
|
1894
|
+
case "v2": return new V2(doc).init();
|
|
1895
|
+
case "v3": return new V3(doc).init();
|
|
1896
|
+
case "v3_1": return new V3_1(doc).init();
|
|
1897
|
+
default: throw new Error(`Not a valid OpenAPI version: ${version}`);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
function getAdaptor(type) {
|
|
1902
|
+
switch (type) {
|
|
1903
|
+
case "axios": return new AxiosAdapter();
|
|
1904
|
+
default: return new FetchAdapter();
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async function codeGen(initOptions) {
|
|
1908
|
+
const startTime = Date.now();
|
|
1909
|
+
const { verbose } = initOptions;
|
|
1910
|
+
if (verbose) logger$1.setLevel("debug");
|
|
1911
|
+
else logger$1.setLevel("info");
|
|
1912
|
+
logger$1.info(`Fetch document from ${initOptions.docURL}`);
|
|
1913
|
+
const { enums, schemas, parameters, responses, requestBodies, apis } = new OpenAPIProvider(initOptions, await Base.fetchDoc(initOptions.docURL, initOptions.requestOptions));
|
|
1914
|
+
const adaptor = getAdaptor(initOptions.adaptor ?? "fetch");
|
|
1915
|
+
const code = await Generator.genCode({
|
|
1916
|
+
enums,
|
|
1917
|
+
schemas,
|
|
1918
|
+
parameters,
|
|
1919
|
+
responses,
|
|
1920
|
+
requestBodies,
|
|
1921
|
+
apis
|
|
1922
|
+
}, initOptions, adaptor);
|
|
1923
|
+
if (initOptions.output) await Generator.write(code, initOptions.output);
|
|
1924
|
+
const duration = Date.now() - startTime;
|
|
1925
|
+
return {
|
|
1926
|
+
code,
|
|
1927
|
+
stats: {
|
|
1928
|
+
endpoints: Object.keys(apis).length,
|
|
1929
|
+
schemas: Object.keys(schemas).length,
|
|
1930
|
+
duration
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region src/vite-plugin/index.ts
|
|
1936
|
+
const PLUGIN_NAME = "api-code-gen";
|
|
1937
|
+
const logger = createScopedLogger("api-code-gen");
|
|
1938
|
+
/**
|
|
1939
|
+
* Run TypeScript type checking on generated file
|
|
1940
|
+
*/
|
|
1941
|
+
async function runTypeCheck(filePath) {
|
|
1942
|
+
const { execaCommand } = await import("execa");
|
|
1943
|
+
const errors = [];
|
|
1944
|
+
try {
|
|
1945
|
+
await execaCommand(`npx tsc ${filePath} --noEmit`, { shell: true });
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
if (error instanceof Error) errors.push(error.message);
|
|
1948
|
+
}
|
|
1949
|
+
return errors;
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Validate spec path exists
|
|
1953
|
+
*/
|
|
1954
|
+
async function validateSpecPath(specPath) {
|
|
1955
|
+
if (specPath.startsWith("http://") || specPath.startsWith("https://")) return;
|
|
1956
|
+
const filePath = specPath.replace(/^file:\/\//, "");
|
|
1957
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
1958
|
+
if (!await fs.pathExists(absolutePath)) throw createErrors.specNotFound(absolutePath);
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Generate code for a single API configuration
|
|
1962
|
+
*/
|
|
1963
|
+
async function generateForOption(option) {
|
|
1964
|
+
const { name, typeCheck = true, verbose, ...restOptions } = option;
|
|
1965
|
+
try {
|
|
1966
|
+
console.log(`\x1b[36m├─\x1b[0m ${name}`);
|
|
1967
|
+
const config = await loadConfig({
|
|
1968
|
+
name,
|
|
1969
|
+
cliOptions: {
|
|
1970
|
+
...restOptions,
|
|
1971
|
+
verbose
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
await validateSpecPath(config.spec);
|
|
1975
|
+
if (config.output) {
|
|
1976
|
+
const outputDir = path.dirname(config.output);
|
|
1977
|
+
await fs.ensureDir(outputDir);
|
|
1978
|
+
}
|
|
1979
|
+
let docURL = config.spec;
|
|
1980
|
+
if (!docURL.startsWith("http://") && !docURL.startsWith("https://")) if (docURL.startsWith("/") || docURL.match(/^[A-Za-z]:/)) docURL = `file://${docURL}`;
|
|
1981
|
+
else docURL = path.resolve(process.cwd(), docURL);
|
|
1982
|
+
const result = await codeGen({
|
|
1983
|
+
...toProviderOptions(config),
|
|
1984
|
+
docURL
|
|
1985
|
+
});
|
|
1986
|
+
if (config.output) await fs.writeFile(config.output, result.code);
|
|
1987
|
+
if (typeCheck && config.output) {
|
|
1988
|
+
const typeErrors = await runTypeCheck(config.output);
|
|
1989
|
+
if (typeErrors.length > 0) {
|
|
1990
|
+
logger.warn(`Type check failed for ${config.output}`);
|
|
1991
|
+
if (verbose) for (const error of typeErrors) logger.warn(` ${error}`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
return {
|
|
1995
|
+
success: true,
|
|
1996
|
+
name,
|
|
1997
|
+
output: config.output,
|
|
1998
|
+
stats: result.stats
|
|
1999
|
+
};
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
return {
|
|
2002
|
+
success: false,
|
|
2003
|
+
name,
|
|
2004
|
+
error
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Main Vite plugin function
|
|
2010
|
+
*
|
|
2011
|
+
* @example
|
|
2012
|
+
* ```ts
|
|
2013
|
+
* // vite.config.ts
|
|
2014
|
+
* import { apiCodeGenPlugin } from '@moccona/apicodegen/vite';
|
|
2015
|
+
*
|
|
2016
|
+
* export default defineConfig({
|
|
2017
|
+
* plugins: [
|
|
2018
|
+
* apiCodeGenPlugin([
|
|
2019
|
+
* {
|
|
2020
|
+
* name: 'my-api',
|
|
2021
|
+
* spec: './openapi.json',
|
|
2022
|
+
* output: './src/api/generated.ts',
|
|
2023
|
+
* baseURL: 'https://api.example.com',
|
|
2024
|
+
* },
|
|
2025
|
+
* ]),
|
|
2026
|
+
* ],
|
|
2027
|
+
* });
|
|
2028
|
+
* ```
|
|
2029
|
+
*/
|
|
2030
|
+
function apiCodeGenPlugin(options) {
|
|
2031
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
2032
|
+
logger.warn("No API configurations provided to apiCodeGenPlugin");
|
|
2033
|
+
return { name: PLUGIN_NAME };
|
|
2034
|
+
}
|
|
2035
|
+
return {
|
|
2036
|
+
name: PLUGIN_NAME,
|
|
2037
|
+
async config(_config, env) {
|
|
2038
|
+
console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
|
|
2039
|
+
console.log(`\x1b[1m\x1b[36mAPI Code Gen\x1b[0m`);
|
|
2040
|
+
console.log(`\x1b[90mMode:\x1b[0m ${env?.command || "unknown"}`);
|
|
2041
|
+
console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
|
|
2042
|
+
const results = await Promise.all(options.map(generateForOption));
|
|
2043
|
+
const successCount = results.filter((r) => r.success).length;
|
|
2044
|
+
const failCount = options.length - successCount;
|
|
2045
|
+
console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
|
|
2046
|
+
for (const result of results) if (result.success) {
|
|
2047
|
+
const { name, output, stats } = result;
|
|
2048
|
+
if (stats) console.log(`\x1b[32m✓\x1b[0m ${name} → ${output} (${stats.endpoints} endpoints, ${stats.schemas} schemas) ${stats.duration}ms`);
|
|
2049
|
+
else console.log(`\x1b[32m✓\x1b[0m ${name} → ${output || "N/A"}`);
|
|
2050
|
+
} else {
|
|
2051
|
+
const { name, error } = result;
|
|
2052
|
+
if (isApicodegenError(error)) {
|
|
2053
|
+
console.log(`\x1b[31m✗\x1b[0m ${name}`);
|
|
2054
|
+
console.log(`\x1b[90m${formatError(error, true)}\x1b[0m`);
|
|
2055
|
+
} else {
|
|
2056
|
+
const wrapped = wrapError(error, {
|
|
2057
|
+
code: "E_GENERATION_FAILED",
|
|
2058
|
+
message: `Failed to generate API "${name}"`
|
|
2059
|
+
});
|
|
2060
|
+
console.log(`\x1b[31m✗\x1b[0m ${name}`);
|
|
2061
|
+
console.log(`\x1b[90m${formatError(wrapped, true)}\x1b[0m`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
|
|
2065
|
+
const totalDuration = results.reduce((sum, r) => sum + (r.stats?.duration || 0), 0);
|
|
2066
|
+
const totalEndpoints = results.reduce((sum, r) => sum + (r.stats?.endpoints || 0), 0);
|
|
2067
|
+
const totalSchemas = results.reduce((sum, r) => sum + (r.stats?.schemas || 0), 0);
|
|
2068
|
+
if (failCount === 0) console.log(`\x1b[32m✓\x1b[0m API Code Gen - Complete (\x1b[90m${successCount}/${options.length} succeeded\x1b[0m, ${totalEndpoints} endpoints, ${totalSchemas} schemas, ${totalDuration}ms\x1b[0m)`);
|
|
2069
|
+
else console.log(`\x1b[33m⚠\x1b[0m API Code Gen - Complete (\x1b[90m${successCount} succeeded, ${failCount} failed\x1b[0m, ${totalEndpoints} endpoints, ${totalSchemas} schemas, ${totalDuration}ms\x1b[0m)`);
|
|
2070
|
+
console.log(`\x1b[1m\x1b[36m${"─".repeat(50)}\x1b[0m`);
|
|
2071
|
+
return {};
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
//#endregion
|
|
2076
|
+
export { Adapter, Adaptors, ApicodegenError, ArraySchemaType, AxiosAdapter, Base, Colors, ErrorCodes, FetchAdapter, Generator, HttpMethods, MediaTypes, NonArraySchemaType, OpenAPIProvider, OpenAPIVersion, ParameterIn, Provider, SchemaFormatType, SchemaType, SuccessHttpStatusCode, apiCodeGenPlugin, codeGen, configToCLIOptions, createErrors, formatError, isApicodegenError, loadConfig, printError, toProviderOptions, wrapError };
|
|
2077
|
+
|
|
2078
|
+
//# sourceMappingURL=index.mjs.map
|