swaggular 0.1.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/app/get-parse-args.js +48 -0
- package/dist/app/parse-variables.js +24 -0
- package/dist/builders/build-comments.js +25 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.js +48 -0
- package/dist/cli/variables.d.ts +2 -0
- package/dist/cli/variables.js +23 -0
- package/dist/core/state/interface-state.d.ts +17 -0
- package/dist/core/state/interface-state.js +21 -0
- package/dist/core/state/service-state.d.ts +9 -0
- package/dist/core/state/service-state.js +18 -0
- package/dist/core/state/swagger-state.d.ts +14 -0
- package/dist/core/state/swagger-state.js +36 -0
- package/dist/examples/services/get.example.js +1 -0
- package/dist/generators/generate-comments.js +40 -0
- package/dist/generators/generate-interface.js +219 -0
- package/dist/generators/generate-service.js +302 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +45 -0
- package/dist/models/file-content.js +2 -0
- package/dist/models/grouped-paths.js +2 -0
- package/dist/models/interface-data.js +2 -0
- package/dist/models/parsed-args.js +2 -0
- package/dist/models/service-data.js +2 -0
- package/dist/models/types.js +29 -0
- package/dist/parsers/path-grouper.d.ts +17 -0
- package/dist/parsers/path-grouper.js +207 -0
- package/dist/parsers/swagger-parser.d.ts +4 -0
- package/dist/parsers/swagger-parser.js +53 -0
- package/dist/renderers/generate-comments.d.ts +3 -0
- package/dist/renderers/generate-comments.js +40 -0
- package/dist/renderers/generate-interface.d.ts +12 -0
- package/dist/renderers/generate-interface.js +209 -0
- package/dist/renderers/generate-service.d.ts +9 -0
- package/dist/renderers/generate-service.js +178 -0
- package/dist/stores/interface-data-store.js +62 -0
- package/dist/stores/services-store.js +18 -0
- package/dist/stores/swagger-store.js +324 -0
- package/dist/templates/angular-template.js +23 -0
- package/dist/templates/paged-request-template.js +22 -0
- package/dist/templates/paged-result-template.js +13 -0
- package/dist/templates/services/angular-template.d.ts +15 -0
- package/dist/templates/services/angular-template.js +24 -0
- package/dist/templates/services/http-params-handler.d.ts +2 -0
- package/dist/templates/services/http-params-handler.js +12 -0
- package/dist/templates/types/extends-from-types.d.ts +3 -0
- package/dist/templates/types/extends-from-types.js +72 -0
- package/dist/templates/types/generic-types.d.ts +4 -0
- package/dist/templates/types/generic-types.js +72 -0
- package/dist/translators/add-import-to-interface.js +21 -0
- package/dist/translators/generate-interfaces.js +134 -0
- package/dist/translators/generate-services.js +192 -0
- package/dist/translators/join-with-templates.js +16 -0
- package/dist/translators/splitPaths.js +8 -0
- package/dist/types/file-content.d.ts +6 -0
- package/dist/types/file-content.js +2 -0
- package/dist/types/grouped-paths.d.ts +7 -0
- package/dist/types/grouped-paths.js +2 -0
- package/dist/types/interface-data.d.ts +13 -0
- package/dist/types/interface-data.js +2 -0
- package/dist/types/parsed-args.d.ts +9 -0
- package/dist/types/parsed-args.js +2 -0
- package/dist/types/service-data.d.ts +21 -0
- package/dist/types/service-data.js +2 -0
- package/dist/types/types.d.ts +23 -0
- package/dist/types/types.js +29 -0
- package/dist/utils/build-types.d.ts +2 -0
- package/dist/utils/build-types.js +66 -0
- package/dist/utils/create-file.d.ts +7 -0
- package/dist/utils/create-file.js +74 -0
- package/dist/utils/generate-imports.js +33 -0
- package/dist/utils/grouping-paths.js +68 -0
- package/dist/utils/method-builders.js +77 -0
- package/dist/utils/object-utils.d.ts +1 -0
- package/dist/utils/object-utils.js +11 -0
- package/dist/utils/path-utils.d.ts +7 -0
- package/dist/utils/path-utils.js +61 -0
- package/dist/utils/string-utils.d.ts +13 -0
- package/dist/utils/string-utils.js +47 -0
- package/dist/utils/to-case.js +47 -0
- package/dist/utils/type-guard.d.ts +5 -0
- package/dist/utils/type-guard.js +35 -0
- package/package.json +64 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateServices = generateServices;
|
|
4
|
+
exports.generateServiceFiles = generateServiceFiles;
|
|
5
|
+
exports.buildMethods = buildMethods;
|
|
6
|
+
exports.buildMethod = buildMethod;
|
|
7
|
+
exports.computeName = computeName;
|
|
8
|
+
const services_store_1 = require("../stores/services-store");
|
|
9
|
+
const swagger_store_1 = require("../stores/swagger-store");
|
|
10
|
+
const angular_template_1 = require("../templates/services/angular-template");
|
|
11
|
+
const build_types_1 = require("../utils/build-types");
|
|
12
|
+
const path_utils_1 = require("../utils/path-utils");
|
|
13
|
+
const string_utils_1 = require("../utils/string-utils");
|
|
14
|
+
const type_guard_1 = require("../utils/type-guard");
|
|
15
|
+
const generate_interface_1 = require("./generate-interface");
|
|
16
|
+
const generate_comments_1 = require("./generate-comments");
|
|
17
|
+
/**
|
|
18
|
+
* Pick the paths grouped by scope and generate services for them.
|
|
19
|
+
* Each group will generate one service data.
|
|
20
|
+
* Each service data will contain the methods for the paths.
|
|
21
|
+
*
|
|
22
|
+
* The name will be the group name concatenated with Service
|
|
23
|
+
*
|
|
24
|
+
* The imports will be the types used in the methods. Including
|
|
25
|
+
* the response type and the parameters types.
|
|
26
|
+
*
|
|
27
|
+
* The base url will be the base url of the group compound by the
|
|
28
|
+
* base segments of the group.
|
|
29
|
+
*
|
|
30
|
+
* Each method will be generated based on a path
|
|
31
|
+
*
|
|
32
|
+
* The method name will be computed based on the path.
|
|
33
|
+
* It has to follow some stantardized rules.
|
|
34
|
+
*
|
|
35
|
+
* The method parameters will be computed based on the path parameters
|
|
36
|
+
* The method path will be computed based on the path, but it will be
|
|
37
|
+
* computed as `${this.baseUrl}/something/else/{id}`
|
|
38
|
+
* The method response type will be computed based on the path response
|
|
39
|
+
* The method parameters will be computed based on the path parameters
|
|
40
|
+
* The method parameter name will be computed based on the path parameter name if
|
|
41
|
+
* the parameters has the property "in" set to "path". If the parameter has the property
|
|
42
|
+
* "in" set to "query", it will be computed as "parameters" and query parameters should be grouped
|
|
43
|
+
* in the same object. Check computeParametersName() method in generate-interface.ts to
|
|
44
|
+
* understand how the name is computed.
|
|
45
|
+
*
|
|
46
|
+
* The method parameter in property will be computed based on the path parameter "in" property
|
|
47
|
+
* The method parameter type will be computed based on the path parameter type property.
|
|
48
|
+
* Check computeParametersName() method in generate-interface.ts to
|
|
49
|
+
* understand how the name is computed.
|
|
50
|
+
* The method parameter required will be computed based on the path parameter required property
|
|
51
|
+
* @returns ServiceData[]
|
|
52
|
+
*/
|
|
53
|
+
function generateServices() {
|
|
54
|
+
const servicesData = [];
|
|
55
|
+
const groupedPaths = swagger_store_1.swaggerStore.getPathsGroupedByScope();
|
|
56
|
+
const paths = swagger_store_1.swaggerStore.getPaths();
|
|
57
|
+
if (!groupedPaths || !paths)
|
|
58
|
+
return;
|
|
59
|
+
for (const [key, value] of Object.entries(groupedPaths)) {
|
|
60
|
+
const methods = buildMethods(value);
|
|
61
|
+
const importsSet = new Set();
|
|
62
|
+
methods.forEach((method) => {
|
|
63
|
+
const allTypes = [
|
|
64
|
+
method.responseType,
|
|
65
|
+
...method.parameters.map((p) => p.type),
|
|
66
|
+
];
|
|
67
|
+
allTypes.forEach((t) => {
|
|
68
|
+
const identifiers = t.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || [];
|
|
69
|
+
identifiers.forEach((id) => {
|
|
70
|
+
if (!(0, type_guard_1.isNativeType)(id) && id !== "void") {
|
|
71
|
+
importsSet.add(id);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
const serviceData = {
|
|
77
|
+
name: key + "Service",
|
|
78
|
+
imports: Array.from(importsSet),
|
|
79
|
+
baseUrl: value.baseUrl,
|
|
80
|
+
methods: methods,
|
|
81
|
+
};
|
|
82
|
+
servicesData.push(serviceData);
|
|
83
|
+
}
|
|
84
|
+
services_store_1.serviceStore.addServices(servicesData);
|
|
85
|
+
}
|
|
86
|
+
function generateServiceFiles(locations) {
|
|
87
|
+
const services = Object.values(services_store_1.serviceStore.services);
|
|
88
|
+
const filesContent = [];
|
|
89
|
+
for (const service of services) {
|
|
90
|
+
const methodsString = service.methods
|
|
91
|
+
.map((method) => generateMethodString(method))
|
|
92
|
+
.join("\n\n");
|
|
93
|
+
const templateParams = {
|
|
94
|
+
name: service.name.replace("Service", ""),
|
|
95
|
+
baseUrl: service.baseUrl,
|
|
96
|
+
methods: methodsString,
|
|
97
|
+
imports: service.imports.join(", "),
|
|
98
|
+
hasHttpParamsHandler: service.methods.some((m) => m.parameters.some((p) => p.in === "query")),
|
|
99
|
+
};
|
|
100
|
+
// If imports are empty, we might want to clean up the template later,
|
|
101
|
+
// but for now we follow the existing template structure.
|
|
102
|
+
filesContent.push({
|
|
103
|
+
location: ["services", ...(locations?.[service.name] ?? [])],
|
|
104
|
+
name: (0, string_utils_1.toKebabCase)(service.name.replace("Service", "")) + ".service",
|
|
105
|
+
content: (0, angular_template_1.angularServiceTemplate)(templateParams),
|
|
106
|
+
extension: "ts",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return filesContent;
|
|
110
|
+
}
|
|
111
|
+
function generateMethodString(method) {
|
|
112
|
+
const paramsSignature = method.parameters
|
|
113
|
+
.map((p) => `${p.name}${p.required ? "" : "?"}: ${p.type}`)
|
|
114
|
+
.join(", ");
|
|
115
|
+
const httpMethod = method.method.toLowerCase();
|
|
116
|
+
const responseType = method.responseType;
|
|
117
|
+
// Convert {id} to ${id} in path
|
|
118
|
+
let path = method.path.replace(/{([^}]+)}/g, "${$1}");
|
|
119
|
+
if (!path.startsWith("`")) {
|
|
120
|
+
// Remove the first $
|
|
121
|
+
path = `\`${path.slice(1)}\``;
|
|
122
|
+
}
|
|
123
|
+
let body = "";
|
|
124
|
+
const queryParam = method.parameters.find((p) => p.in === "query");
|
|
125
|
+
if (queryParam) {
|
|
126
|
+
body += ` const params = HttpHelper.toHttpParams(${queryParam.name} ?? {});\n`;
|
|
127
|
+
}
|
|
128
|
+
const options = [];
|
|
129
|
+
if (queryParam)
|
|
130
|
+
options.push("params");
|
|
131
|
+
if (responseType === "Blob" || responseType === "File") {
|
|
132
|
+
options.push("responseType: 'blob'");
|
|
133
|
+
}
|
|
134
|
+
if (httpMethod === "delete" &&
|
|
135
|
+
method.parameters.some((p) => p.in === "body")) {
|
|
136
|
+
options.push("body: " + method.parameters.find((p) => p.in === "body")?.name);
|
|
137
|
+
}
|
|
138
|
+
const optionsString = options.length > 0 ? `, { ${options.join(", ")} }` : "";
|
|
139
|
+
if (httpMethod === "get" || httpMethod === "delete") {
|
|
140
|
+
body += ` return this.http.${httpMethod}<${responseType}>(${path}${optionsString});`;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// post, put, patch
|
|
144
|
+
const dataParam = method.parameters.find((p) => p.in === "body")?.name || "null";
|
|
145
|
+
body += ` return this.http.${httpMethod}<${responseType}>(${path}, ${dataParam}${optionsString});`;
|
|
146
|
+
}
|
|
147
|
+
return `${method.comments}\n public ${method.name}(${paramsSignature}): Observable<${responseType}> {
|
|
148
|
+
${body}
|
|
149
|
+
}`;
|
|
150
|
+
}
|
|
151
|
+
function buildMethods(groupedPath) {
|
|
152
|
+
const methods = [];
|
|
153
|
+
const pathData = swagger_store_1.swaggerStore.getPaths();
|
|
154
|
+
const usedNames = [];
|
|
155
|
+
for (const path of groupedPath.paths) {
|
|
156
|
+
if (!pathData || !pathData[path])
|
|
157
|
+
continue;
|
|
158
|
+
for (const [httpMethod, pathInfo] of Object.entries(pathData[path])) {
|
|
159
|
+
if (typeof pathInfo !== "object")
|
|
160
|
+
continue;
|
|
161
|
+
const method = buildMethod(path, httpMethod, pathInfo, groupedPath, usedNames);
|
|
162
|
+
methods.push(method);
|
|
163
|
+
usedNames.push(method.name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return methods;
|
|
167
|
+
}
|
|
168
|
+
function buildMethod(path, httpMethod, pathInfo, groupedPath, usedNames) {
|
|
169
|
+
const baseUrl = groupedPath.baseUrl;
|
|
170
|
+
const extraSegments = (0, path_utils_1.getExtraSegments)(path, baseUrl).join("/");
|
|
171
|
+
const methodPath = extraSegments
|
|
172
|
+
? `\${this.baseUrl}/${extraSegments}`
|
|
173
|
+
: `\${this.baseUrl}`;
|
|
174
|
+
const responseType = getMethodResponseType(pathInfo);
|
|
175
|
+
const parameters = getMethodParameters(path, httpMethod, pathInfo, groupedPath);
|
|
176
|
+
const method = {
|
|
177
|
+
name: computeName(path, httpMethod, usedNames),
|
|
178
|
+
path: methodPath,
|
|
179
|
+
method: httpMethod,
|
|
180
|
+
parameters: parameters,
|
|
181
|
+
responseType: responseType,
|
|
182
|
+
comments: (0, generate_comments_1.generateServiceComments)(pathInfo.summary || "", responseType, parameters),
|
|
183
|
+
};
|
|
184
|
+
return method;
|
|
185
|
+
}
|
|
186
|
+
function getMethodResponseType(pathInfo) {
|
|
187
|
+
const responses = pathInfo.responses;
|
|
188
|
+
if (!responses)
|
|
189
|
+
return "void";
|
|
190
|
+
const successResponse = responses["200"] || responses["201"] || responses["default"];
|
|
191
|
+
if (!successResponse)
|
|
192
|
+
return "void";
|
|
193
|
+
if ((0, type_guard_1.isReference)(successResponse)) {
|
|
194
|
+
return "any";
|
|
195
|
+
}
|
|
196
|
+
const content = successResponse.content;
|
|
197
|
+
if (!content || !content["application/json"])
|
|
198
|
+
return "void";
|
|
199
|
+
const schema = content["application/json"].schema;
|
|
200
|
+
if (!schema)
|
|
201
|
+
return "void";
|
|
202
|
+
return (0, build_types_1.switchTypeJson)(schema);
|
|
203
|
+
}
|
|
204
|
+
function getMethodParameters(path, httpMethod, pathInfo, groupedPath) {
|
|
205
|
+
const parameters = [];
|
|
206
|
+
const openApiParameters = pathInfo.parameters || [];
|
|
207
|
+
// Path parameters
|
|
208
|
+
openApiParameters.forEach((param) => {
|
|
209
|
+
if ((0, type_guard_1.isReference)(param))
|
|
210
|
+
return;
|
|
211
|
+
if (param.in === "path") {
|
|
212
|
+
parameters.push({
|
|
213
|
+
name: param.name,
|
|
214
|
+
in: "path",
|
|
215
|
+
required: param.required || false,
|
|
216
|
+
type: (0, build_types_1.switchTypeJson)(param.schema),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Query parameters (grouped)
|
|
221
|
+
const queryParams = openApiParameters.filter((param) => !(0, type_guard_1.isReference)(param) && param.in === "query");
|
|
222
|
+
if (queryParams.length > 0) {
|
|
223
|
+
parameters.push({
|
|
224
|
+
name: "parameters",
|
|
225
|
+
in: "query",
|
|
226
|
+
required: queryParams.some((p) => p.required),
|
|
227
|
+
type: (0, generate_interface_1.computeParametersName)(httpMethod, path, groupedPath),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Request Body
|
|
231
|
+
if (pathInfo.requestBody) {
|
|
232
|
+
let bodyType = "any";
|
|
233
|
+
if ((0, type_guard_1.isReference)(pathInfo.requestBody)) {
|
|
234
|
+
bodyType = pathInfo.requestBody.$ref.split("/").pop();
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const content = pathInfo.requestBody.content;
|
|
238
|
+
if (content && content["application/json"]) {
|
|
239
|
+
const schema = content["application/json"].schema;
|
|
240
|
+
if (schema) {
|
|
241
|
+
bodyType = (0, build_types_1.switchTypeJson)(schema);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
parameters.push({
|
|
246
|
+
name: "data",
|
|
247
|
+
in: "body",
|
|
248
|
+
required: true,
|
|
249
|
+
type: bodyType,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return parameters;
|
|
253
|
+
}
|
|
254
|
+
function computeName(path, httpMethod, usedNames) {
|
|
255
|
+
const dict = {
|
|
256
|
+
get: "get",
|
|
257
|
+
post: "create",
|
|
258
|
+
put: "update",
|
|
259
|
+
delete: "delete",
|
|
260
|
+
patch: "patch",
|
|
261
|
+
};
|
|
262
|
+
const parts = path.split("/").filter((p) => p);
|
|
263
|
+
const endsInVariable = (0, path_utils_1.isVariable)(parts[parts.length - 1]);
|
|
264
|
+
if (endsInVariable) {
|
|
265
|
+
const name = dict[httpMethod];
|
|
266
|
+
if (usedNames.includes(name)) {
|
|
267
|
+
return name + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 2]);
|
|
268
|
+
}
|
|
269
|
+
return name;
|
|
270
|
+
}
|
|
271
|
+
if (httpMethod === "get" || httpMethod === "put" || httpMethod === "patch") {
|
|
272
|
+
let name = dict[httpMethod] + "All";
|
|
273
|
+
if (!usedNames.includes(name)) {
|
|
274
|
+
return name;
|
|
275
|
+
}
|
|
276
|
+
name = dict[httpMethod] + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
277
|
+
if (!usedNames.includes(name)) {
|
|
278
|
+
return name;
|
|
279
|
+
}
|
|
280
|
+
return dict[httpMethod] + "By" + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
281
|
+
}
|
|
282
|
+
if (httpMethod === "post") {
|
|
283
|
+
let name = dict[httpMethod];
|
|
284
|
+
if (!usedNames.includes(name)) {
|
|
285
|
+
return name;
|
|
286
|
+
}
|
|
287
|
+
name = (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
288
|
+
if (!usedNames.includes(name)) {
|
|
289
|
+
return name;
|
|
290
|
+
}
|
|
291
|
+
name = "createBy" + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
292
|
+
if (!usedNames.includes(name)) {
|
|
293
|
+
return name;
|
|
294
|
+
}
|
|
295
|
+
return "create" + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
296
|
+
}
|
|
297
|
+
const name = dict[httpMethod];
|
|
298
|
+
if (usedNames.includes(name)) {
|
|
299
|
+
return name + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
300
|
+
}
|
|
301
|
+
return name + "By" + (0, string_utils_1.kebabToPascalCase)(parts[parts.length - 1]);
|
|
302
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.main = main;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const swagger_parser_1 = require("./parsers/swagger-parser");
|
|
10
|
+
const generate_interface_1 = require("./renderers/generate-interface");
|
|
11
|
+
const generate_service_1 = require("./renderers/generate-service");
|
|
12
|
+
const args_1 = require("./cli/args");
|
|
13
|
+
const variables_1 = require("./cli/variables");
|
|
14
|
+
const create_file_1 = require("./utils/create-file");
|
|
15
|
+
async function main() {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = (0, args_1.getParseArgs)(process.argv.slice(2));
|
|
18
|
+
const variables = (0, variables_1.toVariables)(parsed);
|
|
19
|
+
swagger_parser_1.SwaggerParser.parse(parsed.args.input, {
|
|
20
|
+
mode: variables.groupingMode,
|
|
21
|
+
segmentsToIgnore: variables.segmentsToIgnore,
|
|
22
|
+
ignoreVariables: variables.ignoreVariables,
|
|
23
|
+
});
|
|
24
|
+
(0, generate_interface_1.generateInterfaces)();
|
|
25
|
+
(0, generate_service_1.generateServices)();
|
|
26
|
+
const interfaceFiles = (0, generate_interface_1.generateInterfacesFiles)();
|
|
27
|
+
const serviceFiles = (0, generate_service_1.generateServiceFiles)();
|
|
28
|
+
if (parsed.args.noGenerate) {
|
|
29
|
+
console.log('No se generaron archivos');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (fs_1.default.existsSync('results')) {
|
|
33
|
+
fs_1.default.rmSync('results', { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
await (0, create_file_1.createFileFromFileContents)('results/models', interfaceFiles);
|
|
36
|
+
await (0, create_file_1.createFileFromFileContents)('results', serviceFiles);
|
|
37
|
+
console.log('Archivo creado correctamente');
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error('Failed to read JSON:', err);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (require.main === module) {
|
|
44
|
+
main();
|
|
45
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AngularRequestType = exports.AngularResponseType = exports.NativeType = void 0;
|
|
4
|
+
var NativeType;
|
|
5
|
+
(function (NativeType) {
|
|
6
|
+
NativeType["String"] = "string";
|
|
7
|
+
NativeType["Number"] = "number";
|
|
8
|
+
NativeType["Boolean"] = "boolean";
|
|
9
|
+
NativeType["Array"] = "Array";
|
|
10
|
+
NativeType["Date"] = "Date";
|
|
11
|
+
NativeType["File"] = "File";
|
|
12
|
+
NativeType["Any"] = "any";
|
|
13
|
+
NativeType["Void"] = "void";
|
|
14
|
+
})(NativeType || (exports.NativeType = NativeType = {}));
|
|
15
|
+
var AngularResponseType;
|
|
16
|
+
(function (AngularResponseType) {
|
|
17
|
+
AngularResponseType["Json"] = "json";
|
|
18
|
+
AngularResponseType["Text"] = "text";
|
|
19
|
+
AngularResponseType["Blob"] = "blob";
|
|
20
|
+
AngularResponseType["ArrayBuffer"] = "arrayBuffer";
|
|
21
|
+
})(AngularResponseType || (exports.AngularResponseType = AngularResponseType = {}));
|
|
22
|
+
var AngularRequestType;
|
|
23
|
+
(function (AngularRequestType) {
|
|
24
|
+
AngularRequestType["Get"] = "get";
|
|
25
|
+
AngularRequestType["Post"] = "post";
|
|
26
|
+
AngularRequestType["Put"] = "put";
|
|
27
|
+
AngularRequestType["Delete"] = "delete";
|
|
28
|
+
AngularRequestType["Patch"] = "patch";
|
|
29
|
+
})(AngularRequestType || (exports.AngularRequestType = AngularRequestType = {}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import { GroupedPaths } from '../types/grouped-paths';
|
|
3
|
+
export interface GroupPathsOptions {
|
|
4
|
+
mode: 'tags' | 'path';
|
|
5
|
+
segmentsToIgnore?: string[];
|
|
6
|
+
ignoreVariables?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class PathGrouper {
|
|
9
|
+
static groupPaths(paths: OpenAPIV3.PathsObject, options?: GroupPathsOptions): GroupedPaths;
|
|
10
|
+
private static groupByTags;
|
|
11
|
+
private static groupByPath;
|
|
12
|
+
private static splitRecursively;
|
|
13
|
+
private static addToFinalGroups;
|
|
14
|
+
private static getNormalizedSegments;
|
|
15
|
+
private static getMethodCount;
|
|
16
|
+
private static getCommonSegments;
|
|
17
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PathGrouper = void 0;
|
|
4
|
+
const path_utils_1 = require("../utils/path-utils");
|
|
5
|
+
const string_utils_1 = require("../utils/string-utils");
|
|
6
|
+
class PathGrouper {
|
|
7
|
+
static groupPaths(paths, options = { mode: 'tags' }) {
|
|
8
|
+
if (options.mode === 'tags') {
|
|
9
|
+
return this.groupByTags(paths, options);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
return this.groupByPath(paths, options);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
static groupByTags(paths, options) {
|
|
16
|
+
const tagGroups = {};
|
|
17
|
+
const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
|
|
18
|
+
for (const [pathKey, pathItem] of Object.entries(paths)) {
|
|
19
|
+
if (!pathItem)
|
|
20
|
+
continue;
|
|
21
|
+
const tags = new Set();
|
|
22
|
+
for (const method of methods) {
|
|
23
|
+
const operation = pathItem[method];
|
|
24
|
+
if (operation?.tags && operation.tags.length > 0) {
|
|
25
|
+
operation.tags.forEach((tag) => tags.add((0, string_utils_1.removeAllWhitespace)(tag)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (tags.size === 0) {
|
|
29
|
+
const groupKey = 'Default';
|
|
30
|
+
if (!tagGroups[groupKey])
|
|
31
|
+
tagGroups[groupKey] = [];
|
|
32
|
+
tagGroups[groupKey].push(pathKey);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
tags.forEach((tag) => {
|
|
36
|
+
if (!tagGroups[tag])
|
|
37
|
+
tagGroups[tag] = [];
|
|
38
|
+
tagGroups[tag].push(pathKey);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const finalGroups = {};
|
|
43
|
+
const sortedTags = Object.keys(tagGroups).sort();
|
|
44
|
+
for (const tagName of sortedTags) {
|
|
45
|
+
const pathKeys = tagGroups[tagName].sort();
|
|
46
|
+
const totalMethods = pathKeys.reduce((sum, pk) => sum + this.getMethodCount(paths[pk]), 0);
|
|
47
|
+
if (totalMethods <= 8) {
|
|
48
|
+
const groupName = (0, string_utils_1.toPascalCase)([tagName]);
|
|
49
|
+
finalGroups[groupName] = {
|
|
50
|
+
groupName,
|
|
51
|
+
baseSegments: this.getCommonSegments(pathKeys),
|
|
52
|
+
paths: pathKeys,
|
|
53
|
+
baseUrl: (0, path_utils_1.findCommonBaseUrl)(pathKeys),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const commonSegs = this.getCommonSegments(pathKeys);
|
|
58
|
+
this.splitRecursively(paths, pathKeys, commonSegs, options, finalGroups, commonSegs.length, tagName);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return finalGroups;
|
|
62
|
+
}
|
|
63
|
+
static groupByPath(paths, options) {
|
|
64
|
+
const finalGroups = {};
|
|
65
|
+
const pathKeys = Object.keys(paths).filter((k) => !!paths[k]);
|
|
66
|
+
const initialBuckets = {};
|
|
67
|
+
for (const pk of pathKeys) {
|
|
68
|
+
const normalized = this.getNormalizedSegments(pk, options);
|
|
69
|
+
const first = normalized.length > 0 ? normalized[0] : 'Default';
|
|
70
|
+
if (!initialBuckets[first])
|
|
71
|
+
initialBuckets[first] = [];
|
|
72
|
+
initialBuckets[first].push(pk);
|
|
73
|
+
}
|
|
74
|
+
const sortedPrimaryKeys = Object.keys(initialBuckets).sort();
|
|
75
|
+
for (const primaryKey of sortedPrimaryKeys) {
|
|
76
|
+
const pathsInBucket = initialBuckets[primaryKey];
|
|
77
|
+
const prefix = primaryKey === 'Default' ? [] : [primaryKey];
|
|
78
|
+
this.splitRecursively(paths, pathsInBucket, prefix, options, finalGroups, 0);
|
|
79
|
+
}
|
|
80
|
+
const sortedResult = {};
|
|
81
|
+
const sortedGroupNames = Object.keys(finalGroups).sort();
|
|
82
|
+
for (const name of sortedGroupNames) {
|
|
83
|
+
sortedResult[name] = finalGroups[name];
|
|
84
|
+
}
|
|
85
|
+
return sortedResult;
|
|
86
|
+
}
|
|
87
|
+
static splitRecursively(allPaths, currentPaths, currentPrefix, options, finalGroups, initialDepth, customNamePrefix) {
|
|
88
|
+
const totalMethods = currentPaths.reduce((sum, pk) => sum + this.getMethodCount(allPaths[pk]), 0);
|
|
89
|
+
const depth = currentPrefix.length;
|
|
90
|
+
if (totalMethods <= 8) {
|
|
91
|
+
this.addToFinalGroups(currentPaths, currentPrefix, finalGroups, initialDepth, customNamePrefix);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const stayers = [];
|
|
95
|
+
const branchMap = {};
|
|
96
|
+
for (const pk of currentPaths) {
|
|
97
|
+
const normalized = this.getNormalizedSegments(pk, options);
|
|
98
|
+
if (normalized.length > depth) {
|
|
99
|
+
const next = normalized[depth];
|
|
100
|
+
if (!branchMap[next])
|
|
101
|
+
branchMap[next] = [];
|
|
102
|
+
branchMap[next].push(pk);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
stayers.push(pk);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const nextSegs = Object.keys(branchMap);
|
|
109
|
+
if (nextSegs.length === 0) {
|
|
110
|
+
this.addToFinalGroups(currentPaths, currentPrefix, finalGroups, initialDepth, customNamePrefix);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const movingBranches = [];
|
|
114
|
+
const remainingToStay = [...stayers];
|
|
115
|
+
for (const seg of nextSegs) {
|
|
116
|
+
const paths = branchMap[seg];
|
|
117
|
+
const count = paths.reduce((sum, pk) => sum + this.getMethodCount(allPaths[pk]), 0);
|
|
118
|
+
if (count >= 2) {
|
|
119
|
+
movingBranches.push({ seg, paths });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
remainingToStay.push(...paths);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (movingBranches.length === 0) {
|
|
126
|
+
this.addToFinalGroups(currentPaths, currentPrefix, finalGroups, initialDepth, customNamePrefix);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (movingBranches.length === 1 && remainingToStay.length === 0) {
|
|
130
|
+
this.splitRecursively(allPaths, currentPaths, [...currentPrefix, movingBranches[0].seg], options, finalGroups, initialDepth, customNamePrefix);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (remainingToStay.length > 0) {
|
|
134
|
+
this.addToFinalGroups(remainingToStay, currentPrefix, finalGroups, initialDepth, customNamePrefix);
|
|
135
|
+
}
|
|
136
|
+
for (const branch of movingBranches.sort((a, b) => a.seg.localeCompare(b.seg))) {
|
|
137
|
+
this.splitRecursively(allPaths, branch.paths, [...currentPrefix, branch.seg], options, finalGroups, initialDepth, customNamePrefix);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
static addToFinalGroups(paths, prefix, finalGroups, initialDepth, customNamePrefix) {
|
|
141
|
+
let groupName;
|
|
142
|
+
if (customNamePrefix) {
|
|
143
|
+
const trailing = prefix.slice(initialDepth);
|
|
144
|
+
groupName = (0, string_utils_1.toPascalCase)([customNamePrefix, ...trailing]);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
groupName = prefix.length > 0 ? (0, string_utils_1.toPascalCase)(prefix) : 'Default';
|
|
148
|
+
}
|
|
149
|
+
if (finalGroups[groupName]) {
|
|
150
|
+
const existing = finalGroups[groupName];
|
|
151
|
+
existing.paths = [...new Set([...existing.paths, ...paths])].sort();
|
|
152
|
+
existing.baseSegments = this.getCommonSegments(existing.paths);
|
|
153
|
+
existing.baseUrl = (0, path_utils_1.findCommonBaseUrl)(existing.paths);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
finalGroups[groupName] = {
|
|
157
|
+
groupName,
|
|
158
|
+
baseSegments: this.getCommonSegments(paths),
|
|
159
|
+
paths: paths.sort(),
|
|
160
|
+
baseUrl: (0, path_utils_1.findCommonBaseUrl)(paths),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
static getNormalizedSegments(path, options) {
|
|
165
|
+
const segments = path.split('/').filter((s) => s);
|
|
166
|
+
const result = [];
|
|
167
|
+
for (const segment of segments) {
|
|
168
|
+
if (options.segmentsToIgnore?.includes(segment))
|
|
169
|
+
continue;
|
|
170
|
+
if (options.ignoreVariables && (0, path_utils_1.isVariable)(segment))
|
|
171
|
+
continue;
|
|
172
|
+
result.push(segment);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
static getMethodCount(pathItem) {
|
|
177
|
+
const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
|
|
178
|
+
let count = 0;
|
|
179
|
+
for (const method of methods) {
|
|
180
|
+
if (pathItem[method]) {
|
|
181
|
+
count++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return count;
|
|
185
|
+
}
|
|
186
|
+
static getCommonSegments(paths) {
|
|
187
|
+
if (paths.length === 0)
|
|
188
|
+
return [];
|
|
189
|
+
const pathSegments = paths.map((p) => p
|
|
190
|
+
.replace(/^\/+api\/+/, '')
|
|
191
|
+
.split('/')
|
|
192
|
+
.filter((s) => s));
|
|
193
|
+
const firstPath = pathSegments[0];
|
|
194
|
+
const common = [];
|
|
195
|
+
for (let i = 0; i < firstPath.length; i++) {
|
|
196
|
+
const segment = firstPath[i];
|
|
197
|
+
if (pathSegments.every((ps) => ps[i] === segment)) {
|
|
198
|
+
common.push(segment);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return common;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.PathGrouper = PathGrouper;
|