swaggular 0.2.3 → 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 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 {};
@@ -17,5 +17,8 @@ class InterfaceState {
17
17
  this.generatedInterfaces[inter.name] = inter;
18
18
  }
19
19
  }
20
+ getInterface(name) {
21
+ return this.generatedInterfaces[name] || this.generatedEnums[name];
22
+ }
20
23
  }
21
24
  exports.interfaceState = new InterfaceState();
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");
@@ -16,6 +12,7 @@ async function main() {
16
12
  try {
17
13
  const parsed = (0, args_1.getParseArgs)(process.argv.slice(2));
18
14
  const variables = (0, variables_1.toVariables)(parsed);
15
+ console.log('Generating interfaces and services using Swaggular@' + process.env.npm_package_version);
19
16
  swagger_parser_1.SwaggerParser.parse(variables.input, {
20
17
  mode: variables.groupingMode,
21
18
  segmentsToIgnore: variables.segmentsToIgnore,
@@ -29,9 +26,6 @@ async function main() {
29
26
  console.log('Files not generated');
30
27
  return;
31
28
  }
32
- if (fs_1.default.existsSync(variables.output)) {
33
- fs_1.default.rmSync(variables.output, { recursive: true, force: true });
34
- }
35
29
  await (0, create_file_1.createFileFromFileContents)(`${variables.output}/models`, interfaceFiles);
36
30
  await (0, create_file_1.createFileFromFileContents)(variables.output, serviceFiles);
37
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
- const swagger = JSON.parse(content);
46
- if (!swagger)
47
- return;
48
- const pathsGroupedByScope = path_grouper_1.PathGrouper.groupPaths(swagger.paths, options);
49
- swagger_state_1.swaggerState.setSwagger(swagger);
50
- swagger_state_1.swaggerState.setPathsGroupedByScope(pathsGroupedByScope);
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
- comments.push(`${parameters.map((p) => `@param ${p.name} : ${p.type}`).join('\n')}`);
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
- return `\t/**\n${comments.map((c) => `\t* ${c}\n`).join('')} */`;
42
+ const result = `\t/**\n${comments.map((c) => `\t * ${c}\n`).join('')} \t */`;
43
+ return result;
41
44
  }
@@ -154,7 +154,11 @@ function generateComponentsSchemas() {
154
154
  const interData = {
155
155
  name: key,
156
156
  type: 'interface',
157
- imports: properties.filter((p) => !(0, type_guard_1.isNativeType)(p.type)).map((p) => p.type),
157
+ imports: properties
158
+ .filter((p) => !(0, type_guard_1.isNativeType)(p.type))
159
+ .map((p) => {
160
+ return p.type.replaceAll('[]', '');
161
+ }),
158
162
  properties,
159
163
  };
160
164
  const generic = (0, generic_types_1.isGenericType)(interData);
@@ -175,7 +179,7 @@ function generateInterfacesFiles(locations) {
175
179
  const filesContent = [];
176
180
  for (const [key, value] of Object.entries(interfacesData)) {
177
181
  const location = [value.type === 'enum' ? 'enums' : 'dtos', ...(locations?.[key] ?? [])];
178
- const content = generateContent(value, location.length);
182
+ const content = generateContent(value, location.length + 1);
179
183
  const extraName = value.type === 'enum' ? 'enum' : 'dto';
180
184
  filesContent.push({
181
185
  location: location,
@@ -188,11 +192,13 @@ function generateInterfacesFiles(locations) {
188
192
  }
189
193
  function generateContent(interfaceData, deep = 0) {
190
194
  const imports = [...interfaceData.imports];
191
- const importsTemplate = imports.length > 0
192
- ? `import { ${imports.join(', ')} } from "${'../'.repeat(deep)}models";`
193
- : '';
195
+ const path = '../'.repeat(deep);
196
+ const importsTemplate = imports.length > 0 ? `import { ${imports.join(', ')} } from "${path}models";` : '';
194
197
  if (interfaceData.type === 'interface') {
195
- const content = `${importsTemplate}\n\nexport interface ${interfaceData.name} ${interfaceData.extendsFrom && interfaceData.extendsFrom.length > 0 ? `extends ${interfaceData.extendsFrom.join(', ')}` : ''} {
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(', ')}` : ''} {
196
202
  ${interfaceData.properties
197
203
  .map((p) => `${p.comments}\n\t${(0, string_utils_1.lowerFirst)(p.name)}${p.optional ? '?' : ''}: ${p.type};`)
198
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
- addTypeToImports(param.type, imports);
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.parameters.some((p) => p.in === 'query')),
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, parameters),
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
- function buildMethodName(method, path, groupedPath, usedNames) {
121
- const dict = {
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 prefix = dict[method];
129
- const extraSegments = (0, path_utils_1.getExtraSegments)(path, groupedPath.baseUrl);
130
- // Create name segments, removing variables from name
131
- const nameSegments = extraSegments.map((s) => s.replace(/[{}]/g, ''));
132
- const extra = (0, string_utils_1.kebabToPascalCase)(nameSegments.join(''));
133
- let name = prefix + (0, string_utils_1.upFirst)(extra);
134
- if (usedNames.includes(name)) {
135
- name += (0, string_utils_1.upFirst)(method);
136
- }
137
- return name;
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 params = method.parameters
195
- .map((p) => `${p.name}${p.required ? '' : '?'}: ${p.type}`)
196
- .join(', ');
197
- let pathStr = method.path
198
- .split('/')
199
- .filter((s) => s !== '')
200
- .map((s) => ((0, path_utils_1.isVariable)(s) ? `\${${s.replace(/[{}]/g, '')}}` : s))
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
- if (pathStr) {
203
- pathStr = '/' + pathStr;
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
- const queryParams = method.parameters.filter((p) => p.in === 'query');
206
- const bodyParam = method.parameters.find((p) => p.in === 'body');
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 url = `\`\${this.baseUrl}${pathStr}\``;
212
- const methodCall = bodyParam
213
- ? `this.http.${method.method}<${method.responseType}>(${url}, ${bodyParam.name}${options})`
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}<${method.responseType}>(${url}, {}${options})`
216
- : `this.http.${method.method}<${method.responseType}>(${url}${options})`;
359
+ ? `this.http.${method.method}${genericType}(${url}, {}${options})`
360
+ : `this.http.${method.method}${genericType}(${url}${options})`;
217
361
  return `${method.comments}
218
- ${method.name}(${params}): Observable<${method.responseType}> {
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.
@@ -12,6 +12,7 @@ export interface ServiceDataMethod {
12
12
  parameters: ServiceDataParameter[];
13
13
  responseType: string;
14
14
  comments: string;
15
+ queryParamType?: string;
15
16
  }
16
17
  export interface ServiceDataParameter {
17
18
  name: string;
@@ -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
- const string_utils_1 = require("./string-utils");
12
- function generateFile(content, group) {
13
- const dir = `results/${group}`;
14
- if (fs_1.default.existsSync(dir)) {
15
- for (const file of fs_1.default.readdirSync(dir)) {
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
- else {
20
- fs_1.default.mkdirSync(dir, { recursive: true });
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
- await fs_1.default.promises.mkdir(dir, { recursive: true });
48
- await fs_1.default.promises.writeFile(path_1.default.join(dir, `${file.name}.${file.extension}`), file.content, 'utf8');
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
- if (indexContent) {
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.2.3",
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"