swaggular 0.2.4 → 0.3.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 +8 -0
- package/dist/core/state/interface-state.d.ts +1 -0
- package/dist/core/state/interface-state.js +3 -0
- package/dist/index.js +0 -7
- package/dist/parsers/swagger-parser.d.ts +2 -0
- package/dist/parsers/swagger-parser.js +24 -6
- package/dist/renderers/generate-comments.js +6 -3
- package/dist/renderers/generate-interface.js +4 -1
- package/dist/renderers/generate-service.d.ts +1 -1
- package/dist/renderers/generate-service.js +184 -40
- package/dist/templates/services/http-params-handler.js +1 -3
- package/dist/types/service-data.d.ts +1 -0
- package/dist/utils/create-file.d.ts +0 -5
- package/dist/utils/create-file.js +14 -36
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Swaggular
|
|
2
2
|
|
|
3
3
|
A powerful tool to generate Angular services and models from Swagger/OpenAPI specifications.
|
|
4
|
+
Note: Currently, it does not detects generic types or common interfaces. If you want a more
|
|
5
|
+
customized version, please download the project in github and modify the templates folder.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
@@ -63,6 +65,12 @@ The generated files will be available in the `results` folder.
|
|
|
63
65
|
|
|
64
66
|
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
|
|
65
67
|
|
|
68
|
+
### 🚀 Roadmap
|
|
69
|
+
|
|
70
|
+
- [ ] Add TSDoc comments
|
|
71
|
+
- [ ] Add function to identify common properties and generate interfaces
|
|
72
|
+
- [ ] Add function to generate interfaces and services for just one path
|
|
73
|
+
|
|
66
74
|
## License
|
|
67
75
|
|
|
68
76
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -12,6 +12,7 @@ declare class InterfaceState implements InterfaceStateI {
|
|
|
12
12
|
setComponentsSchemas(componentsSchemas: Record<string, IJsonSchema>): void;
|
|
13
13
|
getComponentsSchema(name: string): IJsonSchema | undefined;
|
|
14
14
|
addInterfaces(interfacesData: InterfaceData[]): void;
|
|
15
|
+
getInterface(name: string): InterfaceData | undefined;
|
|
15
16
|
}
|
|
16
17
|
export declare const interfaceState: InterfaceState;
|
|
17
18
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
4
|
exports.main = main;
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
5
|
const swagger_parser_1 = require("./parsers/swagger-parser");
|
|
10
6
|
const generate_interface_1 = require("./renderers/generate-interface");
|
|
11
7
|
const generate_service_1 = require("./renderers/generate-service");
|
|
@@ -30,9 +26,6 @@ async function main() {
|
|
|
30
26
|
console.log('Files not generated');
|
|
31
27
|
return;
|
|
32
28
|
}
|
|
33
|
-
if (fs_1.default.existsSync(variables.output)) {
|
|
34
|
-
fs_1.default.rmSync(variables.output, { recursive: true, force: true });
|
|
35
|
-
}
|
|
36
29
|
await (0, create_file_1.createFileFromFileContents)(`${variables.output}/models`, interfaceFiles);
|
|
37
30
|
await (0, create_file_1.createFileFromFileContents)(variables.output, serviceFiles);
|
|
38
31
|
console.log('Files created successfully');
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
1
2
|
import { GroupPathsOptions } from './path-grouper';
|
|
2
3
|
export declare class SwaggerParser {
|
|
3
4
|
static parse(filePath: string, options?: GroupPathsOptions): void;
|
|
5
|
+
static loadJsonOrYaml(filePath: string): OpenAPIV3.Document;
|
|
4
6
|
}
|
|
@@ -32,22 +32,40 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.SwaggerParser = void 0;
|
|
37
40
|
const fs = __importStar(require("fs"));
|
|
38
41
|
const path = __importStar(require("path"));
|
|
39
42
|
const path_grouper_1 = require("./path-grouper");
|
|
40
43
|
const swagger_state_1 = require("../core/state/swagger-state");
|
|
44
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
41
45
|
class SwaggerParser {
|
|
42
46
|
static parse(filePath, options = { mode: 'tags' }) {
|
|
47
|
+
try {
|
|
48
|
+
const swagger = this.loadJsonOrYaml(filePath);
|
|
49
|
+
const pathsGroupedByScope = path_grouper_1.PathGrouper.groupPaths(swagger.paths, options);
|
|
50
|
+
swagger_state_1.swaggerState.setSwagger(swagger);
|
|
51
|
+
swagger_state_1.swaggerState.setPathsGroupedByScope(pathsGroupedByScope);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('Failed to parse swagger file:', error);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
static loadJsonOrYaml(filePath) {
|
|
43
59
|
const absolutePath = path.resolve(filePath);
|
|
60
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
44
61
|
const content = fs.readFileSync(absolutePath, { encoding: 'utf8' });
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
if (ext === '.json') {
|
|
63
|
+
return JSON.parse(content);
|
|
64
|
+
}
|
|
65
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
66
|
+
return yaml_1.default.parse(content);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Unsupported file extension: "${ext}". Use .json, .yaml or .yml`);
|
|
51
69
|
}
|
|
52
70
|
}
|
|
53
71
|
exports.SwaggerParser = SwaggerParser;
|
|
@@ -26,16 +26,19 @@ function generateInterfaceComments(param) {
|
|
|
26
26
|
}
|
|
27
27
|
function generateServiceComments(summary, responseType, parameters) {
|
|
28
28
|
const summarySplited = [];
|
|
29
|
-
const summaryWords = summary.split(' ');
|
|
29
|
+
const summaryWords = summary.replaceAll('\r', ' ').replaceAll('\n', ' ').split(' ');
|
|
30
30
|
for (let i = 0; i < summaryWords.length; i += 10) {
|
|
31
31
|
summarySplited.push(summaryWords.slice(i, i + 10).join(' '));
|
|
32
32
|
}
|
|
33
33
|
const comments = summarySplited;
|
|
34
34
|
if (parameters && parameters.length > 0) {
|
|
35
|
-
|
|
35
|
+
parameters.forEach((p) => {
|
|
36
|
+
comments.push(`@param ${p.name} : ${p.type}`);
|
|
37
|
+
});
|
|
36
38
|
}
|
|
37
39
|
if (responseType) {
|
|
38
40
|
comments.push(`@returns Observable<${responseType}>`);
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
const result = `\t/**\n${comments.map((c) => `\t * ${c}\n`).join('')} \t */`;
|
|
43
|
+
return result;
|
|
41
44
|
}
|
|
@@ -195,7 +195,10 @@ function generateContent(interfaceData, deep = 0) {
|
|
|
195
195
|
const path = '../'.repeat(deep);
|
|
196
196
|
const importsTemplate = imports.length > 0 ? `import { ${imports.join(', ')} } from "${path}models";` : '';
|
|
197
197
|
if (interfaceData.type === 'interface') {
|
|
198
|
-
const
|
|
198
|
+
const genericTypes = interfaceData.properties
|
|
199
|
+
.filter((p) => ['T', 'R', 'P'].includes(p.type.replace('[]', '')))
|
|
200
|
+
.map((p) => p.type.replace('[]', ''));
|
|
201
|
+
const content = `${importsTemplate}\n\nexport interface ${interfaceData.name}${genericTypes.length > 0 ? `<${genericTypes.join(', ')}>` : ''} ${interfaceData.extendsFrom && interfaceData.extendsFrom.length > 0 ? `extends ${interfaceData.extendsFrom.join(', ')}` : ''} {
|
|
199
202
|
${interfaceData.properties
|
|
200
203
|
.map((p) => `${p.comments}\n\t${(0, string_utils_1.lowerFirst)(p.name)}${p.optional ? '?' : ''}: ${p.type};`)
|
|
201
204
|
.join('\n')}
|
|
@@ -7,4 +7,4 @@ import { ServiceDataMethod } from '../types/service-data';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function generateServices(): void;
|
|
9
9
|
export declare function generateServiceFiles(locations?: Record<string, string[]>): FileContent[];
|
|
10
|
-
export declare function buildMethods(groupedPath: GroupedPath, pathData: OpenAPIV3.PathsObject): ServiceDataMethod[];
|
|
10
|
+
export declare function buildMethods(groupedPath: GroupedPath, pathData: OpenAPIV3.PathsObject, serviceName: string): ServiceDataMethod[];
|
|
@@ -12,6 +12,9 @@ const string_utils_1 = require("../utils/string-utils");
|
|
|
12
12
|
const type_guard_1 = require("../utils/type-guard");
|
|
13
13
|
const generate_comments_1 = require("./generate-comments");
|
|
14
14
|
const object_utils_1 = require("../utils/object-utils");
|
|
15
|
+
const generate_interface_1 = require("./generate-interface");
|
|
16
|
+
const http_params_handler_1 = require("../templates/services/http-params-handler");
|
|
17
|
+
const interface_state_1 = require("../core/state/interface-state");
|
|
15
18
|
/**
|
|
16
19
|
* Pick the paths grouped by scope and generate services for them.
|
|
17
20
|
*/
|
|
@@ -22,13 +25,18 @@ function generateServices() {
|
|
|
22
25
|
if (!groupedPaths || !paths)
|
|
23
26
|
return;
|
|
24
27
|
for (const [key, value] of Object.entries(groupedPaths)) {
|
|
25
|
-
const serviceMethods = buildMethods(value, paths);
|
|
28
|
+
const serviceMethods = buildMethods(value, paths, key);
|
|
26
29
|
const imports = new Set();
|
|
27
30
|
serviceMethods.forEach((method) => {
|
|
28
31
|
addTypeToImports(method.responseType, imports);
|
|
29
32
|
method.parameters.forEach((param) => {
|
|
30
|
-
|
|
33
|
+
if (!method.queryParamType || param.in !== 'query') {
|
|
34
|
+
addTypeToImports(param.type, imports);
|
|
35
|
+
}
|
|
31
36
|
});
|
|
37
|
+
if (method.queryParamType) {
|
|
38
|
+
imports.add(method.queryParamType);
|
|
39
|
+
}
|
|
32
40
|
});
|
|
33
41
|
const serviceData = {
|
|
34
42
|
name: key,
|
|
@@ -71,7 +79,7 @@ function generateServiceFiles(locations) {
|
|
|
71
79
|
baseUrl: service.baseUrl,
|
|
72
80
|
methods: methods,
|
|
73
81
|
imports: service.imports.join(', '),
|
|
74
|
-
hasHttpParamsHandler: service.methods.some((m) => m.
|
|
82
|
+
hasHttpParamsHandler: service.methods.some((m) => !!m.queryParamType),
|
|
75
83
|
};
|
|
76
84
|
const content = (0, angular_template_1.angularTemplate)(templateParams);
|
|
77
85
|
filesContent.push({
|
|
@@ -83,7 +91,7 @@ function generateServiceFiles(locations) {
|
|
|
83
91
|
}
|
|
84
92
|
return filesContent;
|
|
85
93
|
}
|
|
86
|
-
function buildMethods(groupedPath, pathData) {
|
|
94
|
+
function buildMethods(groupedPath, pathData, serviceName) {
|
|
87
95
|
const methods = [];
|
|
88
96
|
const usedNames = [];
|
|
89
97
|
for (const path of groupedPath.paths) {
|
|
@@ -92,17 +100,32 @@ function buildMethods(groupedPath, pathData) {
|
|
|
92
100
|
const pathItem = pathData[path];
|
|
93
101
|
const operations = pathItemToMethods(pathItem);
|
|
94
102
|
for (const [method, operation] of Object.entries(operations)) {
|
|
95
|
-
const name = buildMethodName(method, path, groupedPath, usedNames);
|
|
103
|
+
const name = buildMethodName(method, path, groupedPath, usedNames, serviceName);
|
|
96
104
|
usedNames.push(name);
|
|
97
105
|
const parameters = buildParameters(operation, pathItem.parameters);
|
|
98
106
|
const responseType = buildResponseType(operation.responses);
|
|
107
|
+
const queryParams = parameters.filter((p) => p.in === 'query');
|
|
108
|
+
let queryParamType;
|
|
109
|
+
if (queryParams.length > 0) {
|
|
110
|
+
queryParamType = (0, generate_interface_1.computeParametersName)(method, path, groupedPath);
|
|
111
|
+
}
|
|
112
|
+
const commentParams = parameters.filter((p) => p.in !== 'query');
|
|
113
|
+
if (queryParamType) {
|
|
114
|
+
commentParams.push({
|
|
115
|
+
name: 'queryParams',
|
|
116
|
+
type: queryParamType,
|
|
117
|
+
in: 'query',
|
|
118
|
+
required: false,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
99
121
|
methods.push({
|
|
100
122
|
name,
|
|
101
123
|
path: (0, path_utils_1.getExtraSegments)(path, groupedPath.baseUrl).join('/'),
|
|
102
124
|
method: method,
|
|
103
125
|
parameters,
|
|
104
126
|
responseType,
|
|
105
|
-
comments: (0, generate_comments_1.generateServiceComments)(operation.summary ?? '', responseType,
|
|
127
|
+
comments: (0, generate_comments_1.generateServiceComments)(operation.summary ?? '', responseType, commentParams),
|
|
128
|
+
queryParamType,
|
|
106
129
|
});
|
|
107
130
|
}
|
|
108
131
|
}
|
|
@@ -117,24 +140,130 @@ function pathItemToMethods(pathItem) {
|
|
|
117
140
|
patch: pathItem.patch,
|
|
118
141
|
});
|
|
119
142
|
}
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
/**
|
|
144
|
+
* buildMethodName: High-performance semantic naming algorithm.
|
|
145
|
+
*
|
|
146
|
+
* This engine generates clean, non-redundant, and intuitive method names by
|
|
147
|
+
* analyzing the relationship between the base URL and extra path segments.
|
|
148
|
+
*
|
|
149
|
+
* For Mutation methods (POST, PUT, PATCH, DELETE), it prioritizes using the
|
|
150
|
+
* path segments as the method name itself if they describe an action,
|
|
151
|
+
* avoiding generic "create" or "update" prefixes where possible.
|
|
152
|
+
*/
|
|
153
|
+
function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
|
|
154
|
+
// 1. Core mapping of HTTP verbs to professional action prefixes
|
|
155
|
+
const verbMap = {
|
|
122
156
|
get: 'get',
|
|
123
157
|
post: 'create',
|
|
124
158
|
put: 'update',
|
|
125
159
|
delete: 'delete',
|
|
126
160
|
patch: 'patch',
|
|
127
161
|
};
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
const defaultVerb = verbMap[method.toLowerCase()] || method.toLowerCase();
|
|
163
|
+
const segments = (0, path_utils_1.getExtraSegments)(path, groupedPath.baseUrl);
|
|
164
|
+
// 2. Redundancy Filtering: Remove segments already implied by the service name
|
|
165
|
+
const serviceWords = serviceName.split(/(?=[A-Z])/).map((w) => w.toLowerCase());
|
|
166
|
+
const cleanSegments = segments.filter((seg) => {
|
|
167
|
+
if ((0, path_utils_1.isVariable)(seg))
|
|
168
|
+
return true;
|
|
169
|
+
const segWords = seg.split('-').map((w) => w.toLowerCase());
|
|
170
|
+
return !segWords.every((sw) => serviceWords.includes(sw));
|
|
171
|
+
});
|
|
172
|
+
// 3. Structural Analysis: Identify resources and variable positioning
|
|
173
|
+
const staticBefore = [];
|
|
174
|
+
const variableParts = [];
|
|
175
|
+
const staticAfter = [];
|
|
176
|
+
let passedVariable = false;
|
|
177
|
+
for (const segment of cleanSegments) {
|
|
178
|
+
if ((0, path_utils_1.isVariable)(segment)) {
|
|
179
|
+
variableParts.push(segment.replace(/[{}]/g, ''));
|
|
180
|
+
passedVariable = true;
|
|
181
|
+
}
|
|
182
|
+
else if (passedVariable) {
|
|
183
|
+
staticAfter.push(segment);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
staticBefore.push(segment);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const nameParts = [];
|
|
190
|
+
// 4. Logic specific to data fetching (GET)
|
|
191
|
+
if (method.toLowerCase() === 'get') {
|
|
192
|
+
nameParts.push('get');
|
|
193
|
+
if (staticAfter.length > 0) {
|
|
194
|
+
nameParts.push((0, string_utils_1.toPascalCase)(staticAfter));
|
|
195
|
+
nameParts.push('ById');
|
|
196
|
+
}
|
|
197
|
+
else if (staticBefore.length > 0) {
|
|
198
|
+
nameParts.push((0, string_utils_1.toPascalCase)(staticBefore));
|
|
199
|
+
if (variableParts.length > 0)
|
|
200
|
+
nameParts.push('ById');
|
|
201
|
+
}
|
|
202
|
+
else if (variableParts.length > 0) {
|
|
203
|
+
nameParts.push('ById');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 5. Logic for state mutation (POST, PUT, PATCH, DELETE)
|
|
207
|
+
else {
|
|
208
|
+
const actionParts = [...staticBefore, ...staticAfter];
|
|
209
|
+
if (actionParts.length > 0) {
|
|
210
|
+
// If the path contains an action (e.g., /assign, /send-to-user), use it directly.
|
|
211
|
+
nameParts.push((0, string_utils_1.toPascalCase)(actionParts));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// No extra segments, use the default verb prefix (e.g., create, update)
|
|
215
|
+
nameParts.push((0, string_utils_1.upFirst)(defaultVerb));
|
|
216
|
+
}
|
|
217
|
+
if (variableParts.length > 0) {
|
|
218
|
+
nameParts.push('ById');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 6. Pattern-based Refinement: Detect and prioritize standard REST suffixes
|
|
222
|
+
const normalizedPath = path.toLowerCase();
|
|
223
|
+
const suffixes = [
|
|
224
|
+
{ pattern: '/all', label: 'All' },
|
|
225
|
+
{ pattern: '/search', label: 'Search' },
|
|
226
|
+
{ pattern: '/stats', label: 'Stats' },
|
|
227
|
+
{ pattern: '/history', label: 'History' },
|
|
228
|
+
{ pattern: '/summary', label: 'Summary' },
|
|
229
|
+
{ pattern: '/details', label: 'Details' },
|
|
230
|
+
];
|
|
231
|
+
for (const suffix of suffixes) {
|
|
232
|
+
if (normalizedPath.endsWith(suffix.pattern)) {
|
|
233
|
+
if (!nameParts.join('').endsWith(suffix.label)) {
|
|
234
|
+
nameParts.push(suffix.label);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// 7. Base candidate assembly
|
|
240
|
+
let candidateName = (0, string_utils_1.lowerFirst)(nameParts.join(''));
|
|
241
|
+
// Simplification for the primary resource GET
|
|
242
|
+
if (candidateName === 'getById' && !usedNames.includes('get')) {
|
|
243
|
+
candidateName = 'get';
|
|
244
|
+
}
|
|
245
|
+
// Fallback for empty/ambiguous names
|
|
246
|
+
if (candidateName === '' || (candidateName === defaultVerb && segments.length > 0)) {
|
|
247
|
+
candidateName = (0, string_utils_1.lowerFirst)(defaultVerb + (0, string_utils_1.toPascalCase)(segments.map((s) => s.replace(/[{}]/g, ''))));
|
|
248
|
+
}
|
|
249
|
+
// 8. Multi-strategy Collision Resolution
|
|
250
|
+
let finalName = candidateName;
|
|
251
|
+
let counter = 0;
|
|
252
|
+
while (usedNames.includes(finalName)) {
|
|
253
|
+
counter++;
|
|
254
|
+
if (counter === 1 && variableParts.length > 0) {
|
|
255
|
+
finalName = candidateName + (0, string_utils_1.upFirst)(variableParts[variableParts.length - 1]);
|
|
256
|
+
}
|
|
257
|
+
else if (counter === 2) {
|
|
258
|
+
finalName = (0, string_utils_1.lowerFirst)(defaultVerb + (0, string_utils_1.toPascalCase)(segments.map((s) => s.replace(/[{}]/g, ''))));
|
|
259
|
+
if (usedNames.includes(finalName))
|
|
260
|
+
finalName += (0, string_utils_1.upFirst)(method);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
finalName = candidateName + counter;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return finalName;
|
|
138
267
|
}
|
|
139
268
|
function buildParameters(operation, pathParameters) {
|
|
140
269
|
const results = [];
|
|
@@ -142,9 +271,12 @@ function buildParameters(operation, pathParameters) {
|
|
|
142
271
|
for (const param of allParams) {
|
|
143
272
|
if ((0, type_guard_1.isReference)(param)) {
|
|
144
273
|
const ref = param.$ref.split('/').pop();
|
|
274
|
+
console.log('🚀 ~ buildParameters ~ ref:', ref);
|
|
275
|
+
const interfaceData = interface_state_1.interfaceState.getInterface(ref);
|
|
276
|
+
console.log('🚀 ~ buildParameters ~ interfaceData:', interfaceData);
|
|
145
277
|
results.push({
|
|
146
278
|
name: (0, string_utils_1.lowerFirst)(ref),
|
|
147
|
-
in: 'query',
|
|
279
|
+
in: interfaceData?.type !== undefined ? 'query' : 'path',
|
|
148
280
|
required: true,
|
|
149
281
|
type: ref,
|
|
150
282
|
});
|
|
@@ -157,7 +289,6 @@ function buildParameters(operation, pathParameters) {
|
|
|
157
289
|
type: (0, build_types_1.switchTypeJson)(param.schema),
|
|
158
290
|
});
|
|
159
291
|
}
|
|
160
|
-
// Handle requestBody in V3
|
|
161
292
|
if (operation.requestBody) {
|
|
162
293
|
let bodyType = 'any';
|
|
163
294
|
if (!(0, type_guard_1.isReference)(operation.requestBody)) {
|
|
@@ -177,6 +308,7 @@ function buildParameters(operation, pathParameters) {
|
|
|
177
308
|
type: bodyType,
|
|
178
309
|
});
|
|
179
310
|
}
|
|
311
|
+
console.log('🚀 ~ buildParameters ~ results:', results);
|
|
180
312
|
return results;
|
|
181
313
|
}
|
|
182
314
|
function buildResponseType(responses) {
|
|
@@ -191,31 +323,43 @@ function buildResponseType(responses) {
|
|
|
191
323
|
return (0, build_types_1.switchTypeJson)(content.schema);
|
|
192
324
|
}
|
|
193
325
|
function buildMethodTemplate(method) {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
.
|
|
200
|
-
|
|
326
|
+
const pathParams = method.parameters.filter((p) => p.in === 'path');
|
|
327
|
+
const bodyParam = method.parameters.find((p) => p.in === 'body');
|
|
328
|
+
const methodArgs = [];
|
|
329
|
+
pathParams.forEach((p) => methodArgs.push(`${p.name}: ${p.type}`));
|
|
330
|
+
if (bodyParam) {
|
|
331
|
+
methodArgs.push(`${bodyParam.name === 'formData' ? 'formData' : 'body'}: ${bodyParam.type}`);
|
|
332
|
+
}
|
|
333
|
+
if (method.queryParamType) {
|
|
334
|
+
methodArgs.push(`queryParams?: ${method.queryParamType}`);
|
|
335
|
+
}
|
|
336
|
+
const argsString = methodArgs.join(', ');
|
|
337
|
+
const pathSegments = method.path.split('/').filter((s) => s !== '');
|
|
338
|
+
const pathStr = pathSegments
|
|
339
|
+
.map((s) => ((0, path_utils_1.isVariable)(s) ? `\${${(0, string_utils_1.lowerFirst)(s.replace(/[{}]/g, ''))}}` : s))
|
|
201
340
|
.join('/');
|
|
202
|
-
|
|
203
|
-
|
|
341
|
+
const url = `\`\${this.baseUrl}${pathStr ? '/' + pathStr : ''}\``;
|
|
342
|
+
let handler = '';
|
|
343
|
+
const optionsList = [];
|
|
344
|
+
if (method.queryParamType) {
|
|
345
|
+
handler = `\n\t\t${(0, http_params_handler_1.httpParamsHandler)('queryParams')}`;
|
|
346
|
+
optionsList.push('params');
|
|
204
347
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
let options = '';
|
|
208
|
-
if (queryParams.length > 0) {
|
|
209
|
-
options = `, { params: HttpHelper.toHttpParams({ ${queryParams.map((p) => p.name).join(', ')} }) }`;
|
|
348
|
+
if (['Blob', 'File'].includes(method.responseType)) {
|
|
349
|
+
optionsList.push("responseType: 'blob'");
|
|
210
350
|
}
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
351
|
+
const options = optionsList.length > 0 ? `, { ${optionsList.join(', ')} }` : '';
|
|
352
|
+
const bodyArg = bodyParam ? (bodyParam.name === 'formData' ? 'formData' : 'body') : null;
|
|
353
|
+
const genericType = ['Blob', 'File'].includes(method.responseType)
|
|
354
|
+
? ''
|
|
355
|
+
: `<${method.responseType}>`;
|
|
356
|
+
const methodCall = bodyArg
|
|
357
|
+
? `this.http.${method.method}${genericType}(${url}, ${bodyArg}${options})`
|
|
214
358
|
: ['post', 'put', 'patch'].includes(method.method)
|
|
215
|
-
? `this.http.${method.method}
|
|
216
|
-
: `this.http.${method.method}
|
|
359
|
+
? `this.http.${method.method}${genericType}(${url}, {}${options})`
|
|
360
|
+
: `this.http.${method.method}${genericType}(${url}${options})`;
|
|
217
361
|
return `${method.comments}
|
|
218
|
-
${method.name}(${
|
|
362
|
+
${method.name}(${argsString}): Observable<${method.responseType}> {${handler}
|
|
219
363
|
return ${methodCall};
|
|
220
364
|
}`;
|
|
221
365
|
}
|
|
@@ -8,9 +8,7 @@ exports.httpParamsHandlerImport = httpParamsHandlerImport;
|
|
|
8
8
|
* used in your service.
|
|
9
9
|
*/
|
|
10
10
|
function httpParamsHandler(params) {
|
|
11
|
-
return `
|
|
12
|
-
const params = HttpHelper.toHttpParams(${params} || {})
|
|
13
|
-
`;
|
|
11
|
+
return `const params = HttpHelper.toHttpParams(${params} || {})`;
|
|
14
12
|
}
|
|
15
13
|
/**
|
|
16
14
|
* Modify this function according to your needs and your template.
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import { FileContent } from '../types/file-content';
|
|
2
|
-
export declare function generateFile(content: string, group: string): void;
|
|
3
|
-
export declare function createFolderWithFiles(folderPath: string, files: string[], filesContent: Record<string, {
|
|
4
|
-
content: string;
|
|
5
|
-
imports: string[];
|
|
6
|
-
}>, withIndex?: boolean): void;
|
|
7
2
|
export declare function createFileFromFileContents(basePath: string, fileContent: FileContent[]): Promise<void>;
|
|
@@ -3,49 +3,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateFile = generateFile;
|
|
7
|
-
exports.createFolderWithFiles = createFolderWithFiles;
|
|
8
6
|
exports.createFileFromFileContents = createFileFromFileContents;
|
|
9
7
|
const fs_1 = __importDefault(require("fs"));
|
|
10
8
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
fs_1.default.unlinkSync(path_1.default.join(dir, file));
|
|
9
|
+
async function writeIfDifferent(filePath, content) {
|
|
10
|
+
try {
|
|
11
|
+
const existingContent = await fs_1.default.promises.readFile(filePath, 'utf8');
|
|
12
|
+
if (existingContent === content) {
|
|
13
|
+
return;
|
|
17
14
|
}
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
fs_1.default.writeFileSync(path_1.default.join(dir, `${new Date().toISOString().replaceAll(':', '-')}.ts`), content, 'utf8');
|
|
23
|
-
}
|
|
24
|
-
function createFolderWithFiles(folderPath, files, filesContent, withIndex = true) {
|
|
25
|
-
fs_1.default.mkdirSync(folderPath, { recursive: true });
|
|
26
|
-
const exports = [];
|
|
27
|
-
for (const fileName of files) {
|
|
28
|
-
if (!fileName || !filesContent[fileName]) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
const realFileName = (0, string_utils_1.toKebabCase)(fileName);
|
|
32
|
-
const finalName = `${realFileName}.model.ts`;
|
|
33
|
-
const filePath = path_1.default.join(folderPath, finalName);
|
|
34
|
-
fs_1.default.writeFileSync(filePath, filesContent[fileName].content, 'utf8');
|
|
35
|
-
if (withIndex) {
|
|
36
|
-
exports.push(`export * from "./${realFileName}.model";`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (withIndex && exports.length > 0) {
|
|
40
|
-
fs_1.default.writeFileSync(path_1.default.join(folderPath, 'index.ts'), exports.join('\n'), 'utf8');
|
|
16
|
+
catch {
|
|
17
|
+
// File doesn't exist, proceed to write
|
|
41
18
|
}
|
|
19
|
+
const dir = path_1.default.dirname(filePath);
|
|
20
|
+
await fs_1.default.promises.mkdir(dir, { recursive: true });
|
|
21
|
+
await fs_1.default.promises.writeFile(filePath, content, 'utf8');
|
|
42
22
|
}
|
|
43
23
|
async function createFileFromFileContents(basePath, fileContent) {
|
|
44
24
|
const folderExports = new Map();
|
|
45
25
|
for (const file of fileContent) {
|
|
46
26
|
const dir = path_1.default.join(basePath, ...file.location);
|
|
47
|
-
|
|
48
|
-
await
|
|
27
|
+
const filePath = path_1.default.join(dir, `${file.name}.${file.extension}`);
|
|
28
|
+
await writeIfDifferent(filePath, file.content);
|
|
49
29
|
if (file.extension.endsWith('ts') && file.name !== 'index') {
|
|
50
30
|
if (!folderExports.has(dir)) {
|
|
51
31
|
folderExports.set(dir, new Set());
|
|
@@ -66,9 +46,7 @@ async function createFileFromFileContents(basePath, fileContent) {
|
|
|
66
46
|
const indexContent = Array.from(exports)
|
|
67
47
|
.sort()
|
|
68
48
|
.map((item) => `export * from "./${item}";`)
|
|
69
|
-
.join('\n');
|
|
70
|
-
|
|
71
|
-
await fs_1.default.promises.writeFile(path_1.default.join(dir, 'index.ts'), indexContent + '\n', 'utf8');
|
|
72
|
-
}
|
|
49
|
+
.join('\n') + '\n';
|
|
50
|
+
await writeIfDifferent(path_1.default.join(dir, 'index.ts'), indexContent);
|
|
73
51
|
}
|
|
74
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swaggular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"bin": {
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
"typescript-eslint": "^8.55.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"openapi-types": "^12.1.3"
|
|
67
|
+
"openapi-types": "^12.1.3",
|
|
68
|
+
"yaml": "^2.8.2"
|
|
68
69
|
},
|
|
69
70
|
"engines": {
|
|
70
71
|
"node": ">=18.0.0"
|