swaggular 0.3.0 → 0.5.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/dist/cli/args.js CHANGED
@@ -18,6 +18,12 @@ function getParseArgs(argv) {
18
18
  args[k] = true;
19
19
  }
20
20
  else {
21
+ // Special handling for --config to allow relative paths
22
+ if (k === 'config') {
23
+ args[k] = next;
24
+ i++;
25
+ continue;
26
+ }
21
27
  args[k] = next;
22
28
  i++;
23
29
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toVariables = toVariables;
4
+ const config_loader_1 = require("../utils/config-loader");
4
5
  function toVariables(parsed) {
5
6
  const variables = {};
6
7
  if (parsed.args.mode) {
@@ -11,11 +12,25 @@ function toVariables(parsed) {
11
12
  variables.segmentsToIgnore = parsed.args.segmentsToIgnore.split(',');
12
13
  }
13
14
  }
15
+ const configPath = parsed.args.config;
16
+ const fileConfig = (0, config_loader_1.loadConfig)(configPath);
17
+ const mergedConfig = { ...fileConfig, ...variables };
14
18
  return {
15
- input: parsed.args.input || 'swagger.json',
16
- output: parsed.args.output || 'results',
19
+ input: parsed.args.input ||
20
+ parsed.args.i ||
21
+ parsed.positional[0] ||
22
+ mergedConfig.input ||
23
+ 'swagger.json',
24
+ output: parsed.args.output ||
25
+ parsed.args.o ||
26
+ parsed.positional[1] ||
27
+ mergedConfig.output ||
28
+ 'results',
17
29
  noGenerate: parsed.args.noGenerate || false,
18
- groupingMode: variables.groupingMode || 'path',
19
- segmentsToIgnore: variables.segmentsToIgnore || ['api'],
30
+ groupingMode: variables.groupingMode || mergedConfig.groupingMode || 'path',
31
+ segmentsToIgnore: variables.segmentsToIgnore || mergedConfig.segmentsToIgnore || ['api'],
32
+ // Pass entire config if needed for templates
33
+ templates: mergedConfig.templates,
34
+ types: mergedConfig.types,
20
35
  };
21
36
  }
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -7,12 +7,15 @@ export interface InterfaceStateI {
7
7
  declare class InterfaceState implements InterfaceStateI {
8
8
  generatedInterfaces: Record<string, InterfaceData>;
9
9
  generatedEnums: Record<string, InterfaceData>;
10
+ typeMappings: Record<string, string>;
10
11
  componentsSchemas: Record<string, IJsonSchema>;
11
12
  constructor();
12
13
  setComponentsSchemas(componentsSchemas: Record<string, IJsonSchema>): void;
13
14
  getComponentsSchema(name: string): IJsonSchema | undefined;
14
15
  addInterfaces(interfacesData: InterfaceData[]): void;
15
16
  getInterface(name: string): InterfaceData | undefined;
17
+ addTypeMapping(original: string, mapped: string): void;
18
+ getTypeMapping(original: string): string | undefined;
16
19
  }
17
20
  export declare const interfaceState: InterfaceState;
18
21
  export {};
@@ -4,6 +4,7 @@ exports.interfaceState = void 0;
4
4
  class InterfaceState {
5
5
  generatedInterfaces = {};
6
6
  generatedEnums = {};
7
+ typeMappings = {};
7
8
  componentsSchemas = {};
8
9
  constructor() { }
9
10
  setComponentsSchemas(componentsSchemas) {
@@ -20,5 +21,11 @@ class InterfaceState {
20
21
  getInterface(name) {
21
22
  return this.generatedInterfaces[name] || this.generatedEnums[name];
22
23
  }
24
+ addTypeMapping(original, mapped) {
25
+ this.typeMappings[original] = mapped;
26
+ }
27
+ getTypeMapping(original) {
28
+ return this.typeMappings[original];
29
+ }
23
30
  }
24
31
  exports.interfaceState = new InterfaceState();
@@ -8,6 +8,7 @@ export declare class SwaggerState {
8
8
  setPathsGroupedByScope(pathsGroupedByScope: GroupedPaths): void;
9
9
  getPaths(): OpenAPIV3.PathsObject | undefined;
10
10
  getSchemas(): Record<string, OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject> | undefined;
11
+ getParameters(): Record<string, OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject> | undefined;
11
12
  getPathsGroupedByScope(): GroupedPaths | undefined;
12
13
  getGroupByPath(path: string): import("../../types/grouped-paths").GroupedPath | undefined;
13
14
  }
@@ -19,6 +19,9 @@ class SwaggerState {
19
19
  getSchemas() {
20
20
  return this.swagger?.components?.schemas;
21
21
  }
22
+ getParameters() {
23
+ return this.swagger?.components?.parameters;
24
+ }
22
25
  getPathsGroupedByScope() {
23
26
  return this.pathsGroupedByScope;
24
27
  }
package/dist/index.js CHANGED
@@ -18,10 +18,10 @@ async function main() {
18
18
  segmentsToIgnore: variables.segmentsToIgnore,
19
19
  ignoreVariables: true,
20
20
  });
21
- (0, generate_interface_1.generateInterfaces)();
21
+ (0, generate_interface_1.generateInterfaces)(variables);
22
22
  (0, generate_service_1.generateServices)();
23
- const interfaceFiles = (0, generate_interface_1.generateInterfacesFiles)();
24
- const serviceFiles = (0, generate_service_1.generateServiceFiles)();
23
+ const interfaceFiles = (0, generate_interface_1.generateInterfacesFiles)(undefined, variables);
24
+ const serviceFiles = (0, generate_service_1.generateServiceFiles)(undefined, variables.templates);
25
25
  if (variables.noGenerate) {
26
26
  console.log('Files not generated');
27
27
  return;
@@ -2,11 +2,12 @@ import { OpenAPIV3 } from 'openapi-types';
2
2
  import { FileContent } from '../types/file-content';
3
3
  import { GroupedPath } from '../types/grouped-paths';
4
4
  import { InterfaceData, InterfaceDataProperty } from '../types/interface-data';
5
+ import { SwaggularConfig } from '../types/config';
5
6
  export declare function parametersToIProperties(parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[]): InterfaceDataProperty[];
6
7
  export declare function propertiesToIProperties(properties: Record<string, OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>): InterfaceDataProperty[];
7
- export declare function generateInterfaces(): void;
8
- export declare function generateWithParameters(): void;
8
+ export declare function generateInterfaces(templatesConfig?: SwaggularConfig): void;
9
+ export declare function generateWithParameters(templatesConfig?: SwaggularConfig): void;
9
10
  export declare function computeParametersName(method: string, innerPath: string, groupedPath: GroupedPath): string;
10
- export declare function generateComponentsSchemas(): void;
11
- export declare function generateInterfacesFiles(locations?: Record<string, string[]>): FileContent[];
11
+ export declare function generateComponentsSchemas(templatesConfig?: SwaggularConfig): void;
12
+ export declare function generateInterfacesFiles(locations?: Record<string, string[]>, templatesConfig?: SwaggularConfig): FileContent[];
12
13
  export declare function generateContent(interfaceData: InterfaceData, deep?: number): string;
@@ -10,8 +10,6 @@ exports.generateInterfacesFiles = generateInterfacesFiles;
10
10
  exports.generateContent = generateContent;
11
11
  const interface_state_1 = require("../core/state/interface-state");
12
12
  const swagger_state_1 = require("../core/state/swagger-state");
13
- const extends_from_types_1 = require("../templates/types/extends-from-types");
14
- const generic_types_1 = require("../templates/types/generic-types");
15
13
  const build_types_1 = require("../utils/build-types");
16
14
  const object_utils_1 = require("../utils/object-utils");
17
15
  const path_utils_1 = require("../utils/path-utils");
@@ -23,12 +21,22 @@ function parametersToIProperties(parameters) {
23
21
  for (const param of parameters) {
24
22
  if ((0, type_guard_1.isReference)(param)) {
25
23
  const ref = param.$ref.split('/').pop();
26
- properties.push({
27
- name: (0, string_utils_1.lowerFirst)(ref),
28
- type: ref,
29
- optional: false,
30
- comments: '',
31
- });
24
+ if (param.$ref.includes('/components/parameters/')) {
25
+ const parameters = swagger_state_1.swaggerState.getParameters();
26
+ const parameter = parameters?.[ref];
27
+ if (parameter && !(0, type_guard_1.isReference)(parameter)) {
28
+ if (parameter.in !== 'query')
29
+ continue;
30
+ const tsType = parameter.schema ? (0, build_types_1.switchTypeJson)(parameter.schema) : 'any';
31
+ properties.push({
32
+ name: (0, string_utils_1.lowerFirst)(parameter.name),
33
+ type: tsType,
34
+ optional: !parameter.required,
35
+ comments: (0, generate_comments_1.generateInterfaceComments)(parameter.schema),
36
+ });
37
+ continue;
38
+ }
39
+ }
32
40
  continue;
33
41
  }
34
42
  if (param.in !== 'query')
@@ -65,16 +73,49 @@ function propertiesToIProperties(properties) {
65
73
  }
66
74
  return interfaceDataProperties;
67
75
  }
68
- function generateInterfaces() {
69
- generateComponentsSchemas();
70
- generateWithParameters();
76
+ function isGenericType(data, genericTypes) {
77
+ for (const genericType of genericTypes) {
78
+ if (genericType.properties.length !== data.properties.length)
79
+ continue;
80
+ const dataProperties = data.properties.map((p) => p.name);
81
+ const genericTypeProperties = genericType.properties.map((p) => p.name);
82
+ if (genericTypeProperties.some((p) => !dataProperties.includes(p)))
83
+ continue;
84
+ return true; // Match found
85
+ }
86
+ return false;
87
+ }
88
+ function computeExtendsFromType(data, extendsTypes) {
89
+ for (const extendsFromType of extendsTypes) {
90
+ const dataPropertiesReq = data.properties.filter((p) => !p.optional).map((p) => p.name);
91
+ const extendsFromTypePropertiesReq = extendsFromType.properties
92
+ .filter((p) => !p.optional)
93
+ .map((p) => p.name);
94
+ if (extendsFromTypePropertiesReq.some((p) => !dataPropertiesReq.includes(p))) {
95
+ continue;
96
+ }
97
+ const extensionProperties = extendsFromType.properties.map((p) => p.name.toLowerCase());
98
+ return {
99
+ ...data,
100
+ properties: data.properties.filter((p) => !extensionProperties.includes(p.name.toLowerCase())),
101
+ imports: [...data.imports, extendsFromType.name],
102
+ extendsFrom: [...(data.extendsFrom ?? []), extendsFromType.name],
103
+ };
104
+ }
105
+ return data;
106
+ }
107
+ function generateInterfaces(templatesConfig) {
108
+ generateComponentsSchemas(templatesConfig);
109
+ generateWithParameters(templatesConfig);
71
110
  }
72
- function generateWithParameters() {
111
+ function generateWithParameters(templatesConfig) {
73
112
  const paths = swagger_state_1.swaggerState.getPaths();
74
113
  const groupedPaths = swagger_state_1.swaggerState.getPathsGroupedByScope();
75
114
  if (!paths || !groupedPaths)
76
115
  return;
77
116
  const interfacesData = [];
117
+ const genericTypes = templatesConfig?.types?.generic ?? [];
118
+ const extendsTypes = templatesConfig?.types?.extendsFrom ?? [];
78
119
  for (const groupedPath of Object.values(groupedPaths)) {
79
120
  for (const innerPath of groupedPath.paths) {
80
121
  const pathInfo = paths[innerPath];
@@ -101,10 +142,10 @@ function generateWithParameters() {
101
142
  imports: properties.filter((p) => !(0, type_guard_1.isNativeType)(p.type)).map((p) => p.type),
102
143
  properties,
103
144
  };
104
- const generic = (0, generic_types_1.isGenericType)(interData);
145
+ const generic = isGenericType(interData, genericTypes);
105
146
  if (generic)
106
147
  continue;
107
- const interDataWithExtendsFrom = (0, extends_from_types_1.computeExtendsFromType)(interData);
148
+ const interDataWithExtendsFrom = computeExtendsFromType(interData, extendsTypes);
108
149
  interfacesData.push(interDataWithExtendsFrom);
109
150
  }
110
151
  }
@@ -121,13 +162,22 @@ function computeParametersName(method, innerPath, groupedPath) {
121
162
  };
122
163
  const name = dict[method];
123
164
  const extra = (0, string_utils_1.kebabToPascalCase)((0, path_utils_1.removeVariablesFromPath)((0, path_utils_1.getExtraSegments)(innerPath, (0, path_utils_1.createBaseUrl)(groupedPath.baseSegments)).join('')));
124
- return name + groupedPath.groupName + (0, string_utils_1.upFirst)(extra) + 'Params';
165
+ // Avoid duplication if extra starts with groupName
166
+ let suffix = extra;
167
+ const groupName = groupedPath.groupName;
168
+ if (suffix.startsWith(groupName)) {
169
+ suffix = suffix.substring(groupName.length);
170
+ }
171
+ return name + groupName + (0, string_utils_1.upFirst)(suffix) + 'Params';
125
172
  }
126
- function generateComponentsSchemas() {
173
+ function generateComponentsSchemas(templatesConfig) {
127
174
  const schemas = swagger_state_1.swaggerState.getSchemas();
128
175
  if (!schemas)
129
176
  return;
130
177
  const interfaces = [];
178
+ const genericMappings = new Map();
179
+ const genericTypes = templatesConfig?.types?.generic ?? [];
180
+ const extendsTypes = templatesConfig?.types?.extendsFrom ?? [];
131
181
  for (const [key, value] of Object.entries(schemas)) {
132
182
  if ((0, type_guard_1.isReference)(value))
133
183
  continue;
@@ -147,6 +197,29 @@ function generateComponentsSchemas() {
147
197
  }),
148
198
  };
149
199
  interfaces.push(interData);
200
+ continue;
201
+ }
202
+ const targetType = (0, build_types_1.switchTypeJson)(value);
203
+ if (targetType) {
204
+ const imports = [];
205
+ const baseType = targetType.replace('[]', '');
206
+ if (!(0, type_guard_1.isNativeType)(baseType)) {
207
+ imports.push(baseType);
208
+ }
209
+ interfaces.push({
210
+ name: key,
211
+ type: 'type',
212
+ imports,
213
+ properties: [
214
+ {
215
+ name: '',
216
+ type: targetType,
217
+ optional: false,
218
+ comments: (0, generate_comments_1.generateInterfaceComments)(value),
219
+ },
220
+ ],
221
+ });
222
+ continue;
150
223
  }
151
224
  continue;
152
225
  }
@@ -161,24 +234,56 @@ function generateComponentsSchemas() {
161
234
  }),
162
235
  properties,
163
236
  };
164
- const generic = (0, generic_types_1.isGenericType)(interData);
237
+ const generic = isGenericType(interData, genericTypes);
165
238
  if (generic) {
239
+ const genericType = genericTypes.find((g) => {
240
+ if (g.properties.length !== interData.properties.length)
241
+ return false;
242
+ const gProps = g.properties.map((p) => p.name);
243
+ const dProps = interData.properties.map((p) => p.name);
244
+ return !gProps.some((p) => !dProps.includes(p));
245
+ });
246
+ if (genericType) {
247
+ const genericName = computeGenericType(interData, genericType);
248
+ genericMappings.set(key, genericName);
249
+ interface_state_1.interfaceState.addTypeMapping(key, genericName);
250
+ }
166
251
  continue;
167
252
  }
168
- const interDataWithExtendsFrom = (0, extends_from_types_1.computeExtendsFromType)(interData);
253
+ const interDataWithExtendsFrom = computeExtendsFromType(interData, extendsTypes);
169
254
  interfaces.push(interDataWithExtendsFrom);
170
255
  }
256
+ // Update properties of all interfaces to use generic mappings
257
+ for (const inter of interfaces) {
258
+ inter.properties.forEach((p) => {
259
+ if (genericMappings.has(p.type)) {
260
+ p.type = genericMappings.get(p.type);
261
+ }
262
+ else if (p.type.endsWith('[]')) {
263
+ const base = p.type.replace('[]', '');
264
+ if (genericMappings.has(base)) {
265
+ p.type = genericMappings.get(base) + '[]';
266
+ }
267
+ }
268
+ });
269
+ }
171
270
  interface_state_1.interfaceState.addInterfaces(interfaces);
172
271
  }
173
- function generateInterfacesFiles(locations) {
272
+ function computeGenericType(newInterface, genericType) {
273
+ const typeParam = newInterface.name.replace(genericType.name, '');
274
+ return `${genericType.name}<${typeParam}>`;
275
+ }
276
+ function generateInterfacesFiles(locations, templatesConfig) {
277
+ const genericTypes = templatesConfig?.types?.generic ?? [];
278
+ const extendsTypes = templatesConfig?.types?.extendsFrom ?? [];
174
279
  const interfacesData = [
175
- ...generic_types_1.genericTypesData,
176
- ...extends_from_types_1.extendsFromTypes,
280
+ ...genericTypes,
281
+ ...extendsTypes,
177
282
  ...Array.from(Object.values(interface_state_1.interfaceState.generatedInterfaces)),
178
283
  ];
179
284
  const filesContent = [];
180
- for (const [key, value] of Object.entries(interfacesData)) {
181
- const location = [value.type === 'enum' ? 'enums' : 'dtos', ...(locations?.[key] ?? [])];
285
+ for (const value of interfacesData) {
286
+ const location = [value.type === 'enum' ? 'enums' : 'dtos', ...(locations?.[value.name] ?? [])];
182
287
  const content = generateContent(value, location.length + 1);
183
288
  const extraName = value.type === 'enum' ? 'enum' : 'dto';
184
289
  filesContent.push({
@@ -211,5 +316,9 @@ function generateContent(interfaceData, deep = 0) {
211
316
  }`;
212
317
  return content;
213
318
  }
319
+ if (interfaceData.type === 'type') {
320
+ const targetType = interfaceData.properties[0].type;
321
+ return `${importsTemplate}\n\nexport type ${interfaceData.name} = ${targetType};`;
322
+ }
214
323
  return '';
215
324
  }
@@ -2,9 +2,10 @@ import { OpenAPIV3 } from 'openapi-types';
2
2
  import { FileContent } from '../types/file-content';
3
3
  import { GroupedPath } from '../types/grouped-paths';
4
4
  import { ServiceDataMethod } from '../types/service-data';
5
+ import { SwaggularConfig } from '../types/config';
5
6
  /**
6
7
  * Pick the paths grouped by scope and generate services for them.
7
8
  */
8
9
  export declare function generateServices(): void;
9
- export declare function generateServiceFiles(locations?: Record<string, string[]>): FileContent[];
10
+ export declare function generateServiceFiles(locations?: Record<string, string[]>, templatesConfig?: SwaggularConfig['templates']): FileContent[];
10
11
  export declare function buildMethods(groupedPath: GroupedPath, pathData: OpenAPIV3.PathsObject, serviceName: string): ServiceDataMethod[];
@@ -5,7 +5,6 @@ exports.generateServiceFiles = generateServiceFiles;
5
5
  exports.buildMethods = buildMethods;
6
6
  const service_state_1 = require("../core/state/service-state");
7
7
  const swagger_state_1 = require("../core/state/swagger-state");
8
- const angular_template_1 = require("../templates/services/angular-template");
9
8
  const build_types_1 = require("../utils/build-types");
10
9
  const path_utils_1 = require("../utils/path-utils");
11
10
  const string_utils_1 = require("../utils/string-utils");
@@ -13,8 +12,8 @@ const type_guard_1 = require("../utils/type-guard");
13
12
  const generate_comments_1 = require("./generate-comments");
14
13
  const object_utils_1 = require("../utils/object-utils");
15
14
  const generate_interface_1 = require("./generate-interface");
16
- const http_params_handler_1 = require("../templates/services/http-params-handler");
17
15
  const interface_state_1 = require("../core/state/interface-state");
16
+ const template_renderer_1 = require("../utils/template-renderer");
18
17
  /**
19
18
  * Pick the paths grouped by scope and generate services for them.
20
19
  */
@@ -64,24 +63,34 @@ function addTypeToImports(type, imports) {
64
63
  imports.add(baseType);
65
64
  }
66
65
  }
67
- function generateServiceFiles(locations) {
66
+ function generateServiceFiles(locations, templatesConfig) {
68
67
  const services = Object.values(service_state_1.serviceState.services);
69
68
  const filesContent = [];
70
69
  for (const service of services) {
71
70
  const location = ['services', ...(locations?.[service.name] ?? [])];
71
+ const serviceTemplateConfig = templatesConfig?.service;
72
+ const templatePath = serviceTemplateConfig?.path || 'templates/angular-service.template';
72
73
  const methods = service.methods
73
74
  .map((method) => {
74
- return buildMethodTemplate(method);
75
+ return buildMethodTemplate(method, serviceTemplateConfig?.options?.httpParamsHandler);
75
76
  })
76
77
  .join('\n\n');
78
+ const allImports = Array.from(new Set([...service.imports])).sort();
79
+ const hasHttpParams = service.methods.some((m) => !!m.queryParamType);
77
80
  const templateParams = {
78
81
  name: service.name,
79
82
  baseUrl: service.baseUrl,
80
83
  methods: methods,
81
- imports: service.imports.join(', '),
82
- hasHttpParamsHandler: service.methods.some((m) => !!m.queryParamType),
84
+ imports: allImports.join(', '),
85
+ modelImports: allImports.length > 0 ? `import { ${allImports.join(', ')} } from '../models';` : '',
86
+ hasHttpParamsHandler: hasHttpParams,
87
+ httpParamsHandler: serviceTemplateConfig?.options?.httpParamsHandler,
88
+ httpParamsHandlerImport: hasHttpParams
89
+ ? serviceTemplateConfig?.options?.httpParamsHandlerImport
90
+ : '',
91
+ extraAngularImports: hasHttpParams ? ', HttpParams' : '',
83
92
  };
84
- const content = (0, angular_template_1.angularTemplate)(templateParams);
93
+ const content = (0, template_renderer_1.renderServiceTemplate)(templatePath, templateParams);
85
94
  filesContent.push({
86
95
  location: location,
87
96
  content,
@@ -140,18 +149,7 @@ function pathItemToMethods(pathItem) {
140
149
  patch: pathItem.patch,
141
150
  });
142
151
  }
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
152
  function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
154
- // 1. Core mapping of HTTP verbs to professional action prefixes
155
153
  const verbMap = {
156
154
  get: 'get',
157
155
  post: 'create',
@@ -161,7 +159,6 @@ function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
161
159
  };
162
160
  const defaultVerb = verbMap[method.toLowerCase()] || method.toLowerCase();
163
161
  const segments = (0, path_utils_1.getExtraSegments)(path, groupedPath.baseUrl);
164
- // 2. Redundancy Filtering: Remove segments already implied by the service name
165
162
  const serviceWords = serviceName.split(/(?=[A-Z])/).map((w) => w.toLowerCase());
166
163
  const cleanSegments = segments.filter((seg) => {
167
164
  if ((0, path_utils_1.isVariable)(seg))
@@ -169,7 +166,6 @@ function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
169
166
  const segWords = seg.split('-').map((w) => w.toLowerCase());
170
167
  return !segWords.every((sw) => serviceWords.includes(sw));
171
168
  });
172
- // 3. Structural Analysis: Identify resources and variable positioning
173
169
  const staticBefore = [];
174
170
  const variableParts = [];
175
171
  const staticAfter = [];
@@ -187,7 +183,6 @@ function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
187
183
  }
188
184
  }
189
185
  const nameParts = [];
190
- // 4. Logic specific to data fetching (GET)
191
186
  if (method.toLowerCase() === 'get') {
192
187
  nameParts.push('get');
193
188
  if (staticAfter.length > 0) {
@@ -203,22 +198,18 @@ function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
203
198
  nameParts.push('ById');
204
199
  }
205
200
  }
206
- // 5. Logic for state mutation (POST, PUT, PATCH, DELETE)
207
201
  else {
208
202
  const actionParts = [...staticBefore, ...staticAfter];
209
203
  if (actionParts.length > 0) {
210
- // If the path contains an action (e.g., /assign, /send-to-user), use it directly.
211
204
  nameParts.push((0, string_utils_1.toPascalCase)(actionParts));
212
205
  }
213
206
  else {
214
- // No extra segments, use the default verb prefix (e.g., create, update)
215
207
  nameParts.push((0, string_utils_1.upFirst)(defaultVerb));
216
208
  }
217
209
  if (variableParts.length > 0) {
218
210
  nameParts.push('ById');
219
211
  }
220
212
  }
221
- // 6. Pattern-based Refinement: Detect and prioritize standard REST suffixes
222
213
  const normalizedPath = path.toLowerCase();
223
214
  const suffixes = [
224
215
  { pattern: '/all', label: 'All' },
@@ -236,17 +227,13 @@ function buildMethodName(method, path, groupedPath, usedNames, serviceName) {
236
227
  break;
237
228
  }
238
229
  }
239
- // 7. Base candidate assembly
240
230
  let candidateName = (0, string_utils_1.lowerFirst)(nameParts.join(''));
241
- // Simplification for the primary resource GET
242
231
  if (candidateName === 'getById' && !usedNames.includes('get')) {
243
232
  candidateName = 'get';
244
233
  }
245
- // Fallback for empty/ambiguous names
246
234
  if (candidateName === '' || (candidateName === defaultVerb && segments.length > 0)) {
247
235
  candidateName = (0, string_utils_1.lowerFirst)(defaultVerb + (0, string_utils_1.toPascalCase)(segments.map((s) => s.replace(/[{}]/g, ''))));
248
236
  }
249
- // 8. Multi-strategy Collision Resolution
250
237
  let finalName = candidateName;
251
238
  let counter = 0;
252
239
  while (usedNames.includes(finalName)) {
@@ -271,14 +258,26 @@ function buildParameters(operation, pathParameters) {
271
258
  for (const param of allParams) {
272
259
  if ((0, type_guard_1.isReference)(param)) {
273
260
  const ref = param.$ref.split('/').pop();
274
- console.log('🚀 ~ buildParameters ~ ref:', ref);
275
261
  const interfaceData = interface_state_1.interfaceState.getInterface(ref);
276
- console.log('🚀 ~ buildParameters ~ interfaceData:', interfaceData);
262
+ let paramType = interface_state_1.interfaceState.getTypeMapping(ref) || ref;
263
+ let inType = interfaceData?.type !== undefined ? 'query' : 'path';
264
+ let required = true;
265
+ if (param.$ref.includes('/components/parameters/')) {
266
+ const parameters = swagger_state_1.swaggerState.getParameters();
267
+ const parameter = parameters?.[ref];
268
+ if (parameter && !(0, type_guard_1.isReference)(parameter)) {
269
+ inType = parameter.in;
270
+ required = parameter.required ?? false;
271
+ if (parameter.schema) {
272
+ paramType = (0, build_types_1.switchTypeJson)(parameter.schema);
273
+ }
274
+ }
275
+ }
277
276
  results.push({
278
277
  name: (0, string_utils_1.lowerFirst)(ref),
279
- in: interfaceData?.type !== undefined ? 'query' : 'path',
280
- required: true,
281
- type: ref,
278
+ in: inType,
279
+ required,
280
+ type: paramType,
282
281
  });
283
282
  continue;
284
283
  }
@@ -308,21 +307,22 @@ function buildParameters(operation, pathParameters) {
308
307
  type: bodyType,
309
308
  });
310
309
  }
311
- console.log('🚀 ~ buildParameters ~ results:', results);
312
310
  return results;
313
311
  }
314
312
  function buildResponseType(responses) {
315
313
  const success = responses['200'] || responses['201'] || responses['204'] || responses['default'];
316
314
  if (!success)
317
315
  return 'any';
318
- if ((0, type_guard_1.isReference)(success))
319
- return success.$ref.split('/').pop();
316
+ if ((0, type_guard_1.isReference)(success)) {
317
+ const name = success.$ref.split('/').pop();
318
+ return interface_state_1.interfaceState.getTypeMapping(name) || name;
319
+ }
320
320
  const content = success.content?.['application/json'];
321
321
  if (!content || !content.schema)
322
322
  return 'any';
323
323
  return (0, build_types_1.switchTypeJson)(content.schema);
324
324
  }
325
- function buildMethodTemplate(method) {
325
+ function buildMethodTemplate(method, httpParamsHandlerTemplate) {
326
326
  const pathParams = method.parameters.filter((p) => p.in === 'path');
327
327
  const bodyParam = method.parameters.find((p) => p.in === 'body');
328
328
  const methodArgs = [];
@@ -339,10 +339,8 @@ function buildMethodTemplate(method) {
339
339
  .map((s) => ((0, path_utils_1.isVariable)(s) ? `\${${(0, string_utils_1.lowerFirst)(s.replace(/[{}]/g, ''))}}` : s))
340
340
  .join('/');
341
341
  const url = `\`\${this.baseUrl}${pathStr ? '/' + pathStr : ''}\``;
342
- let handler = '';
343
342
  const optionsList = [];
344
343
  if (method.queryParamType) {
345
- handler = `\n\t\t${(0, http_params_handler_1.httpParamsHandler)('queryParams')}`;
346
344
  optionsList.push('params');
347
345
  }
348
346
  if (['Blob', 'File'].includes(method.responseType)) {
@@ -358,8 +356,30 @@ function buildMethodTemplate(method) {
358
356
  : ['post', 'put', 'patch'].includes(method.method)
359
357
  ? `this.http.${method.method}${genericType}(${url}, {}${options})`
360
358
  : `this.http.${method.method}${genericType}(${url}${options})`;
361
- return `${method.comments}
362
- ${method.name}(${argsString}): Observable<${method.responseType}> {${handler}
363
- return ${methodCall};
359
+ let paramsLogic = '';
360
+ if (method.queryParamType) {
361
+ if (httpParamsHandlerTemplate) {
362
+ paramsLogic = `\n\t\t${httpParamsHandlerTemplate.replace('${params}', 'queryParams')}`;
363
+ }
364
+ else {
365
+ paramsLogic = `
366
+ let params = new HttpParams();
367
+ if (queryParams) {
368
+ for (const [key, value] of Object.entries(queryParams)) {
369
+ if (value === undefined || value === null) continue;
370
+ if (Array.isArray(value)) {
371
+ value.forEach((v) => (params = params.append(key, v)));
372
+ } else if (typeof value === 'object' && !(value instanceof Date) && !(value instanceof Blob)) {
373
+ params = params.set(key, JSON.stringify(value));
374
+ } else {
375
+ params = params.set(key, value as any);
376
+ }
377
+ }
378
+ }`;
379
+ }
380
+ }
381
+ return `${method.comments}
382
+ ${method.name}(${argsString}): Observable<${method.responseType}> {${paramsLogic}
383
+ return ${methodCall};
364
384
  }`;
365
385
  }
@@ -7,12 +7,8 @@ export interface AngularServiceTemplate {
7
7
  methods: string;
8
8
  imports: string;
9
9
  hasHttpParamsHandler: boolean;
10
- }
11
- export interface AngularServiceResult {
12
- forTemplate: AngularServiceTemplate[];
13
- parametersTypes: Record<string, {
14
- method: string;
15
- parameterType: string;
16
- }>;
10
+ configImports?: string[];
11
+ httpParamsHandler?: string;
12
+ httpParamsHandlerImport?: string;
17
13
  }
18
14
  export declare const angularTemplate: (params: AngularServiceTemplate) => string;
@@ -1,14 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.angularTemplate = void 0;
4
- const http_params_handler_1 = require("./http-params-handler");
5
4
  const angularTemplate = (params) => {
5
+ const configImports = params.configImports
6
+ ? params.configImports.map((i) => `import { ${i} } from '@angular/core';`).join('\n') // Placeholder logic, actual imports might be more complex
7
+ : '';
8
+ // Actually, let's keep it simple. The config imports should probably be raw strings or structured.
9
+ // For now, let's assume the template receives the RAW import strings from the config if provided,
10
+ // OR we construct them here.
11
+ // User wants: "can make it so it can work without any custom template (just the most generic one)"
12
+ // and "make it so it works with a JSON configuration".
13
+ const httpParamsHandlerImport = params.httpParamsHandlerImport || '';
6
14
  return `
7
15
  import { HttpClient } from "@angular/common/http";
8
16
  import { Observable } from "rxjs";
9
17
  import { inject, Injectable } from '@angular/core';
10
18
  import { ${params.imports} } from '../models';
11
- ${params.hasHttpParamsHandler ? (0, http_params_handler_1.httpParamsHandlerImport)() : ''}
19
+ ${httpParamsHandlerImport}
12
20
 
13
21
  @Injectable({
14
22
  providedIn: "root"
@@ -0,0 +1,22 @@
1
+ import { ServiceTemplateParams } from './template';
2
+ import { InterfaceData } from './interface-data';
3
+ export interface SwaggularConfig {
4
+ types?: {
5
+ extendsFrom?: InterfaceData[];
6
+ generic?: InterfaceData[];
7
+ };
8
+ templates?: {
9
+ service?: {
10
+ path: string;
11
+ options?: Partial<ServiceTemplateParams>;
12
+ transform?: (options: ServiceTemplateParams) => string;
13
+ content?: string;
14
+ httpParamsHandler?: string;
15
+ httpParamsHandlerImport?: string;
16
+ };
17
+ };
18
+ input?: string;
19
+ output?: string;
20
+ groupingMode?: 'tags' | 'path';
21
+ segmentsToIgnore?: string[];
22
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,11 +1,14 @@
1
+ import { SwaggularConfig } from './config';
1
2
  export type ParsedArgs = {
2
3
  args: Record<string, string | boolean>;
3
4
  positional: string[];
4
5
  };
5
6
  export interface ArgsVariables {
6
- groupingMode: 'tags' | 'path';
7
- segmentsToIgnore: string[];
8
7
  input: string;
9
8
  output: string;
10
9
  noGenerate: boolean;
10
+ groupingMode: 'tags' | 'path';
11
+ segmentsToIgnore: string[];
12
+ templates?: SwaggularConfig['templates'];
13
+ types?: SwaggularConfig['types'];
11
14
  }
@@ -0,0 +1,11 @@
1
+ export interface ServiceTemplateParams {
2
+ name: string;
3
+ baseUrl: string;
4
+ methods: string;
5
+ imports: string;
6
+ hasHttpParamsHandler: boolean;
7
+ httpParamsHandler?: string;
8
+ httpParamsHandlerImport?: string;
9
+ modelImports?: string;
10
+ extraAngularImports?: string;
11
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildTypes = buildTypes;
4
4
  exports.switchTypeJson = switchTypeJson;
5
+ const interface_state_1 = require("../core/state/interface-state");
5
6
  function buildTypes(masterSchema, defaultType = 'any') {
6
7
  if (!masterSchema)
7
8
  return defaultType;
@@ -18,12 +19,8 @@ function buildTypes(masterSchema, defaultType = 'any') {
18
19
  return defaultType;
19
20
  const ref = schema?.['$ref'];
20
21
  if (ref) {
21
- const iface = ref.split('/').pop();
22
- const isPagedResultDto = iface.endsWith('PagedResultDto');
23
- if (isPagedResultDto) {
24
- return `PagedResultDto<${iface.replace('PagedResultDto', '')}>`;
25
- }
26
- return iface;
22
+ const name = ref.split('/').pop();
23
+ return interface_state_1.interfaceState.getTypeMapping(name) || name;
27
24
  }
28
25
  return switchTypeJson(schema);
29
26
  }
@@ -34,12 +31,8 @@ function switchTypeJson(schema) {
34
31
  const type = schema?.['type'];
35
32
  const format = schema?.['format'];
36
33
  if (ref) {
37
- const iface = ref.split('/').pop();
38
- const isPagedResultDto = iface.endsWith('PagedResultDto');
39
- if (isPagedResultDto) {
40
- return `PagedResultDto<${iface.replace('PagedResultDto', '')}>`;
41
- }
42
- return iface;
34
+ const name = ref.split('/').pop();
35
+ return interface_state_1.interfaceState.getTypeMapping(name) || name;
43
36
  }
44
37
  if (type === 'object') {
45
38
  return 'any';
@@ -0,0 +1,2 @@
1
+ import { SwaggularConfig } from '../types/config';
2
+ export declare function loadConfig(configPath?: string): SwaggularConfig;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadConfig = loadConfig;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function loadConfig(configPath) {
40
+ const defaultConfigPath = 'swaggular.config.json';
41
+ const resolvedPath = path.resolve(configPath || defaultConfigPath);
42
+ if (fs.existsSync(resolvedPath)) {
43
+ try {
44
+ const fileContent = fs.readFileSync(resolvedPath, 'utf-8');
45
+ return JSON.parse(fileContent);
46
+ }
47
+ catch (error) {
48
+ console.error(`Error parsing config file at ${resolvedPath}:`, error);
49
+ return {};
50
+ }
51
+ }
52
+ return {};
53
+ }
@@ -1,9 +1,4 @@
1
1
  export declare function kebabToCamelCase(str: string): string;
2
- /**
3
- *
4
- * @param str :
5
- * @returns
6
- */
7
2
  export declare function kebabToPascalCase(str: string): string;
8
3
  export declare function removeAllWhitespace(text: string): string;
9
4
  export declare function lowerFirst(str: string): string;
@@ -11,11 +11,6 @@ exports.toPascalCase = toPascalCase;
11
11
  function kebabToCamelCase(str) {
12
12
  return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
13
13
  }
14
- /**
15
- *
16
- * @param str :
17
- * @returns
18
- */
19
14
  function kebabToPascalCase(str) {
20
15
  const camel = str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
21
16
  return camel.charAt(0).toUpperCase() + camel.slice(1);
@@ -0,0 +1,2 @@
1
+ import { ServiceTemplateParams } from '../types/template';
2
+ export declare function renderServiceTemplate(templatePath: string, params: ServiceTemplateParams): string;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.renderServiceTemplate = renderServiceTemplate;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function renderServiceTemplate(templatePath, params) {
40
+ // 1. Read the template file
41
+ const absolutePath = path.resolve(templatePath);
42
+ let templateContent;
43
+ try {
44
+ if (fs.existsSync(absolutePath)) {
45
+ templateContent = fs.readFileSync(absolutePath, 'utf-8');
46
+ }
47
+ else {
48
+ console.error(`Template file not found at ${absolutePath}`);
49
+ return '';
50
+ }
51
+ }
52
+ catch (err) {
53
+ console.error(`Error reading template file at ${absolutePath}`, err);
54
+ return '';
55
+ }
56
+ // 2. Perform replacements
57
+ // Simple mustache-like replacement: {{variable}}
58
+ // We can use a regex or simple replaceAll if available (Node 15+) or split/join.
59
+ let result = templateContent;
60
+ // Replace known params
61
+ result = result.replace(/{{name}}/g, params.name);
62
+ result = result.replace(/{{baseUrl}}/g, params.baseUrl);
63
+ result = result.replace(/{{methods}}/g, params.methods);
64
+ result = result.replace(/{{modelImports}}/g, params.modelImports || '');
65
+ result = result.replace(/{{imports}}/g, params.imports);
66
+ // Handle conditional logic for httpParamsHandlerImport
67
+ result = result.replace(/{{httpParamsHandlerImport}}/g, params.httpParamsHandlerImport || '');
68
+ result = result.replace(/{{extraAngularImports}}/g, params.extraAngularImports || '');
69
+ // Cleanup excessive newlines (3 or more, potentially with spaces) to just 2
70
+ result = result.replace(/(\r?\n\s*){3,}/g, '\n\n');
71
+ return result;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggular",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "bin": {