ng-openapi 0.0.2

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.
Files changed (6) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -0
  3. package/cli.cjs +1827 -0
  4. package/index.d.ts +169 -0
  5. package/index.js +1807 -0
  6. package/package.json +86 -0
package/cli.cjs ADDED
@@ -0,0 +1,1827 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/lib/cli.ts
28
+ var import_commander = require("commander");
29
+ var path6 = __toESM(require("path"));
30
+ var fs4 = __toESM(require("fs"));
31
+
32
+ // src/lib/core/swagger-parser.ts
33
+ var fs = __toESM(require("fs"));
34
+ var SwaggerParser = class {
35
+ static {
36
+ __name(this, "SwaggerParser");
37
+ }
38
+ spec;
39
+ constructor(swaggerPath) {
40
+ const swaggerContent = fs.readFileSync(swaggerPath, "utf8");
41
+ this.spec = JSON.parse(swaggerContent);
42
+ }
43
+ getDefinitions() {
44
+ return this.spec.definitions || this.spec.components?.schemas || {};
45
+ }
46
+ getDefinition(name) {
47
+ const definitions = this.getDefinitions();
48
+ return definitions[name];
49
+ }
50
+ resolveReference(ref) {
51
+ const parts = ref.split("/");
52
+ const definitionName = parts[parts.length - 1];
53
+ return this.getDefinition(definitionName);
54
+ }
55
+ getAllDefinitionNames() {
56
+ return Object.keys(this.getDefinitions());
57
+ }
58
+ };
59
+
60
+ // src/lib/core/generator.ts
61
+ var import_ts_morph4 = require("ts-morph");
62
+
63
+ // src/lib/generators/type/type.generator.ts
64
+ var import_ts_morph = require("ts-morph");
65
+
66
+ // src/lib/config/constants.ts
67
+ var disableLinting = `/* @ts-nocheck */
68
+ /* eslint-disable */
69
+ /* @noformat */
70
+ /* @formatter:off */
71
+ `;
72
+ var authorComment = `/**
73
+ * Generated by ng-openapi
74
+ `;
75
+ var defaultHeaderComment = disableLinting + authorComment;
76
+ var TYPE_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated TypeScript interfaces from Swagger specification
77
+ * Do not edit this file manually
78
+ */
79
+ `;
80
+ var SERVICE_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated service exports
81
+ * Do not edit this file manually
82
+ */
83
+ `;
84
+ var SERVICE_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((controllerName) => defaultHeaderComment + `* Generated Angular service for ${controllerName} controller
85
+ * Do not edit this file manually
86
+ */
87
+ `, "SERVICE_GENERATOR_HEADER_COMMENT");
88
+
89
+ // src/lib/generators/type/type.generator.ts
90
+ var TypeGenerator = class {
91
+ static {
92
+ __name(this, "TypeGenerator");
93
+ }
94
+ project;
95
+ parser;
96
+ sourceFile;
97
+ generatedTypes = /* @__PURE__ */ new Set();
98
+ config;
99
+ constructor(swaggerPath, outputRoot, config) {
100
+ this.config = config;
101
+ const outputPath = outputRoot + "/models/index.ts";
102
+ this.project = new import_ts_morph.Project({
103
+ compilerOptions: {
104
+ declaration: true,
105
+ target: import_ts_morph.ScriptTarget.ES2022,
106
+ module: import_ts_morph.ModuleKind.Preserve,
107
+ strict: true,
108
+ ...this.config.compilerOptions
109
+ }
110
+ });
111
+ try {
112
+ this.parser = new SwaggerParser(swaggerPath);
113
+ this.sourceFile = this.project.createSourceFile(outputPath, "", {
114
+ overwrite: true
115
+ });
116
+ } catch (error) {
117
+ console.error("Error initializing TypeGenerator:", error);
118
+ throw error;
119
+ }
120
+ }
121
+ generate() {
122
+ try {
123
+ const definitions = this.parser.getDefinitions();
124
+ if (!definitions || Object.keys(definitions).length === 0) {
125
+ console.warn("No definitions found in swagger file");
126
+ return;
127
+ }
128
+ this.sourceFile.insertText(0, TYPE_GENERATOR_HEADER_COMMENT);
129
+ Object.entries(definitions).forEach(([name, definition]) => {
130
+ this.generateInterface(name, definition);
131
+ });
132
+ this.sourceFile.saveSync();
133
+ } catch (error) {
134
+ console.error("Error in generate():", error);
135
+ throw new Error(`Failed to generate types: ${error instanceof Error ? error.message : "Unknown error"}`);
136
+ }
137
+ }
138
+ generateInterface(name, definition) {
139
+ const interfaceName = this.pascalCaseForEnums(name);
140
+ if (this.generatedTypes.has(interfaceName)) {
141
+ return;
142
+ }
143
+ this.generatedTypes.add(interfaceName);
144
+ if (definition.enum) {
145
+ this.generateEnum(interfaceName, definition);
146
+ return;
147
+ }
148
+ if (definition.allOf) {
149
+ this.generateCompositeType(interfaceName, definition);
150
+ return;
151
+ }
152
+ const interfaceDeclaration = this.sourceFile.addInterface({
153
+ name: interfaceName,
154
+ isExported: true,
155
+ docs: definition.description ? [
156
+ definition.description
157
+ ] : void 0
158
+ });
159
+ this.addInterfaceProperties(interfaceDeclaration, definition);
160
+ }
161
+ generateEnum(name, definition) {
162
+ if (!definition.enum?.length) return;
163
+ const isStringEnum = definition.enum.some((value) => typeof value === "string");
164
+ if (isStringEnum) {
165
+ const unionType = definition.enum.map((value) => typeof value === "string" ? `'${this.escapeString(value)}'` : String(value)).join(" | ");
166
+ this.sourceFile.addTypeAlias({
167
+ name,
168
+ type: unionType,
169
+ isExported: true,
170
+ docs: definition.description ? [
171
+ definition.description
172
+ ] : void 0
173
+ });
174
+ } else if (definition.description && this.config.options.generateEnumBasedOnDescription) {
175
+ const enumDeclaration = this.sourceFile.addEnum({
176
+ name,
177
+ isExported: true
178
+ });
179
+ try {
180
+ const enumValueObjects = JSON.parse(definition.description);
181
+ enumValueObjects.forEach((enumValueObject) => {
182
+ enumDeclaration.addMember({
183
+ name: enumValueObject.Name,
184
+ value: enumValueObject.Value
185
+ });
186
+ });
187
+ } catch (e) {
188
+ console.error(`Failed to parse enum description for ${name}`);
189
+ definition.enum.forEach((value) => {
190
+ const enumKey = this.toEnumKey(value);
191
+ enumDeclaration.addMember({
192
+ name: enumKey,
193
+ value
194
+ });
195
+ });
196
+ }
197
+ } else {
198
+ const enumDeclaration = this.sourceFile.addEnum({
199
+ name,
200
+ isExported: true,
201
+ docs: definition.description ? [
202
+ definition.description
203
+ ] : void 0
204
+ });
205
+ definition.enum.forEach((value) => {
206
+ const enumKey = this.toEnumKey(value);
207
+ enumDeclaration.addMember({
208
+ name: enumKey,
209
+ value
210
+ });
211
+ });
212
+ }
213
+ }
214
+ generateCompositeType(name, definition) {
215
+ let typeExpression = "";
216
+ if (definition.allOf) {
217
+ const types = definition.allOf.map((def) => this.resolveSwaggerType(def)).filter((type) => type !== "any" && type !== "unknown");
218
+ typeExpression = types.length > 0 ? types.join(" & ") : "Record<string, unknown>";
219
+ }
220
+ this.sourceFile.addTypeAlias({
221
+ name,
222
+ type: typeExpression,
223
+ isExported: true,
224
+ docs: definition.description ? [
225
+ definition.description
226
+ ] : void 0
227
+ });
228
+ }
229
+ addInterfaceProperties(interfaceDeclaration, definition) {
230
+ if (!definition.properties && definition.additionalProperties === false) {
231
+ interfaceDeclaration.addIndexSignature({
232
+ keyName: "key",
233
+ keyType: "string",
234
+ returnType: "never"
235
+ });
236
+ return;
237
+ }
238
+ if (!definition.properties && definition.additionalProperties === true) {
239
+ interfaceDeclaration.addIndexSignature({
240
+ keyName: "key",
241
+ keyType: "string",
242
+ returnType: "any"
243
+ });
244
+ return;
245
+ }
246
+ if (!definition.properties) {
247
+ console.warn(`No properties found for interface ${interfaceDeclaration.getName()}`);
248
+ return;
249
+ }
250
+ Object.entries(definition.properties).forEach(([propertyName, property]) => {
251
+ const isRequired = definition.required?.includes(propertyName) ?? false;
252
+ const isReadOnly = property.readOnly;
253
+ const propertyType = this.resolveSwaggerType(property);
254
+ const sanitizedName = this.sanitizePropertyName(propertyName);
255
+ interfaceDeclaration.addProperty({
256
+ name: sanitizedName,
257
+ type: propertyType,
258
+ isReadonly: isReadOnly,
259
+ hasQuestionToken: !isRequired,
260
+ docs: property.description ? [
261
+ property.description
262
+ ] : void 0
263
+ });
264
+ });
265
+ }
266
+ resolveSwaggerType(schema) {
267
+ if (schema.$ref) {
268
+ return this.resolveReference(schema.$ref);
269
+ }
270
+ if (schema.enum) {
271
+ return schema.enum.map((value) => typeof value === "string" ? `'${this.escapeString(value)}'` : String(value)).join(" | ");
272
+ }
273
+ if (schema.allOf) {
274
+ return schema.allOf.map((def) => this.resolveSwaggerType(def)).filter((type) => type !== "any" && type !== "unknown").join(" & ") || "Record";
275
+ }
276
+ if (schema.oneOf) {
277
+ return schema.oneOf.map((def) => this.resolveSwaggerType(def)).filter((type) => type !== "any" && type !== "unknown").join(" | ") || "unknown";
278
+ }
279
+ if (schema.anyOf) {
280
+ return schema.anyOf.map((def) => this.resolveSwaggerType(def)).filter((type) => type !== "any" && type !== "unknown").join(" | ") || "unknown";
281
+ }
282
+ if (schema.type === "array") {
283
+ const itemType = schema.items ? this.getArrayItemType(schema.items) : "unknown";
284
+ return `${itemType}[]`;
285
+ }
286
+ if (schema.type === "object") {
287
+ if (schema.properties) {
288
+ return this.generateInlineObjectType(schema);
289
+ }
290
+ if (schema.additionalProperties) {
291
+ const valueType = typeof schema.additionalProperties === "object" ? this.resolveSwaggerType(schema.additionalProperties) : "unknown";
292
+ return `Record<string, ${valueType}>`;
293
+ }
294
+ return "Record<string, unknown>";
295
+ }
296
+ return this.mapSwaggerTypeToTypeScript(schema.type, schema.format, schema.nullable);
297
+ }
298
+ generateInlineObjectType(definition) {
299
+ if (!definition.properties) {
300
+ if (definition.additionalProperties) {
301
+ const additionalType = typeof definition.additionalProperties === "object" ? this.resolveSwaggerType(definition.additionalProperties) : "unknown";
302
+ return `Record<string, ${additionalType}>`;
303
+ }
304
+ return "Record<string, unknown>";
305
+ }
306
+ const properties = Object.entries(definition.properties).map(([key, prop]) => {
307
+ const isRequired = definition.required?.includes(key) ?? false;
308
+ const questionMark = isRequired ? "" : "?";
309
+ const sanitizedKey = this.sanitizePropertyName(key);
310
+ return `${sanitizedKey}${questionMark}: ${this.resolveSwaggerType(prop)}`;
311
+ }).join("; ");
312
+ return `{ ${properties} }`;
313
+ }
314
+ resolveReference(ref) {
315
+ try {
316
+ const refDefinition = this.parser.resolveReference(ref);
317
+ const refName = ref.split("/").pop();
318
+ if (!refName) {
319
+ console.warn(`Invalid reference format: ${ref}`);
320
+ return "unknown";
321
+ }
322
+ const typeName = this.pascalCaseForEnums(refName);
323
+ if (refDefinition && !this.generatedTypes.has(typeName)) {
324
+ this.generateInterface(refName, refDefinition);
325
+ }
326
+ return typeName;
327
+ } catch (error) {
328
+ console.warn(`Failed to resolve reference ${ref}:`, error);
329
+ return "unknown";
330
+ }
331
+ }
332
+ mapSwaggerTypeToTypeScript(type, format, isNullable) {
333
+ if (!type) return "unknown";
334
+ switch (type) {
335
+ case "string":
336
+ if (format === "date" || format === "date-time") {
337
+ const dateType = this.config.options.dateType === "Date" ? "Date" : "string";
338
+ return this.nullableType(dateType, isNullable);
339
+ }
340
+ if (format === "binary") return "Blob";
341
+ if (format === "uuid") return "string";
342
+ if (format === "email") return "string";
343
+ if (format === "uri") return "string";
344
+ return this.nullableType("string", isNullable);
345
+ case "number":
346
+ case "integer":
347
+ return this.nullableType("number", isNullable);
348
+ case "boolean":
349
+ return this.nullableType("boolean", isNullable);
350
+ case "array":
351
+ return this.nullableType("unknown[]", isNullable);
352
+ case "object":
353
+ return this.nullableType("Record<string, unknown>", isNullable);
354
+ case "null":
355
+ return this.nullableType("null", isNullable);
356
+ default:
357
+ console.warn(`Unknown swagger type: ${type}`);
358
+ return this.nullableType("unknown", isNullable);
359
+ }
360
+ }
361
+ nullableType(type, isNullable) {
362
+ return type + (isNullable ? " | null" : "");
363
+ }
364
+ pascalCaseForEnums(str) {
365
+ return str.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1");
366
+ }
367
+ sanitizePropertyName(name) {
368
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
369
+ return `"${name}"`;
370
+ }
371
+ return name;
372
+ }
373
+ toEnumKey(value) {
374
+ return value.toString().replace(/[^a-zA-Z0-9]/g, "_").replace(/^([0-9])/, "_$1").toUpperCase();
375
+ }
376
+ getArrayItemType(items) {
377
+ if (Array.isArray(items)) {
378
+ const types = items.map((item) => this.resolveSwaggerType(item));
379
+ return `[${types.join(", ")}]`;
380
+ } else {
381
+ return this.resolveSwaggerType(items);
382
+ }
383
+ }
384
+ escapeString(str) {
385
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
386
+ }
387
+ };
388
+
389
+ // src/lib/generators/utility/token.generator.ts
390
+ var import_ts_morph2 = require("ts-morph");
391
+ var path = __toESM(require("path"));
392
+ var TokenGenerator = class {
393
+ static {
394
+ __name(this, "TokenGenerator");
395
+ }
396
+ project;
397
+ constructor(project) {
398
+ this.project = project;
399
+ }
400
+ generate(outputDir) {
401
+ const tokensDir = path.join(outputDir, "tokens");
402
+ const filePath = path.join(tokensDir, "index.ts");
403
+ const sourceFile = this.project.createSourceFile(filePath, "", {
404
+ overwrite: true
405
+ });
406
+ sourceFile.addImportDeclaration({
407
+ namedImports: [
408
+ "InjectionToken"
409
+ ],
410
+ moduleSpecifier: "@angular/core"
411
+ });
412
+ sourceFile.addVariableStatement({
413
+ isExported: true,
414
+ declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
415
+ declarations: [
416
+ {
417
+ name: "BASE_PATH",
418
+ initializer: `new InjectionToken<string>('BASE_PATH', {
419
+ providedIn: 'root',
420
+ factory: () => '/api', // Default fallback
421
+ })`
422
+ }
423
+ ],
424
+ leadingTrivia: `/**
425
+ * Injection token for the base API path
426
+ */
427
+ `
428
+ });
429
+ sourceFile.saveSync();
430
+ }
431
+ };
432
+
433
+ // src/lib/generators/utility/file-download.generator.ts
434
+ var path2 = __toESM(require("path"));
435
+ var FileDownloadGenerator = class {
436
+ static {
437
+ __name(this, "FileDownloadGenerator");
438
+ }
439
+ project;
440
+ constructor(project) {
441
+ this.project = project;
442
+ }
443
+ generate(outputDir) {
444
+ const utilsDir = path2.join(outputDir, "utils");
445
+ const filePath = path2.join(utilsDir, "file-download.ts");
446
+ const sourceFile = this.project.createSourceFile(filePath, "", {
447
+ overwrite: true
448
+ });
449
+ sourceFile.addImportDeclaration({
450
+ namedImports: [
451
+ "Observable"
452
+ ],
453
+ moduleSpecifier: "rxjs"
454
+ });
455
+ sourceFile.addImportDeclaration({
456
+ namedImports: [
457
+ "tap"
458
+ ],
459
+ moduleSpecifier: "rxjs/operators"
460
+ });
461
+ sourceFile.addFunction({
462
+ name: "downloadFile",
463
+ isExported: true,
464
+ parameters: [
465
+ {
466
+ name: "blob",
467
+ type: "Blob"
468
+ },
469
+ {
470
+ name: "filename",
471
+ type: "string"
472
+ },
473
+ {
474
+ name: "mimeType",
475
+ type: "string",
476
+ hasQuestionToken: true
477
+ }
478
+ ],
479
+ returnType: "void",
480
+ statements: `
481
+ // Create a temporary URL for the blob
482
+ const url = window.URL.createObjectURL(blob);
483
+
484
+ // Create a temporary anchor element and trigger download
485
+ const link = document.createElement('a');
486
+ link.href = url;
487
+ link.download = filename;
488
+
489
+ // Append to body, click, and remove
490
+ document.body.appendChild(link);
491
+ link.click();
492
+ document.body.removeChild(link);
493
+
494
+ // Clean up the URL
495
+ window.URL.revokeObjectURL(url);`
496
+ });
497
+ sourceFile.addFunction({
498
+ name: "downloadFileOperator",
499
+ isExported: true,
500
+ typeParameters: [
501
+ {
502
+ name: "T",
503
+ constraint: "Blob"
504
+ }
505
+ ],
506
+ parameters: [
507
+ {
508
+ name: "filename",
509
+ type: "string | ((blob: T) => string)"
510
+ },
511
+ {
512
+ name: "mimeType",
513
+ type: "string",
514
+ hasQuestionToken: true
515
+ }
516
+ ],
517
+ returnType: "(source: Observable<T>) => Observable<T>",
518
+ statements: `
519
+ return (source: Observable<T>) => {
520
+ return source.pipe(
521
+ tap((blob: T) => {
522
+ const actualFilename = typeof filename === 'function' ? filename(blob) : filename;
523
+ downloadFile(blob, actualFilename, mimeType);
524
+ })
525
+ );
526
+ };`
527
+ });
528
+ sourceFile.addFunction({
529
+ name: "extractFilenameFromContentDisposition",
530
+ isExported: true,
531
+ parameters: [
532
+ {
533
+ name: "contentDisposition",
534
+ type: "string | null"
535
+ },
536
+ {
537
+ name: "fallbackFilename",
538
+ type: "string",
539
+ initializer: '"download"'
540
+ }
541
+ ],
542
+ returnType: "string",
543
+ statements: `
544
+ if (!contentDisposition) {
545
+ return fallbackFilename;
546
+ }
547
+
548
+ // Try to extract filename from Content-Disposition header
549
+ // Supports both "filename=" and "filename*=" formats
550
+ const filenameMatch = contentDisposition.match(/filename\\*?=['"]?([^'"\\n;]+)['"]?/i);
551
+
552
+ if (filenameMatch && filenameMatch[1]) {
553
+ // Decode if it's RFC 5987 encoded (filename*=UTF-8''...)
554
+ const filename = filenameMatch[1];
555
+ if (filename.includes("''")) {
556
+ const parts = filename.split("''");
557
+ if (parts.length === 2) {
558
+ try {
559
+ return decodeURIComponent(parts[1]);
560
+ } catch {
561
+ return parts[1];
562
+ }
563
+ }
564
+ }
565
+ return filename;
566
+ }
567
+
568
+ return fallbackFilename;`
569
+ });
570
+ sourceFile.saveSync();
571
+ }
572
+ };
573
+
574
+ // src/lib/generators/utility/date-transformer.generator.ts
575
+ var path3 = __toESM(require("path"));
576
+ var DateTransformerGenerator = class {
577
+ static {
578
+ __name(this, "DateTransformerGenerator");
579
+ }
580
+ project;
581
+ constructor(project) {
582
+ this.project = project;
583
+ }
584
+ generate(outputDir) {
585
+ const utilsDir = path3.join(outputDir, "utils");
586
+ const filePath = path3.join(utilsDir, "date-transformer.ts");
587
+ const sourceFile = this.project.createSourceFile(filePath, "", {
588
+ overwrite: true
589
+ });
590
+ sourceFile.addImportDeclaration({
591
+ namedImports: [
592
+ "HttpInterceptor",
593
+ "HttpRequest",
594
+ "HttpHandler",
595
+ "HttpEvent",
596
+ "HttpResponse"
597
+ ],
598
+ moduleSpecifier: "@angular/common/http"
599
+ });
600
+ sourceFile.addImportDeclaration({
601
+ namedImports: [
602
+ "Injectable"
603
+ ],
604
+ moduleSpecifier: "@angular/core"
605
+ });
606
+ sourceFile.addImportDeclaration({
607
+ namedImports: [
608
+ "Observable"
609
+ ],
610
+ moduleSpecifier: "rxjs"
611
+ });
612
+ sourceFile.addImportDeclaration({
613
+ namedImports: [
614
+ "map"
615
+ ],
616
+ moduleSpecifier: "rxjs/operators"
617
+ });
618
+ sourceFile.addVariableStatement({
619
+ isExported: true,
620
+ declarationKind: "const",
621
+ declarations: [
622
+ {
623
+ name: "ISO_DATE_REGEX",
624
+ initializer: "/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?$/"
625
+ }
626
+ ]
627
+ });
628
+ sourceFile.addFunction({
629
+ name: "transformDates",
630
+ isExported: true,
631
+ parameters: [
632
+ {
633
+ name: "obj",
634
+ type: "any"
635
+ }
636
+ ],
637
+ returnType: "any",
638
+ statements: `
639
+ if (obj === null || obj === undefined || typeof obj !== 'object') {
640
+ return obj;
641
+ }
642
+
643
+ if (obj instanceof Date) {
644
+ return obj;
645
+ }
646
+
647
+ if (Array.isArray(obj)) {
648
+ return obj.map(item => transformDates(item));
649
+ }
650
+
651
+ if (typeof obj === 'object') {
652
+ const transformed: any = {};
653
+ for (const key of Object.keys(obj)) {
654
+ const value = obj[key];
655
+ if (typeof value === 'string' && ISO_DATE_REGEX.test(value)) {
656
+ transformed[key] = new Date(value);
657
+ } else {
658
+ transformed[key] = transformDates(value);
659
+ }
660
+ }
661
+ return transformed;
662
+ }
663
+
664
+ return obj;`
665
+ });
666
+ sourceFile.addClass({
667
+ name: "DateInterceptor",
668
+ isExported: true,
669
+ decorators: [
670
+ {
671
+ name: "Injectable",
672
+ arguments: []
673
+ }
674
+ ],
675
+ implements: [
676
+ "HttpInterceptor"
677
+ ],
678
+ methods: [
679
+ {
680
+ name: "intercept",
681
+ parameters: [
682
+ {
683
+ name: "req",
684
+ type: "HttpRequest<any>"
685
+ },
686
+ {
687
+ name: "next",
688
+ type: "HttpHandler"
689
+ }
690
+ ],
691
+ returnType: "Observable<HttpEvent<any>>",
692
+ statements: `
693
+ return next.handle(req).pipe(
694
+ map(event => {
695
+ if (event instanceof HttpResponse && event.body) {
696
+ return event.clone({ body: transformDates(event.body) });
697
+ }
698
+ return event;
699
+ })
700
+ );`
701
+ }
702
+ ]
703
+ });
704
+ sourceFile.saveSync();
705
+ }
706
+ };
707
+
708
+ // src/lib/generators/service/service.generator.ts
709
+ var import_ts_morph3 = require("ts-morph");
710
+ var path4 = __toESM(require("path"));
711
+
712
+ // src/lib/utils/string.utils.ts
713
+ function camelCase(str) {
714
+ const cleaned = str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase());
715
+ return cleaned.charAt(0).toLowerCase() + cleaned.slice(1);
716
+ }
717
+ __name(camelCase, "camelCase");
718
+ function kebabCase(str) {
719
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
720
+ }
721
+ __name(kebabCase, "kebabCase");
722
+ function pascalCase(str) {
723
+ return str.replace(/(?:^|[-_])([a-z])/g, (_, char) => char.toUpperCase());
724
+ }
725
+ __name(pascalCase, "pascalCase");
726
+
727
+ // src/lib/utils/type.utils.ts
728
+ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, context = "type") {
729
+ let schema;
730
+ let nullable;
731
+ if (typeof schemaOrType === "string" || schemaOrType === void 0) {
732
+ schema = {
733
+ type: schemaOrType,
734
+ format: typeof formatOrNullable === "string" ? formatOrNullable : void 0
735
+ };
736
+ nullable = typeof formatOrNullable === "boolean" ? formatOrNullable : isNullable;
737
+ } else {
738
+ schema = schemaOrType;
739
+ nullable = typeof formatOrNullable === "boolean" ? formatOrNullable : schema.nullable;
740
+ }
741
+ if (!schema) {
742
+ return "any";
743
+ }
744
+ if (schema.$ref) {
745
+ const refName = schema.$ref.split("/").pop();
746
+ return nullableType(pascalCase(refName), nullable);
747
+ }
748
+ if (schema.type === "array") {
749
+ const itemType = schema.items ? getTypeScriptType(schema.items, config, void 0, void 0, context) : "unknown";
750
+ return nullable ? `(${itemType}[] | null)` : `${itemType}[]`;
751
+ }
752
+ switch (schema.type) {
753
+ case "string":
754
+ if (schema.format === "date" || schema.format === "date-time") {
755
+ const dateType = config.options.dateType === "Date" ? "Date" : "string";
756
+ return nullableType(dateType, nullable);
757
+ }
758
+ if (schema.format === "binary") {
759
+ const binaryType = context === "type" ? "Blob" : "File";
760
+ return nullableType(binaryType, nullable);
761
+ }
762
+ if (schema.format === "uuid" || schema.format === "email" || schema.format === "uri" || schema.format === "hostname" || schema.format === "ipv4" || schema.format === "ipv6") {
763
+ return nullableType("string", nullable);
764
+ }
765
+ return nullableType("string", nullable);
766
+ case "number":
767
+ case "integer":
768
+ return nullableType("number", nullable);
769
+ case "boolean":
770
+ return nullableType("boolean", nullable);
771
+ case "object":
772
+ return nullableType(context === "type" ? "Record<string, unknown>" : "any", nullable);
773
+ case "null":
774
+ return "null";
775
+ default:
776
+ console.warn(`Unknown swagger type: ${schema.type}`);
777
+ return nullableType("any", nullable);
778
+ }
779
+ }
780
+ __name(getTypeScriptType, "getTypeScriptType");
781
+ function nullableType(type, isNullable) {
782
+ return type + (isNullable ? " | null" : "");
783
+ }
784
+ __name(nullableType, "nullableType");
785
+
786
+ // src/lib/generators/service/service-method/service-method-body.generator.ts
787
+ var ServiceMethodBodyGenerator = class {
788
+ static {
789
+ __name(this, "ServiceMethodBodyGenerator");
790
+ }
791
+ config;
792
+ constructor(config) {
793
+ this.config = config;
794
+ }
795
+ generateMethodBody(operation) {
796
+ const context = this.createGenerationContext(operation);
797
+ const bodyParts = [
798
+ this.generateUrlConstruction(operation, context),
799
+ this.generateQueryParams(context),
800
+ this.generateHeaders(context),
801
+ this.generateMultipartFormData(operation, context),
802
+ this.generateRequestOptions(context),
803
+ this.generateHttpRequest(operation, context)
804
+ ];
805
+ return bodyParts.filter(Boolean).join("\n");
806
+ }
807
+ getRequestBodyType(requestBody) {
808
+ const content = requestBody.content || {};
809
+ const jsonContent = content["application/json"];
810
+ if (jsonContent?.schema) {
811
+ return getTypeScriptType(jsonContent.schema, this.config, jsonContent.schema.nullable);
812
+ }
813
+ return "any";
814
+ }
815
+ isMultipartFormData(operation) {
816
+ return !!operation.requestBody?.content?.["multipart/form-data"];
817
+ }
818
+ getFormDataFields(operation) {
819
+ if (!this.isMultipartFormData(operation)) {
820
+ return [];
821
+ }
822
+ const properties = operation.requestBody?.content?.["multipart/form-data"]?.schema?.properties || {};
823
+ return Object.keys(properties);
824
+ }
825
+ getResponseTypeFromResponse(response) {
826
+ const content = response.content || {};
827
+ if (Object.keys(content).length === 0) {
828
+ return "json";
829
+ }
830
+ const responseTypes = [];
831
+ for (const [contentType, mediaType] of Object.entries(content)) {
832
+ const schema = mediaType?.schema;
833
+ const mapping = this.config?.options?.responseTypeMapping || {};
834
+ if (mapping[contentType]) {
835
+ responseTypes.push({
836
+ type: mapping[contentType],
837
+ priority: 1,
838
+ contentType
839
+ });
840
+ continue;
841
+ }
842
+ if (schema?.format === "binary" || schema?.format === "byte") {
843
+ responseTypes.push({
844
+ type: "blob",
845
+ priority: 2,
846
+ contentType
847
+ });
848
+ continue;
849
+ }
850
+ if (schema?.type === "string" && (schema?.format === "binary" || schema?.format === "byte")) {
851
+ responseTypes.push({
852
+ type: "blob",
853
+ priority: 2,
854
+ contentType
855
+ });
856
+ continue;
857
+ }
858
+ const inferredType = this.inferResponseTypeFromContentType(contentType);
859
+ let priority = 3;
860
+ if (inferredType === "json") {
861
+ priority = 2;
862
+ }
863
+ responseTypes.push({
864
+ type: inferredType,
865
+ priority,
866
+ contentType
867
+ });
868
+ }
869
+ responseTypes.sort((a, b) => a.priority - b.priority);
870
+ return responseTypes[0]?.type || "json";
871
+ }
872
+ createGenerationContext(operation) {
873
+ return {
874
+ pathParams: operation.parameters?.filter((p) => p.in === "path") || [],
875
+ queryParams: operation.parameters?.filter((p) => p.in === "query") || [],
876
+ hasBody: !!operation.requestBody,
877
+ isMultipart: this.isMultipartFormData(operation),
878
+ formDataFields: this.getFormDataFields(operation),
879
+ responseType: this.determineResponseType(operation)
880
+ };
881
+ }
882
+ generateUrlConstruction(operation, context) {
883
+ let urlExpression = `\`\${this.basePath}${operation.path}\``;
884
+ if (context.pathParams.length > 0) {
885
+ context.pathParams.forEach((param) => {
886
+ urlExpression = urlExpression.replace(`{${param.name}}`, `\${${param.name}}`);
887
+ });
888
+ }
889
+ return `const url = ${urlExpression};`;
890
+ }
891
+ generateQueryParams(context) {
892
+ if (context.queryParams.length === 0) {
893
+ return "";
894
+ }
895
+ const paramMappings = context.queryParams.map((param) => `if (${param.name} !== undefined) {
896
+ params = params.set('${param.name}', String(${param.name}));
897
+ }`).join("\n");
898
+ return `
899
+ let params = new HttpParams();
900
+ ${paramMappings}`;
901
+ }
902
+ generateHeaders(context) {
903
+ const hasCustomHeaders = this.config.options.customHeaders;
904
+ if (!hasCustomHeaders && !context.isMultipart) {
905
+ return "";
906
+ }
907
+ let headerCode = `
908
+ let headers: HttpHeaders;
909
+ if (options?.headers instanceof HttpHeaders) {
910
+ headers = options.headers;
911
+ } else {
912
+ headers = new HttpHeaders(options?.headers);
913
+ }`;
914
+ if (hasCustomHeaders) {
915
+ headerCode += `
916
+ // Add default headers if not already present
917
+ ${Object.entries(this.config.options.customHeaders || {}).map(([key, value]) => `if (!headers.has('${key}')) {
918
+ headers = headers.set('${key}', '${value}');
919
+ }`).join("\n")}`;
920
+ }
921
+ if (context.isMultipart) {
922
+ headerCode += `
923
+ // Remove Content-Type for multipart (browser will set it with boundary)
924
+ headers = headers.delete('Content-Type');`;
925
+ } else if (!context.isMultipart) {
926
+ headerCode += `
927
+ // Set Content-Type for JSON requests if not already set
928
+ if (!headers.has('Content-Type')) {
929
+ headers = headers.set('Content-Type', 'application/json');
930
+ }`;
931
+ }
932
+ return headerCode;
933
+ }
934
+ generateMultipartFormData(operation, context) {
935
+ if (!context.isMultipart || context.formDataFields.length === 0) {
936
+ return "";
937
+ }
938
+ const formDataAppends = context.formDataFields.map((field) => {
939
+ const fieldSchema = operation.requestBody?.content?.["multipart/form-data"]?.schema?.properties?.[field];
940
+ const isFile = fieldSchema?.type === "string" && fieldSchema?.format === "binary";
941
+ const valueExpression = isFile ? field : `String(${field})`;
942
+ return `if (${field} !== undefined) {
943
+ formData.append('${field}', ${valueExpression});
944
+ }`;
945
+ }).join("\n");
946
+ return `
947
+ const formData = new FormData();
948
+ ${formDataAppends}`;
949
+ }
950
+ generateRequestOptions(context) {
951
+ const options = [];
952
+ options.push("observe: observe as any");
953
+ const hasHeaders = this.config.options.customHeaders || context.isMultipart;
954
+ if (hasHeaders) {
955
+ options.push("headers");
956
+ }
957
+ if (context.queryParams.length > 0) {
958
+ options.push("params");
959
+ }
960
+ if (context.responseType !== "json") {
961
+ options.push(`responseType: '${context.responseType}' as '${context.responseType}'`);
962
+ }
963
+ options.push("reportProgress: options?.reportProgress");
964
+ options.push("withCredentials: options?.withCredentials");
965
+ if (options.length > 0) {
966
+ options.push("context: options?.context");
967
+ }
968
+ const formattedOptions = options.filter((opt) => opt && !opt.includes("undefined")).join(",\n ");
969
+ return `
970
+ const requestOptions: any = {
971
+ ${formattedOptions}
972
+ };`;
973
+ }
974
+ generateHttpRequest(operation, context) {
975
+ const httpMethod = operation.method.toLowerCase();
976
+ let bodyParam = "";
977
+ if (context.hasBody) {
978
+ if (context.isMultipart) {
979
+ bodyParam = "formData";
980
+ } else if (operation.requestBody?.content?.["application/json"]) {
981
+ const bodyType = this.getRequestBodyType(operation.requestBody);
982
+ const isInterface = this.isDataTypeInterface(bodyType);
983
+ bodyParam = isInterface ? camelCase(bodyType) : "requestBody";
984
+ }
985
+ }
986
+ const methodsWithBody = [
987
+ "post",
988
+ "put",
989
+ "patch"
990
+ ];
991
+ if (methodsWithBody.includes(httpMethod)) {
992
+ return `
993
+ return this.httpClient.${httpMethod}(url, ${bodyParam || "null"}, requestOptions);`;
994
+ } else {
995
+ return `
996
+ return this.httpClient.${httpMethod}(url, requestOptions);`;
997
+ }
998
+ }
999
+ determineResponseType(operation) {
1000
+ const successResponses = [
1001
+ "200",
1002
+ "201",
1003
+ "202",
1004
+ "204",
1005
+ "206"
1006
+ ];
1007
+ for (const statusCode of successResponses) {
1008
+ const response = operation.responses?.[statusCode];
1009
+ if (!response) continue;
1010
+ return this.getResponseTypeFromResponse(response);
1011
+ }
1012
+ return "json";
1013
+ }
1014
+ isDataTypeInterface(type) {
1015
+ const invalidTypes = [
1016
+ "any",
1017
+ "File",
1018
+ "string",
1019
+ "number",
1020
+ "boolean",
1021
+ "object",
1022
+ "unknown",
1023
+ "[]",
1024
+ "Array"
1025
+ ];
1026
+ return !invalidTypes.some((invalidType) => type.includes(invalidType));
1027
+ }
1028
+ inferResponseTypeFromContentType(contentType) {
1029
+ const normalizedType = contentType.split(";")[0].trim().toLowerCase();
1030
+ if (normalizedType.includes("json") || normalizedType === "application/ld+json" || normalizedType === "application/hal+json" || normalizedType === "application/vnd.api+json") {
1031
+ return "json";
1032
+ }
1033
+ if (normalizedType.includes("xml") || normalizedType === "application/soap+xml" || normalizedType === "application/atom+xml" || normalizedType === "application/rss+xml") {
1034
+ return "text";
1035
+ }
1036
+ if (normalizedType.startsWith("text/")) {
1037
+ const binaryTextTypes = [
1038
+ "text/rtf",
1039
+ "text/cache-manifest",
1040
+ "text/vcard",
1041
+ "text/calendar"
1042
+ ];
1043
+ if (binaryTextTypes.includes(normalizedType)) {
1044
+ return "blob";
1045
+ }
1046
+ return "text";
1047
+ }
1048
+ if (normalizedType === "application/x-www-form-urlencoded" || normalizedType === "multipart/form-data") {
1049
+ return "text";
1050
+ }
1051
+ if (normalizedType === "application/javascript" || normalizedType === "application/typescript" || normalizedType === "application/css" || normalizedType === "application/yaml" || normalizedType === "application/x-yaml" || normalizedType === "application/toml") {
1052
+ return "text";
1053
+ }
1054
+ if (normalizedType.startsWith("image/") || normalizedType.startsWith("audio/") || normalizedType.startsWith("video/") || normalizedType === "application/pdf" || normalizedType === "application/zip" || normalizedType.includes("octet-stream")) {
1055
+ return "arraybuffer";
1056
+ }
1057
+ return "blob";
1058
+ }
1059
+ };
1060
+
1061
+ // src/lib/generators/service/service-method/service-method-params.generator.ts
1062
+ var ServiceMethodParamsGenerator = class {
1063
+ static {
1064
+ __name(this, "ServiceMethodParamsGenerator");
1065
+ }
1066
+ config;
1067
+ constructor(config) {
1068
+ this.config = config;
1069
+ }
1070
+ generateMethodParameters(operation) {
1071
+ const params = this.generateApiParameters(operation);
1072
+ const optionsParam = this.addOptionsParameter();
1073
+ const combined = [
1074
+ ...params,
1075
+ ...optionsParam
1076
+ ];
1077
+ const seen = /* @__PURE__ */ new Set();
1078
+ const uniqueParams = [];
1079
+ for (const param of combined) {
1080
+ if (!seen.has(param.name)) {
1081
+ seen.add(param.name);
1082
+ uniqueParams.push(param);
1083
+ }
1084
+ }
1085
+ return uniqueParams;
1086
+ }
1087
+ generateApiParameters(operation) {
1088
+ const params = [];
1089
+ const pathParams = operation.parameters?.filter((p) => p.in === "path") || [];
1090
+ pathParams.forEach((param) => {
1091
+ params.push({
1092
+ name: param.name,
1093
+ type: getTypeScriptType(param.schema || param, this.config),
1094
+ hasQuestionToken: !param.required
1095
+ });
1096
+ });
1097
+ if (operation.requestBody && operation.requestBody?.content?.["multipart/form-data"]) {
1098
+ Object.entries(operation.requestBody?.content?.["multipart/form-data"].schema?.properties ?? {}).forEach(([key, value]) => {
1099
+ params.push({
1100
+ name: key,
1101
+ type: getTypeScriptType(value, this.config, value.nullable),
1102
+ hasQuestionToken: !value.required
1103
+ });
1104
+ });
1105
+ }
1106
+ if (operation.requestBody && operation.requestBody?.content?.["application/json"]) {
1107
+ const bodyType = this.getRequestBodyType(operation.requestBody);
1108
+ const isInterface = this.isDataTypeInterface(bodyType);
1109
+ params.push({
1110
+ name: isInterface ? camelCase(bodyType) : "requestBody",
1111
+ type: bodyType,
1112
+ hasQuestionToken: !operation.requestBody.required
1113
+ });
1114
+ }
1115
+ const queryParams = operation.parameters?.filter((p) => p.in === "query") || [];
1116
+ queryParams.forEach((param) => {
1117
+ params.push({
1118
+ name: param.name,
1119
+ type: getTypeScriptType(param.schema || param, this.config),
1120
+ hasQuestionToken: !param.required
1121
+ });
1122
+ });
1123
+ return params;
1124
+ }
1125
+ addOptionsParameter() {
1126
+ return [
1127
+ {
1128
+ name: "observe",
1129
+ type: `'body' | 'events' | 'response'`,
1130
+ hasQuestionToken: true
1131
+ },
1132
+ {
1133
+ name: "options",
1134
+ type: `{ headers?: HttpHeaders; params?: HttpParams; reportProgress?: boolean; responseType?: 'arraybuffer' | 'blob' | 'json' | 'text'; withCredentials?: boolean; context?: HttpContext; }`,
1135
+ hasQuestionToken: true
1136
+ }
1137
+ ];
1138
+ }
1139
+ isDataTypeInterface(type) {
1140
+ const invalidTypes = [
1141
+ "any",
1142
+ "File",
1143
+ "string",
1144
+ "number",
1145
+ "boolean",
1146
+ "object",
1147
+ "unknown",
1148
+ "[]",
1149
+ "Array"
1150
+ ];
1151
+ return !invalidTypes.some((invalidType) => type.includes(invalidType));
1152
+ }
1153
+ getRequestBodyType(requestBody) {
1154
+ const content = requestBody.content || {};
1155
+ const jsonContent = content["application/json"];
1156
+ if (jsonContent?.schema) {
1157
+ return getTypeScriptType(jsonContent.schema, this.config, jsonContent.schema.nullable);
1158
+ }
1159
+ return "any";
1160
+ }
1161
+ };
1162
+
1163
+ // src/lib/generators/service/service-method/service-method-overloads.generator.ts
1164
+ var ServiceMethodOverloadsGenerator = class {
1165
+ static {
1166
+ __name(this, "ServiceMethodOverloadsGenerator");
1167
+ }
1168
+ config;
1169
+ paramsGenerator;
1170
+ constructor(config) {
1171
+ this.config = config;
1172
+ this.paramsGenerator = new ServiceMethodParamsGenerator(config);
1173
+ }
1174
+ generateMethodOverloads(operation) {
1175
+ const observeTypes = [
1176
+ "body",
1177
+ "response",
1178
+ "events"
1179
+ ];
1180
+ const overloads = [];
1181
+ const responseType = this.determineResponseTypeForOperation(operation);
1182
+ observeTypes.forEach((observe) => {
1183
+ const overload = this.generateMethodOverload(operation, observe, responseType);
1184
+ if (overload) {
1185
+ overloads.push(overload);
1186
+ }
1187
+ });
1188
+ return overloads;
1189
+ }
1190
+ generateMethodOverload(operation, observe, responseType) {
1191
+ const responseDataType = this.generateOverloadResponseType(operation);
1192
+ const params = this.generateOverloadParameters(operation, observe, responseType);
1193
+ const returnType = this.generateOverloadReturnType(responseDataType, observe);
1194
+ return {
1195
+ parameters: params,
1196
+ returnType
1197
+ };
1198
+ }
1199
+ generateOverloadParameters(operation, observe, responseType) {
1200
+ const params = this.paramsGenerator.generateApiParameters(operation);
1201
+ const optionsParam = this.addOverloadOptionsParameter(observe, responseType);
1202
+ const combined = [
1203
+ ...params,
1204
+ ...optionsParam
1205
+ ];
1206
+ const seen = /* @__PURE__ */ new Set();
1207
+ const uniqueParams = [];
1208
+ for (const param of combined) {
1209
+ if (!seen.has(param.name)) {
1210
+ seen.add(param.name);
1211
+ uniqueParams.push(param);
1212
+ }
1213
+ }
1214
+ return uniqueParams;
1215
+ }
1216
+ addOverloadOptionsParameter(observe, responseType) {
1217
+ return [
1218
+ {
1219
+ name: "observe",
1220
+ type: `'${observe}'`,
1221
+ hasQuestionToken: true
1222
+ },
1223
+ {
1224
+ name: "options",
1225
+ type: `{ headers?: HttpHeaders; reportProgress?: boolean; responseType?: '${responseType}'; withCredentials?: boolean; context?: HttpContext; }`,
1226
+ hasQuestionToken: true
1227
+ }
1228
+ ];
1229
+ }
1230
+ generateOverloadResponseType(operation) {
1231
+ const response = operation.responses?.["200"] || operation.responses?.["201"] || operation.responses?.["204"];
1232
+ if (!response) {
1233
+ return "any";
1234
+ }
1235
+ return this.getResponseType(response);
1236
+ }
1237
+ generateOverloadReturnType(responseType, observe) {
1238
+ switch (observe) {
1239
+ case "body":
1240
+ return `Observable<${responseType}>`;
1241
+ case "response":
1242
+ return `Observable<HttpResponse<${responseType}>>`;
1243
+ case "events":
1244
+ return `Observable<HttpEvent<${responseType}>>`;
1245
+ default:
1246
+ throw new Error(`Unsupported observe type: ${observe}`);
1247
+ }
1248
+ }
1249
+ getResponseTypeFromResponse(response) {
1250
+ const content = response.content || {};
1251
+ if (Object.keys(content).length === 0) {
1252
+ return "json";
1253
+ }
1254
+ const responseTypes = [];
1255
+ for (const [contentType, mediaType] of Object.entries(content)) {
1256
+ const schema = mediaType?.schema;
1257
+ const mapping = this.config?.options?.responseTypeMapping || {};
1258
+ if (mapping[contentType]) {
1259
+ responseTypes.push({
1260
+ type: mapping[contentType],
1261
+ priority: 1,
1262
+ contentType
1263
+ });
1264
+ continue;
1265
+ }
1266
+ if (schema?.format === "binary" || schema?.format === "byte") {
1267
+ responseTypes.push({
1268
+ type: "blob",
1269
+ priority: 2,
1270
+ contentType
1271
+ });
1272
+ continue;
1273
+ }
1274
+ if (schema?.type === "string" && (schema?.format === "binary" || schema?.format === "byte")) {
1275
+ responseTypes.push({
1276
+ type: "blob",
1277
+ priority: 2,
1278
+ contentType
1279
+ });
1280
+ continue;
1281
+ }
1282
+ const inferredType = this.inferResponseTypeFromContentType(contentType);
1283
+ let priority = 3;
1284
+ if (inferredType === "json") {
1285
+ priority = 2;
1286
+ }
1287
+ responseTypes.push({
1288
+ type: inferredType,
1289
+ priority,
1290
+ contentType
1291
+ });
1292
+ }
1293
+ responseTypes.sort((a, b) => a.priority - b.priority);
1294
+ return responseTypes[0]?.type || "json";
1295
+ }
1296
+ getResponseType(response) {
1297
+ const responseType = this.getResponseTypeFromResponse(response);
1298
+ switch (responseType) {
1299
+ case "blob":
1300
+ return "Blob";
1301
+ case "arraybuffer":
1302
+ return "ArrayBuffer";
1303
+ case "text":
1304
+ return "string";
1305
+ case "json": {
1306
+ const content = response.content || {};
1307
+ for (const [contentType, mediaType] of Object.entries(content)) {
1308
+ if (this.inferResponseTypeFromContentType(contentType) === "json" && mediaType?.schema) {
1309
+ return getTypeScriptType(mediaType.schema, this.config, mediaType.schema.nullable);
1310
+ }
1311
+ }
1312
+ return "any";
1313
+ }
1314
+ default:
1315
+ return "any";
1316
+ }
1317
+ }
1318
+ determineResponseTypeForOperation(operation) {
1319
+ const successResponses = [
1320
+ "200",
1321
+ "201",
1322
+ "202",
1323
+ "204",
1324
+ "206"
1325
+ ];
1326
+ for (const statusCode of successResponses) {
1327
+ const response = operation.responses?.[statusCode];
1328
+ if (!response) continue;
1329
+ return this.getResponseTypeFromResponse(response);
1330
+ }
1331
+ return "json";
1332
+ }
1333
+ inferResponseTypeFromContentType(contentType) {
1334
+ const normalizedType = contentType.split(";")[0].trim().toLowerCase();
1335
+ if (normalizedType.includes("json") || normalizedType === "application/ld+json" || normalizedType === "application/hal+json" || normalizedType === "application/vnd.api+json") {
1336
+ return "json";
1337
+ }
1338
+ if (normalizedType.includes("xml") || normalizedType === "application/soap+xml" || normalizedType === "application/atom+xml" || normalizedType === "application/rss+xml") {
1339
+ return "text";
1340
+ }
1341
+ if (normalizedType.startsWith("text/")) {
1342
+ const binaryTextTypes = [
1343
+ "text/rtf",
1344
+ "text/cache-manifest",
1345
+ "text/vcard",
1346
+ "text/calendar"
1347
+ ];
1348
+ if (binaryTextTypes.includes(normalizedType)) {
1349
+ return "blob";
1350
+ }
1351
+ return "text";
1352
+ }
1353
+ if (normalizedType === "application/x-www-form-urlencoded" || normalizedType === "multipart/form-data") {
1354
+ return "text";
1355
+ }
1356
+ if (normalizedType === "application/javascript" || normalizedType === "application/typescript" || normalizedType === "application/css" || normalizedType === "application/yaml" || normalizedType === "application/x-yaml" || normalizedType === "application/toml") {
1357
+ return "text";
1358
+ }
1359
+ if (normalizedType.startsWith("image/") || normalizedType.startsWith("audio/") || normalizedType.startsWith("video/") || normalizedType === "application/pdf" || normalizedType === "application/zip" || normalizedType.includes("octet-stream")) {
1360
+ return "arraybuffer";
1361
+ }
1362
+ return "blob";
1363
+ }
1364
+ };
1365
+
1366
+ // src/lib/generators/service/service-method.generator.ts
1367
+ var ServiceMethodGenerator = class {
1368
+ static {
1369
+ __name(this, "ServiceMethodGenerator");
1370
+ }
1371
+ config;
1372
+ bodyGenerator;
1373
+ overloadsGenerator;
1374
+ paramsGenerator;
1375
+ constructor(config) {
1376
+ this.config = config;
1377
+ this.bodyGenerator = new ServiceMethodBodyGenerator(config);
1378
+ this.overloadsGenerator = new ServiceMethodOverloadsGenerator(config);
1379
+ this.paramsGenerator = new ServiceMethodParamsGenerator(config);
1380
+ }
1381
+ addServiceMethod(serviceClass, operation) {
1382
+ const methodName = this.generateMethodName(operation);
1383
+ const parameters = this.paramsGenerator.generateMethodParameters(operation);
1384
+ const returnType = this.generateReturnType();
1385
+ const methodBody = this.bodyGenerator.generateMethodBody(operation);
1386
+ const methodOverLoads = this.overloadsGenerator.generateMethodOverloads(operation);
1387
+ serviceClass.addMethod({
1388
+ name: methodName,
1389
+ parameters,
1390
+ returnType,
1391
+ statements: methodBody,
1392
+ overloads: methodOverLoads
1393
+ });
1394
+ }
1395
+ generateMethodName(operation) {
1396
+ if (this.config.options.customizeMethodName) {
1397
+ if (operation.operationId == null) {
1398
+ throw new Error(`Operation ID is required for method name customization of operation: (${operation.method}) ${operation.path}`);
1399
+ }
1400
+ return this.config.options.customizeMethodName(operation.operationId);
1401
+ } else {
1402
+ return this.defaultNameGenerator(operation);
1403
+ }
1404
+ }
1405
+ generateReturnType() {
1406
+ return "Observable<any>";
1407
+ }
1408
+ defaultNameGenerator(operation) {
1409
+ if (operation.operationId) {
1410
+ return camelCase(operation.operationId);
1411
+ }
1412
+ const method = operation.method.toLowerCase();
1413
+ const pathParts = operation.path.split("/").filter((p) => p && !p.startsWith("{"));
1414
+ const resource = pathParts[pathParts.length - 1] || "resource";
1415
+ return `${method}${pascalCase(resource)}`;
1416
+ }
1417
+ };
1418
+
1419
+ // src/lib/generators/service/service.generator.ts
1420
+ var ServiceGenerator = class {
1421
+ static {
1422
+ __name(this, "ServiceGenerator");
1423
+ }
1424
+ project;
1425
+ parser;
1426
+ spec;
1427
+ config;
1428
+ methodGenerator;
1429
+ constructor(swaggerPath, project, config) {
1430
+ this.config = config;
1431
+ this.project = project;
1432
+ this.parser = new SwaggerParser(swaggerPath);
1433
+ this.spec = JSON.parse(require("fs").readFileSync(swaggerPath, "utf8"));
1434
+ this.methodGenerator = new ServiceMethodGenerator(config);
1435
+ }
1436
+ generate(outputRoot) {
1437
+ const outputDir = path4.join(outputRoot, "services");
1438
+ const paths = this.extractPaths();
1439
+ const controllerGroups = this.groupPathsByController(paths);
1440
+ Object.entries(controllerGroups).forEach(([controllerName, operations]) => {
1441
+ this.generateServiceFile(controllerName, operations, outputDir);
1442
+ });
1443
+ }
1444
+ extractPaths() {
1445
+ const paths = [];
1446
+ const swaggerPaths = this.spec.paths || {};
1447
+ Object.entries(swaggerPaths).forEach(([path7, pathItem]) => {
1448
+ const methods = [
1449
+ "get",
1450
+ "post",
1451
+ "put",
1452
+ "patch",
1453
+ "delete",
1454
+ "options",
1455
+ "head"
1456
+ ];
1457
+ methods.forEach((method) => {
1458
+ if (pathItem[method]) {
1459
+ const operation = pathItem[method];
1460
+ paths.push({
1461
+ path: path7,
1462
+ method: method.toUpperCase(),
1463
+ operationId: operation.operationId,
1464
+ summary: operation.summary,
1465
+ description: operation.description,
1466
+ tags: operation.tags || [],
1467
+ parameters: this.parseParameters(operation.parameters || [], pathItem.parameters || []),
1468
+ requestBody: operation.requestBody,
1469
+ responses: operation.responses || {}
1470
+ });
1471
+ }
1472
+ });
1473
+ });
1474
+ return paths;
1475
+ }
1476
+ parseParameters(operationParams, pathParams) {
1477
+ const allParams = [
1478
+ ...pathParams,
1479
+ ...operationParams
1480
+ ];
1481
+ return allParams.map((param) => ({
1482
+ name: param.name,
1483
+ in: param.in,
1484
+ required: param.required || param.in === "path",
1485
+ schema: param.schema,
1486
+ type: param.type,
1487
+ format: param.format,
1488
+ description: param.description
1489
+ }));
1490
+ }
1491
+ groupPathsByController(paths) {
1492
+ const groups = {};
1493
+ paths.forEach((path7) => {
1494
+ let controllerName = "Default";
1495
+ if (path7.tags && path7.tags.length > 0) {
1496
+ controllerName = path7.tags[0];
1497
+ } else {
1498
+ const pathParts = path7.path.split("/").filter((p) => p && !p.startsWith("{"));
1499
+ if (pathParts.length > 1) {
1500
+ controllerName = pascalCase(pathParts[1]);
1501
+ }
1502
+ }
1503
+ controllerName = pascalCase(controllerName);
1504
+ if (!groups[controllerName]) {
1505
+ groups[controllerName] = [];
1506
+ }
1507
+ groups[controllerName].push(path7);
1508
+ });
1509
+ return groups;
1510
+ }
1511
+ generateServiceFile(controllerName, operations, outputDir) {
1512
+ const fileName = `${kebabCase(controllerName)}.service.ts`;
1513
+ const filePath = path4.join(outputDir, fileName);
1514
+ const sourceFile = this.project.createSourceFile(filePath, "", {
1515
+ overwrite: true
1516
+ });
1517
+ const usedTypes = this.collectUsedTypes(operations);
1518
+ this.addImports(sourceFile, usedTypes);
1519
+ this.addServiceClass(sourceFile, controllerName, operations);
1520
+ sourceFile.saveSync();
1521
+ }
1522
+ collectUsedTypes(operations) {
1523
+ const usedTypes = /* @__PURE__ */ new Set();
1524
+ operations.forEach((operation) => {
1525
+ operation.parameters?.forEach((param) => {
1526
+ this.collectTypesFromSchema(param.schema || param, usedTypes);
1527
+ });
1528
+ if (operation.requestBody) {
1529
+ this.collectTypesFromRequestBody(operation.requestBody, usedTypes);
1530
+ }
1531
+ if (operation.responses) {
1532
+ Object.values(operation.responses).forEach((response) => {
1533
+ this.collectTypesFromResponse(response, usedTypes);
1534
+ });
1535
+ }
1536
+ });
1537
+ return usedTypes;
1538
+ }
1539
+ collectTypesFromSchema(schema, usedTypes) {
1540
+ if (!schema) return;
1541
+ if (schema.$ref) {
1542
+ const refName = schema.$ref.split("/").pop();
1543
+ if (refName) {
1544
+ usedTypes.add(pascalCase(refName));
1545
+ }
1546
+ }
1547
+ if (schema.type === "array" && schema.items) {
1548
+ this.collectTypesFromSchema(schema.items, usedTypes);
1549
+ }
1550
+ if (schema.type === "object" && schema.properties) {
1551
+ Object.values(schema.properties).forEach((prop) => {
1552
+ this.collectTypesFromSchema(prop, usedTypes);
1553
+ });
1554
+ }
1555
+ if (schema.allOf) {
1556
+ schema.allOf.forEach((subSchema) => {
1557
+ this.collectTypesFromSchema(subSchema, usedTypes);
1558
+ });
1559
+ }
1560
+ if (schema.oneOf) {
1561
+ schema.oneOf.forEach((subSchema) => {
1562
+ this.collectTypesFromSchema(subSchema, usedTypes);
1563
+ });
1564
+ }
1565
+ if (schema.anyOf) {
1566
+ schema.anyOf.forEach((subSchema) => {
1567
+ this.collectTypesFromSchema(subSchema, usedTypes);
1568
+ });
1569
+ }
1570
+ }
1571
+ collectTypesFromRequestBody(requestBody, usedTypes) {
1572
+ const content = requestBody.content || {};
1573
+ Object.values(content).forEach((mediaType) => {
1574
+ if (mediaType.schema) {
1575
+ this.collectTypesFromSchema(mediaType.schema, usedTypes);
1576
+ }
1577
+ });
1578
+ }
1579
+ collectTypesFromResponse(response, usedTypes) {
1580
+ const content = response.content || {};
1581
+ Object.values(content).forEach((mediaType) => {
1582
+ if (mediaType.schema) {
1583
+ this.collectTypesFromSchema(mediaType.schema, usedTypes);
1584
+ }
1585
+ });
1586
+ }
1587
+ addImports(sourceFile, usedTypes) {
1588
+ sourceFile.addImportDeclarations([
1589
+ {
1590
+ namedImports: [
1591
+ "Injectable",
1592
+ "inject"
1593
+ ],
1594
+ moduleSpecifier: "@angular/core"
1595
+ },
1596
+ {
1597
+ namedImports: [
1598
+ "HttpClient",
1599
+ "HttpParams",
1600
+ "HttpHeaders",
1601
+ "HttpContext",
1602
+ "HttpResponse",
1603
+ "HttpEvent"
1604
+ ],
1605
+ moduleSpecifier: "@angular/common/http"
1606
+ },
1607
+ {
1608
+ namedImports: [
1609
+ "Observable"
1610
+ ],
1611
+ moduleSpecifier: "rxjs"
1612
+ },
1613
+ {
1614
+ namedImports: [
1615
+ "BASE_PATH"
1616
+ ],
1617
+ moduleSpecifier: "../tokens"
1618
+ }
1619
+ ]);
1620
+ if (usedTypes.size > 0) {
1621
+ sourceFile.addImportDeclaration({
1622
+ namedImports: Array.from(usedTypes).sort(),
1623
+ moduleSpecifier: "../models"
1624
+ });
1625
+ }
1626
+ }
1627
+ addServiceClass(sourceFile, controllerName, operations) {
1628
+ const className = `${controllerName}Service`;
1629
+ sourceFile.insertText(0, SERVICE_GENERATOR_HEADER_COMMENT(controllerName));
1630
+ const serviceClass = sourceFile.addClass({
1631
+ name: className,
1632
+ isExported: true,
1633
+ decorators: [
1634
+ {
1635
+ name: "Injectable",
1636
+ arguments: [
1637
+ '{ providedIn: "root" }'
1638
+ ]
1639
+ }
1640
+ ]
1641
+ });
1642
+ serviceClass.addProperty({
1643
+ name: "httpClient",
1644
+ type: "HttpClient",
1645
+ scope: import_ts_morph3.Scope.Private,
1646
+ isReadonly: true,
1647
+ initializer: "inject(HttpClient)"
1648
+ });
1649
+ serviceClass.addProperty({
1650
+ name: "basePath",
1651
+ type: "string",
1652
+ scope: import_ts_morph3.Scope.Private,
1653
+ isReadonly: true,
1654
+ initializer: "inject(BASE_PATH)"
1655
+ });
1656
+ operations.forEach((operation) => {
1657
+ this.methodGenerator.addServiceMethod(serviceClass, operation);
1658
+ });
1659
+ if (this.hasDuplicateMethodNames(serviceClass.getMethods())) {
1660
+ throw new Error(`Duplicate method names found in service class ${className}. Please ensure unique method names for each operation.`);
1661
+ }
1662
+ }
1663
+ hasDuplicateMethodNames(arr) {
1664
+ return new Set(arr.map((method) => method.getName())).size !== arr.length;
1665
+ }
1666
+ };
1667
+
1668
+ // src/lib/generators/service/service-index.generator.ts
1669
+ var fs2 = __toESM(require("fs"));
1670
+ var path5 = __toESM(require("path"));
1671
+ var ServiceIndexGenerator = class {
1672
+ static {
1673
+ __name(this, "ServiceIndexGenerator");
1674
+ }
1675
+ project;
1676
+ constructor(project) {
1677
+ this.project = project;
1678
+ }
1679
+ generateIndex(outputRoot) {
1680
+ const servicesDir = path5.join(outputRoot, "services");
1681
+ const indexPath = path5.join(servicesDir, "index.ts");
1682
+ const sourceFile = this.project.createSourceFile(indexPath, "", {
1683
+ overwrite: true
1684
+ });
1685
+ sourceFile.insertText(0, SERVICE_INDEX_GENERATOR_HEADER_COMMENT);
1686
+ const serviceFiles = fs2.readdirSync(servicesDir).filter((file) => file.endsWith(".service.ts")).map((file) => file.replace(".service.ts", ""));
1687
+ serviceFiles.forEach((serviceName) => {
1688
+ const className = pascalCase(serviceName) + "Service";
1689
+ sourceFile.addExportDeclaration({
1690
+ namedExports: [
1691
+ className
1692
+ ],
1693
+ moduleSpecifier: `./${serviceName}.service`
1694
+ });
1695
+ });
1696
+ sourceFile.saveSync();
1697
+ }
1698
+ };
1699
+
1700
+ // src/lib/core/generator.ts
1701
+ var fs3 = __toESM(require("fs"));
1702
+ async function generateFromConfig(config) {
1703
+ if (!fs3.existsSync(config.input)) {
1704
+ throw new Error(`Input file not found: ${config.input}`);
1705
+ }
1706
+ const outputPath = config.output;
1707
+ const generateServices = config.options.generateServices ?? true;
1708
+ if (!fs3.existsSync(outputPath)) {
1709
+ fs3.mkdirSync(outputPath, {
1710
+ recursive: true
1711
+ });
1712
+ }
1713
+ try {
1714
+ const project = new import_ts_morph4.Project({
1715
+ compilerOptions: {
1716
+ declaration: true,
1717
+ target: import_ts_morph4.ScriptTarget.ES2022,
1718
+ module: import_ts_morph4.ModuleKind.Preserve,
1719
+ strict: true,
1720
+ ...config.compilerOptions
1721
+ }
1722
+ });
1723
+ const typeGenerator = new TypeGenerator(config.input, outputPath, config);
1724
+ typeGenerator.generate();
1725
+ console.log(`\u2705 TypeScript interfaces generated`);
1726
+ if (generateServices) {
1727
+ const tokenGenerator = new TokenGenerator(project);
1728
+ tokenGenerator.generate(outputPath);
1729
+ if (config.options.dateType === "Date") {
1730
+ const dateTransformer = new DateTransformerGenerator(project);
1731
+ dateTransformer.generate(outputPath);
1732
+ console.log(`\u2705 Date transformer generated`);
1733
+ }
1734
+ const fileDownloadHelper = new FileDownloadGenerator(project);
1735
+ fileDownloadHelper.generate(outputPath);
1736
+ console.log(`\u2705 File download helper generated`);
1737
+ const serviceGenerator = new ServiceGenerator(config.input, project, config);
1738
+ serviceGenerator.generate(outputPath);
1739
+ const indexGenerator = new ServiceIndexGenerator(project);
1740
+ indexGenerator.generateIndex(outputPath);
1741
+ console.log(`\u2705 Angular services generated`);
1742
+ }
1743
+ console.log("\u{1F389} Generation completed successfully at:", outputPath);
1744
+ } catch (error) {
1745
+ if (error instanceof Error) {
1746
+ console.error("\u274C Error during generation:", error.message);
1747
+ } else {
1748
+ console.error("\u274C Unknown error during generation:", error);
1749
+ }
1750
+ throw error;
1751
+ }
1752
+ }
1753
+ __name(generateFromConfig, "generateFromConfig");
1754
+
1755
+ // src/lib/cli.ts
1756
+ var program = new import_commander.Command();
1757
+ async function loadConfigFile(configPath) {
1758
+ const resolvedPath = path6.resolve(configPath);
1759
+ if (!fs4.existsSync(resolvedPath)) {
1760
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
1761
+ }
1762
+ delete require.cache[require.resolve(resolvedPath)];
1763
+ try {
1764
+ if (resolvedPath.endsWith(".ts")) {
1765
+ require("ts-node/register");
1766
+ }
1767
+ const configModule = require(resolvedPath);
1768
+ const config = configModule.default || configModule.config || configModule;
1769
+ if (!config.input || !config.output) {
1770
+ throw new Error('Configuration must include "input" and "output" properties');
1771
+ }
1772
+ return config;
1773
+ } catch (error) {
1774
+ throw new Error(`Failed to load configuration file: ${error instanceof Error ? error.message : error}`);
1775
+ }
1776
+ }
1777
+ __name(loadConfigFile, "loadConfigFile");
1778
+ async function generateFromOptions(options) {
1779
+ try {
1780
+ if (options.config) {
1781
+ const config = await loadConfigFile(options.config);
1782
+ await generateFromConfig(config);
1783
+ } else if (options.input) {
1784
+ const inputPath = path6.resolve(options.input);
1785
+ if (!fs4.existsSync(inputPath)) {
1786
+ console.error(`Error: Input file not found: ${inputPath}`);
1787
+ process.exit(1);
1788
+ }
1789
+ const config = {
1790
+ input: inputPath,
1791
+ output: options.output || "./src/generated",
1792
+ options: {
1793
+ dateType: options.dateType || "Date",
1794
+ enumStyle: "enum",
1795
+ generateEnumBasedOnDescription: true,
1796
+ generateServices: !options.typesOnly
1797
+ }
1798
+ };
1799
+ await generateFromConfig(config);
1800
+ } else {
1801
+ console.error("Error: Either --config or --input option is required");
1802
+ program.help();
1803
+ process.exit(1);
1804
+ }
1805
+ console.log("\u2728 Generation completed successfully!");
1806
+ } catch (error) {
1807
+ console.error("\u274C Generation failed:", error instanceof Error ? error.message : error);
1808
+ process.exit(1);
1809
+ }
1810
+ }
1811
+ __name(generateFromOptions, "generateFromOptions");
1812
+ program.name("ng-openapi").description("Generate Angular services and types from Swagger/OpenAPI spec").version("0.0.1").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
1813
+ await generateFromOptions(options);
1814
+ });
1815
+ program.command("generate").alias("gen").description("Generate code from Swagger specification").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
1816
+ await generateFromOptions(options);
1817
+ });
1818
+ program.on("--help", () => {
1819
+ console.log("");
1820
+ console.log("Examples:");
1821
+ console.log(" $ ng-openapi -c ./openapi.config.ts");
1822
+ console.log(" $ ng-openapi -i ./swagger.json -o ./src/api");
1823
+ console.log(" $ ng-openapi generate -c ./openapi.config.ts");
1824
+ console.log(" $ ng-openapi generate -i ./api.yaml --types-only");
1825
+ });
1826
+ program.parse();
1827
+ //# sourceMappingURL=cli.cjs.map