@ygorazambuja/sauron 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +570 -0
- package/bin.ts +15 -0
- package/docs/plugins.md +154 -0
- package/package.json +59 -0
- package/src/cli/args.ts +196 -0
- package/src/cli/config.ts +228 -0
- package/src/cli/main.ts +276 -0
- package/src/cli/project.ts +113 -0
- package/src/cli/types.ts +22 -0
- package/src/generators/angular.ts +76 -0
- package/src/generators/fetch.ts +994 -0
- package/src/generators/missing-definitions.ts +776 -0
- package/src/generators/type-coverage.ts +938 -0
- package/src/index.ts +47 -0
- package/src/plugins/builtin/angular.ts +146 -0
- package/src/plugins/builtin/axios.ts +307 -0
- package/src/plugins/builtin/fetch.ts +140 -0
- package/src/plugins/registry.ts +84 -0
- package/src/plugins/runner.ts +174 -0
- package/src/plugins/types.ts +97 -0
- package/src/schemas/swagger.ts +134 -0
- package/src/utils/index.ts +1599 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { SwaggerOrOpenAPISchema } from "../schemas/swagger";
|
|
3
|
+
import type {
|
|
4
|
+
OpenApiOperation,
|
|
5
|
+
OpenApiPath,
|
|
6
|
+
OpenApiSchema,
|
|
7
|
+
OperationTypeInfo,
|
|
8
|
+
OperationTypeMap,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate method name.
|
|
13
|
+
* @param path Input parameter `path`.
|
|
14
|
+
* @param httpMethod Input parameter `httpMethod`.
|
|
15
|
+
* @param operation Input parameter `operation`.
|
|
16
|
+
* @returns Generate method name output as `string`.
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const result = generateMethodName("value", "value", {});
|
|
20
|
+
* // result: string
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function generateMethodName(
|
|
24
|
+
path: string,
|
|
25
|
+
httpMethod: string,
|
|
26
|
+
operation: OpenApiOperation,
|
|
27
|
+
): string {
|
|
28
|
+
const pathParts = path.split("/").filter((part) => part && part !== "api");
|
|
29
|
+
const tags = operation.tags || [];
|
|
30
|
+
|
|
31
|
+
const baseName = resolveBaseMethodName(pathParts, tags);
|
|
32
|
+
|
|
33
|
+
const sanitizedBaseName = baseName.replace(/[^a-zA-Z0-9]/g, "");
|
|
34
|
+
const methodPrefix = httpMethod.charAt(0).toUpperCase() + httpMethod.slice(1);
|
|
35
|
+
|
|
36
|
+
const hasPathParams = path.includes("{");
|
|
37
|
+
const hasQueryParams =
|
|
38
|
+
operation.parameters?.some((p) => p.in === "query") || false;
|
|
39
|
+
const hasBody = !!operation.requestBody;
|
|
40
|
+
const additionalSuffix = resolveMethodSuffix(
|
|
41
|
+
httpMethod,
|
|
42
|
+
hasPathParams,
|
|
43
|
+
hasQueryParams,
|
|
44
|
+
hasBody,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return methodPrefix + sanitizedBaseName + additionalSuffix;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve base method name.
|
|
52
|
+
* @param pathParts Input parameter `pathParts`.
|
|
53
|
+
* @param tags Input parameter `tags`.
|
|
54
|
+
* @returns Resolve base method name output as `string`.
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const result = resolveBaseMethodName([], []);
|
|
58
|
+
* // result: string
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
function resolveBaseMethodName(pathParts: string[], tags: string[]): string {
|
|
62
|
+
if (pathParts.length > 1) {
|
|
63
|
+
return pathParts
|
|
64
|
+
.map((part) => {
|
|
65
|
+
if (part.startsWith("{")) {
|
|
66
|
+
return `By${part.slice(1, -1).charAt(0).toUpperCase()}${part.slice(2, -1)}`;
|
|
67
|
+
}
|
|
68
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
69
|
+
})
|
|
70
|
+
.join("");
|
|
71
|
+
}
|
|
72
|
+
if (tags.length > 0) {
|
|
73
|
+
return tags
|
|
74
|
+
.map((tag) => tag.charAt(0).toUpperCase() + tag.slice(1))
|
|
75
|
+
.join("");
|
|
76
|
+
}
|
|
77
|
+
return "Api";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve method suffix.
|
|
82
|
+
* @param httpMethod Input parameter `httpMethod`.
|
|
83
|
+
* @param hasPathParams Input parameter `hasPathParams`.
|
|
84
|
+
* @param hasQueryParams Input parameter `hasQueryParams`.
|
|
85
|
+
* @param hasBody Input parameter `hasBody`.
|
|
86
|
+
* @returns Resolve method suffix output as `string`.
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const result = resolveMethodSuffix("value", true, true, true);
|
|
90
|
+
* // result: string
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function resolveMethodSuffix(
|
|
94
|
+
httpMethod: string,
|
|
95
|
+
hasPathParams: boolean,
|
|
96
|
+
hasQueryParams: boolean,
|
|
97
|
+
hasBody: boolean,
|
|
98
|
+
): string {
|
|
99
|
+
if (hasPathParams && httpMethod === "get") {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
if (hasQueryParams && httpMethod === "get") {
|
|
103
|
+
return "WithParams";
|
|
104
|
+
}
|
|
105
|
+
if (hasBody && ["post", "put", "patch"].includes(httpMethod)) {
|
|
106
|
+
return "Create";
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* To pascal case.
|
|
113
|
+
* @param value Input parameter `value`.
|
|
114
|
+
* @returns To pascal case output as `string`.
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const result = toPascalCase("value");
|
|
118
|
+
* // result: string
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
function toPascalCase(value: string): string {
|
|
122
|
+
const sanitized = value
|
|
123
|
+
.replace(/[^a-zA-Z0-9]+/g, " ")
|
|
124
|
+
.split(" ")
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
127
|
+
.join("");
|
|
128
|
+
|
|
129
|
+
if (!sanitized) {
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
134
|
+
return `Type${sanitized}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return sanitized;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Sanitize type name.
|
|
142
|
+
* @param value Input parameter `value`.
|
|
143
|
+
* @returns Sanitize type name output as `string`.
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* const result = sanitizeTypeName("value");
|
|
147
|
+
* // result: string
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function sanitizeTypeName(value: string): string {
|
|
151
|
+
const sanitized = toPascalCase(value);
|
|
152
|
+
return sanitized || "Type";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Resolve type name.
|
|
157
|
+
* @param value Input parameter `value`.
|
|
158
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
159
|
+
* @returns Resolve type name output as `string`.
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const result = resolveTypeName("value", new Map());
|
|
163
|
+
* // result: string
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
function resolveTypeName(
|
|
167
|
+
value: string,
|
|
168
|
+
typeNameMap?: Map<string, string>,
|
|
169
|
+
): string {
|
|
170
|
+
return typeNameMap?.get(value) ?? sanitizeTypeName(value);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Convert param schema to type.
|
|
175
|
+
* @param schema Input parameter `schema`.
|
|
176
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
177
|
+
* @returns Convert param schema to type output as `string`.
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* const result = convertParamSchemaToType({}, new Map());
|
|
181
|
+
* // result: string
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
function convertParamSchemaToType(
|
|
185
|
+
schema: any,
|
|
186
|
+
typeNameMap?: Map<string, string>,
|
|
187
|
+
): string {
|
|
188
|
+
if (!schema || typeof schema !== "object") {
|
|
189
|
+
return "any";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (schema.$ref && typeof schema.$ref === "string") {
|
|
193
|
+
const refParts = schema.$ref.split("/");
|
|
194
|
+
const rawName = refParts[refParts.length - 1];
|
|
195
|
+
return rawName ? resolveTypeName(rawName, typeNameMap) : "any";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (Array.isArray(schema.enum)) {
|
|
199
|
+
const unionValues = schema.enum
|
|
200
|
+
.map((enumValue: unknown) =>
|
|
201
|
+
typeof enumValue === "string" ? `"${enumValue}"` : String(enumValue),
|
|
202
|
+
)
|
|
203
|
+
.join(" | ");
|
|
204
|
+
return unionValues || "any";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) {
|
|
208
|
+
const variants = (schema.anyOf || schema.oneOf || [])
|
|
209
|
+
.map((variant: any) => convertParamSchemaToType(variant, typeNameMap))
|
|
210
|
+
.filter(Boolean);
|
|
211
|
+
return variants.join(" | ") || "any";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (Array.isArray(schema.allOf)) {
|
|
215
|
+
const variants = schema.allOf
|
|
216
|
+
.map((variant: any) => convertParamSchemaToType(variant, typeNameMap))
|
|
217
|
+
.filter(Boolean);
|
|
218
|
+
return variants.join(" & ") || "any";
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (schema.type === "array" && schema.items) {
|
|
222
|
+
const itemType = convertParamSchemaToType(schema.items, typeNameMap);
|
|
223
|
+
return `${itemType}[]`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (schema.type === "object" && schema.properties) {
|
|
227
|
+
const requiredProperties = Array.isArray(schema.required)
|
|
228
|
+
? schema.required
|
|
229
|
+
: [];
|
|
230
|
+
const hasExplicitRequiredList = requiredProperties.length > 0;
|
|
231
|
+
const entries = Object.entries(schema.properties as Record<string, any>);
|
|
232
|
+
|
|
233
|
+
if (entries.length === 0) {
|
|
234
|
+
return "{}";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const propertyDefinitions = entries.map(
|
|
238
|
+
([propertyName, propertySchema]) => {
|
|
239
|
+
const propertyType = convertParamSchemaToType(
|
|
240
|
+
propertySchema,
|
|
241
|
+
typeNameMap,
|
|
242
|
+
);
|
|
243
|
+
const isRequired = hasExplicitRequiredList
|
|
244
|
+
? requiredProperties.includes(propertyName)
|
|
245
|
+
: true;
|
|
246
|
+
const optionalMarker = isRequired ? "" : "?";
|
|
247
|
+
return `${propertyName}${optionalMarker}: ${propertyType};`;
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
return `{ ${propertyDefinitions.join(" ")} }`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let typeScriptType = resolvePrimitiveSchemaType(schema);
|
|
255
|
+
|
|
256
|
+
if (schema.nullable === true) {
|
|
257
|
+
typeScriptType += " | null";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return typeScriptType;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const primitiveSchemaTypeMap: Record<string, string> = {
|
|
264
|
+
string: "string",
|
|
265
|
+
number: "number",
|
|
266
|
+
integer: "number",
|
|
267
|
+
boolean: "boolean",
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Resolve primitive schema type.
|
|
272
|
+
* @param schema Input parameter `schema`.
|
|
273
|
+
* @returns Resolve primitive schema type output as `string`.
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const result = resolvePrimitiveSchemaType({});
|
|
277
|
+
* // result: string
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
function resolvePrimitiveSchemaType(schema: any): string {
|
|
281
|
+
if (schema.type === "string" && schema.format === "numeric") {
|
|
282
|
+
return "number";
|
|
283
|
+
}
|
|
284
|
+
return primitiveSchemaTypeMap[schema.type] ?? "any";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Add param type imports.
|
|
289
|
+
* @param paramTypes Input parameter `paramTypes`.
|
|
290
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* addParamTypeImports([], new Set());
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
function addParamTypeImports(paramTypes: string[], usedTypes: Set<string>) {
|
|
297
|
+
for (const type of paramTypes) {
|
|
298
|
+
const parts = type.split(/[|&]/).map((part) => part.trim());
|
|
299
|
+
for (let part of parts) {
|
|
300
|
+
while (part.endsWith("[]")) {
|
|
301
|
+
part = part.slice(0, -2);
|
|
302
|
+
}
|
|
303
|
+
if (!part) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
part === "string" ||
|
|
308
|
+
part === "number" ||
|
|
309
|
+
part === "boolean" ||
|
|
310
|
+
part === "any" ||
|
|
311
|
+
part === "unknown" ||
|
|
312
|
+
part === "object" ||
|
|
313
|
+
part === "null" ||
|
|
314
|
+
part === "undefined" ||
|
|
315
|
+
part === "Date"
|
|
316
|
+
) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (
|
|
320
|
+
part.startsWith('"') ||
|
|
321
|
+
part.startsWith("'") ||
|
|
322
|
+
part.startsWith("{") ||
|
|
323
|
+
/^[0-9]/.test(part)
|
|
324
|
+
) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
usedTypes.add(part);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Build parameter info.
|
|
334
|
+
* @param path Input parameter `path`.
|
|
335
|
+
* @param operation Input parameter `operation`.
|
|
336
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* buildParameterInfo("value", {}, new Map());
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
function buildParameterInfo(
|
|
343
|
+
path: string,
|
|
344
|
+
operation: OpenApiOperation,
|
|
345
|
+
typeNameMap?: Map<string, string>,
|
|
346
|
+
) {
|
|
347
|
+
const usedNames = new Set<string>();
|
|
348
|
+
|
|
349
|
+
const makeUniqueName = (base: string, suffix: string) => {
|
|
350
|
+
if (!usedNames.has(base)) {
|
|
351
|
+
usedNames.add(base);
|
|
352
|
+
return base;
|
|
353
|
+
}
|
|
354
|
+
const candidate = `${base}${suffix}`;
|
|
355
|
+
if (!usedNames.has(candidate)) {
|
|
356
|
+
usedNames.add(candidate);
|
|
357
|
+
return candidate;
|
|
358
|
+
}
|
|
359
|
+
let counter = 2;
|
|
360
|
+
while (usedNames.has(`${candidate}${counter}`)) {
|
|
361
|
+
counter++;
|
|
362
|
+
}
|
|
363
|
+
const unique = `${candidate}${counter}`;
|
|
364
|
+
usedNames.add(unique);
|
|
365
|
+
return unique;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const pathParams: Array<{ name: string; varName: string; type: string }> = [];
|
|
369
|
+
const queryParams: Array<{
|
|
370
|
+
name: string;
|
|
371
|
+
varName: string;
|
|
372
|
+
required: boolean;
|
|
373
|
+
type: string;
|
|
374
|
+
}> = [];
|
|
375
|
+
let bodyParam: { name: string; varName: string } | null = null;
|
|
376
|
+
|
|
377
|
+
const pathParamMatches = path.match(/\{([^}]+)\}/g);
|
|
378
|
+
if (pathParamMatches) {
|
|
379
|
+
const pathParamSchemas =
|
|
380
|
+
operation.parameters?.filter((param) => param.in === "path") || [];
|
|
381
|
+
for (const match of pathParamMatches) {
|
|
382
|
+
const paramName = match.slice(1, -1);
|
|
383
|
+
usedNames.add(paramName);
|
|
384
|
+
const schema = pathParamSchemas.find(
|
|
385
|
+
(param) => param.name === paramName,
|
|
386
|
+
)?.schema;
|
|
387
|
+
const type = schema
|
|
388
|
+
? convertParamSchemaToType(schema, typeNameMap)
|
|
389
|
+
: "any";
|
|
390
|
+
pathParams.push({ name: paramName, varName: paramName, type });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (operation.parameters) {
|
|
395
|
+
for (const param of operation.parameters) {
|
|
396
|
+
if (param.in === "query") {
|
|
397
|
+
const varName = makeUniqueName(param.name, "Query");
|
|
398
|
+
queryParams.push({
|
|
399
|
+
name: param.name,
|
|
400
|
+
varName,
|
|
401
|
+
required: !!param.required,
|
|
402
|
+
type: convertParamSchemaToType(param.schema, typeNameMap),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (operation.requestBody) {
|
|
409
|
+
const varName = makeUniqueName("body", "Payload");
|
|
410
|
+
bodyParam = { name: "body", varName };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return { pathParams, queryParams, bodyParam };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Generate params interface.
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* generateParamsInterface();
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
function generateParamsInterface(
|
|
424
|
+
methodName: string,
|
|
425
|
+
queryParams: Array<{ name: string; required: boolean; type: string }>,
|
|
426
|
+
): string {
|
|
427
|
+
const props = queryParams.map((param) => {
|
|
428
|
+
const optional = param.required ? "" : "?";
|
|
429
|
+
return ` ${param.name}${optional}: ${param.type};`;
|
|
430
|
+
});
|
|
431
|
+
return `export interface ${methodName}Params {\n${props.join("\n")}\n}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Extract method parameters.
|
|
436
|
+
* @param path Input parameter `path`.
|
|
437
|
+
* @param operation Input parameter `operation`.
|
|
438
|
+
* @param typeInfo Input parameter `typeInfo`.
|
|
439
|
+
* @param _components Input parameter `_components`.
|
|
440
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
441
|
+
* @param methodName Input parameter `methodName`.
|
|
442
|
+
* @returns Extract method parameters output as `string`.
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* const result = extractMethodParameters("value", {}, {}, {}, new Map(), "value");
|
|
446
|
+
* // result: string
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
export function extractMethodParameters(
|
|
450
|
+
path: string,
|
|
451
|
+
operation: OpenApiOperation,
|
|
452
|
+
typeInfo?: OperationTypeInfo,
|
|
453
|
+
_components?: any,
|
|
454
|
+
typeNameMap?: Map<string, string>,
|
|
455
|
+
methodName?: string,
|
|
456
|
+
): string {
|
|
457
|
+
const requiredParams: string[] = [];
|
|
458
|
+
const optionalParams: string[] = [];
|
|
459
|
+
const { pathParams, queryParams, bodyParam } = buildParameterInfo(
|
|
460
|
+
path,
|
|
461
|
+
operation,
|
|
462
|
+
typeNameMap,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
for (const param of pathParams) {
|
|
466
|
+
requiredParams.push(`${param.varName}: ${param.type}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (queryParams.length > 0 && methodName) {
|
|
470
|
+
requiredParams.push(`params: ${methodName}Params`);
|
|
471
|
+
}
|
|
472
|
+
if (!(queryParams.length > 0 && methodName)) {
|
|
473
|
+
for (const param of queryParams) {
|
|
474
|
+
if (param.required) {
|
|
475
|
+
requiredParams.push(`${param.varName}: ${param.type}`);
|
|
476
|
+
}
|
|
477
|
+
if (!param.required) {
|
|
478
|
+
optionalParams.push(`${param.varName}?: ${param.type}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (bodyParam) {
|
|
484
|
+
const bodyType =
|
|
485
|
+
typeInfo?.requestType ??
|
|
486
|
+
extractRequestType(operation, typeNameMap) ??
|
|
487
|
+
"any";
|
|
488
|
+
requiredParams.push(`${bodyParam.varName}: ${bodyType}`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return [...requiredParams, ...optionalParams].join(", ");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Extract response type.
|
|
496
|
+
* @param operation Input parameter `operation`.
|
|
497
|
+
* @param _components Input parameter `_components`.
|
|
498
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
499
|
+
* @returns Extract response type output as `string`.
|
|
500
|
+
* @example
|
|
501
|
+
* ```ts
|
|
502
|
+
* const result = extractResponseType({}, {}, new Map());
|
|
503
|
+
* // result: string
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
export function extractResponseType(
|
|
507
|
+
operation: OpenApiOperation,
|
|
508
|
+
_components?: any,
|
|
509
|
+
typeNameMap?: Map<string, string>,
|
|
510
|
+
): string {
|
|
511
|
+
const response =
|
|
512
|
+
operation.responses?.["200"] ||
|
|
513
|
+
operation.responses?.["201"] ||
|
|
514
|
+
(Object.keys(operation.responses || {}).find(
|
|
515
|
+
(key) => key.startsWith("2") && operation.responses?.[key],
|
|
516
|
+
) &&
|
|
517
|
+
operation.responses?.[
|
|
518
|
+
Object.keys(operation.responses).find((key) => key.startsWith("2"))!
|
|
519
|
+
]);
|
|
520
|
+
|
|
521
|
+
if (!response || typeof response !== "object") {
|
|
522
|
+
return "any";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const content = (response as any).content;
|
|
526
|
+
if (content?.["application/json"]?.schema) {
|
|
527
|
+
const schema = content["application/json"].schema;
|
|
528
|
+
|
|
529
|
+
if (schema.$ref && typeof schema.$ref === "string") {
|
|
530
|
+
const refParts = schema.$ref.split("/");
|
|
531
|
+
const typeName = refParts[refParts.length - 1];
|
|
532
|
+
return typeName ? resolveTypeName(typeName, typeNameMap) : "any";
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (schema.type === "array" && schema.items?.$ref) {
|
|
536
|
+
const refParts = schema.items.$ref.split("/");
|
|
537
|
+
const itemTypeName = refParts[refParts.length - 1];
|
|
538
|
+
return itemTypeName
|
|
539
|
+
? `${resolveTypeName(itemTypeName, typeNameMap)}[]`
|
|
540
|
+
: "any[]";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return "any";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return "any";
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get preferred content schema.
|
|
551
|
+
* @example
|
|
552
|
+
* ```ts
|
|
553
|
+
* getPreferredContentSchema();
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
function getPreferredContentSchema(
|
|
557
|
+
content?: Record<string, { schema: OpenApiSchema }>,
|
|
558
|
+
): OpenApiSchema | undefined {
|
|
559
|
+
if (!content) {
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (content["application/json"]?.schema) {
|
|
564
|
+
return content["application/json"].schema;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const firstKey = Object.keys(content)[0];
|
|
568
|
+
return firstKey ? content[firstKey]?.schema : undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Extract request type.
|
|
573
|
+
* @param operation Input parameter `operation`.
|
|
574
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
575
|
+
* @returns Extract request type output as `string | undefined`.
|
|
576
|
+
* @example
|
|
577
|
+
* ```ts
|
|
578
|
+
* const result = extractRequestType({}, new Map());
|
|
579
|
+
* // result: string | undefined
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
function extractRequestType(
|
|
583
|
+
operation: OpenApiOperation,
|
|
584
|
+
typeNameMap?: Map<string, string>,
|
|
585
|
+
): string | undefined {
|
|
586
|
+
const schema = getPreferredContentSchema(operation.requestBody?.content);
|
|
587
|
+
if (!schema) {
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (schema.$ref && typeof schema.$ref === "string") {
|
|
592
|
+
const refParts = schema.$ref.split("/");
|
|
593
|
+
const rawName = refParts[refParts.length - 1];
|
|
594
|
+
return rawName ? resolveTypeName(rawName, typeNameMap) : undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (schema.type === "array" && schema.items?.$ref) {
|
|
598
|
+
const refParts = schema.items.$ref.split("/");
|
|
599
|
+
const itemTypeName = refParts[refParts.length - 1];
|
|
600
|
+
return itemTypeName
|
|
601
|
+
? `${resolveTypeName(itemTypeName, typeNameMap)}[]`
|
|
602
|
+
: undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return undefined;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Create fetch http methods.
|
|
610
|
+
* @param data Input parameter `data`.
|
|
611
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
612
|
+
* @param operationTypes Input parameter `operationTypes`.
|
|
613
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
614
|
+
* @returns Create fetch http methods output as `unknown`.
|
|
615
|
+
* @example
|
|
616
|
+
* ```ts
|
|
617
|
+
* const result = createFetchHttpMethods({}, new Set(), {}, new Map());
|
|
618
|
+
* // result: unknown
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
export function createFetchHttpMethods(
|
|
622
|
+
data: z.infer<typeof SwaggerOrOpenAPISchema>,
|
|
623
|
+
usedTypes?: Set<string>,
|
|
624
|
+
operationTypes?: OperationTypeMap,
|
|
625
|
+
typeNameMap?: Map<string, string>,
|
|
626
|
+
): { methods: string[]; paramsInterfaces: string[] } {
|
|
627
|
+
if (!data.paths) {
|
|
628
|
+
return { methods: [], paramsInterfaces: [] };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const methods: string[] = [];
|
|
632
|
+
const paramsInterfaces: string[] = [];
|
|
633
|
+
const pathEntries = Object.entries(data.paths);
|
|
634
|
+
|
|
635
|
+
for (const [path, pathItem] of pathEntries) {
|
|
636
|
+
const result = generateFetchMethodsForPath(
|
|
637
|
+
path,
|
|
638
|
+
pathItem as OpenApiPath,
|
|
639
|
+
data.components,
|
|
640
|
+
usedTypes,
|
|
641
|
+
operationTypes,
|
|
642
|
+
typeNameMap,
|
|
643
|
+
);
|
|
644
|
+
methods.push(...result.methods);
|
|
645
|
+
paramsInterfaces.push(...result.paramsInterfaces);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return { methods, paramsInterfaces };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Generate fetch methods for path.
|
|
653
|
+
* @param path Input parameter `path`.
|
|
654
|
+
* @param operations Input parameter `operations`.
|
|
655
|
+
* @param components Input parameter `components`.
|
|
656
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
657
|
+
* @param operationTypes Input parameter `operationTypes`.
|
|
658
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
659
|
+
* @returns Generate fetch methods for path output as `unknown`.
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* const result = generateFetchMethodsForPath("value", {}, {}, new Set(), {}, new Map());
|
|
663
|
+
* // result: unknown
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
function generateFetchMethodsForPath(
|
|
667
|
+
path: string,
|
|
668
|
+
operations: OpenApiPath,
|
|
669
|
+
components?: any,
|
|
670
|
+
usedTypes?: Set<string>,
|
|
671
|
+
operationTypes?: OperationTypeMap,
|
|
672
|
+
typeNameMap?: Map<string, string>,
|
|
673
|
+
): { methods: string[]; paramsInterfaces: string[] } {
|
|
674
|
+
const methods: string[] = [];
|
|
675
|
+
const paramsInterfaces: string[] = [];
|
|
676
|
+
const httpMethods = [
|
|
677
|
+
"get",
|
|
678
|
+
"post",
|
|
679
|
+
"put",
|
|
680
|
+
"delete",
|
|
681
|
+
"patch",
|
|
682
|
+
"head",
|
|
683
|
+
"options",
|
|
684
|
+
] as const;
|
|
685
|
+
|
|
686
|
+
for (const httpMethod of httpMethods) {
|
|
687
|
+
if (operations[httpMethod]) {
|
|
688
|
+
const result = generateFetchMethod(
|
|
689
|
+
path,
|
|
690
|
+
httpMethod,
|
|
691
|
+
operations[httpMethod],
|
|
692
|
+
components,
|
|
693
|
+
usedTypes,
|
|
694
|
+
operationTypes,
|
|
695
|
+
typeNameMap,
|
|
696
|
+
);
|
|
697
|
+
if (result) {
|
|
698
|
+
methods.push(result.method);
|
|
699
|
+
if (result.paramsInterface) {
|
|
700
|
+
paramsInterfaces.push(result.paramsInterface);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return { methods, paramsInterfaces };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Generate fetch method.
|
|
711
|
+
* @param path Input parameter `path`.
|
|
712
|
+
* @param httpMethod Input parameter `httpMethod`.
|
|
713
|
+
* @param operation Input parameter `operation`.
|
|
714
|
+
* @param components Input parameter `components`.
|
|
715
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
716
|
+
* @param operationTypes Input parameter `operationTypes`.
|
|
717
|
+
* @param typeNameMap Input parameter `typeNameMap`.
|
|
718
|
+
* @returns Generate fetch method output as `unknown`.
|
|
719
|
+
* @example
|
|
720
|
+
* ```ts
|
|
721
|
+
* const result = generateFetchMethod("value", "value", {}, {}, new Set(), {}, new Map());
|
|
722
|
+
* // result: unknown
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
725
|
+
function generateFetchMethod(
|
|
726
|
+
path: string,
|
|
727
|
+
httpMethod: string,
|
|
728
|
+
operation: OpenApiOperation,
|
|
729
|
+
components?: any,
|
|
730
|
+
usedTypes?: Set<string>,
|
|
731
|
+
operationTypes?: OperationTypeMap,
|
|
732
|
+
typeNameMap?: Map<string, string>,
|
|
733
|
+
): { method: string; paramsInterface?: string } | null {
|
|
734
|
+
try {
|
|
735
|
+
const methodName = generateMethodName(path, httpMethod, operation);
|
|
736
|
+
const paramInfo = buildParameterInfo(path, operation, typeNameMap);
|
|
737
|
+
const typeInfo = operationTypes?.[path]?.[httpMethod];
|
|
738
|
+
const parameters = extractMethodParameters(
|
|
739
|
+
path,
|
|
740
|
+
operation,
|
|
741
|
+
typeInfo,
|
|
742
|
+
components,
|
|
743
|
+
typeNameMap,
|
|
744
|
+
methodName,
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const requestType =
|
|
748
|
+
typeInfo?.requestType ?? extractRequestType(operation, typeNameMap);
|
|
749
|
+
let responseType =
|
|
750
|
+
typeInfo?.responseType ??
|
|
751
|
+
extractResponseType(operation, components, typeNameMap);
|
|
752
|
+
if (
|
|
753
|
+
responseType === "any" &&
|
|
754
|
+
requestType &&
|
|
755
|
+
["post", "put", "patch"].includes(httpMethod)
|
|
756
|
+
) {
|
|
757
|
+
responseType = requestType;
|
|
758
|
+
}
|
|
759
|
+
const returnType =
|
|
760
|
+
responseType !== "any" ? `Promise<${responseType}>` : "Promise<any>";
|
|
761
|
+
|
|
762
|
+
if (requestType) {
|
|
763
|
+
usedTypes?.add(requestType);
|
|
764
|
+
}
|
|
765
|
+
if (usedTypes && responseType !== "any" && !responseType.includes("[]")) {
|
|
766
|
+
usedTypes.add(responseType);
|
|
767
|
+
}
|
|
768
|
+
if (usedTypes && responseType.includes("[]")) {
|
|
769
|
+
const baseType = responseType.replace("[]", "");
|
|
770
|
+
usedTypes.add(baseType);
|
|
771
|
+
}
|
|
772
|
+
if (usedTypes) {
|
|
773
|
+
const paramTypes = [
|
|
774
|
+
...paramInfo.pathParams.map((param) => param.type),
|
|
775
|
+
...paramInfo.queryParams.map((param) => param.type),
|
|
776
|
+
];
|
|
777
|
+
addParamTypeImports(paramTypes, usedTypes);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
let paramsInterface: string | undefined;
|
|
781
|
+
const queryParams = paramInfo.queryParams || [];
|
|
782
|
+
const hasQueryParams = queryParams.length > 0;
|
|
783
|
+
if (hasQueryParams) {
|
|
784
|
+
paramsInterface = generateParamsInterface(methodName, queryParams);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const hasPathParams = path.includes("{");
|
|
788
|
+
|
|
789
|
+
if (hasQueryParams) {
|
|
790
|
+
const queryStringLine = `const queryString = qs.stringify({ ...params }, { skipNull: true, skipEmptyString: true });`;
|
|
791
|
+
const queryUrl = createQueryUrl(path, hasPathParams);
|
|
792
|
+
return {
|
|
793
|
+
method: buildFetchMethodWithQueryString(
|
|
794
|
+
methodName,
|
|
795
|
+
parameters,
|
|
796
|
+
returnType,
|
|
797
|
+
queryUrl,
|
|
798
|
+
operation,
|
|
799
|
+
paramInfo,
|
|
800
|
+
queryStringLine,
|
|
801
|
+
httpMethod,
|
|
802
|
+
),
|
|
803
|
+
paramsInterface,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const url = createPathUrl(path, hasPathParams);
|
|
808
|
+
|
|
809
|
+
const fetchOptions: string[] = [];
|
|
810
|
+
fetchOptions.push(`method: '${httpMethod.toUpperCase()}'`);
|
|
811
|
+
fetchOptions.push(`headers: {
|
|
812
|
+
'Content-Type': 'application/json',
|
|
813
|
+
}`);
|
|
814
|
+
|
|
815
|
+
if (operation.requestBody) {
|
|
816
|
+
const bodyVar = paramInfo.bodyParam?.varName || "body";
|
|
817
|
+
fetchOptions.push(`body: JSON.stringify(${bodyVar})`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const optionsString = fetchOptions.join(",\n ");
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
method: ` async ${methodName}(${parameters}): ${returnType} {
|
|
824
|
+
const response = await fetch(${url}, {
|
|
825
|
+
${optionsString}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
if (!response.ok) {
|
|
829
|
+
throw new Error(\`HTTP error! status: \${response.status}\`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return await response.json();
|
|
833
|
+
}`,
|
|
834
|
+
paramsInterface,
|
|
835
|
+
};
|
|
836
|
+
} catch (error) {
|
|
837
|
+
console.warn(
|
|
838
|
+
`Warning: Could not generate fetch method for ${httpMethod.toUpperCase()} ${path}:`,
|
|
839
|
+
error,
|
|
840
|
+
);
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Build fetch method with query string.
|
|
847
|
+
* @example
|
|
848
|
+
* ```ts
|
|
849
|
+
* buildFetchMethodWithQueryString();
|
|
850
|
+
* ```
|
|
851
|
+
*/
|
|
852
|
+
function buildFetchMethodWithQueryString(
|
|
853
|
+
methodName: string,
|
|
854
|
+
parameters: string,
|
|
855
|
+
returnType: string,
|
|
856
|
+
url: string,
|
|
857
|
+
operation: OpenApiOperation,
|
|
858
|
+
paramInfo: { bodyParam: { varName: string } | null },
|
|
859
|
+
queryStringLine: string,
|
|
860
|
+
httpMethod: string,
|
|
861
|
+
): string {
|
|
862
|
+
const fetchOptions: string[] = [];
|
|
863
|
+
fetchOptions.push(`method: '${httpMethod.toUpperCase()}'`);
|
|
864
|
+
fetchOptions.push(`headers: {
|
|
865
|
+
'Content-Type': 'application/json',
|
|
866
|
+
}`);
|
|
867
|
+
|
|
868
|
+
if (operation.requestBody) {
|
|
869
|
+
const bodyVar = paramInfo.bodyParam?.varName || "body";
|
|
870
|
+
fetchOptions.push(`body: JSON.stringify(${bodyVar})`);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const optionsString = fetchOptions.join(",\n ");
|
|
874
|
+
|
|
875
|
+
return ` async ${methodName}(${parameters}): ${returnType} {
|
|
876
|
+
${queryStringLine}
|
|
877
|
+
const response = await fetch(${url}, {
|
|
878
|
+
${optionsString}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
if (!response.ok) {
|
|
882
|
+
throw new Error(\`HTTP error! status: \${response.status}\`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return await response.json();
|
|
886
|
+
}`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Create path url.
|
|
891
|
+
* @param path Input parameter `path`.
|
|
892
|
+
* @param hasPathParams Input parameter `hasPathParams`.
|
|
893
|
+
* @returns Create path url output as `string`.
|
|
894
|
+
* @example
|
|
895
|
+
* ```ts
|
|
896
|
+
* const result = createPathUrl("value", true);
|
|
897
|
+
* // result: string
|
|
898
|
+
* ```
|
|
899
|
+
*/
|
|
900
|
+
function createPathUrl(path: string, hasPathParams: boolean): string {
|
|
901
|
+
if (!hasPathParams) {
|
|
902
|
+
return `this.buildUrl(\`${path}\`)`;
|
|
903
|
+
}
|
|
904
|
+
const pathWithParams = path.replace(/\{([^}]+)\}/g, "${$1}");
|
|
905
|
+
return `this.buildUrl(\`${pathWithParams}\`)`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Create query url.
|
|
910
|
+
* @param path Input parameter `path`.
|
|
911
|
+
* @param hasPathParams Input parameter `hasPathParams`.
|
|
912
|
+
* @returns Create query url output as `string`.
|
|
913
|
+
* @example
|
|
914
|
+
* ```ts
|
|
915
|
+
* const result = createQueryUrl("value", true);
|
|
916
|
+
* // result: string
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
919
|
+
function createQueryUrl(path: string, hasPathParams: boolean): string {
|
|
920
|
+
if (!hasPathParams) {
|
|
921
|
+
return `this.buildUrl(\`${path}\${queryString ? \`?\${queryString}\` : ""}\`)`;
|
|
922
|
+
}
|
|
923
|
+
const pathWithParams = path.replace(/\{([^}]+)\}/g, "${$1}");
|
|
924
|
+
return `this.buildUrl(\`${pathWithParams}\${queryString ? \`?\${queryString}\` : ""}\`)`;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Generate fetch service.
|
|
929
|
+
* @param methods Input parameter `methods`.
|
|
930
|
+
* @param _modelsPath Input parameter `_modelsPath`.
|
|
931
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
932
|
+
* @param paramsInterfaces Input parameter `paramsInterfaces`.
|
|
933
|
+
* @returns Generate fetch service output as `string`.
|
|
934
|
+
* @example
|
|
935
|
+
* ```ts
|
|
936
|
+
* const result = generateFetchService([], "value", new Set(), []);
|
|
937
|
+
* // result: string
|
|
938
|
+
* ```
|
|
939
|
+
*/
|
|
940
|
+
export function generateFetchService(
|
|
941
|
+
methods: string[],
|
|
942
|
+
_modelsPath: string,
|
|
943
|
+
usedTypes: Set<string>,
|
|
944
|
+
paramsInterfaces: string[] = [],
|
|
945
|
+
): string {
|
|
946
|
+
let importStatement = "";
|
|
947
|
+
if (usedTypes.size > 0) {
|
|
948
|
+
const importList = Array.from(usedTypes).join(", ");
|
|
949
|
+
const importPath = "../models";
|
|
950
|
+
importStatement = `import { ${importList} } from "${importPath}";\n`;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const interfacesBlock =
|
|
954
|
+
paramsInterfaces.length > 0 ? `${paramsInterfaces.join("\n\n")}\n\n` : "";
|
|
955
|
+
|
|
956
|
+
const serviceTemplate = `// Generated fetch-based HTTP client
|
|
957
|
+
import qs from "query-string";
|
|
958
|
+
${importStatement}\n${interfacesBlock}\nexport class SauronApiClient {
|
|
959
|
+
private baseUrl = ''; // Configure your base URL
|
|
960
|
+
|
|
961
|
+
constructor(baseUrl?: string) {
|
|
962
|
+
if (baseUrl) {
|
|
963
|
+
this.baseUrl = baseUrl;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
setBaseUrl(baseUrl: string): void {
|
|
968
|
+
this.baseUrl = baseUrl;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private buildUrl(path: string): string {
|
|
972
|
+
if (/^(https?:)?\\/\\//i.test(path)) {
|
|
973
|
+
return path;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const normalizedBase = this.baseUrl.replace(/\\/+$/, "");
|
|
977
|
+
const normalizedPath = path.startsWith("/") ? path : \`/\${path}\`;
|
|
978
|
+
|
|
979
|
+
if (!normalizedBase) {
|
|
980
|
+
return normalizedPath;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return \`\${normalizedBase}\${normalizedPath}\`;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
${methods.join("\n\n")}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Export a default instance
|
|
990
|
+
export const sauronApi = new SauronApiClient();
|
|
991
|
+
`;
|
|
992
|
+
|
|
993
|
+
return serviceTemplate;
|
|
994
|
+
}
|