ng-openapi 0.3.3 → 0.4.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.
Files changed (4) hide show
  1. package/cli.cjs +1412 -340
  2. package/index.d.ts +368 -28
  3. package/index.js +1491 -330
  4. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -42,6 +42,28 @@ function isUrl(input) {
42
42
  __name(isUrl, "isUrl");
43
43
 
44
44
  // ../shared/src/errors.ts
45
+ var NG_OPENAPI_ERROR_BRAND = "__ngOpenApiError";
46
+ var ERROR_LINEAGE = /* @__PURE__ */ new WeakMap();
47
+ var FALLBACK_LINEAGE = Object.freeze([
48
+ "NgOpenApiError"
49
+ ]);
50
+ function registerError(cls, lineage) {
51
+ ERROR_LINEAGE.set(cls, Object.freeze([
52
+ ...lineage
53
+ ]));
54
+ return cls;
55
+ }
56
+ __name(registerError, "registerError");
57
+ function inheritedLineage(cls) {
58
+ for (let current = cls; typeof current === "function"; current = Object.getPrototypeOf(current)) {
59
+ const lineage = ERROR_LINEAGE.get(current);
60
+ if (lineage) {
61
+ return lineage;
62
+ }
63
+ }
64
+ return FALLBACK_LINEAGE;
65
+ }
66
+ __name(inheritedLineage, "inheritedLineage");
45
67
  var NgOpenApiError = class extends Error {
46
68
  static {
47
69
  __name(this, "NgOpenApiError");
@@ -50,10 +72,73 @@ var NgOpenApiError = class extends Error {
50
72
  cause;
51
73
  constructor(message, cause) {
52
74
  super(message);
53
- this.name = new.target.name;
75
+ const lineage = inheritedLineage(new.target);
76
+ this.name = lineage[0];
54
77
  this.cause = cause;
78
+ Object.defineProperty(this, NG_OPENAPI_ERROR_BRAND, {
79
+ value: lineage,
80
+ enumerable: false,
81
+ writable: false,
82
+ configurable: false
83
+ });
84
+ }
85
+ /**
86
+ * `message` and `name` are non-enumerable on Error, so the default
87
+ * JSON.stringify dropped both — the least useful possible serialization for
88
+ * something that exists to be logged.
89
+ *
90
+ * The payload is spread rather than enumerated: `source`, `issues`,
91
+ * `operation`, `names` and `placeholders` are why these classes are typed
92
+ * in the first place, and listing fields by hand silently drops whichever
93
+ * ones a later subclass adds.
94
+ */
95
+ toJSON() {
96
+ const cause = this.cause instanceof Error ? this.cause.message : this.cause;
97
+ return {
98
+ name: this.name,
99
+ message: this.message,
100
+ ...cause === void 0 ? {} : {
101
+ cause
102
+ },
103
+ // Own enumerable properties: the brand is deliberately not one.
104
+ ...Object.fromEntries(Object.entries(this).filter(([key]) => key !== "cause"))
105
+ };
106
+ }
107
+ /**
108
+ * Recognizes branded errors from another bundled copy of this module, so
109
+ * `error instanceof SpecLoadError` works for a plugin-thrown error too.
110
+ * The prototype chain is checked first, so a caller's own subclass of these
111
+ * classes still matches even though it carries no lineage entry.
112
+ *
113
+ * Never throws and never runs foreign code: both reflective reads sit
114
+ * inside the try, because a Proxy traps `getPrototypeOf` just as readily as
115
+ * `getOwnPropertyDescriptor`.
116
+ */
117
+ static [Symbol.hasInstance](value) {
118
+ if (typeof value !== "object" || value === null) {
119
+ return false;
120
+ }
121
+ try {
122
+ if (Object.prototype.isPrototypeOf.call(this.prototype, value)) {
123
+ return true;
124
+ }
125
+ const expected = ERROR_LINEAGE.get(this)?.[0];
126
+ if (expected === void 0) {
127
+ return false;
128
+ }
129
+ const descriptor = Object.getOwnPropertyDescriptor(value, NG_OPENAPI_ERROR_BRAND);
130
+ if (!descriptor || descriptor.enumerable || !("value" in descriptor)) {
131
+ return false;
132
+ }
133
+ return Array.isArray(descriptor.value) && descriptor.value.includes(expected);
134
+ } catch {
135
+ return false;
136
+ }
55
137
  }
56
138
  };
139
+ registerError(NgOpenApiError, [
140
+ "NgOpenApiError"
141
+ ]);
57
142
  var SpecLoadError = class extends NgOpenApiError {
58
143
  static {
59
144
  __name(this, "SpecLoadError");
@@ -65,6 +150,10 @@ var SpecLoadError = class extends NgOpenApiError {
65
150
  this.source = source;
66
151
  }
67
152
  };
153
+ registerError(SpecLoadError, [
154
+ "SpecLoadError",
155
+ "NgOpenApiError"
156
+ ]);
68
157
  var SpecParseError = class extends NgOpenApiError {
69
158
  static {
70
159
  __name(this, "SpecParseError");
@@ -76,6 +165,113 @@ var SpecParseError = class extends NgOpenApiError {
76
165
  this.source = source;
77
166
  }
78
167
  };
168
+ registerError(SpecParseError, [
169
+ "SpecParseError",
170
+ "NgOpenApiError"
171
+ ]);
172
+ function describeOperation(operation) {
173
+ const location = `(${operation.method}) ${operation.path}`;
174
+ return operation.operationId ? `${operation.operationId} (${location})` : location;
175
+ }
176
+ __name(describeOperation, "describeOperation");
177
+ function captureOperation(operation) {
178
+ return Object.freeze({
179
+ operationId: operation.operationId,
180
+ method: operation.method,
181
+ path: operation.path
182
+ });
183
+ }
184
+ __name(captureOperation, "captureOperation");
185
+ var InvalidIdentifierError = class extends NgOpenApiError {
186
+ static {
187
+ __name(this, "InvalidIdentifierError");
188
+ }
189
+ /** The rejected name, verbatim; absent when no name could be derived. */
190
+ identifier;
191
+ /** The operation whose name was being derived. */
192
+ operation;
193
+ constructor(message, operation, identifier) {
194
+ super(message);
195
+ this.operation = captureOperation(operation);
196
+ this.identifier = identifier;
197
+ }
198
+ };
199
+ registerError(InvalidIdentifierError, [
200
+ "InvalidIdentifierError",
201
+ "NgOpenApiError"
202
+ ]);
203
+ var DuplicateGeneratedNameError = class extends NgOpenApiError {
204
+ static {
205
+ __name(this, "DuplicateGeneratedNameError");
206
+ }
207
+ /** The colliding generated names. */
208
+ names;
209
+ /** The operations that produced them, when known. */
210
+ operations;
211
+ constructor(message, names, operations = []) {
212
+ super(message);
213
+ this.names = Object.freeze([
214
+ ...names
215
+ ]);
216
+ this.operations = Object.freeze(operations.map(captureOperation));
217
+ }
218
+ };
219
+ registerError(DuplicateGeneratedNameError, [
220
+ "DuplicateGeneratedNameError",
221
+ "NgOpenApiError"
222
+ ]);
223
+ var UnresolvedPathTemplateError = class extends NgOpenApiError {
224
+ static {
225
+ __name(this, "UnresolvedPathTemplateError");
226
+ }
227
+ /** The path as written in the spec. */
228
+ path;
229
+ /** Placeholder names with no declared parameter. */
230
+ placeholders;
231
+ constructor(message, path15, placeholders) {
232
+ super(message);
233
+ this.path = path15;
234
+ this.placeholders = Object.freeze([
235
+ ...placeholders
236
+ ]);
237
+ }
238
+ };
239
+ registerError(UnresolvedPathTemplateError, [
240
+ "UnresolvedPathTemplateError",
241
+ "NgOpenApiError"
242
+ ]);
243
+ var ConfigValidationError = class extends NgOpenApiError {
244
+ static {
245
+ __name(this, "ConfigValidationError");
246
+ }
247
+ issues;
248
+ constructor(issues) {
249
+ super(`Invalid ng-openapi configuration:
250
+ ${issues.map((issue) => ` - ${issue}`).join("\n")}`);
251
+ this.issues = Object.freeze([
252
+ ...issues
253
+ ]);
254
+ }
255
+ };
256
+ registerError(ConfigValidationError, [
257
+ "ConfigValidationError",
258
+ "NgOpenApiError"
259
+ ]);
260
+ var ConfigLoadError = class extends NgOpenApiError {
261
+ static {
262
+ __name(this, "ConfigLoadError");
263
+ }
264
+ /** Path of the config file that failed to load. */
265
+ source;
266
+ constructor(message, source, cause) {
267
+ super(message, cause);
268
+ this.source = source;
269
+ }
270
+ };
271
+ registerError(ConfigLoadError, [
272
+ "ConfigLoadError",
273
+ "NgOpenApiError"
274
+ ]);
79
275
 
80
276
  // ../shared/src/core/spec-loader.ts
81
277
  async function loadSpecContent(pathOrUrl) {
@@ -177,6 +373,430 @@ function detectFormat(content) {
177
373
  }
178
374
  __name(detectFormat, "detectFormat");
179
375
 
376
+ // ../shared/src/utils/string.utils.ts
377
+ var IDENTIFIER_SEPARATORS = /(?:_|[^\p{ID_Continue}$])+(.)?/gu;
378
+ var INVALID_IDENTIFIER_START = /^[^\p{ID_Start}$_]/u;
379
+ function toIdentifier(converted) {
380
+ if (converted === "") {
381
+ return "_";
382
+ }
383
+ return converted.replace(INVALID_IDENTIFIER_START, (char) => `_${char}`);
384
+ }
385
+ __name(toIdentifier, "toIdentifier");
386
+ function isValidIdentifier(name) {
387
+ return /^[\p{ID_Start}$_][\p{ID_Continue}$]*$/u.test(name);
388
+ }
389
+ __name(isValidIdentifier, "isValidIdentifier");
390
+ function camelCase(str) {
391
+ const converted = str.replace(IDENTIFIER_SEPARATORS, (_, char) => char ? char.toUpperCase() : "").replace(/^./u, (char) => char.toLowerCase());
392
+ return toIdentifier(converted);
393
+ }
394
+ __name(camelCase, "camelCase");
395
+ function kebabCase(str) {
396
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase();
397
+ }
398
+ __name(kebabCase, "kebabCase");
399
+ function pascalCase(str) {
400
+ const converted = str.replace(IDENTIFIER_SEPARATORS, (_, char) => char ? char.toUpperCase() : "").replace(/^./u, (char) => char.toUpperCase());
401
+ return toIdentifier(converted);
402
+ }
403
+ __name(pascalCase, "pascalCase");
404
+ function pascalCaseForEnums(str) {
405
+ const converted = str.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1");
406
+ return converted === "" ? "_" : converted;
407
+ }
408
+ __name(pascalCaseForEnums, "pascalCaseForEnums");
409
+ function capitalizeFirst(str) {
410
+ return str.charAt(0).toUpperCase() + str.slice(1);
411
+ }
412
+ __name(capitalizeFirst, "capitalizeFirst");
413
+
414
+ // ../shared/src/core/inline-nested-refs.ts
415
+ function inlineNestedRefs(spec, onWarning) {
416
+ const ctx = {
417
+ root: spec,
418
+ onWarning,
419
+ warned: /* @__PURE__ */ new Set(),
420
+ emittedPerCause: /* @__PURE__ */ new Map(),
421
+ deferred: /* @__PURE__ */ new Map(),
422
+ budget: MAX_INLINED_NODES,
423
+ budgetExhausted: false
424
+ };
425
+ const result = transform(spec, ctx, [], false);
426
+ flushDeferredWarnings(ctx);
427
+ return result;
428
+ }
429
+ __name(inlineNestedRefs, "inlineNestedRefs");
430
+ var MAX_REF_DEPTH = 64;
431
+ var MAX_INLINED_NODES = 1e5;
432
+ var MAX_WARNINGS_PER_CAUSE = 10;
433
+ var WARNING_CAUSE_LABELS = {
434
+ cyclic: "cyclic nested $ref(s) left in place",
435
+ unresolvable: "unresolvable nested $ref(s) left in place",
436
+ "not-a-schema": "nested $ref(s) resolving to a non-schema value",
437
+ "not-a-schema-position": "nested $ref(s) that do not address a schema position",
438
+ depth: "nested $ref(s) past the pointer-hop limit",
439
+ budget: "nested $ref(s) left uninlined after the expansion budget ran out",
440
+ "sibling-conflict": "nested $ref site(s) whose siblings overrode the target",
441
+ "sibling-on-boolean": "nested $ref site(s) whose siblings were dropped on a boolean target",
442
+ "malformed-ref": "non-string $ref value(s)",
443
+ "unsupported-pointer-root": "deep $ref(s) into a root this pass does not inline"
444
+ };
445
+ var REFUSED = /* @__PURE__ */ Symbol("refused");
446
+ function transform(node, ctx, chain, keysAreNames) {
447
+ if (Array.isArray(node)) {
448
+ let changed2 = false;
449
+ const items = node.map((item) => {
450
+ const next = transform(item, ctx, chain, false);
451
+ changed2 ||= next !== item;
452
+ return next;
453
+ });
454
+ return changed2 ? items : node;
455
+ }
456
+ if (!isPlainObject(node)) {
457
+ return node;
458
+ }
459
+ const ref = keysAreNames ? void 0 : node["$ref"];
460
+ if (typeof ref === "string") {
461
+ if (isDeepSchemaPointer(ref)) {
462
+ const inlined = inlineTarget(node, ref, ctx, chain);
463
+ if (inlined !== REFUSED) {
464
+ return inlined;
465
+ }
466
+ } else if (isDeepPointerIntoUnsupportedRoot(ref)) {
467
+ const emittedName = pascalCase(ref.split("/").pop() ?? "");
468
+ warnOnce(ctx, "unsupported-pointer-root", ref, `Nested $ref "${ref}" points into a part of the document this generator does not inline (only #/components/schemas/\u2026 and #/definitions/\u2026 are) \u2014 it will be emitted as the type "${emittedName}", which nothing defines. Promote the target to a named schema and reference that. ${WRONG_TYPE_SUFFIX}`);
469
+ }
470
+ } else if (ref !== void 0) {
471
+ warnOnce(ctx, "malformed-ref", safeStringify(ref), `Ignoring a "$ref" whose value is ${describeKind(ref)}, not a string (${safeStringify(ref)}) \u2014 a $ref must be a JSON pointer string.`);
472
+ }
473
+ let changed = false;
474
+ const entries = Object.entries(node).map(([key, value]) => {
475
+ const next = transformEntry(key, value, ctx, chain, keysAreNames);
476
+ changed ||= next !== value;
477
+ return [
478
+ key,
479
+ next
480
+ ];
481
+ });
482
+ return changed ? Object.fromEntries(entries) : node;
483
+ }
484
+ __name(transform, "transform");
485
+ function transformEntry(key, value, ctx, chain, keysAreNames) {
486
+ if (!keysAreNames && PAYLOAD_KEYS.has(key)) {
487
+ return value;
488
+ }
489
+ return transform(value, ctx, chain, !keysAreNames && NAME_KEYED_CONTAINERS.has(key));
490
+ }
491
+ __name(transformEntry, "transformEntry");
492
+ function inlineTarget(node, ref, ctx, chain) {
493
+ if (chain.includes(ref)) {
494
+ warnOnce(ctx, "cyclic", ref, `Nested $ref "${ref}" is cyclic and cannot be inlined finitely \u2014 restructure the target as a named definition. ${WRONG_TYPE_SUFFIX}`);
495
+ return REFUSED;
496
+ }
497
+ const target = resolvePointer(ctx.root, ref);
498
+ if (target === void 0) {
499
+ warnOnce(ctx, "unresolvable", ref, `Could not resolve nested $ref "${ref}". ${WRONG_TYPE_SUFFIX}`);
500
+ return REFUSED;
501
+ }
502
+ if (!isPlainObject(target) && typeof target !== "boolean") {
503
+ warnOnce(ctx, "not-a-schema", ref, `Nested $ref "${ref}" resolves to ${describeKind(target)}, not a schema \u2014 point it at a schema object. ${WRONG_TYPE_SUFFIX}`);
504
+ return REFUSED;
505
+ }
506
+ const nonSchemaSegment = firstNonSchemaSegment(ref);
507
+ if (nonSchemaSegment !== void 0) {
508
+ warnOnce(ctx, "not-a-schema-position", ref, `Nested $ref "${ref}" does not address a schema \u2014 its "${nonSchemaSegment}" segment is not a schema-valued position (expected one of properties/<name>, items, additionalProperties, allOf|anyOf|oneOf/<index>, not, $defs/<name>). ${WRONG_TYPE_SUFFIX}`);
509
+ return REFUSED;
510
+ }
511
+ if (chain.length >= MAX_REF_DEPTH) {
512
+ warnOnce(ctx, "depth", ref, `Nested $ref "${ref}" is more than ${MAX_REF_DEPTH} pointer hops deep and was left uninlined \u2014 restructure the target as a named definition. ${WRONG_TYPE_SUFFIX}`);
513
+ return REFUSED;
514
+ }
515
+ if (ctx.budgetExhausted) {
516
+ warnOnce(ctx, "budget", ref, `Nested $ref "${ref}" was left uninlined: the ${MAX_INLINED_NODES}-node expansion budget was already exhausted earlier in this spec. ${WRONG_TYPE_SUFFIX}`);
517
+ return REFUSED;
518
+ }
519
+ const cost = countNodes(target);
520
+ if (cost > ctx.budget) {
521
+ ctx.budgetExhausted = true;
522
+ warnOnce(ctx, "budget", ref, `Inlining nested $refs exceeded the ${MAX_INLINED_NODES}-node expansion budget at "${ref}" \u2014 it and every deep $ref after it were left uninlined. The spec expands combinatorially; restructure the repeated targets as named definitions. ${WRONG_TYPE_SUFFIX}`);
523
+ return REFUSED;
524
+ }
525
+ ctx.budget -= cost;
526
+ const inlined = transform(structuredClone(target), ctx, [
527
+ ...chain,
528
+ ref
529
+ ], false);
530
+ return mergeSiblings(node, inlined, ref, ctx, chain);
531
+ }
532
+ __name(inlineTarget, "inlineTarget");
533
+ var WRONG_TYPE_SUFFIX = "The generated type will not match the spec (generated files ship @ts-nocheck, so this surfaces as a silently wrong type rather than a compile error).";
534
+ var PAYLOAD_KEYS = /* @__PURE__ */ new Set([
535
+ "example",
536
+ "examples",
537
+ "default",
538
+ "enum",
539
+ "const"
540
+ ]);
541
+ var NAME_KEYED_CONTAINERS = /* @__PURE__ */ new Set([
542
+ // Schema keyword maps
543
+ "properties",
544
+ "patternProperties",
545
+ "dependentSchemas",
546
+ "$defs",
547
+ "definitions",
548
+ // Document-level maps keyed by name, status code, path or media type
549
+ "schemas",
550
+ "responses",
551
+ "parameters",
552
+ "requestBodies",
553
+ "headers",
554
+ "securitySchemes",
555
+ "links",
556
+ "callbacks",
557
+ "encoding",
558
+ "variables",
559
+ "content",
560
+ "paths",
561
+ "webhooks",
562
+ "pathItems"
563
+ ]);
564
+ function mergeSiblings(node, inlined, ref, ctx, chain) {
565
+ const siblings = Object.entries(node).filter(([key]) => key !== "$ref");
566
+ if (siblings.length === 0) {
567
+ return inlined;
568
+ }
569
+ if (!isPlainObject(inlined)) {
570
+ const dropped = siblings.map(([key]) => key);
571
+ warnOnce(ctx, "sibling-on-boolean", ref, `Nested $ref "${ref}" resolves to a boolean schema, so the keys next to it (${dropped.join(", ")}) were dropped.`, dropped);
572
+ return inlined;
573
+ }
574
+ const conflicts = siblings.filter(([key, value]) => !ANNOTATION_ONLY_KEYS.has(key) && Object.prototype.hasOwnProperty.call(inlined, key) && !sameValue(value, inlined[key])).map(([key]) => key);
575
+ if (conflicts.length > 0) {
576
+ warnOnce(ctx, "sibling-conflict", ref, `Nested $ref "${ref}" sits next to key(s) (${conflicts.join(", ")}) that its target also defines \u2014 the local value won and the target's was dropped, per JSON Schema 2020-12, where $ref composes with its siblings; OpenAPI 3.0 would have ignored the local one instead. The generated type follows the sibling, not the target \u2014 drop it if the target's value was meant to apply.`, conflicts);
577
+ }
578
+ return Object.fromEntries([
579
+ ...Object.entries(inlined),
580
+ // The ref node is a schema, so its keys are keywords: a payload sibling
581
+ // stays verbatim, exactly as it would one level up.
582
+ ...siblings.map(([key, value]) => [
583
+ key,
584
+ transformEntry(key, value, ctx, chain, false)
585
+ ])
586
+ ]);
587
+ }
588
+ __name(mergeSiblings, "mergeSiblings");
589
+ var ANNOTATION_ONLY_KEYS = /* @__PURE__ */ new Set([
590
+ "description",
591
+ "title",
592
+ "summary",
593
+ "example",
594
+ "examples",
595
+ "externalDocs",
596
+ "deprecated",
597
+ "xml",
598
+ "$comment"
599
+ ]);
600
+ function sameValue(a, b) {
601
+ if (a === b) {
602
+ return true;
603
+ }
604
+ if (Array.isArray(a) || Array.isArray(b)) {
605
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => sameValue(item, b[index]));
606
+ }
607
+ if (isPlainObject(a) && isPlainObject(b)) {
608
+ const keys = Object.keys(a);
609
+ return keys.length === Object.keys(b).length && keys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && sameValue(a[key], b[key]));
610
+ }
611
+ return typeof a === "object" && typeof b === "object" && safeStringify(a) === safeStringify(b);
612
+ }
613
+ __name(sameValue, "sameValue");
614
+ function warnOnce(ctx, cause, ref, message, site) {
615
+ const key = site === void 0 ? `${cause}:${ref}` : `${cause}:${ref}|${[
616
+ ...site
617
+ ].sort().join(",")}`;
618
+ if (ctx.warned.has(key)) {
619
+ return;
620
+ }
621
+ ctx.warned.add(key);
622
+ const emitted = ctx.emittedPerCause.get(cause) ?? 0;
623
+ if (emitted >= MAX_WARNINGS_PER_CAUSE) {
624
+ const refs = ctx.deferred.get(cause) ?? /* @__PURE__ */ new Set();
625
+ refs.add(ref);
626
+ ctx.deferred.set(cause, refs);
627
+ return;
628
+ }
629
+ ctx.emittedPerCause.set(cause, emitted + 1);
630
+ ctx.onWarning?.(message);
631
+ }
632
+ __name(warnOnce, "warnOnce");
633
+ function flushDeferredWarnings(ctx) {
634
+ for (const [cause, refs] of ctx.deferred) {
635
+ ctx.onWarning?.(`\u2026and ${refs.size} more ${WARNING_CAUSE_LABELS[cause]} (only the first ${MAX_WARNINGS_PER_CAUSE} are reported in full): ${[
636
+ ...refs
637
+ ].join(", ")}.`);
638
+ }
639
+ }
640
+ __name(flushDeferredWarnings, "flushDeferredWarnings");
641
+ function describeKind(value) {
642
+ if (value === null) {
643
+ return "null";
644
+ }
645
+ if (Array.isArray(value)) {
646
+ return "an array";
647
+ }
648
+ if (isPlainObject(value)) {
649
+ return "an object";
650
+ }
651
+ if (typeof value === "object") {
652
+ const className = Object.getPrototypeOf(value)?.constructor?.name;
653
+ return typeof className === "string" ? `a ${className}` : "a non-plain object";
654
+ }
655
+ return `a ${typeof value}`;
656
+ }
657
+ __name(describeKind, "describeKind");
658
+ function safeStringify(value) {
659
+ try {
660
+ return JSON.stringify(value) ?? String(value);
661
+ } catch {
662
+ return String(value);
663
+ }
664
+ }
665
+ __name(safeStringify, "safeStringify");
666
+ function countNodes(value) {
667
+ if (Array.isArray(value)) {
668
+ return value.reduce((total, item) => total + countNodes(item), 1);
669
+ }
670
+ if (!isPlainObject(value)) {
671
+ return 1;
672
+ }
673
+ return Object.values(value).reduce((total, item) => total + countNodes(item), 1);
674
+ }
675
+ __name(countNodes, "countNodes");
676
+ function isDeepSchemaPointer(ref) {
677
+ if (!ref.startsWith("#/")) {
678
+ return false;
679
+ }
680
+ const segments = ref.slice(2).split("/");
681
+ if (segments[0] === "components" && segments[1] === "schemas") {
682
+ return segments.length > 3;
683
+ }
684
+ if (segments[0] === "definitions") {
685
+ return segments.length > 2;
686
+ }
687
+ return false;
688
+ }
689
+ __name(isDeepSchemaPointer, "isDeepSchemaPointer");
690
+ function isDeepPointerIntoUnsupportedRoot(ref) {
691
+ if (!ref.startsWith("#/") || isDeepSchemaPointer(ref)) {
692
+ return false;
693
+ }
694
+ const segments = ref.slice(2).split("/");
695
+ const wholeEntryDepth = segments[0] === "components" ? 3 : 2;
696
+ return segments.length > wholeEntryDepth;
697
+ }
698
+ __name(isDeepPointerIntoUnsupportedRoot, "isDeepPointerIntoUnsupportedRoot");
699
+ var SCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
700
+ "not",
701
+ "additionalProperties",
702
+ "unevaluatedItems",
703
+ "unevaluatedProperties",
704
+ "contains",
705
+ "propertyNames",
706
+ "contentSchema",
707
+ "if",
708
+ "then",
709
+ "else"
710
+ ]);
711
+ var SCHEMA_MAP_KEYWORDS = /* @__PURE__ */ new Set([
712
+ "properties",
713
+ "patternProperties",
714
+ "dependentSchemas",
715
+ "$defs",
716
+ "definitions"
717
+ ]);
718
+ var SCHEMA_LIST_KEYWORDS = /* @__PURE__ */ new Set([
719
+ "allOf",
720
+ "anyOf",
721
+ "oneOf",
722
+ "prefixItems"
723
+ ]);
724
+ var SCHEMA_OR_LIST_KEYWORDS = /* @__PURE__ */ new Set([
725
+ "items",
726
+ "additionalItems"
727
+ ]);
728
+ function firstNonSchemaSegment(ref) {
729
+ const segments = pointerSegments(ref);
730
+ const rest = segments.slice(segments[0] === "definitions" ? 2 : 3);
731
+ let position = "schema";
732
+ for (const segment of rest) {
733
+ if (position === "schema-map") {
734
+ position = "schema";
735
+ continue;
736
+ }
737
+ if (position === "schema-list") {
738
+ if (toArrayIndex(segment) === void 0) {
739
+ return segment;
740
+ }
741
+ position = "schema";
742
+ continue;
743
+ }
744
+ if (position === "schema-or-list" && toArrayIndex(segment) !== void 0) {
745
+ position = "schema";
746
+ continue;
747
+ }
748
+ if (SCHEMA_KEYWORDS.has(segment)) {
749
+ position = "schema";
750
+ } else if (SCHEMA_MAP_KEYWORDS.has(segment)) {
751
+ position = "schema-map";
752
+ } else if (SCHEMA_LIST_KEYWORDS.has(segment)) {
753
+ position = "schema-list";
754
+ } else if (SCHEMA_OR_LIST_KEYWORDS.has(segment)) {
755
+ position = "schema-or-list";
756
+ } else {
757
+ return segment;
758
+ }
759
+ }
760
+ return position === "schema" || position === "schema-or-list" ? void 0 : rest[rest.length - 1];
761
+ }
762
+ __name(firstNonSchemaSegment, "firstNonSchemaSegment");
763
+ function pointerSegments(ref) {
764
+ return ref.slice(2).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
765
+ }
766
+ __name(pointerSegments, "pointerSegments");
767
+ function resolvePointer(root, ref) {
768
+ let current = root;
769
+ const segments = pointerSegments(ref);
770
+ for (const segment of segments) {
771
+ if (Array.isArray(current)) {
772
+ const index = toArrayIndex(segment);
773
+ if (index === void 0 || index >= current.length) {
774
+ return void 0;
775
+ }
776
+ current = current[index];
777
+ continue;
778
+ }
779
+ if (!isPlainObject(current) || !Object.prototype.hasOwnProperty.call(current, segment)) {
780
+ return void 0;
781
+ }
782
+ current = current[segment];
783
+ }
784
+ return current;
785
+ }
786
+ __name(resolvePointer, "resolvePointer");
787
+ function toArrayIndex(segment) {
788
+ return /^(?:0|[1-9][0-9]*)$/.test(segment) ? Number(segment) : void 0;
789
+ }
790
+ __name(toArrayIndex, "toArrayIndex");
791
+ function isPlainObject(value) {
792
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
793
+ return false;
794
+ }
795
+ const proto = Object.getPrototypeOf(value);
796
+ return proto === Object.prototype || proto === null;
797
+ }
798
+ __name(isPlainObject, "isPlainObject");
799
+
180
800
  // ../shared/src/utils/content-types.constants.ts
181
801
  var CONTENT_TYPES = {
182
802
  MULTIPART: "multipart/form-data",
@@ -193,7 +813,7 @@ function extractPaths(swaggerPaths = {}, methods = [
193
813
  "delete",
194
814
  "options",
195
815
  "head"
196
- ]) {
816
+ ], onWarning, resolveParameter) {
197
817
  const paths = [];
198
818
  Object.entries(swaggerPaths).forEach(([path15, pathItem]) => {
199
819
  methods.forEach((method) => {
@@ -202,11 +822,11 @@ function extractPaths(swaggerPaths = {}, methods = [
202
822
  paths.push({
203
823
  path: path15,
204
824
  method: method.toUpperCase(),
205
- operationId: operation.operationId,
825
+ operationId: asName(operation.operationId),
206
826
  summary: operation.summary,
207
827
  description: operation.description,
208
- tags: operation.tags || [],
209
- parameters: parseParameters(operation.parameters || [], pathItem.parameters || []),
828
+ tags: Array.isArray(operation.tags) ? operation.tags.flatMap(nameOrNothing) : [],
829
+ parameters: parseParameters(operation.parameters || [], pathItem.parameters || [], `(${method.toUpperCase()}) ${path15}`, onWarning, resolveParameter),
210
830
  requestBody: operation.requestBody,
211
831
  responses: operation.responses || {}
212
832
  });
@@ -216,13 +836,54 @@ function extractPaths(swaggerPaths = {}, methods = [
216
836
  return paths;
217
837
  }
218
838
  __name(extractPaths, "extractPaths");
219
- function parseParameters(operationParams, pathParams) {
839
+ function asName(value) {
840
+ if (typeof value === "string") {
841
+ return value;
842
+ }
843
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : void 0;
844
+ }
845
+ __name(asName, "asName");
846
+ function nameOrNothing(value) {
847
+ const name = asName(value);
848
+ return name === void 0 ? [] : [
849
+ name
850
+ ];
851
+ }
852
+ __name(nameOrNothing, "nameOrNothing");
853
+ function parseParameters(operationParams, pathParams, location, onWarning, resolveParameter) {
220
854
  const allParams = [
221
855
  ...pathParams,
222
856
  ...operationParams
223
- ];
224
- return allParams.map((param) => ({
225
- name: param.name,
857
+ ].flatMap((param) => {
858
+ const ref = param.$ref;
859
+ if (typeof ref !== "string") {
860
+ return [
861
+ param
862
+ ];
863
+ }
864
+ const resolution = resolveParameter?.(ref) ?? {
865
+ problem: "cannot be resolved here: no reusable parameters were provided"
866
+ };
867
+ if ("parameter" in resolution) {
868
+ return [
869
+ resolution.parameter
870
+ ];
871
+ }
872
+ onWarning?.(`A parameter of ${location} references "${ref}", which ${resolution.problem}. The parameter was skipped.`);
873
+ return [];
874
+ });
875
+ const named = allParams.filter((param) => {
876
+ const name = asName(param.name);
877
+ if (name !== void 0 && name !== "") {
878
+ return true;
879
+ }
880
+ const where = typeof param.in === "string" ? `${param.in} parameter` : "parameter";
881
+ const shown = param.name === void 0 ? "no name" : `name ${JSON.stringify(param.name)}`;
882
+ onWarning?.(`A ${where} of ${location} has ${shown} and was skipped. Give it a name in the spec.`);
883
+ return false;
884
+ });
885
+ return named.map((param) => ({
886
+ name: asName(param.name),
226
887
  in: param.in,
227
888
  required: param.required || param.in === "path",
228
889
  schema: param.schema,
@@ -233,23 +894,38 @@ function parseParameters(operationParams, pathParams) {
233
894
  }
234
895
  __name(parseParameters, "parseParameters");
235
896
 
236
- // ../shared/src/utils/string.utils.ts
237
- function camelCase(str) {
238
- return str.replace(/[-_.\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toLowerCase());
897
+ // ../shared/src/emit/literal.emit.ts
898
+ function quoteLiteral(value) {
899
+ return `'${escapeSingleQuoted(value)}'`;
239
900
  }
240
- __name(camelCase, "camelCase");
241
- function kebabCase(str) {
242
- return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase();
901
+ __name(quoteLiteral, "quoteLiteral");
902
+ function escapeSingleQuoted(value) {
903
+ return value.replace(/[\\']/g, (char) => `\\${char}`).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
243
904
  }
244
- __name(kebabCase, "kebabCase");
245
- function pascalCase(str) {
246
- return str.replace(/[-_.\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toUpperCase());
905
+ __name(escapeSingleQuoted, "escapeSingleQuoted");
906
+ function escapeTemplateLiteral(value) {
907
+ return value.replace(/[\\`]/g, (char) => `\\${char}`).replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
247
908
  }
248
- __name(pascalCase, "pascalCase");
249
- function pascalCaseForEnums(str) {
250
- return str.replace(/[^a-zA-Z0-9]/g, "_").replace(/(?:^|_)([a-z])/g, (_, char) => char.toUpperCase()).replace(/^([0-9])/, "_$1");
909
+ __name(escapeTemplateLiteral, "escapeTemplateLiteral");
910
+ function escapeDoubleQuoted(value) {
911
+ return value.replace(/[\\"]/g, (char) => `\\${char}`).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
251
912
  }
252
- __name(pascalCaseForEnums, "pascalCaseForEnums");
913
+ __name(escapeDoubleQuoted, "escapeDoubleQuoted");
914
+ function emitPropertyName(name) {
915
+ return IDENTIFIER_NAME.test(name) ? name : `"${escapeDoubleQuoted(name)}"`;
916
+ }
917
+ __name(emitPropertyName, "emitPropertyName");
918
+ var IDENTIFIER_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
919
+ function escapeJsDoc(text) {
920
+ return text.replace(/\*\//g, "*\\/");
921
+ }
922
+ __name(escapeJsDoc, "escapeJsDoc");
923
+ function emitDocs(description) {
924
+ return typeof description === "string" && description ? [
925
+ escapeJsDoc(description)
926
+ ] : void 0;
927
+ }
928
+ __name(emitDocs, "emitDocs");
253
929
 
254
930
  // ../shared/src/utils/functions/class-names.ts
255
931
  function decorate(base, defaultSuffix, naming) {
@@ -293,7 +969,7 @@ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, c
293
969
  switch (schema.type) {
294
970
  case "string":
295
971
  if (schema.enum) {
296
- return schema.enum.map((value) => typeof value === "string" ? `'${escapeString(value)}'` : String(value)).join(" | ");
972
+ return schema.enum.map((value) => typeof value === "string" ? `'${escapeSingleQuoted(value)}'` : String(value)).join(" | ");
297
973
  }
298
974
  if (schema.format === "date" || schema.format === "date-time") {
299
975
  const dateType = config.options.dateType === "Date" ? "Date" : "string";
@@ -329,10 +1005,6 @@ function nullableType(type, isNullable) {
329
1005
  return type + (isNullable ? " | null" : "");
330
1006
  }
331
1007
  __name(nullableType, "nullableType");
332
- function escapeString(str) {
333
- return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
334
- }
335
- __name(escapeString, "escapeString");
336
1008
 
337
1009
  // ../shared/src/utils/functions/extract-swagger-response-type.ts
338
1010
  function getResponseTypeFromResponse(response, responseTypeMapping) {
@@ -480,7 +1152,7 @@ function getResponseType(response, config) {
480
1152
  __name(getResponseType, "getResponseType");
481
1153
 
482
1154
  // ../shared/src/core/normalize.ts
483
- function normalizeSpec(spec) {
1155
+ function normalizeSpec(spec, onWarning) {
484
1156
  const rawDefinitions = spec.definitions || spec.components?.schemas || {};
485
1157
  const definitions = Object.fromEntries(Object.entries(rawDefinitions).map(([name, definition]) => [
486
1158
  name,
@@ -490,6 +1162,7 @@ function normalizeSpec(spec) {
490
1162
  const parts = ref.split("/");
491
1163
  return definitions[parts[parts.length - 1]];
492
1164
  }, "resolveReference");
1165
+ const resolveParameter = createParameterResolver(spec);
493
1166
  return {
494
1167
  version: spec.swagger ? {
495
1168
  type: "swagger",
@@ -499,7 +1172,7 @@ function normalizeSpec(spec) {
499
1172
  version: spec.openapi
500
1173
  } : null,
501
1174
  definitions,
502
- operations: extractPaths(spec.paths).map((operation) => normalizeOperation(normalizeOperationSchemas(operation), resolveReference)),
1175
+ operations: extractPaths(spec.paths, void 0, onWarning, resolveParameter).map((operation) => normalizeOperation(normalizeOperationSchemas(operation), resolveReference, onWarning)),
503
1176
  resolveReference
504
1177
  };
505
1178
  }
@@ -589,24 +1262,29 @@ function normalizeContentSchemas(content) {
589
1262
  ]));
590
1263
  }
591
1264
  __name(normalizeContentSchemas, "normalizeContentSchemas");
592
- function normalizeOperation(operation, resolveRef) {
1265
+ function normalizeOperation(operation, resolveRef, onWarning) {
1266
+ warnAboutUnboundParameters(operation, onWarning);
593
1267
  const content = operation.requestBody?.content;
594
1268
  const isMultipart = !!content?.[CONTENT_TYPES.MULTIPART];
595
1269
  const isUrlEncoded = !!content?.[CONTENT_TYPES.FORM_URLENCODED] && !content?.[CONTENT_TYPES.JSON];
596
1270
  const formDataSchema = isMultipart ? resolveBodySchema(operation.requestBody, CONTENT_TYPES.MULTIPART, resolveRef) : void 0;
597
1271
  const urlEncodedSchema = isUrlEncoded ? resolveBodySchema(operation.requestBody, CONTENT_TYPES.FORM_URLENCODED, resolveRef) : void 0;
598
1272
  const responseInfo = determineResponseInfo(operation);
1273
+ const pathParams = operation.parameters?.filter((p) => p.in === "path") || [];
1274
+ const queryParams = operation.parameters?.filter((p) => p.in === "query") || [];
1275
+ const formDataFields = Object.keys(formDataSchema?.properties || {});
1276
+ const urlEncodedFields = Object.keys(urlEncodedSchema?.properties || {});
599
1277
  return {
600
1278
  ...operation,
601
- pathParams: operation.parameters?.filter((p) => p.in === "path") || [],
602
- queryParams: operation.parameters?.filter((p) => p.in === "query") || [],
1279
+ pathParams,
1280
+ queryParams,
603
1281
  hasBody: !!operation.requestBody,
604
1282
  isMultipart,
605
1283
  isUrlEncoded,
606
1284
  formDataSchema,
607
- formDataFields: Object.keys(formDataSchema?.properties || {}),
1285
+ formDataFields,
608
1286
  urlEncodedSchema,
609
- urlEncodedFields: Object.keys(urlEncodedSchema?.properties || {}),
1287
+ urlEncodedFields,
610
1288
  responseType: responseInfo.responseType,
611
1289
  acceptHeader: responseInfo.acceptHeader
612
1290
  };
@@ -635,6 +1313,68 @@ function determineResponseInfo(operation) {
635
1313
  };
636
1314
  }
637
1315
  __name(determineResponseInfo, "determineResponseInfo");
1316
+ function createParameterResolver(spec) {
1317
+ const components = (spec.swagger ? spec.parameters : spec.components?.parameters) ?? {};
1318
+ const prefix = spec.swagger ? "#/parameters/" : "#/components/parameters/";
1319
+ return (ref) => {
1320
+ const chain = [];
1321
+ let current = ref;
1322
+ for (; ; ) {
1323
+ if (chain.includes(current)) {
1324
+ return {
1325
+ problem: `is part of a reference cycle (${[
1326
+ ...chain,
1327
+ current
1328
+ ].map((r) => `"${r}"`).join(" -> ")})`
1329
+ };
1330
+ }
1331
+ chain.push(current);
1332
+ const hash = current.indexOf("#");
1333
+ if (hash > 0) {
1334
+ return {
1335
+ problem: `points into another document ("${current.slice(0, hash)}"); references into other documents are not supported, so bundle the spec into one document first`
1336
+ };
1337
+ }
1338
+ if (!current.startsWith(prefix)) {
1339
+ return {
1340
+ problem: `is not a parameter component pointer (expected "${prefix}<name>")`
1341
+ };
1342
+ }
1343
+ const name = current.slice(prefix.length).replace(/~1/g, "/").replace(/~0/g, "~");
1344
+ const candidate = Object.prototype.hasOwnProperty.call(components, name) ? components[name] : void 0;
1345
+ if (!candidate || typeof candidate !== "object") {
1346
+ return {
1347
+ problem: `does not resolve: there is no parameter component named "${name}"`
1348
+ };
1349
+ }
1350
+ const next = candidate.$ref;
1351
+ if (typeof next === "string") {
1352
+ current = next;
1353
+ continue;
1354
+ }
1355
+ return {
1356
+ parameter: candidate
1357
+ };
1358
+ }
1359
+ };
1360
+ }
1361
+ __name(createParameterResolver, "createParameterResolver");
1362
+ function warnAboutUnboundParameters(operation, onWarning) {
1363
+ for (const param of operation.parameters ?? []) {
1364
+ if (param.in === "path" || param.in === "query") {
1365
+ continue;
1366
+ }
1367
+ if (param.in === "header" || param.in === "cookie") {
1368
+ if (param.required) {
1369
+ onWarning?.(`Required ${param.in} parameter "${param.name}" of ${describeOperation(operation)} is not bound by the generated clients \u2014 callers must pass it through the trailing options parameter.`);
1370
+ }
1371
+ continue;
1372
+ }
1373
+ const kind = param.in === "formData" || param.in === "body" ? `Swagger 2.0 \`in: ${param.in}\`` : `\`in: ${String(param.in)}\``;
1374
+ onWarning?.(`${kind} parameter "${param.name}" of ${describeOperation(operation)} is not supported and was dropped` + (param.required ? " (it is marked required)" : "") + ". Describe it as a requestBody, or as a path or query parameter, to have it generated.");
1375
+ }
1376
+ }
1377
+ __name(warnAboutUnboundParameters, "warnAboutUnboundParameters");
638
1378
 
639
1379
  // ../shared/src/core/swagger-parser.ts
640
1380
  var SwaggerParser = class _SwaggerParser {
@@ -643,24 +1383,36 @@ var SwaggerParser = class _SwaggerParser {
643
1383
  }
644
1384
  spec;
645
1385
  normalized;
646
- constructor(spec, config) {
647
- const isInputValid = config.validateInput?.(spec) ?? true;
648
- if (!isInputValid) {
649
- throw new SpecParseError("Swagger spec is not valid. Check your `validateInput` condition.");
650
- }
1386
+ /** Non-fatal problems found while normalizing — a parameter with no usable name, for one. */
1387
+ onWarning;
1388
+ constructor(spec, onWarning) {
651
1389
  this.spec = spec;
1390
+ this.onWarning = onWarning;
652
1391
  }
653
1392
  /**
654
1393
  * Loads, parses and wraps a spec.
655
1394
  *
1395
+ * @param onWarning receives non-fatal spec problems found while parsing or
1396
+ * normalizing — a deep-pointer `$ref` that cannot be inlined, a parameter
1397
+ * with no usable name.
656
1398
  * @throws SpecLoadError when the file/URL cannot be read.
657
- * @throws SpecParseError when the content cannot be parsed or the
658
- * config's `validateInput` hook rejects the spec.
1399
+ * @throws SpecParseError when the content cannot be parsed, `$ref` inlining
1400
+ * fails, or the config's `validateInput` hook rejects the spec.
659
1401
  */
660
- static async create(swaggerPathOrUrl, config) {
1402
+ static async create(swaggerPathOrUrl, config, onWarning) {
661
1403
  const swaggerContent = await loadSpecContent(swaggerPathOrUrl);
662
1404
  const spec = parseSpecContent(swaggerContent, swaggerPathOrUrl);
663
- return new _SwaggerParser(spec, config);
1405
+ const isInputValid = config.validateInput?.(spec) ?? true;
1406
+ if (!isInputValid) {
1407
+ throw new SpecParseError("Swagger spec is not valid. Check your `validateInput` condition.");
1408
+ }
1409
+ let inlinedSpec;
1410
+ try {
1411
+ inlinedSpec = inlineNestedRefs(spec, onWarning);
1412
+ } catch (error) {
1413
+ throw new SpecParseError(`Failed to inline nested $refs in the spec: ${error instanceof Error ? error.message : String(error)}`, swaggerPathOrUrl, error);
1414
+ }
1415
+ return new _SwaggerParser(inlinedSpec, onWarning);
664
1416
  }
665
1417
  /**
666
1418
  * The version-free model generators consume. Computed once and cached —
@@ -668,7 +1420,7 @@ var SwaggerParser = class _SwaggerParser {
668
1420
  * can be used as Map keys across generators.
669
1421
  */
670
1422
  getNormalizedSpec() {
671
- this.normalized ??= normalizeSpec(this.spec);
1423
+ this.normalized ??= normalizeSpec(this.spec, this.onWarning);
672
1424
  return this.normalized;
673
1425
  }
674
1426
  /** Definition map regardless of version: 2.0 `definitions` or 3.x `components.schemas`. */
@@ -698,25 +1450,29 @@ var SwaggerParser = class _SwaggerParser {
698
1450
  }
699
1451
  /** Whether the spec declares a supported version (Swagger 2.x or OpenAPI 3.x). */
700
1452
  isValidSpec() {
701
- return !!(this.spec.swagger && this.spec.swagger.startsWith("2.") || this.spec.openapi && this.spec.openapi.startsWith("3."));
1453
+ return specVersionOf(this.spec.swagger, "2.") || specVersionOf(this.spec.openapi, "3.");
702
1454
  }
703
1455
  /** Detected flavor + literal version string, or null when neither field is present. */
704
1456
  getSpecVersion() {
705
- if (this.spec.swagger) {
1457
+ if (this.spec.swagger !== void 0 && this.spec.swagger !== null) {
706
1458
  return {
707
1459
  type: "swagger",
708
- version: this.spec.swagger
1460
+ version: String(this.spec.swagger)
709
1461
  };
710
1462
  }
711
- if (this.spec.openapi) {
1463
+ if (this.spec.openapi !== void 0 && this.spec.openapi !== null) {
712
1464
  return {
713
1465
  type: "openapi",
714
- version: this.spec.openapi
1466
+ version: String(this.spec.openapi)
715
1467
  };
716
1468
  }
717
1469
  return null;
718
1470
  }
719
1471
  };
1472
+ function specVersionOf(value, majorPrefix) {
1473
+ return typeof value === "string" || typeof value === "number" ? String(value).startsWith(majorPrefix) : false;
1474
+ }
1475
+ __name(specVersionOf, "specVersionOf");
720
1476
 
721
1477
  // ../shared/src/emit/headers.emit.ts
722
1478
  function emitHeaders(options) {
@@ -737,7 +1493,7 @@ ${emitDefaultHeaderGuards(customHeaders)}`;
737
1493
  headerCode += `
738
1494
  // Advertise the response content type declared in the spec
739
1495
  if (!headers.has('Accept')) {
740
- headers = headers.set('Accept', '${accept}');
1496
+ headers = headers.set('Accept', ${quoteLiteral(accept)});
741
1497
  }`;
742
1498
  }
743
1499
  if (contentType?.isMultipart) {
@@ -761,38 +1517,273 @@ if (!headers.has('Content-Type')) {
761
1517
  }
762
1518
  __name(emitHeaders, "emitHeaders");
763
1519
  function emitDefaultHeaderGuards(customHeaders) {
764
- return Object.entries(customHeaders).map(([key, value]) => `if (!headers.has('${key}')) {
765
- headers = headers.set('${key}', '${value}');
766
- }`).join("\n");
1520
+ return Object.entries(customHeaders).map(([key, value]) => {
1521
+ const name = quoteLiteral(key);
1522
+ return `if (!headers.has(${name})) {
1523
+ headers = headers.set(${name}, ${quoteLiteral(value)});
1524
+ }`;
1525
+ }).join("\n");
767
1526
  }
768
1527
  __name(emitDefaultHeaderGuards, "emitDefaultHeaderGuards");
769
1528
 
1529
+ // ../shared/src/utils/functions/get-request-body-type.ts
1530
+ function getRequestBodyType(requestBody, config) {
1531
+ const content = requestBody.content || {};
1532
+ const jsonContent = content[CONTENT_TYPES.JSON];
1533
+ if (jsonContent?.schema) {
1534
+ return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable);
1535
+ }
1536
+ return "any";
1537
+ }
1538
+ __name(getRequestBodyType, "getRequestBodyType");
1539
+
1540
+ // ../shared/src/utils/functions/is-data-type-interface.ts
1541
+ function isDataTypeInterface(type) {
1542
+ const invalidTypes = [
1543
+ "any",
1544
+ "File",
1545
+ "string",
1546
+ "number",
1547
+ "boolean",
1548
+ "object",
1549
+ "unknown",
1550
+ "[]",
1551
+ "Array"
1552
+ ];
1553
+ return !invalidTypes.some((invalidType) => type.includes(invalidType));
1554
+ }
1555
+ __name(isDataTypeInterface, "isDataTypeInterface");
1556
+
1557
+ // ../shared/src/utils/functions/argument-names.ts
1558
+ var RESERVED_WORDS = [
1559
+ "arguments",
1560
+ "await",
1561
+ "break",
1562
+ "case",
1563
+ "catch",
1564
+ "class",
1565
+ "const",
1566
+ "continue",
1567
+ "debugger",
1568
+ "default",
1569
+ "delete",
1570
+ "do",
1571
+ "else",
1572
+ "enum",
1573
+ "eval",
1574
+ "export",
1575
+ "extends",
1576
+ "false",
1577
+ "finally",
1578
+ "for",
1579
+ "function",
1580
+ "if",
1581
+ "implements",
1582
+ "import",
1583
+ "in",
1584
+ "instanceof",
1585
+ "interface",
1586
+ "let",
1587
+ "new",
1588
+ "null",
1589
+ "package",
1590
+ "private",
1591
+ "protected",
1592
+ "public",
1593
+ "return",
1594
+ "static",
1595
+ "super",
1596
+ "switch",
1597
+ "this",
1598
+ "throw",
1599
+ "true",
1600
+ "try",
1601
+ "typeof",
1602
+ "var",
1603
+ "void",
1604
+ "while",
1605
+ "with",
1606
+ "yield"
1607
+ ];
1608
+ var SERVICE_ARGUMENT_PROFILE = Object.freeze({
1609
+ reserved: Object.freeze([
1610
+ "observe",
1611
+ "options",
1612
+ "url",
1613
+ "params",
1614
+ "headers",
1615
+ "formData",
1616
+ "formBody"
1617
+ ]),
1618
+ bindsRequestBody: true
1619
+ });
1620
+ var RESOURCE_ARGUMENT_PROFILE = Object.freeze({
1621
+ reserved: Object.freeze([
1622
+ "resourceOptions",
1623
+ "requestOptions",
1624
+ "params",
1625
+ "headers"
1626
+ ]),
1627
+ bindsRequestBody: false
1628
+ });
1629
+ var REQUEST_BODY_KEY = /* @__PURE__ */ Symbol("requestBody");
1630
+ function resolveArgumentNames(operation, config, profile) {
1631
+ let byConfig = RESOLVED.get(operation);
1632
+ if (!byConfig) {
1633
+ byConfig = /* @__PURE__ */ new WeakMap();
1634
+ RESOLVED.set(operation, byConfig);
1635
+ }
1636
+ let byProfile = byConfig.get(config);
1637
+ if (!byProfile) {
1638
+ byProfile = /* @__PURE__ */ new WeakMap();
1639
+ byConfig.set(config, byProfile);
1640
+ }
1641
+ const cached = byProfile.get(profile);
1642
+ if (cached) {
1643
+ return cached;
1644
+ }
1645
+ const resolved = computeArgumentNames(operation, config, profile);
1646
+ byProfile.set(profile, resolved);
1647
+ return resolved;
1648
+ }
1649
+ __name(resolveArgumentNames, "resolveArgumentNames");
1650
+ var RESOLVED = /* @__PURE__ */ new WeakMap();
1651
+ function computeArgumentNames(operation, config, profile) {
1652
+ const entries = [
1653
+ ...operation.pathParams.map((param) => ({
1654
+ key: param.name,
1655
+ base: camelCase(param.name)
1656
+ })),
1657
+ ...operation.formDataFields.map((field) => ({
1658
+ key: field,
1659
+ base: camelCase(field)
1660
+ })),
1661
+ ...operation.urlEncodedFields.map((field) => ({
1662
+ key: field,
1663
+ base: camelCase(field)
1664
+ }))
1665
+ ];
1666
+ const bodyBase = profile.bindsRequestBody ? jsonBodyIdentifier(operation, config) : void 0;
1667
+ if (bodyBase !== void 0) {
1668
+ entries.push({
1669
+ key: REQUEST_BODY_KEY,
1670
+ base: bodyBase
1671
+ });
1672
+ }
1673
+ entries.push(...operation.queryParams.map((param) => ({
1674
+ key: param.name,
1675
+ base: camelCase(param.name)
1676
+ })));
1677
+ const identifiers = /* @__PURE__ */ new Map();
1678
+ const used = /* @__PURE__ */ new Set([
1679
+ ...profile.reserved,
1680
+ ...RESERVED_WORDS
1681
+ ]);
1682
+ const renamed = [];
1683
+ const merged = /* @__PURE__ */ new Set();
1684
+ for (const { key, base } of entries) {
1685
+ if (identifiers.has(key)) {
1686
+ if (typeof key === "string") {
1687
+ merged.add(key);
1688
+ }
1689
+ continue;
1690
+ }
1691
+ let identifier = base;
1692
+ let suffix = 2;
1693
+ while (used.has(identifier)) {
1694
+ identifier = `${base}${suffix}`;
1695
+ suffix++;
1696
+ }
1697
+ if (identifier !== base) {
1698
+ renamed.push(Object.freeze({
1699
+ source: typeof key === "string" ? key : base,
1700
+ identifier
1701
+ }));
1702
+ }
1703
+ used.add(identifier);
1704
+ identifiers.set(key, identifier);
1705
+ }
1706
+ return {
1707
+ // The fallback runs the *same* rules the resolver would have, rather
1708
+ // than a bare camelCase: an unregistered wire name otherwise came back
1709
+ // as `class` or `options`, names the resolver never assigns.
1710
+ // Uniquified against everything already assigned, not just the reserved
1711
+ // words: an unregistered wire name that camelCases onto an identifier
1712
+ // this operation already bound would otherwise alias it — compiling
1713
+ // fine and sending the wrong value. Only reachable for operations built
1714
+ // outside the normalizer, but the resolver is public API.
1715
+ of: /* @__PURE__ */ __name((wireName) => identifiers.get(wireName) ?? deriveLocalName(camelCase(wireName), [
1716
+ ...used
1717
+ ]), "of"),
1718
+ body: identifiers.get(REQUEST_BODY_KEY),
1719
+ all: Object.freeze([
1720
+ ...identifiers.values()
1721
+ ]),
1722
+ renamed: Object.freeze(renamed),
1723
+ merged: Object.freeze([
1724
+ ...merged
1725
+ ])
1726
+ };
1727
+ }
1728
+ __name(computeArgumentNames, "computeArgumentNames");
1729
+ function jsonBodyIdentifier(operation, config) {
1730
+ if (!operation.requestBody || operation.isMultipart) {
1731
+ return void 0;
1732
+ }
1733
+ if (!operation.requestBody.content?.[CONTENT_TYPES.JSON]) {
1734
+ return void 0;
1735
+ }
1736
+ const bodyType = getRequestBodyType(operation.requestBody, config);
1737
+ return isDataTypeInterface(bodyType) ? camelCase(bodyType) : "requestBody";
1738
+ }
1739
+ __name(jsonBodyIdentifier, "jsonBodyIdentifier");
1740
+ function deriveLocalName(base, taken) {
1741
+ const used = new Set(taken);
1742
+ let name = base;
1743
+ let suffix = 2;
1744
+ while (used.has(name)) {
1745
+ name = `${base}${suffix}`;
1746
+ suffix++;
1747
+ }
1748
+ return name;
1749
+ }
1750
+ __name(deriveLocalName, "deriveLocalName");
1751
+
770
1752
  // ../shared/src/emit/url.emit.ts
771
1753
  function plainParamValue(identifier) {
772
1754
  return identifier;
773
1755
  }
774
1756
  __name(plainParamValue, "plainParamValue");
775
- function emitUrlExpression(path15, pathParams, paramValue = plainParamValue) {
776
- let urlExpression = `\`\${this.basePath}${path15}\``;
1757
+ function emitUrlExpression(path15, pathParams, argumentNames, paramValue = plainParamValue) {
1758
+ const declared = new Set(pathParams.map((param) => param.name));
1759
+ const unresolved = (path15.match(/{([^{}]*)}/g) ?? []).map((placeholder) => placeholder.slice(1, -1)).filter((name) => !declared.has(name));
1760
+ if (unresolved.length > 0) {
1761
+ throw new UnresolvedPathTemplateError(`Path "${path15}" declares no parameter for ${unresolved.map((name) => `{${name}}`).join(", ")}. Declare each placeholder as a path parameter, or remove it from the path.`, path15, unresolved);
1762
+ }
1763
+ let urlExpression = "`${this.basePath}" + escapeTemplateLiteral(path15) + "`";
777
1764
  pathParams.forEach((param) => {
778
- urlExpression = urlExpression.replace(`{${param.name}}`, `\${${paramValue(camelCase(param.name))}}`);
1765
+ const replacement = "${" + paramValue(argumentNames.of(param.name)) + "}";
1766
+ urlExpression = urlExpression.split(`{${escapeTemplateLiteral(param.name)}}`).join(replacement);
779
1767
  });
780
1768
  return urlExpression;
781
1769
  }
782
1770
  __name(emitUrlExpression, "emitUrlExpression");
783
- function emitUrlConstruction(path15, pathParams) {
784
- return `const url = ${emitUrlExpression(path15, pathParams)};`;
1771
+ function emitUrlConstruction(path15, pathParams, argumentNames) {
1772
+ return `const url = ${emitUrlExpression(path15, pathParams, argumentNames)};`;
785
1773
  }
786
1774
  __name(emitUrlConstruction, "emitUrlConstruction");
787
1775
 
788
1776
  // ../shared/src/emit/query-params.emit.ts
789
- function emitQueryParams(queryParams) {
1777
+ function emitQueryParams(queryParams, argumentNames) {
790
1778
  if (queryParams.length === 0) {
791
1779
  return "";
792
1780
  }
793
- const paramMappings = queryParams.map((param) => `if (${camelCase(param.name)} != null) {
794
- params = HttpParamsBuilder.addToHttpParams(params, ${camelCase(param.name)}, '${param.name}');
795
- }`).join("\n");
1781
+ const paramMappings = queryParams.map((param) => {
1782
+ const identifier = argumentNames.of(param.name);
1783
+ return `if (${identifier} != null) {
1784
+ params = HttpParamsBuilder.addToHttpParams(params, ${identifier}, ${quoteLiteral(param.name)});
1785
+ }`;
1786
+ }).join("\n");
796
1787
  return `
797
1788
  let params = new HttpParams();
798
1789
  ${paramMappings}`;
@@ -854,55 +1845,158 @@ function listGeneratedBarrelDirs(project, rootPath) {
854
1845
  __name(listGeneratedBarrelDirs, "listGeneratedBarrelDirs");
855
1846
 
856
1847
  // ../shared/src/utils/functions/token-names.ts
857
- function getClientContextTokenName(clientName = "default") {
858
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1848
+ function effectiveClientName(clientName) {
1849
+ return clientName ? clientName : "default";
1850
+ }
1851
+ __name(effectiveClientName, "effectiveClientName");
1852
+ function clientNameIdentifier(clientName) {
1853
+ return isValidIdentifier(clientName) ? capitalizeFirst(clientName) : pascalCase(clientName);
1854
+ }
1855
+ __name(clientNameIdentifier, "clientNameIdentifier");
1856
+ function tokenSuffix(clientName) {
1857
+ return effectiveClientName(clientName).toUpperCase().replace(/[^A-Z0-9]/g, "_");
1858
+ }
1859
+ __name(tokenSuffix, "tokenSuffix");
1860
+ function getClientContextTokenName(clientName) {
1861
+ const clientSuffix = tokenSuffix(clientName);
859
1862
  return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
860
1863
  }
861
1864
  __name(getClientContextTokenName, "getClientContextTokenName");
862
- function getBasePathTokenName(clientName = "default") {
863
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1865
+ function getBasePathTokenName(clientName) {
1866
+ const clientSuffix = tokenSuffix(clientName);
864
1867
  return `BASE_PATH_${clientSuffix}`;
865
1868
  }
866
1869
  __name(getBasePathTokenName, "getBasePathTokenName");
867
- function getInterceptorsTokenName(clientName = "default") {
868
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1870
+ function getInterceptorsTokenName(clientName) {
1871
+ const clientSuffix = tokenSuffix(clientName);
869
1872
  return `HTTP_INTERCEPTORS_${clientSuffix}`;
870
1873
  }
871
1874
  __name(getInterceptorsTokenName, "getInterceptorsTokenName");
872
1875
 
873
- // ../shared/src/utils/functions/duplicate-function-name.ts
874
- function hasDuplicateFunctionNames(arr) {
875
- return new Set(arr.map((fn) => fn.getName())).size !== arr.length;
1876
+ // ../shared/src/utils/functions/controller-groups.ts
1877
+ function groupOperationsByController(operations, onWarning) {
1878
+ const groups = {};
1879
+ const controllerByFold = /* @__PURE__ */ new Map();
1880
+ const tagSpellings = /* @__PURE__ */ new Map();
1881
+ const namelessTags = /* @__PURE__ */ new Set();
1882
+ operations.forEach((operation) => {
1883
+ const tag = operation.tags?.[0];
1884
+ let rawName = "Default";
1885
+ if (tag !== void 0) {
1886
+ rawName = tag;
1887
+ } else {
1888
+ const pathParts = operation.path.split("/").filter((part) => part && !part.startsWith("{"));
1889
+ if (pathParts.length > 1) {
1890
+ rawName = pathParts[1];
1891
+ }
1892
+ }
1893
+ const sanitized = pascalCase(rawName);
1894
+ const isNameless = !new RegExp("\\p{L}", "u").test(sanitized);
1895
+ const candidate = isNameless ? "Default" : sanitized;
1896
+ const fold = candidate.toLowerCase();
1897
+ const controllerName = controllerByFold.get(fold) ?? candidate;
1898
+ controllerByFold.set(fold, controllerName);
1899
+ if (tag !== void 0 && isNameless) {
1900
+ namelessTags.add(tag);
1901
+ } else if (tag !== void 0) {
1902
+ const spellings = tagSpellings.get(controllerName) ?? /* @__PURE__ */ new Set();
1903
+ spellings.add(tag);
1904
+ tagSpellings.set(controllerName, spellings);
1905
+ }
1906
+ if (!groups[controllerName]) {
1907
+ groups[controllerName] = [];
1908
+ }
1909
+ groups[controllerName].push(operation);
1910
+ });
1911
+ for (const tag of namelessTags) {
1912
+ onWarning?.(`Tag "${tag}" contains no characters usable in a name \u2014 its operations are generated into the "Default" controller. Rename the tag to give them a file of their own.`);
1913
+ }
1914
+ for (const [controllerName, spellings] of tagSpellings) {
1915
+ if (spellings.size > 1) {
1916
+ onWarning?.(`Tags ${[
1917
+ ...spellings
1918
+ ].map((name) => `"${name}"`).join(" and ")} all map to the controller "${controllerName}" \u2014 their operations are generated into one file. Rename one tag to keep them apart.`);
1919
+ }
1920
+ }
1921
+ return groups;
876
1922
  }
877
- __name(hasDuplicateFunctionNames, "hasDuplicateFunctionNames");
1923
+ __name(groupOperationsByController, "groupOperationsByController");
878
1924
 
879
- // ../shared/src/utils/functions/get-request-body-type.ts
880
- function getRequestBodyType(requestBody, config) {
881
- const content = requestBody.content || {};
882
- const jsonContent = content[CONTENT_TYPES.JSON];
883
- if (jsonContent?.schema) {
884
- return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable);
1925
+ // ../shared/src/utils/functions/method-names.ts
1926
+ var RESERVED_MEMBER_NAMES = Object.freeze([
1927
+ "constructor",
1928
+ "httpClient",
1929
+ "basePath",
1930
+ "clientContextToken",
1931
+ "createContextWithClientId"
1932
+ ]);
1933
+ var RESERVED_MEMBER_SET = new Set(RESERVED_MEMBER_NAMES);
1934
+ function reservedMemberCollision(operation, config) {
1935
+ if (config.options.customizeMethodName || !operation.operationId) {
1936
+ return void 0;
885
1937
  }
886
- return "any";
1938
+ const natural = camelCase(operation.operationId);
1939
+ return RESERVED_MEMBER_SET.has(natural) ? {
1940
+ from: natural,
1941
+ to: `_${natural}`
1942
+ } : void 0;
887
1943
  }
888
- __name(getRequestBodyType, "getRequestBodyType");
1944
+ __name(reservedMemberCollision, "reservedMemberCollision");
1945
+ function getOperationMethodName(operation, config) {
1946
+ const customize = config.options.customizeMethodName;
1947
+ if (!customize) {
1948
+ return defaultOperationMethodName(operation);
1949
+ }
1950
+ if (operation.operationId == null) {
1951
+ throw new InvalidIdentifierError(`customizeMethodName needs an operationId, and ${describeOperation(operation)} has none. Add one to the spec, or drop customizeMethodName to use the derived name.`, operation);
1952
+ }
1953
+ const customName = customize(operation.operationId);
1954
+ if (!isValidIdentifier(customName) || RESERVED_MEMBER_SET.has(customName)) {
1955
+ throw new InvalidIdentifierError(`customizeMethodName returned "${customName}" for ${describeOperation(operation)}, which is not a usable TypeScript method name. Return an identifier \u2014 letters, digits, "_" and "$", not starting with a digit \u2014 and not one of ${RESERVED_MEMBER_NAMES.map((name) => `"${name}"`).join(", ")}.`, operation, customName);
1956
+ }
1957
+ return customName;
1958
+ }
1959
+ __name(getOperationMethodName, "getOperationMethodName");
1960
+ function defaultOperationMethodName(operation) {
1961
+ if (operation.operationId) {
1962
+ const name = camelCase(operation.operationId);
1963
+ return RESERVED_MEMBER_SET.has(name) ? `_${name}` : name;
1964
+ }
1965
+ const method = pascalCase(operation.method.toLowerCase());
1966
+ const pathParts = operation.path.split("/").filter((segment) => segment !== "").map((segment) => pascalCase(segment));
1967
+ const resource = pathParts.join("") || "resource";
1968
+ return `${camelCase(resource)}${method}`;
1969
+ }
1970
+ __name(defaultOperationMethodName, "defaultOperationMethodName");
889
1971
 
890
- // ../shared/src/utils/functions/is-data-type-interface.ts
891
- function isDataTypeInterface(type) {
892
- const invalidTypes = [
893
- "any",
894
- "File",
895
- "string",
896
- "number",
897
- "boolean",
898
- "object",
899
- "unknown",
900
- "[]",
901
- "Array"
1972
+ // ../shared/src/utils/functions/distinct-member-names.ts
1973
+ function assertDistinctMemberNames(serviceClass, className, operations, methodNameOf) {
1974
+ const methodNames = serviceClass.getMethods().map((method) => method.getName());
1975
+ const propertyNames = new Set(serviceClass.getProperties().map((property) => property.getName()));
1976
+ const duplicates = [
1977
+ ...new Set(methodNames.filter((name, index) => methodNames.indexOf(name) !== index || propertyNames.has(name)))
902
1978
  ];
903
- return !invalidTypes.some((invalidType) => type.includes(invalidType));
1979
+ if (duplicates.length === 0) {
1980
+ return;
1981
+ }
1982
+ const byName = new Map(duplicates.map((name) => [
1983
+ name,
1984
+ []
1985
+ ]));
1986
+ for (const operation of operations) {
1987
+ byName.get(methodNameOf(operation))?.push(operation);
1988
+ }
1989
+ const detail = [
1990
+ ...byName
1991
+ ].map(([name, ops]) => {
1992
+ const from = ops.map(describeOperation).join(" and ");
1993
+ return propertyNames.has(name) ? `"${name}" from ${from} (a property of ${className})` : `"${name}" from ${from}`;
1994
+ }).join("; ");
1995
+ throw new DuplicateGeneratedNameError(`Operations map to the same member name in ${className}: ${detail}. Ensure each operationId maps to a unique name.`, duplicates, [
1996
+ ...byName.values()
1997
+ ].flat());
904
1998
  }
905
- __name(isDataTypeInterface, "isDataTypeInterface");
1999
+ __name(assertDistinctMemberNames, "assertDistinctMemberNames");
906
2000
 
907
2001
  // ../shared/src/config/constants.ts
908
2002
  var disableLinting = `// @ts-nocheck
@@ -938,7 +2032,7 @@ var PROVIDER_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated prov
938
2032
  * Do not edit this file manually
939
2033
  */
940
2034
  `;
941
- var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => defaultHeaderComment + `* Generated Base Interceptor for client ${clientName}
2035
+ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => defaultHeaderComment + `* Generated Base Interceptor for client ${escapeJsDoc(clientName)}
942
2036
  * Do not edit this file manually
943
2037
  */
944
2038
  `, "BASE_INTERCEPTOR_HEADER_COMMENT");
@@ -949,11 +2043,9 @@ var ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Genera
949
2043
 
950
2044
  // src/lib/cli.ts
951
2045
  var import_commander = require("commander");
952
- var fs3 = __toESM(require("fs"));
953
- var path14 = __toESM(require("path"));
954
2046
 
955
2047
  // package.json
956
- var version = "0.3.3";
2048
+ var version = "0.4.0";
957
2049
 
958
2050
  // src/lib/core/generator.ts
959
2051
  var import_ts_morph10 = require("ts-morph");
@@ -962,17 +2054,8 @@ var import_ts_morph10 = require("ts-morph");
962
2054
  var import_ts_morph = require("ts-morph");
963
2055
 
964
2056
  // src/lib/generators/type/type-resolver.ts
965
- function escapeString2(str) {
966
- return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
967
- }
968
- __name(escapeString2, "escapeString");
969
- function sanitizePropertyName(name) {
970
- if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
971
- return `"${name}"`;
972
- }
973
- return name;
974
- }
975
- __name(sanitizePropertyName, "sanitizePropertyName");
2057
+ var escapeString = escapeSingleQuoted;
2058
+ var sanitizePropertyName = emitPropertyName;
976
2059
  var TypeResolver = class {
977
2060
  static {
978
2061
  __name(this, "TypeResolver");
@@ -1043,7 +2126,7 @@ var TypeResolver = class {
1043
2126
  return this.resolveReference(schema.$ref);
1044
2127
  }
1045
2128
  if (schema.enum) {
1046
- return schema.enum.map((value) => typeof value === "string" ? `'${escapeString2(value)}'` : String(value)).join(" | ");
2129
+ return schema.enum.map((value) => typeof value === "string" ? `'${escapeString(value)}'` : String(value)).join(" | ");
1047
2130
  }
1048
2131
  if (schema.allOf) {
1049
2132
  return schema.allOf.map((def) => this.resolve(def)).filter((type) => type !== "any" && type !== "unknown").join(" & ") || "Record<string, unknown>";
@@ -1147,9 +2230,7 @@ var EnumBuilder = class {
1147
2230
  }
1148
2231
  build(name, definition) {
1149
2232
  if (!definition.enum?.length) return [];
1150
- const docs = !this.config.options.generateEnumBasedOnDescription && definition.description ? [
1151
- definition.description
1152
- ] : void 0;
2233
+ const docs = this.config.options.generateEnumBasedOnDescription ? void 0 : emitDocs(definition.description);
1153
2234
  if (this.config.options.enumStyle === "enum") {
1154
2235
  return this.buildEnumAsEnum(name, definition, docs);
1155
2236
  } else {
@@ -1190,7 +2271,7 @@ var EnumBuilder = class {
1190
2271
  const objectProperties = [];
1191
2272
  const unionType = definition.enum.map((value) => {
1192
2273
  const key = toEnumKey(value);
1193
- const val = typeof value === "string" ? `'${escapeString2(value)}'` : isNaN(value) ? `'${value}'` : `${value}`;
2274
+ const val = typeof value === "string" ? `'${escapeString(value)}'` : isNaN(value) ? `'${value}'` : `${value}`;
1194
2275
  objectProperties.push(`${key}: ${val} as ${name}`);
1195
2276
  return val;
1196
2277
  }).join(" | ");
@@ -1250,9 +2331,7 @@ var InterfaceBuilder = class {
1250
2331
  kind: import_ts_morph2.StructureKind.Interface,
1251
2332
  name,
1252
2333
  isExported: true,
1253
- docs: definition.description ? [
1254
- definition.description
1255
- ] : void 0,
2334
+ docs: emitDocs(definition.description),
1256
2335
  properties: this.buildProperties(definition),
1257
2336
  indexSignatures: this.buildIndexSignatures(definition)
1258
2337
  };
@@ -1271,9 +2350,7 @@ var InterfaceBuilder = class {
1271
2350
  type: propertyType,
1272
2351
  isReadonly: isReadOnly,
1273
2352
  hasQuestionToken: !isRequired,
1274
- docs: property.description ? [
1275
- property.description
1276
- ] : void 0
2353
+ docs: emitDocs(property.description)
1277
2354
  };
1278
2355
  });
1279
2356
  }
@@ -1443,24 +2520,46 @@ var TypeGenerator = class {
1443
2520
  this.interfaceBuilder = new InterfaceBuilder(this.resolver);
1444
2521
  }
1445
2522
  async generate() {
1446
- try {
1447
- const definitions = this.parser.getNormalizedSpec().definitions;
1448
- if (!definitions || Object.keys(definitions).length === 0) {
1449
- this.onWarning?.("No definitions found in swagger file");
1450
- }
1451
- if (this.config.options.modelFileStructure === "per-type") {
1452
- this.generatePerType(definitions);
1453
- return;
1454
- }
1455
- Object.entries(definitions).forEach(([name, definition]) => {
1456
- this.statements.push(...this.collectTypeStructure(name, definition));
1457
- });
1458
- this.statements.push(...buildSdkTypes(this.config));
1459
- this.applyBatchUpdates();
1460
- await this.finalize();
1461
- } catch (error) {
1462
- throw new Error(`Failed to generate types: ${error instanceof Error ? error.message : "Unknown error"}`);
2523
+ const definitions = this.parser.getNormalizedSpec().definitions;
2524
+ if (!definitions || Object.keys(definitions).length === 0) {
2525
+ this.onWarning?.("No definitions found in swagger file");
2526
+ }
2527
+ this.assertDistinctTypeNames(definitions);
2528
+ if (this.config.options.modelFileStructure === "per-type") {
2529
+ this.generatePerType(definitions);
2530
+ return;
1463
2531
  }
2532
+ Object.entries(definitions).forEach(([name, definition]) => {
2533
+ this.statements.push(...this.collectTypeStructure(name, definition));
2534
+ });
2535
+ this.statements.push(...buildSdkTypes(this.config));
2536
+ this.applyBatchUpdates();
2537
+ await this.finalize();
2538
+ }
2539
+ /**
2540
+ * Two schemas whose names sanitize onto one type name (`Pet-Store` and
2541
+ * `Pet.Store` both become `Pet_Store`) would emit two declarations of one
2542
+ * interface — and TypeScript declaration-merges those, so the compile
2543
+ * check cannot see it and one model silently acquires the other's
2544
+ * properties. Worse than a hard error, hence a hard error.
2545
+ */
2546
+ assertDistinctTypeNames(definitions) {
2547
+ const rawByType = /* @__PURE__ */ new Map();
2548
+ for (const rawName of Object.keys(definitions)) {
2549
+ const typeName = this.resolver.pascalName(rawName);
2550
+ rawByType.set(typeName, [
2551
+ ...rawByType.get(typeName) ?? [],
2552
+ rawName
2553
+ ]);
2554
+ }
2555
+ const collisions = [
2556
+ ...rawByType
2557
+ ].filter(([, raws]) => raws.length > 1);
2558
+ if (collisions.length === 0) {
2559
+ return;
2560
+ }
2561
+ const detail = collisions.map(([typeName, raws]) => `"${typeName}" from schemas ${raws.map((raw) => `"${raw}"`).join(" and ")}`).join("; ");
2562
+ throw new DuplicateGeneratedNameError(`Schemas map to the same type name: ${detail}. Rename one schema to keep them apart.`, collisions.map(([typeName]) => typeName));
1464
2563
  }
1465
2564
  collectTypeStructure(name, definition) {
1466
2565
  const typeName = this.resolver.pascalName(name);
@@ -1484,9 +2583,7 @@ var TypeGenerator = class {
1484
2583
  kind: import_ts_morph4.StructureKind.TypeAlias,
1485
2584
  name: typeName,
1486
2585
  isExported: true,
1487
- docs: definition.description ? [
1488
- definition.description
1489
- ] : void 0,
2586
+ docs: emitDocs(definition.description),
1490
2587
  type: this.resolver.resolve(definition)
1491
2588
  }
1492
2589
  ];
@@ -1503,9 +2600,7 @@ var TypeGenerator = class {
1503
2600
  name,
1504
2601
  type: typeExpression,
1505
2602
  isExported: true,
1506
- docs: definition.description ? [
1507
- definition.description
1508
- ] : void 0
2603
+ docs: emitDocs(definition.description)
1509
2604
  };
1510
2605
  }
1511
2606
  buildArrayTypeAlias(name, definition) {
@@ -1514,9 +2609,7 @@ var TypeGenerator = class {
1514
2609
  kind: import_ts_morph4.StructureKind.TypeAlias,
1515
2610
  name,
1516
2611
  isExported: true,
1517
- docs: definition.description ? [
1518
- definition.description
1519
- ] : void 0,
2612
+ docs: emitDocs(definition.description),
1520
2613
  type: `Array<${itemType}>`
1521
2614
  };
1522
2615
  }
@@ -1524,10 +2617,9 @@ var TypeGenerator = class {
1524
2617
  this.addSdkImports(this.sourceFile);
1525
2618
  this.sourceFile.addStatements(this.statements);
1526
2619
  }
1527
- async finalize() {
2620
+ finalize() {
1528
2621
  this.sourceFile.formatText();
1529
2622
  this.sourceFile.insertText(0, TYPE_GENERATOR_HEADER_COMMENT);
1530
- await this.sourceFile.save();
1531
2623
  }
1532
2624
  generatePerType(definitions) {
1533
2625
  const registry = new ModelFileRegistry(this.onWarning);
@@ -1616,7 +2708,6 @@ var TypeGenerator = class {
1616
2708
  finalizeModelSourceFile(sourceFile) {
1617
2709
  sourceFile.formatText();
1618
2710
  sourceFile.insertText(0, TYPE_GENERATOR_HEADER_COMMENT);
1619
- sourceFile.saveSync();
1620
2711
  }
1621
2712
  };
1622
2713
 
@@ -1629,9 +2720,9 @@ var TokenGenerator = class {
1629
2720
  }
1630
2721
  project;
1631
2722
  clientName;
1632
- constructor(project, clientName = "default") {
2723
+ constructor(project, clientName) {
1633
2724
  this.project = project;
1634
- this.clientName = clientName;
2725
+ this.clientName = effectiveClientName(clientName);
1635
2726
  }
1636
2727
  generate(outputDir) {
1637
2728
  const tokensDir = path2.join(outputDir, "tokens");
@@ -1654,9 +2745,9 @@ var TokenGenerator = class {
1654
2745
  moduleSpecifier: "@angular/common/http"
1655
2746
  }
1656
2747
  ]);
1657
- const basePathTokenName = this.getBasePathTokenName();
1658
- const interceptorsTokenName = this.getInterceptorsTokenName();
1659
- const clientContextTokenName = this.getClientContextTokenName();
2748
+ const basePathTokenName = getBasePathTokenName(this.clientName);
2749
+ const interceptorsTokenName = getInterceptorsTokenName(this.clientName);
2750
+ const clientContextTokenName = getClientContextTokenName(this.clientName);
1660
2751
  sourceFile.addVariableStatement({
1661
2752
  isExported: true,
1662
2753
  declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
@@ -1670,7 +2761,7 @@ var TokenGenerator = class {
1670
2761
  }
1671
2762
  ],
1672
2763
  leadingTrivia: `/**
1673
- * Injection token for the ${this.clientName} client base API path
2764
+ * Injection token for the ${escapeJsDoc(this.clientName)} client base API path
1674
2765
  */
1675
2766
  `
1676
2767
  });
@@ -1687,7 +2778,7 @@ var TokenGenerator = class {
1687
2778
  }
1688
2779
  ],
1689
2780
  leadingTrivia: `/**
1690
- * Injection token for the ${this.clientName} client HTTP interceptor instances
2781
+ * Injection token for the ${escapeJsDoc(this.clientName)} client HTTP interceptor instances
1691
2782
  */
1692
2783
  `
1693
2784
  });
@@ -1697,11 +2788,11 @@ var TokenGenerator = class {
1697
2788
  declarations: [
1698
2789
  {
1699
2790
  name: clientContextTokenName,
1700
- initializer: `new HttpContextToken<string>(() => '${this.clientName}')`
2791
+ initializer: `new HttpContextToken<string>(() => ${quoteLiteral(this.clientName)})`
1701
2792
  }
1702
2793
  ],
1703
2794
  leadingTrivia: `/**
1704
- * HttpContext token to identify requests belonging to the ${this.clientName} client
2795
+ * HttpContext token to identify requests belonging to the ${escapeJsDoc(this.clientName)} client
1705
2796
  */
1706
2797
  `
1707
2798
  });
@@ -1736,19 +2827,6 @@ var TokenGenerator = class {
1736
2827
  });
1737
2828
  }
1738
2829
  sourceFile.formatText();
1739
- sourceFile.saveSync();
1740
- }
1741
- getBasePathTokenName() {
1742
- const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1743
- return `BASE_PATH_${clientSuffix}`;
1744
- }
1745
- getInterceptorsTokenName() {
1746
- const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1747
- return `HTTP_INTERCEPTORS_${clientSuffix}`;
1748
- }
1749
- getClientContextTokenName() {
1750
- const clientSuffix = this.clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
1751
- return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
1752
2830
  }
1753
2831
  };
1754
2832
 
@@ -1888,7 +2966,6 @@ var FileDownloadGenerator = class {
1888
2966
  return fallbackFilename;`
1889
2967
  });
1890
2968
  sourceFile.formatText();
1891
- sourceFile.saveSync();
1892
2969
  }
1893
2970
  };
1894
2971
 
@@ -2042,7 +3119,6 @@ var DateTransformerGenerator = class {
2042
3119
  ]
2043
3120
  });
2044
3121
  sourceFile.formatText();
2045
- sourceFile.saveSync();
2046
3122
  }
2047
3123
  };
2048
3124
 
@@ -2105,7 +3181,6 @@ var MainIndexGenerator = class {
2105
3181
  });
2106
3182
  });
2107
3183
  sourceFile.formatText();
2108
- sourceFile.saveSync();
2109
3184
  }
2110
3185
  };
2111
3186
 
@@ -2121,7 +3196,7 @@ var ProviderGenerator = class {
2121
3196
  constructor(project, config) {
2122
3197
  this.project = project;
2123
3198
  this.config = config;
2124
- this.clientName = config.clientName || "default";
3199
+ this.clientName = effectiveClientName(config.clientName);
2125
3200
  }
2126
3201
  generate(outputDir) {
2127
3202
  const filePath = path6.join(outputDir, "providers.ts");
@@ -2130,7 +3205,7 @@ var ProviderGenerator = class {
2130
3205
  });
2131
3206
  const basePathTokenName = getBasePathTokenName(this.clientName);
2132
3207
  const interceptorsTokenName = getInterceptorsTokenName(this.clientName);
2133
- const baseInterceptorClassName = `${this.capitalizeFirst(this.clientName)}BaseInterceptor`;
3208
+ const baseInterceptorClassName = `${clientNameIdentifier(this.clientName)}BaseInterceptor`;
2134
3209
  sourceFile.addImportDeclarations([
2135
3210
  {
2136
3211
  namedImports: [
@@ -2206,22 +3281,19 @@ var ProviderGenerator = class {
2206
3281
  });
2207
3282
  }
2208
3283
  sourceFile.addInterface({
2209
- name: `${this.capitalizeFirst(this.clientName)}Config`,
3284
+ name: `${clientNameIdentifier(this.clientName)}Config`,
2210
3285
  isExported: true,
2211
- docs: [
2212
- `Configuration options for ${this.clientName} client`
2213
- ],
3286
+ docs: emitDocs(`Configuration options for ${this.clientName} client`),
2214
3287
  properties: configProperties
2215
3288
  });
2216
3289
  this.addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName);
2217
3290
  sourceFile.insertText(0, PROVIDER_GENERATOR_HEADER_COMMENT);
2218
3291
  sourceFile.formatText();
2219
- sourceFile.saveSync();
2220
3292
  }
2221
3293
  addMainProviderFunction(sourceFile, basePathTokenName, interceptorsTokenName, baseInterceptorClassName) {
2222
3294
  const hasDateInterceptor = this.config.options.dateType === "Date";
2223
- const functionName = `provide${this.capitalizeFirst(this.clientName)}Client`;
2224
- const configTypeName = `${this.capitalizeFirst(this.clientName)}Config`;
3295
+ const functionName = `provide${clientNameIdentifier(this.clientName)}Client`;
3296
+ const configTypeName = `${clientNameIdentifier(this.clientName)}Config`;
2225
3297
  const functionBody = `
2226
3298
  const providers: Provider[] = [
2227
3299
  // Base path token for this client
@@ -2269,7 +3341,7 @@ return makeEnvironmentProviders(providers);`;
2269
3341
  name: functionName,
2270
3342
  isExported: true,
2271
3343
  docs: [
2272
- `Provides configuration for ${this.clientName} client`,
3344
+ `Provides configuration for ${escapeJsDoc(this.clientName)} client`,
2273
3345
  "",
2274
3346
  "@example",
2275
3347
  "```typescript",
@@ -2315,9 +3387,6 @@ return makeEnvironmentProviders(providers);`;
2315
3387
  });
2316
3388
  }
2317
3389
  }
2318
- capitalizeFirst(str) {
2319
- return str.charAt(0).toUpperCase() + str.slice(1);
2320
- }
2321
3390
  };
2322
3391
 
2323
3392
  // src/lib/generators/utility/base-interceptor.generator.ts
@@ -2329,9 +3398,9 @@ var BaseInterceptorGenerator = class {
2329
3398
  }
2330
3399
  #project;
2331
3400
  #clientName;
2332
- constructor(project, clientName = "default") {
3401
+ constructor(project, clientName) {
2333
3402
  this.#project = project;
2334
- this.#clientName = clientName;
3403
+ this.#clientName = effectiveClientName(clientName);
2335
3404
  }
2336
3405
  generate(outputDir) {
2337
3406
  const utilsDir = path7.join(outputDir, "utils");
@@ -2374,7 +3443,7 @@ var BaseInterceptorGenerator = class {
2374
3443
  }
2375
3444
  ]);
2376
3445
  sourceFile.addClass({
2377
- name: `${this.capitalizeFirst(this.#clientName)}BaseInterceptor`,
3446
+ name: `${clientNameIdentifier(this.#clientName)}BaseInterceptor`,
2378
3447
  isExported: true,
2379
3448
  decorators: [
2380
3449
  {
@@ -2438,10 +3507,6 @@ var BaseInterceptorGenerator = class {
2438
3507
  });
2439
3508
  sourceFile.insertText(0, BASE_INTERCEPTOR_HEADER_COMMENT(this.#clientName));
2440
3509
  sourceFile.formatText();
2441
- sourceFile.saveSync();
2442
- }
2443
- capitalizeFirst(str) {
2444
- return str.charAt(0).toUpperCase() + str.slice(1);
2445
3510
  }
2446
3511
  };
2447
3512
 
@@ -2474,7 +3539,6 @@ var HttpParamsBuilderGenerator = class {
2474
3539
  });
2475
3540
  this.addMethods(classDeclaration);
2476
3541
  sourceFile.formatText();
2477
- sourceFile.saveSync();
2478
3542
  }
2479
3543
  addMethods(classDeclaration) {
2480
3544
  const methods = [
@@ -2670,22 +3734,23 @@ var ServiceMethodBodyGenerator = class {
2670
3734
  this.config = config;
2671
3735
  }
2672
3736
  generateMethodBody(operation) {
3737
+ const argumentNames = resolveArgumentNames(operation, this.config, SERVICE_ARGUMENT_PROFILE);
2673
3738
  const bodyParts = [
2674
- emitUrlConstruction(operation.path, operation.pathParams),
2675
- emitQueryParams(operation.queryParams),
3739
+ emitUrlConstruction(operation.path, operation.pathParams, argumentNames),
3740
+ emitQueryParams(operation.queryParams, argumentNames),
2676
3741
  emitHeaders({
2677
3742
  optionsExpression: "options",
2678
3743
  customHeaders: this.config.options.customHeaders,
2679
3744
  accept: this.config.options.emitAcceptHeader ?? true ? operation.acceptHeader : void 0,
2680
3745
  contentType: operation
2681
3746
  }),
2682
- this.generateMultipartFormData(operation),
2683
- this.generateUrlEncodedFormData(operation),
2684
- this.generateHttpRequest(operation)
3747
+ this.generateMultipartFormData(operation, argumentNames),
3748
+ this.generateUrlEncodedFormData(operation, argumentNames),
3749
+ this.generateHttpRequest(operation, argumentNames)
2685
3750
  ];
2686
3751
  return bodyParts.filter(Boolean).join("\n");
2687
3752
  }
2688
- generateMultipartFormData(operation) {
3753
+ generateMultipartFormData(operation, argumentNames) {
2689
3754
  if (!operation.isMultipart || operation.formDataFields.length === 0) {
2690
3755
  return "";
2691
3756
  }
@@ -2694,21 +3759,22 @@ var ServiceMethodBodyGenerator = class {
2694
3759
  const fieldSchema = properties[field];
2695
3760
  const isFile = fieldSchema?.type === "string" && fieldSchema?.format === "binary";
2696
3761
  const isArray = fieldSchema?.type === "array";
3762
+ const arg = argumentNames.of(field);
2697
3763
  if (isArray) {
2698
3764
  const itemSchema = Array.isArray(fieldSchema.items) ? fieldSchema.items[0] : fieldSchema.items;
2699
3765
  const isFileArray = itemSchema?.type === "string" && itemSchema?.format === "binary";
2700
3766
  const valueExpression = isFileArray ? "item" : "String(item)";
2701
- return `if (${field} !== undefined && Array.isArray(${field})) {
2702
- ${field}.forEach((item) => {
3767
+ return `if (${arg} !== undefined && Array.isArray(${arg})) {
3768
+ ${arg}.forEach((item) => {
2703
3769
  if (item !== undefined && item !== null) {
2704
- formData.append('${field}', ${valueExpression});
3770
+ formData.append(${quoteLiteral(field)}, ${valueExpression});
2705
3771
  }
2706
3772
  });
2707
3773
  }`;
2708
3774
  } else {
2709
- const valueExpression = isFile ? field : `String(${field})`;
2710
- return `if (${field} !== undefined) {
2711
- formData.append('${field}', ${valueExpression});
3775
+ const valueExpression = isFile ? arg : `String(${arg})`;
3776
+ return `if (${arg} !== undefined) {
3777
+ formData.append(${quoteLiteral(field)}, ${valueExpression});
2712
3778
  }`;
2713
3779
  }
2714
3780
  }).join("\n");
@@ -2716,7 +3782,7 @@ var ServiceMethodBodyGenerator = class {
2716
3782
  const formData = new FormData();
2717
3783
  ${formDataAppends}`;
2718
3784
  }
2719
- generateUrlEncodedFormData(operation) {
3785
+ generateUrlEncodedFormData(operation, argumentNames) {
2720
3786
  if (!operation.isUrlEncoded || operation.urlEncodedFields.length === 0) {
2721
3787
  return "";
2722
3788
  }
@@ -2724,17 +3790,18 @@ ${formDataAppends}`;
2724
3790
  const formBodyAppends = operation.urlEncodedFields.map((field) => {
2725
3791
  const fieldSchema = properties[field];
2726
3792
  const isArray = fieldSchema?.type === "array";
3793
+ const arg = argumentNames.of(field);
2727
3794
  if (isArray) {
2728
- return `if (${field} !== undefined && Array.isArray(${field})) {
2729
- ${field}.forEach((item) => {
3795
+ return `if (${arg} !== undefined && Array.isArray(${arg})) {
3796
+ ${arg}.forEach((item) => {
2730
3797
  if (item !== undefined && item !== null) {
2731
- formBody.append('${field}', String(item));
3798
+ formBody.append(${quoteLiteral(field)}, String(item));
2732
3799
  }
2733
3800
  });
2734
3801
  }`;
2735
3802
  } else {
2736
- return `if (${field} !== undefined && ${field} !== null) {
2737
- formBody.append('${field}', String(${field}));
3803
+ return `if (${arg} !== undefined && ${arg} !== null) {
3804
+ formBody.append(${quoteLiteral(field)}, String(${arg}));
2738
3805
  }`;
2739
3806
  }
2740
3807
  }).join("\n");
@@ -2742,7 +3809,7 @@ ${formDataAppends}`;
2742
3809
  const formBody = new URLSearchParams();
2743
3810
  ${formBodyAppends}`;
2744
3811
  }
2745
- generateHttpRequest(operation) {
3812
+ generateHttpRequest(operation, argumentNames) {
2746
3813
  const httpMethod = operation.method.toLowerCase();
2747
3814
  let bodyParam = "";
2748
3815
  if (operation.hasBody) {
@@ -2751,9 +3818,7 @@ ${formBodyAppends}`;
2751
3818
  } else if (operation.isUrlEncoded) {
2752
3819
  bodyParam = "formBody.toString()";
2753
3820
  } else if (operation.requestBody?.content?.[CONTENT_TYPES.JSON]) {
2754
- const bodyType = getRequestBodyType(operation.requestBody, this.config);
2755
- const isInterface = isDataTypeInterface(bodyType);
2756
- bodyParam = isInterface ? camelCase(bodyType) : "requestBody";
3821
+ bodyParam = argumentNames.body ?? "requestBody";
2757
3822
  }
2758
3823
  }
2759
3824
  const methodsWithBody = [
@@ -2868,9 +3933,10 @@ var ServiceMethodParamsGenerator = class {
2868
3933
  }
2869
3934
  generateApiParameters(operation) {
2870
3935
  const params = [];
3936
+ const argumentNames = resolveArgumentNames(operation, this.config, SERVICE_ARGUMENT_PROFILE);
2871
3937
  operation.pathParams.forEach((param) => {
2872
3938
  params.push({
2873
- name: camelCase(param.name),
3939
+ name: argumentNames.of(param.name),
2874
3940
  // Swagger 2.0 puts type/format/enum on the parameter itself; the
2875
3941
  // spread (vs passing param directly) is needed because Parameter
2876
3942
  // lacks TypeSchema's index signature — a fresh literal satisfies it.
@@ -2884,24 +3950,22 @@ var ServiceMethodParamsGenerator = class {
2884
3950
  if (requestBody) {
2885
3951
  const jsonContent = requestBody.content?.[CONTENT_TYPES.JSON];
2886
3952
  if (operation.isMultipart) {
2887
- params.push(...this.convertObjectToSingleParams(operation.formDataSchema));
3953
+ params.push(...this.convertObjectToSingleParams(operation.formDataSchema, argumentNames));
2888
3954
  }
2889
3955
  if (operation.isUrlEncoded) {
2890
- params.push(...this.convertObjectToSingleParams(operation.urlEncodedSchema));
3956
+ params.push(...this.convertObjectToSingleParams(operation.urlEncodedSchema, argumentNames));
2891
3957
  }
2892
- if (jsonContent && !operation.isMultipart) {
2893
- const bodyType = this.getRequestBodyType(requestBody);
2894
- const isInterface = isDataTypeInterface(bodyType);
3958
+ if (jsonContent && !operation.isMultipart && argumentNames.body) {
2895
3959
  params.push({
2896
- name: isInterface ? camelCase(bodyType) : "requestBody",
2897
- type: bodyType,
3960
+ name: argumentNames.body,
3961
+ type: this.getRequestBodyType(requestBody),
2898
3962
  hasQuestionToken: !requestBody.required
2899
3963
  });
2900
3964
  }
2901
3965
  }
2902
3966
  operation.queryParams.forEach((param) => {
2903
3967
  params.push({
2904
- name: camelCase(param.name),
3968
+ name: argumentNames.of(param.name),
2905
3969
  type: getTypeScriptType(param.schema || {
2906
3970
  ...param
2907
3971
  }, this.config),
@@ -2944,11 +4008,11 @@ var ServiceMethodParamsGenerator = class {
2944
4008
  return "any";
2945
4009
  }
2946
4010
  /** `schema` arrives ref-resolved from the normalizer (formData/urlEncoded schema). */
2947
- convertObjectToSingleParams(schema) {
4011
+ convertObjectToSingleParams(schema, argumentNames) {
2948
4012
  const params = [];
2949
4013
  Object.entries(schema?.properties ?? {}).forEach(([key, value]) => {
2950
4014
  params.push({
2951
- name: key,
4015
+ name: argumentNames.of(key),
2952
4016
  type: getTypeScriptType(value, this.config, value.nullable),
2953
4017
  hasQuestionToken: !schema?.required?.includes(key)
2954
4018
  });
@@ -3085,9 +4149,7 @@ ${methodBody}`;
3085
4149
  returnType,
3086
4150
  statements: methodBody,
3087
4151
  overloads: methodOverLoads,
3088
- docs: operation.description ? [
3089
- operation.description
3090
- ] : void 0
4152
+ docs: emitDocs(operation.description)
3091
4153
  });
3092
4154
  }
3093
4155
  generateSingleRequestParameters(requestObject) {
@@ -3097,29 +4159,11 @@ ${methodBody}`;
3097
4159
  ];
3098
4160
  }
3099
4161
  generateMethodName(operation) {
3100
- if (this.config.options.customizeMethodName) {
3101
- if (operation.operationId == null) {
3102
- throw new Error(`Operation ID is required for method name customization of operation: (${operation.method}) ${operation.path}`);
3103
- }
3104
- return this.config.options.customizeMethodName(operation.operationId);
3105
- } else {
3106
- return this.defaultNameGenerator(operation);
3107
- }
4162
+ return getOperationMethodName(operation, this.config);
3108
4163
  }
3109
4164
  generateReturnType() {
3110
4165
  return "Observable<any>";
3111
4166
  }
3112
- defaultNameGenerator(operation) {
3113
- if (operation.operationId) {
3114
- return camelCase(operation.operationId);
3115
- }
3116
- const method = pascalCase(operation.method.toLowerCase());
3117
- const pathParts = operation.path.split("/").map((str) => {
3118
- return pascalCase(pascalCase(str).replace(/[^a-zA-Z0-9]/g, ""));
3119
- });
3120
- const resource = pathParts.join("") || "resource";
3121
- return `${camelCase(resource)}${method}`;
3122
- }
3123
4167
  };
3124
4168
 
3125
4169
  // src/lib/generators/service/request-params.generator.ts
@@ -3132,8 +4176,10 @@ var RequestParamsGenerator = class {
3132
4176
  paramsGenerator;
3133
4177
  registry = /* @__PURE__ */ new Map();
3134
4178
  usedInterfaceNames = /* @__PURE__ */ new Set();
3135
- constructor(project, config) {
4179
+ onWarning;
4180
+ constructor(project, config, onWarning) {
3136
4181
  this.project = project;
4182
+ this.onWarning = onWarning;
3137
4183
  this.paramsGenerator = new ServiceMethodParamsGenerator(config);
3138
4184
  }
3139
4185
  buildRegistry(controllerGroups, getMethodName) {
@@ -3143,10 +4189,6 @@ var RequestParamsGenerator = class {
3143
4189
  if (parameters.length === 0) {
3144
4190
  return;
3145
4191
  }
3146
- const reserved = parameters.find((param) => param.name === "observe" || param.name === "options");
3147
- if (reserved) {
3148
- throw new Error(`Parameter name '${reserved.name}' conflicts with the reserved '${reserved.name}' method parameter when useSingleRequestParameter is enabled: (${operation.method}) ${operation.path}`);
3149
- }
3150
4192
  const interfaceName = this.reserveInterfaceName(controllerName, getMethodName(operation));
3151
4193
  this.registry.set(operation, ServiceMethodRequestObjectGenerator.createEntry(interfaceName, parameters));
3152
4194
  });
@@ -3166,15 +4208,12 @@ var RequestParamsGenerator = class {
3166
4208
  name: entry.interfaceName,
3167
4209
  isExported: true,
3168
4210
  properties: ServiceMethodRequestObjectGenerator.toInterfaceProperties(entry),
3169
- docs: operation.description ? [
3170
- operation.description
3171
- ] : void 0
4211
+ docs: emitDocs(operation.description)
3172
4212
  });
3173
4213
  });
3174
4214
  this.addMissingImports(sourceFile);
3175
4215
  sourceFile.formatText();
3176
4216
  sourceFile.insertText(0, REQUEST_PARAMS_GENERATOR_HEADER_COMMENT);
3177
- sourceFile.saveSync();
3178
4217
  this.addModelsBarrelExport(outputRoot);
3179
4218
  }
3180
4219
  /**
@@ -3204,19 +4243,23 @@ var RequestParamsGenerator = class {
3204
4243
  base,
3205
4244
  `${pascalCase(controllerName)}${base}`
3206
4245
  ];
3207
- for (const candidate of candidates) {
3208
- if (!this.usedInterfaceNames.has(candidate)) {
3209
- this.usedInterfaceNames.add(candidate);
3210
- return candidate;
3211
- }
4246
+ if (!this.usedInterfaceNames.has(base)) {
4247
+ this.usedInterfaceNames.add(base);
4248
+ return base;
4249
+ }
4250
+ const warnRenamed = /* @__PURE__ */ __name((name) => {
4251
+ this.usedInterfaceNames.add(name);
4252
+ this.onWarning?.(`Request-parameter interface "${base}" is already taken; the parameters of "${methodName}" in ${controllerName} are exposed as "${name}". Renaming the operationId keeps the type name stable.`);
4253
+ return name;
4254
+ }, "warnRenamed");
4255
+ if (!this.usedInterfaceNames.has(candidates[1])) {
4256
+ return warnRenamed(candidates[1]);
3212
4257
  }
3213
4258
  let suffix = 2;
3214
4259
  while (this.usedInterfaceNames.has(`${candidates[1]}${suffix}`)) {
3215
4260
  suffix++;
3216
4261
  }
3217
- const name = `${candidates[1]}${suffix}`;
3218
- this.usedInterfaceNames.add(name);
3219
- return name;
4262
+ return warnRenamed(`${candidates[1]}${suffix}`);
3220
4263
  }
3221
4264
  addModelsBarrelExport(outputRoot) {
3222
4265
  const modelsIndex = this.project.getSourceFile(path9.join(outputRoot, "models", "index.ts"));
@@ -3227,7 +4270,6 @@ var RequestParamsGenerator = class {
3227
4270
  moduleSpecifier: "./request-params"
3228
4271
  });
3229
4272
  modelsIndex.formatText();
3230
- modelsIndex.saveSync();
3231
4273
  }
3232
4274
  };
3233
4275
 
@@ -3256,34 +4298,14 @@ var ServiceGenerator = class {
3256
4298
  this.onWarning?.("No API paths found in the specification");
3257
4299
  return;
3258
4300
  }
3259
- const controllerGroups = this.groupPathsByController(paths);
4301
+ const controllerGroups = groupOperationsByController(paths, this.onWarning);
3260
4302
  if (this.config.options.useSingleRequestParameter) {
3261
- const requestParamsGenerator = new RequestParamsGenerator(this.project, this.config);
4303
+ const requestParamsGenerator = new RequestParamsGenerator(this.project, this.config, this.onWarning);
3262
4304
  this.requestObjects = requestParamsGenerator.buildRegistry(controllerGroups, (operation) => this.methodGenerator.generateMethodName(operation));
3263
4305
  requestParamsGenerator.generate(outputRoot);
3264
4306
  }
3265
4307
  await Promise.all(Object.entries(controllerGroups).map(([controllerName, operations]) => this.generateServiceFile(controllerName, operations, outputDir)));
3266
4308
  }
3267
- groupPathsByController(paths) {
3268
- const groups = {};
3269
- paths.forEach((path15) => {
3270
- let controllerName = "Default";
3271
- if (path15.tags && path15.tags.length > 0) {
3272
- controllerName = path15.tags[0];
3273
- } else {
3274
- const pathParts = path15.path.split("/").filter((p) => p && !p.startsWith("{"));
3275
- if (pathParts.length > 1) {
3276
- controllerName = pascalCase(pathParts[1]);
3277
- }
3278
- }
3279
- controllerName = pascalCase(controllerName);
3280
- if (!groups[controllerName]) {
3281
- groups[controllerName] = [];
3282
- }
3283
- groups[controllerName].push(path15);
3284
- });
3285
- return groups;
3286
- }
3287
4309
  async generateServiceFile(controllerName, operations, outputDir) {
3288
4310
  const fileName = `${camelCase(controllerName)}.service.ts`;
3289
4311
  const filePath = path10.join(outputDir, fileName);
@@ -3293,7 +4315,21 @@ var ServiceGenerator = class {
3293
4315
  this.addServiceClass(sourceFile, controllerName, operations);
3294
4316
  sourceFile.fixMissingImports().formatText();
3295
4317
  sourceFile.insertText(0, SERVICE_GENERATOR_HEADER_COMMENT(controllerName));
3296
- sourceFile.saveSync();
4318
+ }
4319
+ /**
4320
+ * A renamed argument is part of the method's public signature, and the
4321
+ * suffix depends on which other arguments the operation has — so adding or
4322
+ * removing one renumbers the survivor and breaks call sites. Silent is the
4323
+ * one thing that must not happen.
4324
+ */
4325
+ warnAboutRenamedArguments(operation) {
4326
+ const { renamed, merged } = resolveArgumentNames(operation, this.config, SERVICE_ARGUMENT_PROFILE);
4327
+ for (const { source, identifier } of renamed) {
4328
+ this.onWarning?.(`Parameter "${source}" of ${describeOperation(operation)} is exposed as "${identifier}" \u2014 its natural name is already taken by another parameter or by the method itself. Renaming it in the spec keeps the generated signature stable.`);
4329
+ }
4330
+ for (const wireName of merged) {
4331
+ this.onWarning?.(`Parameter "${wireName}" of ${describeOperation(operation)} is declared in more than one location; they collapse into one argument, so the first declaration's type wins and the same value is sent for both.`);
4332
+ }
3297
4333
  }
3298
4334
  addServiceClass(sourceFile, controllerName, operations) {
3299
4335
  const className = getServiceClassName(controllerName, this.config.options.naming?.services);
@@ -3380,13 +4416,24 @@ var ServiceGenerator = class {
3380
4416
  ],
3381
4417
  returnType: "HttpContext",
3382
4418
  statements: `const context = existingContext || new HttpContext();
3383
- return context.set(this.clientContextToken, '${this.config.clientName || "default"}');`
4419
+ return context.set(this.clientContextToken, ${quoteLiteral(effectiveClientName(this.config.clientName))});`
3384
4420
  });
3385
4421
  operations.forEach((operation) => {
4422
+ this.warnAboutRenamedArguments(operation);
4423
+ this.warnAboutReservedMethodName(operation);
3386
4424
  this.methodGenerator.addServiceMethod(serviceClass, operation, this.requestObjects?.get(operation));
3387
4425
  });
3388
- if (hasDuplicateFunctionNames(serviceClass.getMethods())) {
3389
- throw new Error(`Duplicate method names found in service class ${className}. Please ensure unique method names for each operation.`);
4426
+ assertDistinctMemberNames(serviceClass, className, operations, (op) => this.methodGenerator.generateMethodName(op));
4427
+ }
4428
+ /**
4429
+ * A derived method name that landed on a member the class binds itself is
4430
+ * prefixed rather than rejected — the spec is valid — but the rename is
4431
+ * public signature and must be said out loud.
4432
+ */
4433
+ warnAboutReservedMethodName(operation) {
4434
+ const collision = reservedMemberCollision(operation, this.config);
4435
+ if (collision) {
4436
+ this.onWarning?.(`Operation ${describeOperation(operation)} would be named "${collision.from}", which the generated class already binds \u2014 it is emitted as "${collision.to}". Rename the operationId to choose the name.`);
3390
4437
  }
3391
4438
  }
3392
4439
  };
@@ -3423,7 +4470,6 @@ var ServiceIndexGenerator = class {
3423
4470
  moduleSpecifier: `./${serviceName}.service`
3424
4471
  });
3425
4472
  });
3426
- sourceFile.saveSync();
3427
4473
  }
3428
4474
  };
3429
4475
 
@@ -3441,18 +4487,6 @@ var NAMING_KEYS = [
3441
4487
  ];
3442
4488
  var NAME_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
3443
4489
  var NAME_SUFFIX_PATTERN = /^[A-Za-z0-9_]*$/;
3444
- var ConfigValidationError = class extends Error {
3445
- static {
3446
- __name(this, "ConfigValidationError");
3447
- }
3448
- issues;
3449
- constructor(issues) {
3450
- super(`Invalid ng-openapi configuration:
3451
- ${issues.map((issue) => ` - ${issue}`).join("\n")}`);
3452
- this.name = "ConfigValidationError";
3453
- this.issues = issues;
3454
- }
3455
- };
3456
4490
  function validateGeneratorConfig(config) {
3457
4491
  if (!config || typeof config !== "object") {
3458
4492
  throw new ConfigValidationError([
@@ -3611,7 +4645,12 @@ async function generateFromConfig(config, reporter = {}) {
3611
4645
  const outputPath = config.output;
3612
4646
  const generateServices = config.options.generateServices ?? true;
3613
4647
  const warnings = [];
4648
+ const seenWarnings = /* @__PURE__ */ new Set();
3614
4649
  const onWarning = /* @__PURE__ */ __name((message) => {
4650
+ if (seenWarnings.has(message)) {
4651
+ return;
4652
+ }
4653
+ seenWarnings.add(message);
3615
4654
  warnings.push(message);
3616
4655
  reporter.onWarning?.(message);
3617
4656
  }, "onWarning");
@@ -3636,7 +4675,7 @@ async function generateFromConfig(config, reporter = {}) {
3636
4675
  }
3637
4676
  });
3638
4677
  reporter.onPhase?.("processing-spec");
3639
- const swaggerParser = await SwaggerParser.create(config.input, config);
4678
+ const swaggerParser = await SwaggerParser.create(config.input, config, onWarning);
3640
4679
  if (!swaggerParser.isValidSpec()) {
3641
4680
  const versionInfo = swaggerParser.getSpecVersion();
3642
4681
  throw new SpecParseError(`Invalid or unsupported specification format. Expected OpenAPI 3.x or Swagger 2.x. ${versionInfo ? `Found: ${versionInfo.type} ${versionInfo.version}` : "No version info found"}`, config.input);
@@ -3645,7 +4684,7 @@ async function generateFromConfig(config, reporter = {}) {
3645
4684
  const typeGenerator = new TypeGenerator(swaggerParser, project, config, outputPath, onWarning);
3646
4685
  await typeGenerator.generate();
3647
4686
  reporter.onPhase?.("types-generated");
3648
- if (generateServices) {
4687
+ if (generateServices || config.plugins?.length) {
3649
4688
  const tokenGenerator = new TokenGenerator(project, config.clientName);
3650
4689
  tokenGenerator.generate(outputPath);
3651
4690
  if (config.options.dateType === "Date") {
@@ -3660,6 +4699,8 @@ async function generateFromConfig(config, reporter = {}) {
3660
4699
  providerGenerator.generate(outputPath);
3661
4700
  const baseInterceptorGenerator = new BaseInterceptorGenerator(project, config.clientName);
3662
4701
  baseInterceptorGenerator.generate(outputPath);
4702
+ }
4703
+ if (generateServices) {
3663
4704
  const serviceGenerator = new ServiceGenerator(swaggerParser, project, config, onWarning);
3664
4705
  await serviceGenerator.generate(outputPath);
3665
4706
  const indexGenerator = new ServiceIndexGenerator(project, config.options.naming?.services);
@@ -3680,6 +4721,7 @@ async function generateFromConfig(config, reporter = {}) {
3680
4721
  }
3681
4722
  const mainIndexGenerator = new MainIndexGenerator(project, config);
3682
4723
  mainIndexGenerator.generateMainIndex(outputPath);
4724
+ await project.save();
3683
4725
  return {
3684
4726
  client: config.clientName,
3685
4727
  filesWritten: project.getSourceFiles().map((sourceFile) => sourceFile.getFilePath()),
@@ -3689,6 +4731,43 @@ async function generateFromConfig(config, reporter = {}) {
3689
4731
  }
3690
4732
  __name(generateFromConfig, "generateFromConfig");
3691
4733
 
4734
+ // src/lib/core/config-loader.ts
4735
+ var fs3 = __toESM(require("fs"));
4736
+ var path14 = __toESM(require("path"));
4737
+ async function loadConfigFile(configPath) {
4738
+ const resolvedPath = path14.resolve(configPath);
4739
+ if (!fs3.existsSync(resolvedPath)) {
4740
+ throw new ConfigLoadError(`Configuration file not found: ${resolvedPath}`, resolvedPath);
4741
+ }
4742
+ try {
4743
+ delete require.cache[require.resolve(resolvedPath)];
4744
+ if (resolvedPath.endsWith(".ts")) {
4745
+ require("ts-node/register");
4746
+ }
4747
+ const configModule = require(resolvedPath);
4748
+ const config = configModule.default || configModule.config || configModule;
4749
+ if (!config.input || !config.output) {
4750
+ throw new ConfigValidationError([
4751
+ 'Configuration must include "input" and "output" properties'
4752
+ ]);
4753
+ }
4754
+ const configDir = path14.dirname(resolvedPath);
4755
+ if (!isUrl(config.input) && !path14.isAbsolute(config.input)) {
4756
+ config.input = path14.resolve(configDir, config.input);
4757
+ }
4758
+ if (!path14.isAbsolute(config.output)) {
4759
+ config.output = path14.resolve(configDir, config.output);
4760
+ }
4761
+ return config;
4762
+ } catch (error) {
4763
+ if (error instanceof NgOpenApiError) {
4764
+ throw error;
4765
+ }
4766
+ throw new ConfigLoadError(`Failed to load configuration file: ${resolvedPath}`, resolvedPath, error);
4767
+ }
4768
+ }
4769
+ __name(loadConfigFile, "loadConfigFile");
4770
+
3692
4771
  // src/lib/cli.ts
3693
4772
  var program = new import_commander.Command();
3694
4773
  function createConsoleReporter(config) {
@@ -3721,43 +4800,22 @@ async function runGeneration(config) {
3721
4800
  const inputType = isUrl(config.input) ? "URL" : "file";
3722
4801
  const sourceInfo = `from ${inputType}: ${config.input}`;
3723
4802
  const clientPrefix = result.client ? `${result.client} ` : "";
3724
- console.log(`\u{1F389} ${clientPrefix}Generation completed successfully ${sourceInfo} -> ${config.output}`);
4803
+ const outcome = result.warnings.length === 0 ? "completed successfully" : `completed with ${countWarnings(result.warnings.length)}`;
4804
+ console.log(`\u{1F389} ${clientPrefix}Generation ${outcome} ${sourceInfo} -> ${config.output}`);
4805
+ return result.warnings.length;
3725
4806
  }
3726
4807
  __name(runGeneration, "runGeneration");
3727
- async function loadConfigFile(configPath) {
3728
- const resolvedPath = path14.resolve(configPath);
3729
- if (!fs3.existsSync(resolvedPath)) {
3730
- throw new Error(`Configuration file not found: ${resolvedPath}`);
3731
- }
3732
- delete require.cache[require.resolve(resolvedPath)];
3733
- try {
3734
- if (resolvedPath.endsWith(".ts")) {
3735
- require("ts-node/register");
3736
- }
3737
- const configModule = require(resolvedPath);
3738
- const config = configModule.default || configModule.config || configModule;
3739
- if (!config.input || !config.output) {
3740
- throw new Error('Configuration must include "input" and "output" properties');
3741
- }
3742
- const configDir = path14.dirname(resolvedPath);
3743
- if (!isUrl(config.input) && !path14.isAbsolute(config.input)) {
3744
- config.input = path14.resolve(configDir, config.input);
3745
- }
3746
- if (!path14.isAbsolute(config.output)) {
3747
- config.output = path14.resolve(configDir, config.output);
3748
- }
3749
- return config;
3750
- } catch (error) {
3751
- throw new Error(`Failed to load configuration file: ${error instanceof Error ? error.message : error}`);
3752
- }
4808
+ function countWarnings(count) {
4809
+ return `${count} warning${count === 1 ? "" : "s"}`;
3753
4810
  }
3754
- __name(loadConfigFile, "loadConfigFile");
4811
+ __name(countWarnings, "countWarnings");
3755
4812
  async function generateFromOptions(options) {
3756
4813
  const timestamp = (/* @__PURE__ */ new Date()).getTime();
4814
+ let warningCount = 0;
3757
4815
  try {
3758
4816
  if (options.config) {
3759
4817
  const config = await loadConfigFile(options.config);
3760
- await runGeneration(config);
4818
+ warningCount = await runGeneration(config);
3761
4819
  } else if (options.input) {
3762
4820
  const config = {
3763
4821
  input: options.input,
@@ -3771,16 +4829,30 @@ async function generateFromOptions(options) {
3771
4829
  generateServices: !options.typesOnly
3772
4830
  }
3773
4831
  };
3774
- await runGeneration(config);
4832
+ warningCount = await runGeneration(config);
3775
4833
  } else {
3776
4834
  console.error("Error: Either --config or --input option is required");
3777
4835
  program.help({
3778
4836
  error: true
3779
4837
  });
3780
4838
  }
3781
- console.log("\u2728 Generation completed successfully!");
4839
+ if (warningCount === 0) {
4840
+ console.log("\u2728 Generation completed successfully!");
4841
+ } else {
4842
+ console.log(`\u2728 Generation completed with ${countWarnings(warningCount)} \u2014 see above; each describes spec content that was not generated as written.`);
4843
+ }
3782
4844
  } catch (error) {
3783
4845
  console.error("\u274C Generation failed:", error instanceof Error ? error.message : error);
4846
+ const MAX_CAUSES = 3;
4847
+ let cause = error?.cause;
4848
+ for (let depth = 0; cause !== void 0 && cause !== null; depth++) {
4849
+ if (depth === MAX_CAUSES) {
4850
+ console.error(" \u2026 further causes omitted");
4851
+ break;
4852
+ }
4853
+ console.error(" caused by:", cause instanceof Error ? cause.message : cause);
4854
+ cause = cause.cause;
4855
+ }
3784
4856
  if (error instanceof SpecLoadError && isUrl(error.source)) {
3785
4857
  console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
3786
4858
  console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");