@stackwright-pro/openapi 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -107,8 +107,8 @@ var require_compat_dotall_s_transform = __commonJS({
107
107
  this._hasUFlag = ast.flags.includes("u");
108
108
  return true;
109
109
  },
110
- Char: function Char(path2) {
111
- var node = path2.node;
110
+ Char: function Char(path3) {
111
+ var node = path3.node;
112
112
  if (node.kind !== "meta" || node.value !== ".") {
113
113
  return;
114
114
  }
@@ -118,7 +118,7 @@ var require_compat_dotall_s_transform = __commonJS({
118
118
  toValue = "\\u{10FFFF}";
119
119
  toSymbol = "\u{10FFFF}";
120
120
  }
121
- path2.replace({
121
+ path3.replace({
122
122
  type: "CharacterClass",
123
123
  expressions: [{
124
124
  type: "ClassRange",
@@ -163,8 +163,8 @@ var require_compat_named_capturing_groups_transform = __commonJS({
163
163
  getExtra: function getExtra() {
164
164
  return this._groupNames;
165
165
  },
166
- Group: function Group(path2) {
167
- var node = path2.node;
166
+ Group: function Group(path3) {
167
+ var node = path3.node;
168
168
  if (!node.name) {
169
169
  return;
170
170
  }
@@ -172,8 +172,8 @@ var require_compat_named_capturing_groups_transform = __commonJS({
172
172
  delete node.name;
173
173
  delete node.nameRaw;
174
174
  },
175
- Backreference: function Backreference(path2) {
176
- var node = path2.node;
175
+ Backreference: function Backreference(path3) {
176
+ var node = path3.node;
177
177
  if (node.kind !== "name") {
178
178
  return;
179
179
  }
@@ -2015,8 +2015,8 @@ var require_node_path = __commonJS({
2015
2015
  value: function _rebuildIndex(parent, property) {
2016
2016
  var parentPath = NodePath2.getForNode(parent);
2017
2017
  for (var i = 0; i < parent[property].length; i++) {
2018
- var path2 = NodePath2.getForNode(parent[property][i], parentPath, property, i);
2019
- path2.index = i;
2018
+ var path3 = NodePath2.getForNode(parent[property][i], parentPath, property, i);
2019
+ path3.index = i;
2020
2020
  }
2021
2021
  }
2022
2022
  /**
@@ -2088,8 +2088,8 @@ var require_node_path = __commonJS({
2088
2088
  */
2089
2089
  }, {
2090
2090
  key: "hasEqualSource",
2091
- value: function hasEqualSource(path2) {
2092
- return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(path2.node, jsonSkipLoc);
2091
+ value: function hasEqualSource(path3) {
2092
+ return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(path3.node, jsonSkipLoc);
2093
2093
  }
2094
2094
  /**
2095
2095
  * JSON-encodes a node skipping location.
@@ -2140,18 +2140,18 @@ var require_node_path = __commonJS({
2140
2140
  if (!NodePath2.registry.has(node)) {
2141
2141
  NodePath2.registry.set(node, new NodePath2(node, parentPath, prop, index == -1 ? null : index));
2142
2142
  }
2143
- var path2 = NodePath2.registry.get(node);
2143
+ var path3 = NodePath2.registry.get(node);
2144
2144
  if (parentPath !== null) {
2145
- path2.parentPath = parentPath;
2146
- path2.parent = path2.parentPath.node;
2145
+ path3.parentPath = parentPath;
2146
+ path3.parent = path3.parentPath.node;
2147
2147
  }
2148
2148
  if (prop !== null) {
2149
- path2.property = prop;
2149
+ path3.property = prop;
2150
2150
  }
2151
2151
  if (index >= 0) {
2152
- path2.index = index;
2152
+ path3.index = index;
2153
2153
  }
2154
- return path2;
2154
+ return path3;
2155
2155
  }
2156
2156
  /**
2157
2157
  * Initializes the NodePath registry. The registry is a map from
@@ -2631,8 +2631,8 @@ var require_char_surrogate_pair_to_single_unicode_transform = __commonJS({
2631
2631
  shouldRun: function shouldRun(ast) {
2632
2632
  return ast.flags.includes("u");
2633
2633
  },
2634
- Char: function Char(path2) {
2635
- var node = path2.node;
2634
+ Char: function Char(path3) {
2635
+ var node = path3.node;
2636
2636
  if (node.kind !== "unicode" || !node.isSurrogatePair || isNaN(node.codePoint)) {
2637
2637
  return;
2638
2638
  }
@@ -2654,8 +2654,8 @@ var require_char_code_to_simple_char_transform = __commonJS({
2654
2654
  var DIGIT_0_CP = "0".codePointAt(0);
2655
2655
  var DIGIT_9_CP = "9".codePointAt(0);
2656
2656
  module.exports = {
2657
- Char: function Char(path2) {
2658
- var node = path2.node, parent = path2.parent;
2657
+ Char: function Char(path3) {
2658
+ var node = path3.node, parent = path3.parent;
2659
2659
  if (isNaN(node.codePoint) || node.kind === "simple") {
2660
2660
  return;
2661
2661
  }
@@ -2678,7 +2678,7 @@ var require_char_code_to_simple_char_transform = __commonJS({
2678
2678
  if (needsEscape(symbol, parent.type)) {
2679
2679
  newChar.escaped = true;
2680
2680
  }
2681
- path2.replace(newChar);
2681
+ path3.replace(newChar);
2682
2682
  }
2683
2683
  };
2684
2684
  function isSimpleRange(classRange) {
@@ -2713,8 +2713,8 @@ var require_char_case_insensitive_lowercase_transform = __commonJS({
2713
2713
  shouldRun: function shouldRun(ast) {
2714
2714
  return ast.flags.includes("i");
2715
2715
  },
2716
- Char: function Char(path2) {
2717
- var node = path2.node, parent = path2.parent;
2716
+ Char: function Char(path3) {
2717
+ var node = path3.node, parent = path3.parent;
2718
2718
  if (isNaN(node.codePoint)) {
2719
2719
  return;
2720
2720
  }
@@ -2779,11 +2779,11 @@ var require_char_class_remove_duplicates_transform = __commonJS({
2779
2779
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-remove-duplicates-transform.js"(exports, module) {
2780
2780
  "use strict";
2781
2781
  module.exports = {
2782
- CharacterClass: function CharacterClass(path2) {
2783
- var node = path2.node;
2782
+ CharacterClass: function CharacterClass(path3) {
2783
+ var node = path3.node;
2784
2784
  var sources = {};
2785
2785
  for (var i = 0; i < node.expressions.length; i++) {
2786
- var childPath = path2.getChild(i);
2786
+ var childPath = path3.getChild(i);
2787
2787
  var source = childPath.jsonEncode();
2788
2788
  if (sources.hasOwnProperty(source)) {
2789
2789
  childPath.remove();
@@ -2864,17 +2864,17 @@ var require_quantifiers_merge_transform = __commonJS({
2864
2864
  var _require = require_utils();
2865
2865
  var increaseQuantifierByOne = _require.increaseQuantifierByOne;
2866
2866
  module.exports = {
2867
- Repetition: function Repetition(path2) {
2868
- var node = path2.node, parent = path2.parent;
2869
- if (parent.type !== "Alternative" || !path2.index) {
2867
+ Repetition: function Repetition(path3) {
2868
+ var node = path3.node, parent = path3.parent;
2869
+ if (parent.type !== "Alternative" || !path3.index) {
2870
2870
  return;
2871
2871
  }
2872
- var previousSibling = path2.getPreviousSibling();
2872
+ var previousSibling = path3.getPreviousSibling();
2873
2873
  if (!previousSibling) {
2874
2874
  return;
2875
2875
  }
2876
2876
  if (previousSibling.node.type === "Repetition") {
2877
- if (!previousSibling.getChild().hasEqualSource(path2.getChild())) {
2877
+ if (!previousSibling.getChild().hasEqualSource(path3.getChild())) {
2878
2878
  return;
2879
2879
  }
2880
2880
  var _extractFromTo = extractFromTo(previousSibling.node.quantifier), previousSiblingFrom = _extractFromTo.from, previousSiblingTo = _extractFromTo.to;
@@ -2894,7 +2894,7 @@ var require_quantifiers_merge_transform = __commonJS({
2894
2894
  }
2895
2895
  previousSibling.remove();
2896
2896
  } else {
2897
- if (!previousSibling.hasEqualSource(path2.getChild())) {
2897
+ if (!previousSibling.hasEqualSource(path3.getChild())) {
2898
2898
  return;
2899
2899
  }
2900
2900
  increaseQuantifierByOne(node.quantifier);
@@ -2930,38 +2930,38 @@ var require_quantifier_range_to_symbol_transform = __commonJS({
2930
2930
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/quantifier-range-to-symbol-transform.js"(exports, module) {
2931
2931
  "use strict";
2932
2932
  module.exports = {
2933
- Quantifier: function Quantifier(path2) {
2934
- var node = path2.node;
2933
+ Quantifier: function Quantifier(path3) {
2934
+ var node = path3.node;
2935
2935
  if (node.kind !== "Range") {
2936
2936
  return;
2937
2937
  }
2938
- rewriteOpenZero(path2);
2939
- rewriteOpenOne(path2);
2940
- rewriteExactOne(path2);
2938
+ rewriteOpenZero(path3);
2939
+ rewriteOpenOne(path3);
2940
+ rewriteExactOne(path3);
2941
2941
  }
2942
2942
  };
2943
- function rewriteOpenZero(path2) {
2944
- var node = path2.node;
2943
+ function rewriteOpenZero(path3) {
2944
+ var node = path3.node;
2945
2945
  if (node.from !== 0 || node.to) {
2946
2946
  return;
2947
2947
  }
2948
2948
  node.kind = "*";
2949
2949
  delete node.from;
2950
2950
  }
2951
- function rewriteOpenOne(path2) {
2952
- var node = path2.node;
2951
+ function rewriteOpenOne(path3) {
2952
+ var node = path3.node;
2953
2953
  if (node.from !== 1 || node.to) {
2954
2954
  return;
2955
2955
  }
2956
2956
  node.kind = "+";
2957
2957
  delete node.from;
2958
2958
  }
2959
- function rewriteExactOne(path2) {
2960
- var node = path2.node;
2959
+ function rewriteExactOne(path3) {
2960
+ var node = path3.node;
2961
2961
  if (node.from !== 1 || node.to !== 1) {
2962
2962
  return;
2963
2963
  }
2964
- path2.parentPath.replace(path2.parentPath.node.expression);
2964
+ path3.parentPath.replace(path3.parentPath.node.expression);
2965
2965
  }
2966
2966
  }
2967
2967
  });
@@ -2971,13 +2971,13 @@ var require_char_class_classranges_to_chars_transform = __commonJS({
2971
2971
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-classranges-to-chars-transform.js"(exports, module) {
2972
2972
  "use strict";
2973
2973
  module.exports = {
2974
- ClassRange: function ClassRange(path2) {
2975
- var node = path2.node;
2974
+ ClassRange: function ClassRange(path3) {
2975
+ var node = path3.node;
2976
2976
  if (node.from.codePoint === node.to.codePoint) {
2977
- path2.replace(node.from);
2977
+ path3.replace(node.from);
2978
2978
  } else if (node.from.codePoint === node.to.codePoint - 1) {
2979
- path2.getParent().insertChildAt(node.to, path2.index + 1);
2980
- path2.replace(node.from);
2979
+ path3.getParent().insertChildAt(node.to, path3.index + 1);
2980
+ path3.replace(node.from);
2981
2981
  }
2982
2982
  }
2983
2983
  };
@@ -3005,17 +3005,17 @@ var require_char_class_to_meta_transform = __commonJS({
3005
3005
  this._hasIFlag = ast.flags.includes("i");
3006
3006
  this._hasUFlag = ast.flags.includes("u");
3007
3007
  },
3008
- CharacterClass: function CharacterClass(path2) {
3009
- rewriteNumberRanges(path2);
3010
- rewriteWordRanges(path2, this._hasIFlag, this._hasUFlag);
3011
- rewriteWhitespaceRanges(path2);
3008
+ CharacterClass: function CharacterClass(path3) {
3009
+ rewriteNumberRanges(path3);
3010
+ rewriteWordRanges(path3, this._hasIFlag, this._hasUFlag);
3011
+ rewriteWhitespaceRanges(path3);
3012
3012
  }
3013
3013
  };
3014
- function rewriteNumberRanges(path2) {
3015
- var node = path2.node;
3014
+ function rewriteNumberRanges(path3) {
3015
+ var node = path3.node;
3016
3016
  node.expressions.forEach(function(expression, i) {
3017
3017
  if (isFullNumberRange(expression)) {
3018
- path2.getChild(i).replace({
3018
+ path3.getChild(i).replace({
3019
3019
  type: "Char",
3020
3020
  value: "\\d",
3021
3021
  kind: "meta"
@@ -3023,8 +3023,8 @@ var require_char_class_to_meta_transform = __commonJS({
3023
3023
  }
3024
3024
  });
3025
3025
  }
3026
- function rewriteWordRanges(path2, hasIFlag, hasUFlag) {
3027
- var node = path2.node;
3026
+ function rewriteWordRanges(path3, hasIFlag, hasUFlag) {
3027
+ var node = path3.node;
3028
3028
  var numberPath = null;
3029
3029
  var lowerCasePath = null;
3030
3030
  var upperCasePath = null;
@@ -3033,17 +3033,17 @@ var require_char_class_to_meta_transform = __commonJS({
3033
3033
  var u212aPath = null;
3034
3034
  node.expressions.forEach(function(expression, i) {
3035
3035
  if (isMetaChar(expression, "\\d")) {
3036
- numberPath = path2.getChild(i);
3036
+ numberPath = path3.getChild(i);
3037
3037
  } else if (isLowerCaseRange(expression)) {
3038
- lowerCasePath = path2.getChild(i);
3038
+ lowerCasePath = path3.getChild(i);
3039
3039
  } else if (isUpperCaseRange(expression)) {
3040
- upperCasePath = path2.getChild(i);
3040
+ upperCasePath = path3.getChild(i);
3041
3041
  } else if (isUnderscore(expression)) {
3042
- underscorePath = path2.getChild(i);
3042
+ underscorePath = path3.getChild(i);
3043
3043
  } else if (hasIFlag && hasUFlag && isCodePoint(expression, 383)) {
3044
- u017fPath = path2.getChild(i);
3044
+ u017fPath = path3.getChild(i);
3045
3045
  } else if (hasIFlag && hasUFlag && isCodePoint(expression, 8490)) {
3046
- u212aPath = path2.getChild(i);
3046
+ u212aPath = path3.getChild(i);
3047
3047
  }
3048
3048
  });
3049
3049
  if (numberPath && (lowerCasePath && upperCasePath || hasIFlag && (lowerCasePath || upperCasePath)) && underscorePath && (!hasUFlag || !hasIFlag || u017fPath && u212aPath)) {
@@ -3080,8 +3080,8 @@ var require_char_class_to_meta_transform = __commonJS({
3080
3080
  })), [function(node) {
3081
3081
  return node.type === "ClassRange" && isCodePoint(node.from, 8192) && isCodePoint(node.to, 8202);
3082
3082
  }]);
3083
- function rewriteWhitespaceRanges(path2) {
3084
- var node = path2.node;
3083
+ function rewriteWhitespaceRanges(path3) {
3084
+ var node = path3.node;
3085
3085
  if (node.expressions.length < whitespaceRangeTests.length || !whitespaceRangeTests.every(function(test) {
3086
3086
  return node.expressions.some(function(expression) {
3087
3087
  return test(expression);
@@ -3098,9 +3098,9 @@ var require_char_class_to_meta_transform = __commonJS({
3098
3098
  node.expressions.map(function(expression, i) {
3099
3099
  return whitespaceRangeTests.some(function(test) {
3100
3100
  return test(expression);
3101
- }) ? path2.getChild(i) : void 0;
3102
- }).filter(Boolean).forEach(function(path3) {
3103
- return path3.remove();
3101
+ }) ? path3.getChild(i) : void 0;
3102
+ }).filter(Boolean).forEach(function(path4) {
3103
+ return path4.remove();
3104
3104
  });
3105
3105
  }
3106
3106
  function isFullNumberRange(node) {
@@ -3133,9 +3133,9 @@ var require_char_class_to_single_char_transform = __commonJS({
3133
3133
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-to-single-char-transform.js"(exports, module) {
3134
3134
  "use strict";
3135
3135
  module.exports = {
3136
- CharacterClass: function CharacterClass(path2) {
3137
- var node = path2.node;
3138
- if (node.expressions.length !== 1 || !hasAppropriateSiblings(path2) || !isAppropriateChar(node.expressions[0])) {
3136
+ CharacterClass: function CharacterClass(path3) {
3137
+ var node = path3.node;
3138
+ if (node.expressions.length !== 1 || !hasAppropriateSiblings(path3) || !isAppropriateChar(node.expressions[0])) {
3139
3139
  return;
3140
3140
  }
3141
3141
  var _node$expressions$ = node.expressions[0], value = _node$expressions$.value, kind = _node$expressions$.kind, escaped = _node$expressions$.escaped;
@@ -3145,7 +3145,7 @@ var require_char_class_to_single_char_transform = __commonJS({
3145
3145
  }
3146
3146
  value = getInverseMeta(value);
3147
3147
  }
3148
- path2.replace({
3148
+ path3.replace({
3149
3149
  type: "Char",
3150
3150
  value,
3151
3151
  kind,
@@ -3164,8 +3164,8 @@ var require_char_class_to_single_char_transform = __commonJS({
3164
3164
  function getInverseMeta(value) {
3165
3165
  return /[dws]/.test(value) ? value.toUpperCase() : value.toLowerCase();
3166
3166
  }
3167
- function hasAppropriateSiblings(path2) {
3168
- var parent = path2.parent, index = path2.index;
3167
+ function hasAppropriateSiblings(path3) {
3168
+ var parent = path3.parent, index = path3.index;
3169
3169
  if (parent.type !== "Alternative") {
3170
3170
  return true;
3171
3171
  }
@@ -3196,18 +3196,18 @@ var require_char_escape_unescape_transform = __commonJS({
3196
3196
  init: function init(ast) {
3197
3197
  this._hasXFlag = ast.flags.includes("x");
3198
3198
  },
3199
- Char: function Char(path2) {
3200
- var node = path2.node;
3199
+ Char: function Char(path3) {
3200
+ var node = path3.node;
3201
3201
  if (!node.escaped) {
3202
3202
  return;
3203
3203
  }
3204
- if (shouldUnescape(path2, this._hasXFlag)) {
3204
+ if (shouldUnescape(path3, this._hasXFlag)) {
3205
3205
  delete node.escaped;
3206
3206
  }
3207
3207
  }
3208
3208
  };
3209
- function shouldUnescape(path2, hasXFlag) {
3210
- var value = path2.node.value, index = path2.index, parent = path2.parent;
3209
+ function shouldUnescape(path3, hasXFlag) {
3210
+ var value = path3.node.value, index = path3.index, parent = path3.parent;
3211
3211
  if (parent.type !== "CharacterClass" && parent.type !== "ClassRange") {
3212
3212
  return !preservesEscape(value, index, parent, hasXFlag);
3213
3213
  }
@@ -3296,8 +3296,8 @@ var require_char_class_classranges_merge_transform = __commonJS({
3296
3296
  init: function init(ast) {
3297
3297
  this._hasIUFlags = ast.flags.includes("i") && ast.flags.includes("u");
3298
3298
  },
3299
- CharacterClass: function CharacterClass(path2) {
3300
- var node = path2.node;
3299
+ CharacterClass: function CharacterClass(path3) {
3300
+ var node = path3.node;
3301
3301
  var expressions = node.expressions;
3302
3302
  var metas = [];
3303
3303
  expressions.forEach(function(expression2) {
@@ -3515,8 +3515,8 @@ var require_disjunction_remove_duplicates_transform = __commonJS({
3515
3515
  var disjunctionToList = _require.disjunctionToList;
3516
3516
  var listToDisjunction = _require.listToDisjunction;
3517
3517
  module.exports = {
3518
- Disjunction: function Disjunction(path2) {
3519
- var node = path2.node;
3518
+ Disjunction: function Disjunction(path3) {
3519
+ var node = path3.node;
3520
3520
  var uniqueNodesMap = {};
3521
3521
  var parts = disjunctionToList(node).filter(function(part) {
3522
3522
  var encoded = part ? NodePath.getForNode(part).jsonEncode() : "null";
@@ -3526,7 +3526,7 @@ var require_disjunction_remove_duplicates_transform = __commonJS({
3526
3526
  uniqueNodesMap[encoded] = part;
3527
3527
  return true;
3528
3528
  });
3529
- path2.replace(listToDisjunction(parts));
3529
+ path3.replace(listToDisjunction(parts));
3530
3530
  }
3531
3531
  };
3532
3532
  }
@@ -3537,8 +3537,8 @@ var require_group_single_chars_to_char_class = __commonJS({
3537
3537
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/group-single-chars-to-char-class.js"(exports, module) {
3538
3538
  "use strict";
3539
3539
  module.exports = {
3540
- Disjunction: function Disjunction(path2) {
3541
- var node = path2.node, parent = path2.parent;
3540
+ Disjunction: function Disjunction(path3) {
3541
+ var node = path3.node, parent = path3.parent;
3542
3542
  if (!handlers[parent.type]) {
3543
3543
  return;
3544
3544
  }
@@ -3552,20 +3552,20 @@ var require_group_single_chars_to_char_class = __commonJS({
3552
3552
  return charset.get(key);
3553
3553
  })
3554
3554
  };
3555
- handlers[parent.type](path2.getParent(), characterClass);
3555
+ handlers[parent.type](path3.getParent(), characterClass);
3556
3556
  }
3557
3557
  };
3558
3558
  var handlers = {
3559
- RegExp: function RegExp2(path2, characterClass) {
3560
- var node = path2.node;
3559
+ RegExp: function RegExp2(path3, characterClass) {
3560
+ var node = path3.node;
3561
3561
  node.body = characterClass;
3562
3562
  },
3563
- Group: function Group(path2, characterClass) {
3564
- var node = path2.node;
3563
+ Group: function Group(path3, characterClass) {
3564
+ var node = path3.node;
3565
3565
  if (node.capturing) {
3566
3566
  node.expression = characterClass;
3567
3567
  } else {
3568
- path2.replace(characterClass);
3568
+ path3.replace(characterClass);
3569
3569
  }
3570
3570
  }
3571
3571
  };
@@ -3599,16 +3599,16 @@ var require_remove_empty_group_transform = __commonJS({
3599
3599
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/remove-empty-group-transform.js"(exports, module) {
3600
3600
  "use strict";
3601
3601
  module.exports = {
3602
- Group: function Group(path2) {
3603
- var node = path2.node, parent = path2.parent;
3604
- var childPath = path2.getChild();
3602
+ Group: function Group(path3) {
3603
+ var node = path3.node, parent = path3.parent;
3604
+ var childPath = path3.getChild();
3605
3605
  if (node.capturing || childPath) {
3606
3606
  return;
3607
3607
  }
3608
3608
  if (parent.type === "Repetition") {
3609
- path2.getParent().replace(node);
3609
+ path3.getParent().replace(node);
3610
3610
  } else if (parent.type !== "RegExp") {
3611
- path2.remove();
3611
+ path3.remove();
3612
3612
  }
3613
3613
  }
3614
3614
  };
@@ -3630,13 +3630,13 @@ var require_ungroup_transform = __commonJS({
3630
3630
  }
3631
3631
  }
3632
3632
  module.exports = {
3633
- Group: function Group(path2) {
3634
- var node = path2.node, parent = path2.parent;
3635
- var childPath = path2.getChild();
3633
+ Group: function Group(path3) {
3634
+ var node = path3.node, parent = path3.parent;
3635
+ var childPath = path3.getChild();
3636
3636
  if (node.capturing || !childPath) {
3637
3637
  return;
3638
3638
  }
3639
- if (!hasAppropriateSiblings(path2)) {
3639
+ if (!hasAppropriateSiblings(path3)) {
3640
3640
  return;
3641
3641
  }
3642
3642
  if (childPath.node.type === "Disjunction" && parent.type !== "RegExp") {
@@ -3646,20 +3646,20 @@ var require_ungroup_transform = __commonJS({
3646
3646
  return;
3647
3647
  }
3648
3648
  if (childPath.node.type === "Alternative") {
3649
- var parentPath = path2.getParent();
3649
+ var parentPath = path3.getParent();
3650
3650
  if (parentPath.node.type === "Alternative") {
3651
3651
  parentPath.replace({
3652
3652
  type: "Alternative",
3653
- expressions: [].concat(_toConsumableArray(parent.expressions.slice(0, path2.index)), _toConsumableArray(childPath.node.expressions), _toConsumableArray(parent.expressions.slice(path2.index + 1)))
3653
+ expressions: [].concat(_toConsumableArray(parent.expressions.slice(0, path3.index)), _toConsumableArray(childPath.node.expressions), _toConsumableArray(parent.expressions.slice(path3.index + 1)))
3654
3654
  });
3655
3655
  }
3656
3656
  } else {
3657
- path2.replace(childPath.node);
3657
+ path3.replace(childPath.node);
3658
3658
  }
3659
3659
  }
3660
3660
  };
3661
- function hasAppropriateSiblings(path2) {
3662
- var parent = path2.parent, index = path2.index;
3661
+ function hasAppropriateSiblings(path3) {
3662
+ var parent = path3.parent, index = path3.index;
3663
3663
  if (parent.type !== "Alternative") {
3664
3664
  return true;
3665
3665
  }
@@ -3696,22 +3696,22 @@ var require_combine_repeating_patterns_transform = __commonJS({
3696
3696
  var _require = require_utils();
3697
3697
  var increaseQuantifierByOne = _require.increaseQuantifierByOne;
3698
3698
  module.exports = {
3699
- Alternative: function Alternative(path2) {
3700
- var node = path2.node;
3699
+ Alternative: function Alternative(path3) {
3700
+ var node = path3.node;
3701
3701
  var index = 1;
3702
3702
  while (index < node.expressions.length) {
3703
- var child = path2.getChild(index);
3704
- index = Math.max(1, combineRepeatingPatternLeft(path2, child, index));
3703
+ var child = path3.getChild(index);
3704
+ index = Math.max(1, combineRepeatingPatternLeft(path3, child, index));
3705
3705
  if (index >= node.expressions.length) {
3706
3706
  break;
3707
3707
  }
3708
- child = path2.getChild(index);
3709
- index = Math.max(1, combineWithPreviousRepetition(path2, child, index));
3708
+ child = path3.getChild(index);
3709
+ index = Math.max(1, combineWithPreviousRepetition(path3, child, index));
3710
3710
  if (index >= node.expressions.length) {
3711
3711
  break;
3712
3712
  }
3713
- child = path2.getChild(index);
3714
- index = Math.max(1, combineRepetitionWithPrevious(path2, child, index));
3713
+ child = path3.getChild(index);
3714
+ index = Math.max(1, combineRepetitionWithPrevious(path3, child, index));
3715
3715
  index++;
3716
3716
  }
3717
3717
  }
@@ -5842,8 +5842,8 @@ var SchemaResolver = class {
5842
5842
  * @param responseCode - Response code (default: '200')
5843
5843
  * @returns Schema object or undefined
5844
5844
  */
5845
- getResponseSchema(path2, method, responseCode = "200") {
5846
- const pathItem = this.document.paths?.[path2];
5845
+ getResponseSchema(path3, method, responseCode = "200") {
5846
+ const pathItem = this.document.paths?.[path3];
5847
5847
  if (!pathItem) return void 0;
5848
5848
  const operation = pathItem[method];
5849
5849
  if (!operation?.responses) return void 0;
@@ -5869,14 +5869,14 @@ var SchemaResolver = class {
5869
5869
  */
5870
5870
  getAllEndpoints() {
5871
5871
  const endpoints = [];
5872
- for (const [path2, pathItem] of Object.entries(this.document.paths || {})) {
5872
+ for (const [path3, pathItem] of Object.entries(this.document.paths || {})) {
5873
5873
  if (!pathItem) continue;
5874
5874
  const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
5875
5875
  for (const method of methods) {
5876
5876
  const operation = pathItem[method];
5877
5877
  if (operation) {
5878
5878
  endpoints.push({
5879
- path: path2,
5879
+ path: path3,
5880
5880
  method,
5881
5881
  operationId: operation.operationId,
5882
5882
  summary: operation.summary,
@@ -5900,20 +5900,31 @@ var ZodSchemaGenerator = class {
5900
5900
  * @returns Zod schema code as string
5901
5901
  */
5902
5902
  generate(schema, options = {}) {
5903
- const { schemaName = "GeneratedSchema" } = options;
5903
+ const { schemaName = "GeneratedSchema", bare = false } = options;
5904
5904
  const zodSchemaCode = this.schemaToZod(schema);
5905
- return `import { z } from 'zod';
5906
- import isSafe from 'safe-regex';
5907
-
5908
- export const ${schemaName} = ${zodSchemaCode};
5905
+ const body = `export const ${schemaName} = ${zodSchemaCode};
5909
5906
 
5910
5907
  export type ${schemaName}Type = z.infer<typeof ${schemaName}>;
5911
5908
  `;
5909
+ if (bare) {
5910
+ return body;
5911
+ }
5912
+ return `import { z } from 'zod';
5913
+ import isSafe from 'safe-regex';
5914
+
5915
+ ${body}`;
5912
5916
  }
5913
5917
  /**
5914
5918
  * Convert OpenAPI schema to Zod schema code
5915
5919
  */
5916
5920
  schemaToZod(schema, depth = 0) {
5921
+ if ("$ref" in schema && schema.$ref) {
5922
+ const refPath = schema.$ref;
5923
+ console.warn(
5924
+ `\u26A0\uFE0F Unresolved $ref encountered: ${refPath} \u2014 generating z.unknown()`
5925
+ );
5926
+ return `z.unknown() /* unresolved $ref: ${refPath} */`;
5927
+ }
5917
5928
  const isNullable = "nullable" in schema && schema.nullable === true;
5918
5929
  let baseSchema = this.schemaTypeToZod(schema);
5919
5930
  if (schema.description) {
@@ -5942,6 +5953,12 @@ export type ${schemaName}Type = z.infer<typeof ${schemaName}>;
5942
5953
  return this.handleComposition(schema.oneOf, "discriminatedUnion");
5943
5954
  }
5944
5955
  const type = schema.type;
5956
+ if (!type && schema.properties) {
5957
+ return this.objectToZod(schema);
5958
+ }
5959
+ if (!type && schema.additionalProperties !== void 0) {
5960
+ return this.objectToZod(schema);
5961
+ }
5945
5962
  switch (type) {
5946
5963
  case "string":
5947
5964
  return this.stringToZod(schema);
@@ -6196,7 +6213,8 @@ var CollectionProviderGenerator = class {
6196
6213
  const {
6197
6214
  providerName = this.generateProviderName(config),
6198
6215
  baseUrl = this.getBaseUrl(),
6199
- auth
6216
+ auth,
6217
+ bare = false
6200
6218
  } = options;
6201
6219
  const { endpoint, slugField, method = "get", name } = config;
6202
6220
  const collectionName = name || this.sanitizePath(endpoint);
@@ -6206,9 +6224,7 @@ var CollectionProviderGenerator = class {
6206
6224
  throw new Error(`No response schema found for ${method.toUpperCase()} ${endpoint}`);
6207
6225
  }
6208
6226
  const isArray = schema.type === "array";
6209
- const itemSchema = isArray ? schema.items : schema;
6210
6227
  const schemaName = `${this.capitalize(collectionName)}Schema`;
6211
- const typeName = `${this.capitalize(collectionName)}`;
6212
6228
  return this.generateProviderCode({
6213
6229
  providerName,
6214
6230
  collectionName,
@@ -6218,8 +6234,8 @@ var CollectionProviderGenerator = class {
6218
6234
  baseUrl,
6219
6235
  auth,
6220
6236
  schemaName,
6221
- typeName,
6222
- isArray
6237
+ isArray,
6238
+ bare
6223
6239
  });
6224
6240
  }
6225
6241
  /**
@@ -6235,14 +6251,17 @@ var CollectionProviderGenerator = class {
6235
6251
  baseUrl,
6236
6252
  auth,
6237
6253
  schemaName,
6238
- typeName,
6239
- isArray
6254
+ isArray,
6255
+ bare
6240
6256
  } = params;
6241
6257
  const authHeader = this.generateAuthHeader(auth);
6242
- return `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
6243
- import { ${schemaName} } from './schemas';
6258
+ const arraySchemaName = `${schemaName.replace(/Schema$/, "")}ArraySchema`;
6259
+ const validationSchema = isArray ? arraySchemaName : schemaName;
6260
+ const imports = bare ? "" : `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
6261
+ import { ${isArray ? arraySchemaName : schemaName} } from './schemas';
6244
6262
 
6245
- /**
6263
+ `;
6264
+ return `${imports}/**
6246
6265
  * CollectionProvider for ${collectionName}
6247
6266
  *
6248
6267
  * Generated from OpenAPI endpoint: ${method.toUpperCase()} ${endpoint}
@@ -6293,7 +6312,7 @@ export class ${providerName} implements CollectionProvider {
6293
6312
  const data = await response.json();
6294
6313
 
6295
6314
  // Validate response with Zod schema
6296
- const validated = ${isArray ? schemaName : `[${schemaName}]`}.parse(data);
6315
+ const validated = ${validationSchema}.parse(data);
6297
6316
 
6298
6317
  // Convert to CollectionItem format
6299
6318
  return ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionItem(item));
@@ -6404,8 +6423,8 @@ export class ${providerName} implements CollectionProvider {
6404
6423
  /**
6405
6424
  * Sanitize path to valid identifier
6406
6425
  */
6407
- sanitizePath(path2) {
6408
- return path2.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
6426
+ sanitizePath(path3) {
6427
+ return path3.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
6409
6428
  }
6410
6429
  /**
6411
6430
  * Capitalize string
@@ -6430,10 +6449,12 @@ var ClientGenerator = class {
6430
6449
  this.resolver = new SchemaResolver(document);
6431
6450
  this.schemaMapping = schemaMapping;
6432
6451
  this.requiredSchemas = /* @__PURE__ */ new Set();
6452
+ this.generatedRequestSchemas = /* @__PURE__ */ new Set();
6433
6453
  }
6434
6454
  resolver;
6435
6455
  schemaMapping;
6436
6456
  requiredSchemas;
6457
+ generatedRequestSchemas;
6437
6458
  /**
6438
6459
  * Generate typed API client code from OpenAPI document
6439
6460
  */
@@ -6451,6 +6472,7 @@ var ClientGenerator = class {
6451
6472
  throw new Error("No endpoints found in OpenAPI document");
6452
6473
  }
6453
6474
  this.requiredSchemas.clear();
6475
+ this.generatedRequestSchemas.clear();
6454
6476
  let code = this.generateImports(!!schemaMapping, validateResponses);
6455
6477
  code += "\n";
6456
6478
  if (schemaMapping) {
@@ -6525,6 +6547,7 @@ var ClientGenerator = class {
6525
6547
  code += `export const ${schemaName} = ${schemaCode};
6526
6548
 
6527
6549
  `;
6550
+ this.generatedRequestSchemas.add(schemaName);
6528
6551
  }
6529
6552
  }
6530
6553
  return code;
@@ -6834,10 +6857,10 @@ ${paramSchemas.join(",\n")}
6834
6857
  continue;
6835
6858
  }
6836
6859
  const typeName = this.getOperationTypeName(endpoint);
6837
- if (mapping.requestSchema) {
6838
- this.addRequiredSchema(mapping.requestSchema);
6839
- code += `export type ${typeName}Request = z.infer<typeof ${mapping.requestSchema}>;
6840
- `;
6860
+ const requestSchemaName = mapping.requestSchema || typeName + "RequestSchema";
6861
+ if (this.generatedRequestSchemas.has(requestSchemaName)) {
6862
+ this.addRequiredSchema(requestSchemaName);
6863
+ code += "export type " + typeName + "Request = z.infer<typeof " + requestSchemaName + ">;\n";
6841
6864
  }
6842
6865
  this.addRequiredSchema(mapping.responseSchema);
6843
6866
  code += `export type ${typeName}Response = z.infer<typeof schemas.${mapping.responseSchema}>;
@@ -7176,7 +7199,7 @@ ${paramSchemas.join(",\n")}
7176
7199
  * Generate a method for an endpoint with Zod validation (NEW)
7177
7200
  */
7178
7201
  generateMethodWithValidation(endpoint, includeJsDoc, schemaMapping) {
7179
- const { operation, path: path2, method } = endpoint;
7202
+ const { operation, path: path3, method } = endpoint;
7180
7203
  const methodName = this.getMethodName(endpoint);
7181
7204
  const typeName = this.getOperationTypeName(endpoint);
7182
7205
  const operationId = endpoint.operationId || methodName;
@@ -7236,7 +7259,7 @@ ${paramSchemas.join(",\n")}
7236
7259
  if (pathParams.length > 0) {
7237
7260
  code += ` // Build URL with path parameters
7238
7261
  `;
7239
- code += ` let url = this.baseUrl + '${path2}'
7262
+ code += ` let url = this.baseUrl + '${path3}'
7240
7263
  `;
7241
7264
  for (const param of pathParams) {
7242
7265
  code += ` .replace('{${param.name}}', encodeURIComponent(String(request.path.${param.name})))
@@ -7246,7 +7269,7 @@ ${paramSchemas.join(",\n")}
7246
7269
 
7247
7270
  `;
7248
7271
  } else {
7249
- code += ` const url = this.baseUrl + '${path2}';
7272
+ code += ` const url = this.baseUrl + '${path3}';
7250
7273
 
7251
7274
  `;
7252
7275
  }
@@ -7258,7 +7281,7 @@ ${paramSchemas.join(",\n")}
7258
7281
  code += ` if (request.query) {
7259
7282
  `;
7260
7283
  for (const param of queryParams) {
7261
- code += ` if (request.query.${param.name} !== undefined) {
7284
+ code += ` if (request.query.${param.name} != null) {
7262
7285
  `;
7263
7286
  code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
7264
7287
  `;
@@ -7284,7 +7307,7 @@ ${paramSchemas.join(",\n")}
7284
7307
  code += ` method: '${method.toUpperCase()}',
7285
7308
  `;
7286
7309
  if (hasBody) {
7287
- code += ` body: request.body ? JSON.stringify(request.body) : undefined,
7310
+ code += ` body: request.body ? JSON.stringify(request.body) : null,
7288
7311
  `;
7289
7312
  }
7290
7313
  if (headerParams.length > 0) {
@@ -7342,7 +7365,7 @@ ${paramSchemas.join(",\n")}
7342
7365
  * Generate a method for an endpoint (LEGACY)
7343
7366
  */
7344
7367
  generateMethodLegacy(endpoint, includeJsDoc) {
7345
- const { operation, path: path2, method } = endpoint;
7368
+ const { operation, path: path3, method } = endpoint;
7346
7369
  const methodName = this.getMethodName(endpoint);
7347
7370
  const typeName = this.getOperationTypeName(endpoint);
7348
7371
  const params = this.getOperationParameters(operation);
@@ -7378,7 +7401,7 @@ ${paramSchemas.join(",\n")}
7378
7401
  if (pathParams.length > 0) {
7379
7402
  code += ` // Build URL with path parameters
7380
7403
  `;
7381
- code += ` let url = this.baseUrl + '${path2}'
7404
+ code += ` let url = this.baseUrl + '${path3}'
7382
7405
  `;
7383
7406
  for (const param of pathParams) {
7384
7407
  code += ` .replace('{${param.name}}', encodeURIComponent(String(request.path.${param.name})))
@@ -7388,7 +7411,7 @@ ${paramSchemas.join(",\n")}
7388
7411
 
7389
7412
  `;
7390
7413
  } else {
7391
- code += ` const url = this.baseUrl + '${path2}';
7414
+ code += ` const url = this.baseUrl + '${path3}';
7392
7415
 
7393
7416
  `;
7394
7417
  }
@@ -7401,7 +7424,7 @@ ${paramSchemas.join(",\n")}
7401
7424
  code += ` if (request.query) {
7402
7425
  `;
7403
7426
  for (const param of queryParams) {
7404
- code += ` if (request.query.${param.name} !== undefined) {
7427
+ code += ` if (request.query.${param.name} != null) {
7405
7428
  `;
7406
7429
  code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
7407
7430
  `;
@@ -7425,7 +7448,7 @@ ${paramSchemas.join(",\n")}
7425
7448
  code += ` method: '${method.toUpperCase()}',
7426
7449
  `;
7427
7450
  if (hasBody) {
7428
- code += ` body: request.body ? JSON.stringify(request.body) : undefined,
7451
+ code += ` body: request.body ? JSON.stringify(request.body) : null,
7429
7452
  `;
7430
7453
  }
7431
7454
  const headerParams = params.filter((p) => p.in === "header");
@@ -7472,7 +7495,7 @@ ${paramSchemas.join(",\n")}
7472
7495
  const errorBody = await response.text().catch(() => null);
7473
7496
 
7474
7497
  // \u2705 SECURITY FIX: Safe JSON parsing with fallback
7475
- let parsedError: unknown = undefined;
7498
+ let parsedError: unknown;
7476
7499
  if (errorBody) {
7477
7500
  try {
7478
7501
  parsedError = JSON.parse(errorBody);
@@ -7494,7 +7517,7 @@ ${paramSchemas.join(",\n")}
7494
7517
 
7495
7518
  // Handle empty responses (204 No Content, etc.)
7496
7519
  if (response.status === 204 || response.headers.get('content-length') === '0') {
7497
- return undefined as T;
7520
+ return null as unknown as T;
7498
7521
  }
7499
7522
 
7500
7523
  // \u2705 SECURITY FIX: Validate Content-Type before parsing JSON
@@ -7520,14 +7543,14 @@ ${paramSchemas.join(",\n")}
7520
7543
  */
7521
7544
  getAllEndpoints() {
7522
7545
  const endpoints = [];
7523
- for (const [path2, pathItem] of Object.entries(this.document.paths || {})) {
7546
+ for (const [path3, pathItem] of Object.entries(this.document.paths || {})) {
7524
7547
  if (!pathItem) continue;
7525
7548
  const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
7526
7549
  for (const method of methods) {
7527
7550
  const operation = pathItem[method];
7528
7551
  if (operation) {
7529
7552
  endpoints.push({
7530
- path: path2,
7553
+ path: path3,
7531
7554
  method,
7532
7555
  operationId: operation.operationId,
7533
7556
  summary: operation.summary,
@@ -7537,7 +7560,32 @@ ${paramSchemas.join(",\n")}
7537
7560
  }
7538
7561
  }
7539
7562
  }
7540
- return endpoints;
7563
+ return this.deduplicateOperationIds(endpoints);
7564
+ }
7565
+ /**
7566
+ * Deduplicate colliding operationIds by appending a path-based suffix.
7567
+ *
7568
+ * Real-world specs (e.g. SAM.gov) reuse the same operationId across
7569
+ * versioned paths. We make each one unique so the generated client
7570
+ * has no duplicate method names.
7571
+ */
7572
+ deduplicateOperationIds(endpoints) {
7573
+ const seen = /* @__PURE__ */ new Map();
7574
+ return endpoints.map((ep) => {
7575
+ if (!ep.operationId) return ep;
7576
+ const count = seen.get(ep.operationId) ?? 0;
7577
+ seen.set(ep.operationId, count + 1);
7578
+ if (count === 0) return ep;
7579
+ const suffix = this.sanitizePath(ep.path);
7580
+ return { ...ep, operationId: `${ep.operationId}_${suffix}` };
7581
+ });
7582
+ }
7583
+ /**
7584
+ * Turn an API path into a valid, readable identifier suffix.
7585
+ * e.g. '/entity-information/v4/download-entities' -> 'entityInformationV4DownloadEntities'
7586
+ */
7587
+ sanitizePath(path3) {
7588
+ return path3.split("/").filter((seg) => seg && !seg.startsWith("{")).join("-").replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[A-Z]/, (chr) => chr.toLowerCase());
7541
7589
  }
7542
7590
  /**
7543
7591
  * Get parameters for an operation
@@ -7633,12 +7681,556 @@ var OpenAPICollectionProvider = class {
7633
7681
  };
7634
7682
 
7635
7683
  // src/prebuild/OpenAPIPlugin.ts
7684
+ import fs2 from "fs";
7685
+ import path2 from "path";
7686
+
7687
+ // src/utils/EndpointFilter.ts
7688
+ var EndpointFilter = class {
7689
+ include;
7690
+ exclude;
7691
+ constructor(filter) {
7692
+ if (filter?.include !== void 0 && filter.include.length > 0) {
7693
+ this.include = filter.include;
7694
+ } else {
7695
+ this.include = ["/**"];
7696
+ }
7697
+ this.exclude = filter?.exclude ?? [];
7698
+ }
7699
+ /**
7700
+ * Check if a path should be included in code generation.
7701
+ *
7702
+ * Logic:
7703
+ * 1. If path matches any exclude pattern → excluded
7704
+ * 2. If path matches any include pattern → included
7705
+ * 3. Otherwise → excluded (default deny)
7706
+ */
7707
+ matches(path3) {
7708
+ const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`;
7709
+ for (const pattern of this.exclude) {
7710
+ if (this.matchPattern(normalizedPath, pattern)) {
7711
+ return false;
7712
+ }
7713
+ }
7714
+ for (const pattern of this.include) {
7715
+ if (this.matchPattern(normalizedPath, pattern)) {
7716
+ return true;
7717
+ }
7718
+ }
7719
+ return false;
7720
+ }
7721
+ /**
7722
+ * Match path against a pattern.
7723
+ */
7724
+ matchPattern(path3, pattern) {
7725
+ const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
7726
+ if (normalizedPattern === "/") {
7727
+ return true;
7728
+ }
7729
+ const regex = this.globToRegex(normalizedPattern);
7730
+ if (regex.test(path3)) {
7731
+ return true;
7732
+ }
7733
+ if (!normalizedPattern.endsWith("*")) {
7734
+ const patternSegments = normalizedPattern.split("/");
7735
+ const pathSegments = path3.split("/");
7736
+ if (pathSegments.length >= patternSegments.length) {
7737
+ let matches = true;
7738
+ for (let i = 0; i < patternSegments.length; i++) {
7739
+ const pSeg = patternSegments[i];
7740
+ if (pSeg === "*" || pSeg === "**") {
7741
+ continue;
7742
+ }
7743
+ if (pSeg.startsWith("{") && pSeg.endsWith("}")) {
7744
+ continue;
7745
+ }
7746
+ if (pSeg !== pathSegments[i]) {
7747
+ matches = false;
7748
+ break;
7749
+ }
7750
+ }
7751
+ if (matches) {
7752
+ return true;
7753
+ }
7754
+ }
7755
+ }
7756
+ return false;
7757
+ }
7758
+ /**
7759
+ * Convert a glob-like pattern to a RegExp.
7760
+ *
7761
+ * Pattern semantics:
7762
+ * - `*` = single path segment (no slashes), minimum 1 character
7763
+ * - `**` = multiple path segments (includes slashes), zero or more segments
7764
+ * - `{param}` = single path segment parameter
7765
+ * - Exact match segments are literal strings
7766
+ */
7767
+ globToRegex(pattern) {
7768
+ let result = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
7769
+ result = result.replace(/\*\*/g, "\u27EADOUBLESTAR\u27EB");
7770
+ result = result.replace(/\*/g, "\u27EASTAR\u27EB");
7771
+ if (result.startsWith("\u27EADOUBLESTAR\u27EB")) {
7772
+ result = ".*" + result.slice("\u27EADOUBLESTAR\u27EB".length);
7773
+ } else if (result.endsWith("\u27EADOUBLESTAR\u27EB") && result.endsWith("/\u27EADOUBLESTAR\u27EB")) {
7774
+ result = result.slice(0, -"\u27EADOUBLESTAR\u27EB".length - 1) + "(?:/.*)?";
7775
+ } else {
7776
+ result = result.replace(/⟪DOUBLESTAR⟫/g, "(?:/.*)?");
7777
+ }
7778
+ result = result.replace(/⟪STAR⟫/g, "[^/]+");
7779
+ result = result.replace(/\{[^}]+\}/g, "[^/]+");
7780
+ return new RegExp(`^${result}$`);
7781
+ }
7782
+ };
7783
+
7784
+ // src/utils/ApprovedSpecsValidator.ts
7785
+ import crypto from "crypto";
7636
7786
  import fs from "fs";
7787
+ import https from "https";
7788
+ import http from "http";
7789
+ import { URL as URL2 } from "url";
7637
7790
  import path from "path";
7791
+ import { realpathSync } from "fs";
7792
+ import { isIP } from "net";
7793
+ var ApprovedSpecsValidator = class {
7794
+ allowlist;
7795
+ cache = /* @__PURE__ */ new Map();
7796
+ skipHashVerification;
7797
+ ALLOWED_DIRS;
7798
+ MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
7799
+ // 10MB - prevents memory exhaustion attacks
7800
+ /**
7801
+ * Create a new ApprovedSpecsValidator
7802
+ *
7803
+ * @param config - Security configuration from stackwright.yml
7804
+ * @param skipHashVerification - Skip hash check (for testing/development)
7805
+ */
7806
+ constructor(config, skipHashVerification = false) {
7807
+ this.allowlist = config.allowlist || [];
7808
+ this.skipHashVerification = skipHashVerification;
7809
+ this.ALLOWED_DIRS = this.buildAllowedDirs();
7810
+ }
7811
+ /**
7812
+ * Build list of allowed directories for path traversal prevention.
7813
+ * Defaults to cwd, specs subdir, and .stackwright cache dir.
7814
+ */
7815
+ buildAllowedDirs() {
7816
+ const cwd = process.cwd();
7817
+ return [cwd, path.join(cwd, "specs"), path.join(cwd, ".stackwright")];
7818
+ }
7819
+ /**
7820
+ * Validate that a file path is within allowed directories (path traversal prevention).
7821
+ * Uses realpathSync to resolve symlinks and prevent symlink traversal attacks.
7822
+ *
7823
+ * @param filePath - File path to validate
7824
+ * @returns true if path is allowed, false otherwise
7825
+ */
7826
+ isPathAllowed(filePath) {
7827
+ try {
7828
+ const realPath = realpathSync(filePath);
7829
+ return this.ALLOWED_DIRS.some((dir) => {
7830
+ const realDir = realpathSync(dir);
7831
+ return realPath.startsWith(realDir + path.sep) || realPath === realDir;
7832
+ });
7833
+ } catch {
7834
+ return false;
7835
+ }
7836
+ }
7837
+ /**
7838
+ * Check if a URL/path is a path traversal attempt.
7839
+ * This is checked BEFORE the allowlist to prevent bypassing path security.
7840
+ *
7841
+ * @param specUrl - URL or path to check
7842
+ * @returns true if this is a path traversal attempt
7843
+ */
7844
+ isPathTraversalAttempt(specUrl) {
7845
+ if (specUrl.startsWith("file://")) {
7846
+ return true;
7847
+ }
7848
+ if (fs.existsSync(specUrl)) {
7849
+ return !this.isPathAllowed(specUrl);
7850
+ }
7851
+ if (!specUrl.startsWith("http://") && !specUrl.startsWith("https://")) {
7852
+ if (specUrl.includes("../") || specUrl.includes("..\\")) {
7853
+ return true;
7854
+ }
7855
+ }
7856
+ return false;
7857
+ }
7858
+ /**
7859
+ * Validate that a hex string is a valid SHA-256 hash (64 hex characters).
7860
+ *
7861
+ * @param hash - String to validate
7862
+ * @returns true if valid SHA-256 hex format
7863
+ */
7864
+ isValidHex(hash) {
7865
+ return /^[a-fA-F0-9]{64}$/.test(hash);
7866
+ }
7867
+ /**
7868
+ * Validate that a redirect URL is safe (SSRF protection).
7869
+ * Blocks:
7870
+ * - HTTPS → HTTP downgrades
7871
+ * - Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 127.x.x.x)
7872
+ * - Localhost and loopback addresses
7873
+ * - Cloud metadata endpoints
7874
+ * - IPv6 private/link-local addresses
7875
+ *
7876
+ * @param location - Redirect location URL
7877
+ * @param originalProtocol - Protocol of the original request
7878
+ * @returns true if redirect is safe, false if it should be blocked
7879
+ */
7880
+ isRedirectSafe(location, originalProtocol) {
7881
+ try {
7882
+ const redirectUrl = new URL2(location);
7883
+ if (redirectUrl.protocol === "http:" && originalProtocol === "https:") {
7884
+ return false;
7885
+ }
7886
+ const blockedPatterns = [
7887
+ "localhost",
7888
+ "127.0.0.1",
7889
+ "::1",
7890
+ "0.0.0.0",
7891
+ "169.254.169.254",
7892
+ // AWS/GCP/Azure metadata
7893
+ ".metadata.google.internal",
7894
+ // GCP metadata
7895
+ "metadata.google.internal",
7896
+ "metadata.internal"
7897
+ ];
7898
+ for (const pattern of blockedPatterns) {
7899
+ if (redirectUrl.hostname.includes(pattern)) {
7900
+ return false;
7901
+ }
7902
+ }
7903
+ const ipv4Match = redirectUrl.hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
7904
+ if (ipv4Match) {
7905
+ const octets = ipv4Match.slice(1, 5).map(Number);
7906
+ const [first, second] = octets;
7907
+ if (first === 10) {
7908
+ return false;
7909
+ }
7910
+ if (first === 172 && second >= 16 && second <= 31) {
7911
+ return false;
7912
+ }
7913
+ if (first === 192 && second === 168) {
7914
+ return false;
7915
+ }
7916
+ if (first === 127) {
7917
+ return false;
7918
+ }
7919
+ }
7920
+ const ipVersion = isIP(redirectUrl.hostname);
7921
+ if (ipVersion === 6) {
7922
+ const blockedPrefixes = [
7923
+ "::1",
7924
+ // Loopback
7925
+ "fe80:",
7926
+ // Link-local
7927
+ "fc00:",
7928
+ // Unique local
7929
+ "fd",
7930
+ // Unique local (short form)
7931
+ "::ffff:"
7932
+ // IPv4-mapped
7933
+ ];
7934
+ if (blockedPrefixes.some((p) => redirectUrl.hostname.startsWith(p))) {
7935
+ return false;
7936
+ }
7937
+ }
7938
+ return true;
7939
+ } catch {
7940
+ return false;
7941
+ }
7942
+ }
7943
+ /**
7944
+ * Atomically check if path is allowed and read content if so.
7945
+ * Prevents TOCTOU race conditions by combining existence check,
7946
+ * symlink resolution, path validation, and file read in a single operation.
7947
+ *
7948
+ * @param filePath - File path to check and read
7949
+ * @returns Object with content if successful, or error message
7950
+ */
7951
+ readAllowedFile(filePath) {
7952
+ try {
7953
+ fs.lstatSync(filePath);
7954
+ const realPath = fs.realpathSync(filePath);
7955
+ if (!this.isPathAllowed(realPath)) {
7956
+ return { error: "File path outside allowed directories (path traversal blocked)" };
7957
+ }
7958
+ return { content: fs.readFileSync(filePath, "utf8") };
7959
+ } catch (e) {
7960
+ const err = e;
7961
+ if (err.code === "ENOENT") {
7962
+ return { error: "File not found" };
7963
+ }
7964
+ return { error: String(e) };
7965
+ }
7966
+ }
7967
+ /**
7968
+ * Check if security enforcement is enabled
7969
+ */
7970
+ isEnabled() {
7971
+ return this.allowlist.length > 0;
7972
+ }
7973
+ /**
7974
+ * Get the allowlist count
7975
+ */
7976
+ getAllowlistCount() {
7977
+ return this.allowlist.length;
7978
+ }
7979
+ /**
7980
+ * Validate that a spec is on the approved list and matches expected hash.
7981
+ *
7982
+ * @param specUrl - URL or file path to the spec to validate
7983
+ * @returns ValidationResult indicating if the spec is approved
7984
+ */
7985
+ async validate(specUrl) {
7986
+ if (this.isPathTraversalAttempt(specUrl)) {
7987
+ return {
7988
+ valid: false,
7989
+ errorCode: "DOWNLOAD_FAILED",
7990
+ specUrl,
7991
+ error: "File path outside allowed directories (path traversal blocked)"
7992
+ };
7993
+ }
7994
+ const approved = this.allowlist.find((a) => this.urlsMatch(a.url, specUrl));
7995
+ if (!approved) {
7996
+ return {
7997
+ valid: false,
7998
+ errorCode: "SPEC_NOT_ON_ALLOWLIST",
7999
+ specUrl,
8000
+ error: this.formatAllowlistError(specUrl)
8001
+ };
8002
+ }
8003
+ if (this.skipHashVerification) {
8004
+ return { valid: true, specUrl };
8005
+ }
8006
+ const hashResult = await this.getHash(specUrl);
8007
+ if (hashResult.error) {
8008
+ return {
8009
+ valid: false,
8010
+ errorCode: "DOWNLOAD_FAILED",
8011
+ specUrl,
8012
+ error: `Failed to fetch spec '${specUrl}': ${hashResult.error}`
8013
+ };
8014
+ }
8015
+ if (!this.isValidHex(hashResult.hash)) {
8016
+ return {
8017
+ valid: false,
8018
+ errorCode: "DOWNLOAD_FAILED",
8019
+ specUrl,
8020
+ error: "Invalid hash format returned from download"
8021
+ };
8022
+ }
8023
+ if (!this.isValidHex(approved.sha256)) {
8024
+ return {
8025
+ valid: false,
8026
+ errorCode: "DOWNLOAD_FAILED",
8027
+ specUrl,
8028
+ error: "Invalid hash format in approved spec configuration"
8029
+ };
8030
+ }
8031
+ if (!crypto.timingSafeEqual(
8032
+ Buffer.from(hashResult.hash, "hex"),
8033
+ Buffer.from(approved.sha256, "hex")
8034
+ )) {
8035
+ return {
8036
+ valid: false,
8037
+ errorCode: "SPEC_MODIFIED",
8038
+ specUrl,
8039
+ error: this.formatHashMismatchError(approved, hashResult.hash)
8040
+ };
8041
+ }
8042
+ return { valid: true, specUrl };
8043
+ }
8044
+ /**
8045
+ * Validate multiple specs at once (batch validation).
8046
+ * Fails fast on first error.
8047
+ *
8048
+ * @param specUrls - Array of URLs/paths to validate
8049
+ * @returns Map of URL to ValidationResult
8050
+ */
8051
+ async validateAll(specUrls) {
8052
+ const results = /* @__PURE__ */ new Map();
8053
+ for (const url of specUrls) {
8054
+ const result = await this.validate(url);
8055
+ results.set(url, result);
8056
+ if (!result.valid) {
8057
+ return results;
8058
+ }
8059
+ }
8060
+ return results;
8061
+ }
8062
+ /**
8063
+ * Validate all specs and return all results (non-fail-fast).
8064
+ *
8065
+ * @param specUrls - Array of URLs/paths to validate
8066
+ * @returns Map of URL to ValidationResult
8067
+ */
8068
+ async validateAllComplete(specUrls) {
8069
+ const results = /* @__PURE__ */ new Map();
8070
+ for (const url of specUrls) {
8071
+ const result = await this.validate(url);
8072
+ results.set(url, result);
8073
+ }
8074
+ return results;
8075
+ }
8076
+ /**
8077
+ * Get content hash from cache or download.
8078
+ */
8079
+ async getHash(url) {
8080
+ const cached = this.cache.get(url);
8081
+ if (cached) {
8082
+ return { hash: cached.hash };
8083
+ }
8084
+ const contentResult = await this.download(url);
8085
+ if (contentResult.error) {
8086
+ return { error: contentResult.error };
8087
+ }
8088
+ const content = contentResult.content;
8089
+ const hash = crypto.createHash("sha256").update(content).digest("hex");
8090
+ this.cache.set(url, { content, hash });
8091
+ return { hash };
8092
+ }
8093
+ /**
8094
+ * Download content from URL or file.
8095
+ * Includes path traversal and SSRF protection.
8096
+ */
8097
+ async download(url, originalProtocol = "") {
8098
+ if (fs.existsSync(url)) {
8099
+ return this.readAllowedFile(url);
8100
+ }
8101
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
8102
+ return { error: `Invalid URL protocol. Expected http:// or https://, got: ${url}` };
8103
+ }
8104
+ const parsed = new URL2(url);
8105
+ const requestProtocol = originalProtocol || parsed.protocol;
8106
+ return new Promise((resolve) => {
8107
+ const client = url.startsWith("https") ? https : http;
8108
+ const req = client.get(url, { timeout: 3e4 }, (res) => {
8109
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
8110
+ const location = res.headers.location;
8111
+ if (!this.isRedirectSafe(location, requestProtocol)) {
8112
+ resolve({ error: `Unsafe redirect target blocked: ${location}` });
8113
+ return;
8114
+ }
8115
+ this.download(location, requestProtocol).then(resolve);
8116
+ return;
8117
+ }
8118
+ if (res.statusCode !== 200) {
8119
+ resolve({ error: `HTTP ${res.statusCode}` });
8120
+ return;
8121
+ }
8122
+ let data = "";
8123
+ let size = 0;
8124
+ res.on("data", (chunk) => {
8125
+ size += chunk.length;
8126
+ if (size > this.MAX_RESPONSE_SIZE) {
8127
+ req.destroy();
8128
+ resolve({ error: `Response too large (>${this.MAX_RESPONSE_SIZE / 1024 / 1024}MB)` });
8129
+ return;
8130
+ }
8131
+ data += chunk;
8132
+ });
8133
+ res.on("end", () => resolve({ content: data }));
8134
+ res.on("error", (e) => resolve({ error: String(e) }));
8135
+ });
8136
+ req.on("error", (e) => resolve({ error: String(e) }));
8137
+ req.on("timeout", () => {
8138
+ req.destroy();
8139
+ resolve({ error: "Request timeout (30s)" });
8140
+ });
8141
+ });
8142
+ }
8143
+ /**
8144
+ * Check if two URLs match (handles trailing slashes, case sensitivity, etc.).
8145
+ * Strips credentials and hash to prevent bypass via @ symbol.
8146
+ */
8147
+ urlsMatch(url1, url2) {
8148
+ const normalize = (u) => {
8149
+ try {
8150
+ const parsed = new URL2(u);
8151
+ const normalized = `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
8152
+ return normalized.replace(/\/$/, "").toLowerCase();
8153
+ } catch {
8154
+ return u.replace(/\/$/, "").replace(/\\/g, "/").toLowerCase();
8155
+ }
8156
+ };
8157
+ return normalize(url1) === normalize(url2);
8158
+ }
8159
+ /**
8160
+ * Format error message for spec not on allowlist
8161
+ */
8162
+ formatAllowlistError(specUrl) {
8163
+ const lines = [`Spec '${specUrl}' is not on the approved list.`];
8164
+ if (this.allowlist.length === 0) {
8165
+ lines.push("");
8166
+ lines.push("No approved specs are configured.");
8167
+ lines.push("Add approved specs to stackwright.yml under prebuild.security.allowlist");
8168
+ } else {
8169
+ lines.push("");
8170
+ lines.push("Allowed specs:");
8171
+ for (const spec of this.allowlist) {
8172
+ lines.push(` \u2022 ${spec.name}`);
8173
+ lines.push(` ${spec.url}`);
8174
+ }
8175
+ }
8176
+ return lines.join("\n");
8177
+ }
8178
+ /**
8179
+ * Format error message for hash mismatch
8180
+ */
8181
+ formatHashMismatchError(approved, actualHash) {
8182
+ const lines = [
8183
+ `Spec '${approved.url}' has been modified since approval.`,
8184
+ "",
8185
+ "Expected hash (approved): " + approved.sha256,
8186
+ "Actual hash (current): " + actualHash,
8187
+ "",
8188
+ "This may indicate:",
8189
+ " \u2022 The spec has been updated on the server",
8190
+ " \u2022 Network corruption during download",
8191
+ " \u2022 A man-in-the-middle attack",
8192
+ "",
8193
+ "If this is expected, update the sha256 in stackwright.yml."
8194
+ ];
8195
+ return lines.join("\n");
8196
+ }
8197
+ /**
8198
+ * Clear the download cache.
8199
+ * Useful for long-running processes.
8200
+ */
8201
+ clearCache() {
8202
+ this.cache.clear();
8203
+ }
8204
+ };
8205
+
8206
+ // src/prebuild/OpenAPIPlugin.ts
7638
8207
  var OpenAPIPlugin = class {
7639
8208
  name = "@stackwright-pro/openapi";
7640
8209
  async beforeBuild(context) {
7641
8210
  const { siteConfig, projectRoot } = context;
8211
+ const prebuildConfig = siteConfig.prebuild || {};
8212
+ const securityConfig = prebuildConfig.security;
8213
+ let validator = null;
8214
+ if (securityConfig?.enabled) {
8215
+ const skipApprovedSpecs = process.argv.includes("--skip-approved-specs");
8216
+ if (skipApprovedSpecs) {
8217
+ const isProductionBuild = process.env.NODE_ENV === "production" || process.env.CI === "true";
8218
+ if (isProductionBuild) {
8219
+ console.error("\n\u274C FATAL: --skip-approved-specs not allowed in production/CI builds\n");
8220
+ throw new Error(
8221
+ "SECURITY_CONFIG_VIOLATION: Cannot use --skip-approved-specs in production or CI environments"
8222
+ );
8223
+ }
8224
+ console.log(
8225
+ "\n[@stackwright-pro/openapi] \u26A0\uFE0F Approved-specs enforcement DISABLED (--skip-approved-specs)\n"
8226
+ );
8227
+ console.log(" This is ONLY allowed in development mode.\n");
8228
+ } else {
8229
+ validator = new ApprovedSpecsValidator(securityConfig);
8230
+ console.log("\n[@stackwright-pro/openapi] \u{1F512} Approved-specs enforcement ENABLED");
8231
+ console.log(` Approved specs: ${validator.getAllowlistCount()}`);
8232
+ }
8233
+ }
7642
8234
  const integrations = siteConfig.integrations;
7643
8235
  if (!integrations || !Array.isArray(integrations)) {
7644
8236
  return;
@@ -7649,6 +8241,17 @@ var OpenAPIPlugin = class {
7649
8241
  if (openAPIIntegrations.length === 0) {
7650
8242
  return;
7651
8243
  }
8244
+ if (validator) {
8245
+ const specUrls = openAPIIntegrations.map((i) => i.spec);
8246
+ const results = await validator.validateAll(specUrls);
8247
+ for (const [url, result] of results) {
8248
+ if (!result.valid) {
8249
+ this.printSecurityRejection(result);
8250
+ throw new Error(`SPEC_NOT_APPROVED: ${result.error}`);
8251
+ }
8252
+ console.log(` \u2713 Approved: ${url}`);
8253
+ }
8254
+ }
7652
8255
  console.log(
7653
8256
  `
7654
8257
  [@stackwright-pro/openapi] Processing ${openAPIIntegrations.length} integration(s)...`
@@ -7658,23 +8261,74 @@ var OpenAPIPlugin = class {
7658
8261
  }
7659
8262
  console.log("[@stackwright-pro/openapi] Generation complete\n");
7660
8263
  }
8264
+ /**
8265
+ * Print a security rejection error in a formatted box
8266
+ */
8267
+ printSecurityRejection(result) {
8268
+ const lines = [];
8269
+ lines.push(
8270
+ "\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"
8271
+ );
8272
+ lines.push(
8273
+ "\u2551 \u{1F512} SECURITY REJECTED \u2551"
8274
+ );
8275
+ lines.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
8276
+ lines.push(`\u2551 Error: ${(result.errorCode || "UNKNOWN").padEnd(73)} \u2551`);
8277
+ lines.push("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
8278
+ const errorLines = (result.error || "Unknown error").split("\n");
8279
+ const maxLen = 76;
8280
+ for (const line of errorLines) {
8281
+ if (line.length <= maxLen) {
8282
+ lines.push(`\u2551 ${line.padEnd(maxLen)} \u2551`);
8283
+ } else {
8284
+ let remaining = line;
8285
+ while (remaining.length > 0) {
8286
+ lines.push(`\u2551 ${remaining.substring(0, maxLen).padEnd(maxLen)} \u2551`);
8287
+ remaining = remaining.substring(maxLen);
8288
+ }
8289
+ }
8290
+ }
8291
+ lines.push(
8292
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"
8293
+ );
8294
+ for (const line of lines) {
8295
+ console.error(line);
8296
+ }
8297
+ }
7661
8298
  async processIntegration(config, projectRoot) {
7662
- const { name, spec, auth, collections } = config;
8299
+ const { name, spec, auth, mockUrl, collections, endpoints } = config;
7663
8300
  console.log(` - Processing integration: ${name}`);
7664
- const specPath = spec.startsWith("http") ? spec : path.resolve(projectRoot, spec);
8301
+ const specPath = spec.startsWith("http") ? spec : path2.resolve(projectRoot, spec);
7665
8302
  const parser = new OpenAPIParser();
7666
8303
  const { document } = await parser.parse(specPath);
7667
- const outputDir = path.join(projectRoot, "src", "generated", name);
7668
- fs.mkdirSync(outputDir, { recursive: true });
7669
- const schemaMapping = await this.generateSchemas(document, collections || [], outputDir, name);
8304
+ if (mockUrl) {
8305
+ const originalServer = document.servers?.[0]?.url || "unknown";
8306
+ document.servers = [{ url: mockUrl }];
8307
+ console.log(` > Using mock URL: ${mockUrl} (was: ${originalServer})`);
8308
+ }
8309
+ const endpointFilter = new EndpointFilter(endpoints);
8310
+ if (endpoints && (endpoints.include?.length || endpoints.exclude?.length)) {
8311
+ const includeStr = endpoints.include?.join(", ") || "/**";
8312
+ const excludeStr = endpoints.exclude?.length ? ` (exclude: ${endpoints.exclude.join(", ")})` : "";
8313
+ console.log(` > Filter: include [${includeStr}]${excludeStr}`);
8314
+ }
8315
+ const outputDir = path2.join(projectRoot, "src", "generated", name);
8316
+ fs2.mkdirSync(outputDir, { recursive: true });
8317
+ const schemaMapping = await this.generateSchemas(
8318
+ document,
8319
+ collections || [],
8320
+ outputDir,
8321
+ name,
8322
+ endpointFilter
8323
+ );
7670
8324
  await this.generateTypes(document, collections || [], outputDir, name);
7671
8325
  if (collections && collections.length > 0) {
7672
8326
  await this.generateProvider(document, config, outputDir, name);
7673
8327
  }
7674
- await this.generateClient(document, outputDir, name, schemaMapping);
7675
- console.log(` \u2713 Generated code in src/generated/${name}/`);
8328
+ await this.generateClient(document, outputDir, name, schemaMapping, endpointFilter);
8329
+ console.log(` > Generated code in src/generated/${name}/`);
7676
8330
  }
7677
- async generateSchemas(document, collections, outputDir, integrationName) {
8331
+ async generateSchemas(document, collections, outputDir, integrationName, endpointFilter) {
7678
8332
  const resolver = new SchemaResolver(document);
7679
8333
  const zodGenerator = new ZodSchemaGenerator();
7680
8334
  let schemasCode = `/**
@@ -7694,14 +8348,14 @@ import { z } from 'zod';
7694
8348
  const method = "get";
7695
8349
  const schema = resolver.getResponseSchema(endpoint, method);
7696
8350
  if (!schema) {
7697
- console.warn(` \u26A0 No schema found for ${method.toUpperCase()} ${endpoint}`);
8351
+ console.warn(` > No schema found for ${method.toUpperCase()} ${endpoint}`);
7698
8352
  continue;
7699
8353
  }
7700
8354
  const collectionName = this.sanitizeName(endpoint);
7701
8355
  const schemaName = `${this.capitalize(collectionName)}Schema`;
7702
8356
  const isArray = schema.type === "array";
7703
8357
  const itemSchema = isArray ? schema.items : schema;
7704
- const zodCode = zodGenerator.generate(itemSchema, { schemaName });
8358
+ const zodCode = zodGenerator.generate(itemSchema, { schemaName, bare: true });
7705
8359
  schemasCode += zodCode + "\n";
7706
8360
  if (isArray) {
7707
8361
  schemasCode += `export const ${this.capitalize(collectionName)}ArraySchema = z.array(${schemaName});
@@ -7710,22 +8364,27 @@ import { z } from 'zod';
7710
8364
  }
7711
8365
  }
7712
8366
  }
7713
- schemasCode += "\n// Response schemas for all endpoints\n";
8367
+ schemasCode += "\n// Response schemas for filtered endpoints\n";
7714
8368
  const generatedSchemas = /* @__PURE__ */ new Set();
8369
+ let filteredCount = 0;
7715
8370
  const paths = document.paths || {};
7716
- for (const [path2, pathItem] of Object.entries(paths)) {
8371
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
8372
+ if (!endpointFilter.matches(pathStr)) {
8373
+ filteredCount++;
8374
+ continue;
8375
+ }
7717
8376
  const methods = ["get", "post", "put", "patch", "delete"];
7718
8377
  for (const method of methods) {
7719
8378
  const operation = pathItem[method];
7720
8379
  if (!operation) continue;
7721
- const opId = operation.operationId || this.generateOperationId(path2, method);
8380
+ const opId = operation.operationId || this.generateOperationId(pathStr, method);
7722
8381
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
7723
8382
  if (generatedSchemas.has(responseSchemaName)) {
7724
8383
  continue;
7725
8384
  }
7726
- const responseSchema = resolver.getResponseSchema(path2, method);
8385
+ const responseSchema = resolver.getResponseSchema(pathStr, method);
7727
8386
  if (!responseSchema) {
7728
- console.warn(` \u26A0 No response schema for ${method.toUpperCase()} ${path2}`);
8387
+ console.warn(` > No response schema for ${method.toUpperCase()} ${pathStr}`);
7729
8388
  continue;
7730
8389
  }
7731
8390
  const isArray = responseSchema.type === "array";
@@ -7740,32 +8399,44 @@ import { z } from 'zod';
7740
8399
  `;
7741
8400
  generatedSchemas.add(responseSchemaName);
7742
8401
  } else if (isArray) {
7743
- const zodCode = zodGenerator.generate(responseSchema, { schemaName: responseSchemaName });
8402
+ const zodCode = zodGenerator.generate(responseSchema, {
8403
+ schemaName: responseSchemaName,
8404
+ bare: true
8405
+ });
7744
8406
  schemasCode += zodCode + "\n";
7745
8407
  generatedSchemas.add(responseSchemaName);
7746
8408
  } else {
7747
- const zodCode = zodGenerator.generate(responseSchema, { schemaName: responseSchemaName });
8409
+ const zodCode = zodGenerator.generate(responseSchema, {
8410
+ schemaName: responseSchemaName,
8411
+ bare: true
8412
+ });
7748
8413
  schemasCode += zodCode + "\n";
7749
8414
  generatedSchemas.add(responseSchemaName);
7750
8415
  }
7751
8416
  }
7752
8417
  }
8418
+ if (filteredCount > 0) {
8419
+ console.log(` > Skipped ${filteredCount} paths (filtered)`);
8420
+ }
7753
8421
  const schemaMapping = {};
7754
- for (const [path2, pathItem] of Object.entries(paths)) {
8422
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
8423
+ if (!endpointFilter.matches(pathStr)) {
8424
+ continue;
8425
+ }
7755
8426
  const methods = ["get", "post", "put", "patch", "delete"];
7756
8427
  for (const method of methods) {
7757
8428
  const operation = pathItem[method];
7758
8429
  if (!operation) continue;
7759
- const opId = operation.operationId || this.generateOperationId(path2, method);
8430
+ const opId = operation.operationId || this.generateOperationId(pathStr, method);
7760
8431
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
7761
8432
  schemaMapping[opId] = {
7762
8433
  requestSchema: null,
7763
- // Will be added when we generate request schemas
8434
+ // ClientGenerator handles request schema generation + type inference
7764
8435
  responseSchema: responseSchemaName
7765
8436
  };
7766
8437
  }
7767
8438
  }
7768
- fs.writeFileSync(path.join(outputDir, "schemas.ts"), schemasCode);
8439
+ fs2.writeFileSync(path2.join(outputDir, "schemas.ts"), schemasCode);
7769
8440
  return schemaMapping;
7770
8441
  }
7771
8442
  async generateTypes(document, collections, outputDir, integrationName) {
@@ -7790,7 +8461,7 @@ import type * as schemas from './schemas';
7790
8461
  `;
7791
8462
  }
7792
8463
  }
7793
- fs.writeFileSync(path.join(outputDir, "types.ts"), typesCode);
8464
+ fs2.writeFileSync(path2.join(outputDir, "types.ts"), typesCode);
7794
8465
  }
7795
8466
  async generateProvider(document, config, outputDir, integrationName) {
7796
8467
  const generator = new CollectionProviderGenerator(document);
@@ -7798,22 +8469,15 @@ import type * as schemas from './schemas';
7798
8469
  if (!collections || collections.length === 0) {
7799
8470
  return;
7800
8471
  }
7801
- let providerCode = `/**
7802
- * Generated CollectionProvider from OpenAPI spec
7803
- * Integration: ${integrationName}
7804
- *
7805
- * DO NOT EDIT - This file is auto-generated by @stackwright-pro/openapi
7806
- * Regenerate by running: pnpm prebuild
7807
- */
7808
-
7809
- `;
8472
+ const schemaImports = /* @__PURE__ */ new Set();
8473
+ const classBlocks = [];
7810
8474
  for (const collection of collections) {
7811
8475
  const collectionConfig = {
7812
8476
  endpoint: collection.endpoint,
7813
8477
  slugField: collection.slug_field,
7814
8478
  filters: collection.filters
7815
8479
  };
7816
- let providerOptions = {};
8480
+ let providerOptions = { bare: true };
7817
8481
  if (auth) {
7818
8482
  if (auth.type === "bearer" || auth.type === "apiKey") {
7819
8483
  providerOptions.auth = {
@@ -7822,16 +8486,50 @@ import type * as schemas from './schemas';
7822
8486
  };
7823
8487
  } else if (auth.type === "oauth2") {
7824
8488
  console.warn(
7825
- ` \u26A0 OAuth2 auth not yet supported for ${collection.endpoint} (coming soon)`
8489
+ ` > OAuth2 auth not yet supported for ${collection.endpoint} (coming soon)`
7826
8490
  );
7827
8491
  }
7828
8492
  }
7829
8493
  const code = generator.generate(collectionConfig, providerOptions);
7830
- providerCode += code + "\n";
8494
+ classBlocks.push(code);
8495
+ const collectionName = this.sanitizeName(collection.endpoint);
8496
+ const resolver = new SchemaResolver(document);
8497
+ const schema = resolver.getResponseSchema(collection.endpoint, "get");
8498
+ const isArray = schema?.type === "array";
8499
+ if (isArray) {
8500
+ schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
8501
+ } else {
8502
+ schemaImports.add(`${this.capitalize(collectionName)}Schema`);
8503
+ }
7831
8504
  }
7832
- fs.writeFileSync(path.join(outputDir, "provider.ts"), providerCode);
8505
+ let providerCode = `/**
8506
+ * Generated CollectionProvider from OpenAPI spec
8507
+ * Integration: ${integrationName}
8508
+ *
8509
+ * DO NOT EDIT - This file is auto-generated by @stackwright-pro/openapi
8510
+ * Regenerate by running: pnpm prebuild
8511
+ */
8512
+
8513
+ import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
8514
+ import { ${Array.from(schemaImports).join(", ")} } from './schemas';
8515
+
8516
+ `;
8517
+ providerCode += classBlocks.join("\n");
8518
+ fs2.writeFileSync(path2.join(outputDir, "provider.ts"), providerCode);
7833
8519
  }
7834
- async generateClient(document, outputDir, integrationName, schemaMapping) {
8520
+ async generateClient(document, outputDir, integrationName, schemaMapping, endpointFilter) {
8521
+ const paths = document.paths || {};
8522
+ let filteredEndpoints = 0;
8523
+ for (const pathStr of Object.keys(paths)) {
8524
+ if (!endpointFilter.matches(pathStr)) {
8525
+ filteredEndpoints++;
8526
+ }
8527
+ }
8528
+ if (filteredEndpoints > 0) {
8529
+ console.log(
8530
+ ` > Generating client for ${Object.keys(paths).length - filteredEndpoints} endpoints (${filteredEndpoints} filtered)`
8531
+ );
8532
+ }
7835
8533
  const generator = new ClientGenerator(document);
7836
8534
  const className = `${this.capitalize(integrationName)}Client`;
7837
8535
  const clientCode = generator.generate({
@@ -7841,7 +8539,7 @@ import type * as schemas from './schemas';
7841
8539
  validateResponses: true,
7842
8540
  strictValidation: false
7843
8541
  });
7844
- fs.writeFileSync(path.join(outputDir, "client.ts"), clientCode);
8542
+ fs2.writeFileSync(path2.join(outputDir, "client.ts"), clientCode);
7845
8543
  }
7846
8544
  extractComponentName(ref) {
7847
8545
  const parts = ref.split("/");
@@ -7850,20 +8548,20 @@ import type * as schemas from './schemas';
7850
8548
  getOperationTypeName(operationId) {
7851
8549
  return operationId.charAt(0).toUpperCase() + operationId.slice(1);
7852
8550
  }
7853
- generateOperationId(path2, method) {
7854
- const sanitized = this.sanitizeName(path2);
8551
+ generateOperationId(pathStr, method) {
8552
+ const sanitized = this.sanitizeName(pathStr);
7855
8553
  return `${method}${this.capitalize(sanitized)}`;
7856
8554
  }
7857
- getResponseSchemaName(path2, method) {
7858
- const sanitized = this.sanitizeName(path2);
8555
+ getResponseSchemaName(pathStr, method) {
8556
+ const sanitized = this.sanitizeName(pathStr);
7859
8557
  const baseName = this.capitalize(sanitized);
7860
- if (method === "get" && !path2.includes("{")) {
8558
+ if (method === "get" && !pathStr.includes("{")) {
7861
8559
  return `${baseName}ArraySchema`;
7862
8560
  }
7863
8561
  return `${baseName}Schema`;
7864
8562
  }
7865
- sanitizeName(path2) {
7866
- return path2.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
8563
+ sanitizeName(pathStr) {
8564
+ return pathStr.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
7867
8565
  }
7868
8566
  capitalize(str) {
7869
8567
  return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -7872,14 +8570,137 @@ import type * as schemas from './schemas';
7872
8570
  function createOpenAPIPlugin() {
7873
8571
  return new OpenAPIPlugin();
7874
8572
  }
8573
+
8574
+ // src/sources/openapi.ts
8575
+ var BLOCKED_HOST_PATTERNS = [
8576
+ /^localhost$/i,
8577
+ /^127\./,
8578
+ // Loopback (127.0.0.0/8)
8579
+ /^10\./,
8580
+ // Class A private (10.0.0.0/8)
8581
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
8582
+ // Class B private (172.16.0.0/12)
8583
+ /^192\.168\./,
8584
+ // Class C private (192.168.0.0/16)
8585
+ /^169\.254\./,
8586
+ // Link-local (169.254.0.0/16) - AWS/GCP metadata
8587
+ /^0\./,
8588
+ // Current network (0.0.0.0/8)
8589
+ /^::1$/,
8590
+ // IPv6 loopback (no brackets)
8591
+ /^\[::1\]$/i,
8592
+ // IPv6 loopback (with brackets)
8593
+ /^[fF][cCdD][0-9a-fA-F]{2}:/,
8594
+ // IPv6 private (fc00::/7)
8595
+ /^169\.254\.169\.254$/i,
8596
+ // AWS metadata endpoint
8597
+ /^metadata\.googleapis\.com$/i,
8598
+ // GCP metadata endpoint
8599
+ /^100\.100\.100\.200$/
8600
+ // Alibaba Cloud metadata
8601
+ ];
8602
+ var ALLOWED_PROTOCOLS = ["http:", "https:"];
8603
+ function validateFetchUrl(baseUrl) {
8604
+ let url;
8605
+ try {
8606
+ url = new URL(baseUrl);
8607
+ } catch {
8608
+ throw new Error("SSRF Prevention: baseUrl must be a valid absolute URL");
8609
+ }
8610
+ if (!ALLOWED_PROTOCOLS.includes(url.protocol)) {
8611
+ throw new Error(
8612
+ "SSRF Prevention: Only HTTP and HTTPS protocols are allowed, got " + url.protocol
8613
+ );
8614
+ }
8615
+ const hostname = url.hostname.toLowerCase();
8616
+ for (const pattern of BLOCKED_HOST_PATTERNS) {
8617
+ if (pattern.test(hostname)) {
8618
+ throw new Error("SSRF Prevention: Blocked internal network address: " + hostname);
8619
+ }
8620
+ }
8621
+ const ip = url.hostname;
8622
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip)) {
8623
+ const octets = ip.split(".").map(Number);
8624
+ if (octets[0] === 127) {
8625
+ throw new Error("SSRF Prevention: Blocked loopback address: " + ip);
8626
+ }
8627
+ if (octets[0] === 10) {
8628
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8629
+ }
8630
+ if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
8631
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8632
+ }
8633
+ if (octets[0] === 192 && octets[1] === 168) {
8634
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8635
+ }
8636
+ if (octets[0] === 169 && octets[1] === 254) {
8637
+ throw new Error("SSRF Prevention: Blocked link-local address: " + ip);
8638
+ }
8639
+ if (octets[0] === 0) {
8640
+ throw new Error("SSRF Prevention: Blocked current network address: " + ip);
8641
+ }
8642
+ }
8643
+ return url;
8644
+ }
8645
+ function validateEndpoint(endpoint) {
8646
+ if (endpoint.includes("..")) {
8647
+ throw new Error("SSRF Prevention: Path traversal detected in endpoint");
8648
+ }
8649
+ if (endpoint.includes("\\")) {
8650
+ throw new Error("SSRF Prevention: Backslash detected in endpoint");
8651
+ }
8652
+ }
8653
+ function createOpenAPIFetcher(config) {
8654
+ const { baseUrl, endpoint, method = "get", params, body, headers = {}, auth, schema } = config;
8655
+ const validatedBaseUrl = validateFetchUrl(baseUrl);
8656
+ validateEndpoint(endpoint);
8657
+ return async () => {
8658
+ const url = new URL(endpoint, validatedBaseUrl.toString());
8659
+ if (params) {
8660
+ Object.entries(params).forEach(([key, value]) => {
8661
+ url.searchParams.append(key, String(value));
8662
+ });
8663
+ }
8664
+ const requestHeaders = {
8665
+ "Content-Type": "application/json",
8666
+ Accept: "application/json",
8667
+ ...headers
8668
+ };
8669
+ if (auth) {
8670
+ if (auth.type === "bearer" && auth.token) {
8671
+ requestHeaders["Authorization"] = `Bearer ${auth.token}`;
8672
+ } else if (auth.type === "apiKey" && auth.apiKey) {
8673
+ requestHeaders[auth.headerName ?? "X-API-Key"] = auth.apiKey;
8674
+ }
8675
+ }
8676
+ const supportsBody = ["post", "put", "patch", "delete"].includes(method);
8677
+ const response = await fetch(url.toString(), {
8678
+ method: method.toUpperCase(),
8679
+ headers: requestHeaders,
8680
+ body: supportsBody && body ? JSON.stringify(body) : void 0
8681
+ });
8682
+ if (!response.ok) {
8683
+ const safeStatus = response.status;
8684
+ throw new Error("API error: " + safeStatus);
8685
+ }
8686
+ const result = await response.json();
8687
+ if (schema) {
8688
+ return schema.parse(result);
8689
+ }
8690
+ return result;
8691
+ };
8692
+ }
7875
8693
  export {
8694
+ ApprovedSpecsValidator,
7876
8695
  ClientGenerator,
7877
8696
  CollectionProviderGenerator,
8697
+ EndpointFilter,
7878
8698
  OpenAPICollectionProvider,
7879
8699
  OpenAPIParser,
7880
8700
  OpenAPIPlugin,
7881
8701
  SchemaResolver,
7882
8702
  TypeGenerator,
7883
8703
  ZodSchemaGenerator,
8704
+ createOpenAPIFetcher,
7884
8705
  createOpenAPIPlugin
7885
8706
  };