@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.js CHANGED
@@ -113,8 +113,8 @@ var require_compat_dotall_s_transform = __commonJS({
113
113
  this._hasUFlag = ast.flags.includes("u");
114
114
  return true;
115
115
  },
116
- Char: function Char(path2) {
117
- var node = path2.node;
116
+ Char: function Char(path3) {
117
+ var node = path3.node;
118
118
  if (node.kind !== "meta" || node.value !== ".") {
119
119
  return;
120
120
  }
@@ -124,7 +124,7 @@ var require_compat_dotall_s_transform = __commonJS({
124
124
  toValue = "\\u{10FFFF}";
125
125
  toSymbol = "\u{10FFFF}";
126
126
  }
127
- path2.replace({
127
+ path3.replace({
128
128
  type: "CharacterClass",
129
129
  expressions: [{
130
130
  type: "ClassRange",
@@ -169,8 +169,8 @@ var require_compat_named_capturing_groups_transform = __commonJS({
169
169
  getExtra: function getExtra() {
170
170
  return this._groupNames;
171
171
  },
172
- Group: function Group(path2) {
173
- var node = path2.node;
172
+ Group: function Group(path3) {
173
+ var node = path3.node;
174
174
  if (!node.name) {
175
175
  return;
176
176
  }
@@ -178,8 +178,8 @@ var require_compat_named_capturing_groups_transform = __commonJS({
178
178
  delete node.name;
179
179
  delete node.nameRaw;
180
180
  },
181
- Backreference: function Backreference(path2) {
182
- var node = path2.node;
181
+ Backreference: function Backreference(path3) {
182
+ var node = path3.node;
183
183
  if (node.kind !== "name") {
184
184
  return;
185
185
  }
@@ -2021,8 +2021,8 @@ var require_node_path = __commonJS({
2021
2021
  value: function _rebuildIndex(parent, property) {
2022
2022
  var parentPath = NodePath2.getForNode(parent);
2023
2023
  for (var i = 0; i < parent[property].length; i++) {
2024
- var path2 = NodePath2.getForNode(parent[property][i], parentPath, property, i);
2025
- path2.index = i;
2024
+ var path3 = NodePath2.getForNode(parent[property][i], parentPath, property, i);
2025
+ path3.index = i;
2026
2026
  }
2027
2027
  }
2028
2028
  /**
@@ -2094,8 +2094,8 @@ var require_node_path = __commonJS({
2094
2094
  */
2095
2095
  }, {
2096
2096
  key: "hasEqualSource",
2097
- value: function hasEqualSource(path2) {
2098
- return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(path2.node, jsonSkipLoc);
2097
+ value: function hasEqualSource(path3) {
2098
+ return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(path3.node, jsonSkipLoc);
2099
2099
  }
2100
2100
  /**
2101
2101
  * JSON-encodes a node skipping location.
@@ -2146,18 +2146,18 @@ var require_node_path = __commonJS({
2146
2146
  if (!NodePath2.registry.has(node)) {
2147
2147
  NodePath2.registry.set(node, new NodePath2(node, parentPath, prop, index == -1 ? null : index));
2148
2148
  }
2149
- var path2 = NodePath2.registry.get(node);
2149
+ var path3 = NodePath2.registry.get(node);
2150
2150
  if (parentPath !== null) {
2151
- path2.parentPath = parentPath;
2152
- path2.parent = path2.parentPath.node;
2151
+ path3.parentPath = parentPath;
2152
+ path3.parent = path3.parentPath.node;
2153
2153
  }
2154
2154
  if (prop !== null) {
2155
- path2.property = prop;
2155
+ path3.property = prop;
2156
2156
  }
2157
2157
  if (index >= 0) {
2158
- path2.index = index;
2158
+ path3.index = index;
2159
2159
  }
2160
- return path2;
2160
+ return path3;
2161
2161
  }
2162
2162
  /**
2163
2163
  * Initializes the NodePath registry. The registry is a map from
@@ -2637,8 +2637,8 @@ var require_char_surrogate_pair_to_single_unicode_transform = __commonJS({
2637
2637
  shouldRun: function shouldRun(ast) {
2638
2638
  return ast.flags.includes("u");
2639
2639
  },
2640
- Char: function Char(path2) {
2641
- var node = path2.node;
2640
+ Char: function Char(path3) {
2641
+ var node = path3.node;
2642
2642
  if (node.kind !== "unicode" || !node.isSurrogatePair || isNaN(node.codePoint)) {
2643
2643
  return;
2644
2644
  }
@@ -2660,8 +2660,8 @@ var require_char_code_to_simple_char_transform = __commonJS({
2660
2660
  var DIGIT_0_CP = "0".codePointAt(0);
2661
2661
  var DIGIT_9_CP = "9".codePointAt(0);
2662
2662
  module2.exports = {
2663
- Char: function Char(path2) {
2664
- var node = path2.node, parent = path2.parent;
2663
+ Char: function Char(path3) {
2664
+ var node = path3.node, parent = path3.parent;
2665
2665
  if (isNaN(node.codePoint) || node.kind === "simple") {
2666
2666
  return;
2667
2667
  }
@@ -2684,7 +2684,7 @@ var require_char_code_to_simple_char_transform = __commonJS({
2684
2684
  if (needsEscape(symbol, parent.type)) {
2685
2685
  newChar.escaped = true;
2686
2686
  }
2687
- path2.replace(newChar);
2687
+ path3.replace(newChar);
2688
2688
  }
2689
2689
  };
2690
2690
  function isSimpleRange(classRange) {
@@ -2719,8 +2719,8 @@ var require_char_case_insensitive_lowercase_transform = __commonJS({
2719
2719
  shouldRun: function shouldRun(ast) {
2720
2720
  return ast.flags.includes("i");
2721
2721
  },
2722
- Char: function Char(path2) {
2723
- var node = path2.node, parent = path2.parent;
2722
+ Char: function Char(path3) {
2723
+ var node = path3.node, parent = path3.parent;
2724
2724
  if (isNaN(node.codePoint)) {
2725
2725
  return;
2726
2726
  }
@@ -2785,11 +2785,11 @@ var require_char_class_remove_duplicates_transform = __commonJS({
2785
2785
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-remove-duplicates-transform.js"(exports2, module2) {
2786
2786
  "use strict";
2787
2787
  module2.exports = {
2788
- CharacterClass: function CharacterClass(path2) {
2789
- var node = path2.node;
2788
+ CharacterClass: function CharacterClass(path3) {
2789
+ var node = path3.node;
2790
2790
  var sources = {};
2791
2791
  for (var i = 0; i < node.expressions.length; i++) {
2792
- var childPath = path2.getChild(i);
2792
+ var childPath = path3.getChild(i);
2793
2793
  var source = childPath.jsonEncode();
2794
2794
  if (sources.hasOwnProperty(source)) {
2795
2795
  childPath.remove();
@@ -2870,17 +2870,17 @@ var require_quantifiers_merge_transform = __commonJS({
2870
2870
  var _require = require_utils();
2871
2871
  var increaseQuantifierByOne = _require.increaseQuantifierByOne;
2872
2872
  module2.exports = {
2873
- Repetition: function Repetition(path2) {
2874
- var node = path2.node, parent = path2.parent;
2875
- if (parent.type !== "Alternative" || !path2.index) {
2873
+ Repetition: function Repetition(path3) {
2874
+ var node = path3.node, parent = path3.parent;
2875
+ if (parent.type !== "Alternative" || !path3.index) {
2876
2876
  return;
2877
2877
  }
2878
- var previousSibling = path2.getPreviousSibling();
2878
+ var previousSibling = path3.getPreviousSibling();
2879
2879
  if (!previousSibling) {
2880
2880
  return;
2881
2881
  }
2882
2882
  if (previousSibling.node.type === "Repetition") {
2883
- if (!previousSibling.getChild().hasEqualSource(path2.getChild())) {
2883
+ if (!previousSibling.getChild().hasEqualSource(path3.getChild())) {
2884
2884
  return;
2885
2885
  }
2886
2886
  var _extractFromTo = extractFromTo(previousSibling.node.quantifier), previousSiblingFrom = _extractFromTo.from, previousSiblingTo = _extractFromTo.to;
@@ -2900,7 +2900,7 @@ var require_quantifiers_merge_transform = __commonJS({
2900
2900
  }
2901
2901
  previousSibling.remove();
2902
2902
  } else {
2903
- if (!previousSibling.hasEqualSource(path2.getChild())) {
2903
+ if (!previousSibling.hasEqualSource(path3.getChild())) {
2904
2904
  return;
2905
2905
  }
2906
2906
  increaseQuantifierByOne(node.quantifier);
@@ -2936,38 +2936,38 @@ var require_quantifier_range_to_symbol_transform = __commonJS({
2936
2936
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/quantifier-range-to-symbol-transform.js"(exports2, module2) {
2937
2937
  "use strict";
2938
2938
  module2.exports = {
2939
- Quantifier: function Quantifier(path2) {
2940
- var node = path2.node;
2939
+ Quantifier: function Quantifier(path3) {
2940
+ var node = path3.node;
2941
2941
  if (node.kind !== "Range") {
2942
2942
  return;
2943
2943
  }
2944
- rewriteOpenZero(path2);
2945
- rewriteOpenOne(path2);
2946
- rewriteExactOne(path2);
2944
+ rewriteOpenZero(path3);
2945
+ rewriteOpenOne(path3);
2946
+ rewriteExactOne(path3);
2947
2947
  }
2948
2948
  };
2949
- function rewriteOpenZero(path2) {
2950
- var node = path2.node;
2949
+ function rewriteOpenZero(path3) {
2950
+ var node = path3.node;
2951
2951
  if (node.from !== 0 || node.to) {
2952
2952
  return;
2953
2953
  }
2954
2954
  node.kind = "*";
2955
2955
  delete node.from;
2956
2956
  }
2957
- function rewriteOpenOne(path2) {
2958
- var node = path2.node;
2957
+ function rewriteOpenOne(path3) {
2958
+ var node = path3.node;
2959
2959
  if (node.from !== 1 || node.to) {
2960
2960
  return;
2961
2961
  }
2962
2962
  node.kind = "+";
2963
2963
  delete node.from;
2964
2964
  }
2965
- function rewriteExactOne(path2) {
2966
- var node = path2.node;
2965
+ function rewriteExactOne(path3) {
2966
+ var node = path3.node;
2967
2967
  if (node.from !== 1 || node.to !== 1) {
2968
2968
  return;
2969
2969
  }
2970
- path2.parentPath.replace(path2.parentPath.node.expression);
2970
+ path3.parentPath.replace(path3.parentPath.node.expression);
2971
2971
  }
2972
2972
  }
2973
2973
  });
@@ -2977,13 +2977,13 @@ var require_char_class_classranges_to_chars_transform = __commonJS({
2977
2977
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-classranges-to-chars-transform.js"(exports2, module2) {
2978
2978
  "use strict";
2979
2979
  module2.exports = {
2980
- ClassRange: function ClassRange(path2) {
2981
- var node = path2.node;
2980
+ ClassRange: function ClassRange(path3) {
2981
+ var node = path3.node;
2982
2982
  if (node.from.codePoint === node.to.codePoint) {
2983
- path2.replace(node.from);
2983
+ path3.replace(node.from);
2984
2984
  } else if (node.from.codePoint === node.to.codePoint - 1) {
2985
- path2.getParent().insertChildAt(node.to, path2.index + 1);
2986
- path2.replace(node.from);
2985
+ path3.getParent().insertChildAt(node.to, path3.index + 1);
2986
+ path3.replace(node.from);
2987
2987
  }
2988
2988
  }
2989
2989
  };
@@ -3011,17 +3011,17 @@ var require_char_class_to_meta_transform = __commonJS({
3011
3011
  this._hasIFlag = ast.flags.includes("i");
3012
3012
  this._hasUFlag = ast.flags.includes("u");
3013
3013
  },
3014
- CharacterClass: function CharacterClass(path2) {
3015
- rewriteNumberRanges(path2);
3016
- rewriteWordRanges(path2, this._hasIFlag, this._hasUFlag);
3017
- rewriteWhitespaceRanges(path2);
3014
+ CharacterClass: function CharacterClass(path3) {
3015
+ rewriteNumberRanges(path3);
3016
+ rewriteWordRanges(path3, this._hasIFlag, this._hasUFlag);
3017
+ rewriteWhitespaceRanges(path3);
3018
3018
  }
3019
3019
  };
3020
- function rewriteNumberRanges(path2) {
3021
- var node = path2.node;
3020
+ function rewriteNumberRanges(path3) {
3021
+ var node = path3.node;
3022
3022
  node.expressions.forEach(function(expression, i) {
3023
3023
  if (isFullNumberRange(expression)) {
3024
- path2.getChild(i).replace({
3024
+ path3.getChild(i).replace({
3025
3025
  type: "Char",
3026
3026
  value: "\\d",
3027
3027
  kind: "meta"
@@ -3029,8 +3029,8 @@ var require_char_class_to_meta_transform = __commonJS({
3029
3029
  }
3030
3030
  });
3031
3031
  }
3032
- function rewriteWordRanges(path2, hasIFlag, hasUFlag) {
3033
- var node = path2.node;
3032
+ function rewriteWordRanges(path3, hasIFlag, hasUFlag) {
3033
+ var node = path3.node;
3034
3034
  var numberPath = null;
3035
3035
  var lowerCasePath = null;
3036
3036
  var upperCasePath = null;
@@ -3039,17 +3039,17 @@ var require_char_class_to_meta_transform = __commonJS({
3039
3039
  var u212aPath = null;
3040
3040
  node.expressions.forEach(function(expression, i) {
3041
3041
  if (isMetaChar(expression, "\\d")) {
3042
- numberPath = path2.getChild(i);
3042
+ numberPath = path3.getChild(i);
3043
3043
  } else if (isLowerCaseRange(expression)) {
3044
- lowerCasePath = path2.getChild(i);
3044
+ lowerCasePath = path3.getChild(i);
3045
3045
  } else if (isUpperCaseRange(expression)) {
3046
- upperCasePath = path2.getChild(i);
3046
+ upperCasePath = path3.getChild(i);
3047
3047
  } else if (isUnderscore(expression)) {
3048
- underscorePath = path2.getChild(i);
3048
+ underscorePath = path3.getChild(i);
3049
3049
  } else if (hasIFlag && hasUFlag && isCodePoint(expression, 383)) {
3050
- u017fPath = path2.getChild(i);
3050
+ u017fPath = path3.getChild(i);
3051
3051
  } else if (hasIFlag && hasUFlag && isCodePoint(expression, 8490)) {
3052
- u212aPath = path2.getChild(i);
3052
+ u212aPath = path3.getChild(i);
3053
3053
  }
3054
3054
  });
3055
3055
  if (numberPath && (lowerCasePath && upperCasePath || hasIFlag && (lowerCasePath || upperCasePath)) && underscorePath && (!hasUFlag || !hasIFlag || u017fPath && u212aPath)) {
@@ -3086,8 +3086,8 @@ var require_char_class_to_meta_transform = __commonJS({
3086
3086
  })), [function(node) {
3087
3087
  return node.type === "ClassRange" && isCodePoint(node.from, 8192) && isCodePoint(node.to, 8202);
3088
3088
  }]);
3089
- function rewriteWhitespaceRanges(path2) {
3090
- var node = path2.node;
3089
+ function rewriteWhitespaceRanges(path3) {
3090
+ var node = path3.node;
3091
3091
  if (node.expressions.length < whitespaceRangeTests.length || !whitespaceRangeTests.every(function(test) {
3092
3092
  return node.expressions.some(function(expression) {
3093
3093
  return test(expression);
@@ -3104,9 +3104,9 @@ var require_char_class_to_meta_transform = __commonJS({
3104
3104
  node.expressions.map(function(expression, i) {
3105
3105
  return whitespaceRangeTests.some(function(test) {
3106
3106
  return test(expression);
3107
- }) ? path2.getChild(i) : void 0;
3108
- }).filter(Boolean).forEach(function(path3) {
3109
- return path3.remove();
3107
+ }) ? path3.getChild(i) : void 0;
3108
+ }).filter(Boolean).forEach(function(path4) {
3109
+ return path4.remove();
3110
3110
  });
3111
3111
  }
3112
3112
  function isFullNumberRange(node) {
@@ -3139,9 +3139,9 @@ var require_char_class_to_single_char_transform = __commonJS({
3139
3139
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/char-class-to-single-char-transform.js"(exports2, module2) {
3140
3140
  "use strict";
3141
3141
  module2.exports = {
3142
- CharacterClass: function CharacterClass(path2) {
3143
- var node = path2.node;
3144
- if (node.expressions.length !== 1 || !hasAppropriateSiblings(path2) || !isAppropriateChar(node.expressions[0])) {
3142
+ CharacterClass: function CharacterClass(path3) {
3143
+ var node = path3.node;
3144
+ if (node.expressions.length !== 1 || !hasAppropriateSiblings(path3) || !isAppropriateChar(node.expressions[0])) {
3145
3145
  return;
3146
3146
  }
3147
3147
  var _node$expressions$ = node.expressions[0], value = _node$expressions$.value, kind = _node$expressions$.kind, escaped = _node$expressions$.escaped;
@@ -3151,7 +3151,7 @@ var require_char_class_to_single_char_transform = __commonJS({
3151
3151
  }
3152
3152
  value = getInverseMeta(value);
3153
3153
  }
3154
- path2.replace({
3154
+ path3.replace({
3155
3155
  type: "Char",
3156
3156
  value,
3157
3157
  kind,
@@ -3170,8 +3170,8 @@ var require_char_class_to_single_char_transform = __commonJS({
3170
3170
  function getInverseMeta(value) {
3171
3171
  return /[dws]/.test(value) ? value.toUpperCase() : value.toLowerCase();
3172
3172
  }
3173
- function hasAppropriateSiblings(path2) {
3174
- var parent = path2.parent, index = path2.index;
3173
+ function hasAppropriateSiblings(path3) {
3174
+ var parent = path3.parent, index = path3.index;
3175
3175
  if (parent.type !== "Alternative") {
3176
3176
  return true;
3177
3177
  }
@@ -3202,18 +3202,18 @@ var require_char_escape_unescape_transform = __commonJS({
3202
3202
  init: function init(ast) {
3203
3203
  this._hasXFlag = ast.flags.includes("x");
3204
3204
  },
3205
- Char: function Char(path2) {
3206
- var node = path2.node;
3205
+ Char: function Char(path3) {
3206
+ var node = path3.node;
3207
3207
  if (!node.escaped) {
3208
3208
  return;
3209
3209
  }
3210
- if (shouldUnescape(path2, this._hasXFlag)) {
3210
+ if (shouldUnescape(path3, this._hasXFlag)) {
3211
3211
  delete node.escaped;
3212
3212
  }
3213
3213
  }
3214
3214
  };
3215
- function shouldUnescape(path2, hasXFlag) {
3216
- var value = path2.node.value, index = path2.index, parent = path2.parent;
3215
+ function shouldUnescape(path3, hasXFlag) {
3216
+ var value = path3.node.value, index = path3.index, parent = path3.parent;
3217
3217
  if (parent.type !== "CharacterClass" && parent.type !== "ClassRange") {
3218
3218
  return !preservesEscape(value, index, parent, hasXFlag);
3219
3219
  }
@@ -3302,8 +3302,8 @@ var require_char_class_classranges_merge_transform = __commonJS({
3302
3302
  init: function init(ast) {
3303
3303
  this._hasIUFlags = ast.flags.includes("i") && ast.flags.includes("u");
3304
3304
  },
3305
- CharacterClass: function CharacterClass(path2) {
3306
- var node = path2.node;
3305
+ CharacterClass: function CharacterClass(path3) {
3306
+ var node = path3.node;
3307
3307
  var expressions = node.expressions;
3308
3308
  var metas = [];
3309
3309
  expressions.forEach(function(expression2) {
@@ -3521,8 +3521,8 @@ var require_disjunction_remove_duplicates_transform = __commonJS({
3521
3521
  var disjunctionToList = _require.disjunctionToList;
3522
3522
  var listToDisjunction = _require.listToDisjunction;
3523
3523
  module2.exports = {
3524
- Disjunction: function Disjunction(path2) {
3525
- var node = path2.node;
3524
+ Disjunction: function Disjunction(path3) {
3525
+ var node = path3.node;
3526
3526
  var uniqueNodesMap = {};
3527
3527
  var parts = disjunctionToList(node).filter(function(part) {
3528
3528
  var encoded = part ? NodePath.getForNode(part).jsonEncode() : "null";
@@ -3532,7 +3532,7 @@ var require_disjunction_remove_duplicates_transform = __commonJS({
3532
3532
  uniqueNodesMap[encoded] = part;
3533
3533
  return true;
3534
3534
  });
3535
- path2.replace(listToDisjunction(parts));
3535
+ path3.replace(listToDisjunction(parts));
3536
3536
  }
3537
3537
  };
3538
3538
  }
@@ -3543,8 +3543,8 @@ var require_group_single_chars_to_char_class = __commonJS({
3543
3543
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/group-single-chars-to-char-class.js"(exports2, module2) {
3544
3544
  "use strict";
3545
3545
  module2.exports = {
3546
- Disjunction: function Disjunction(path2) {
3547
- var node = path2.node, parent = path2.parent;
3546
+ Disjunction: function Disjunction(path3) {
3547
+ var node = path3.node, parent = path3.parent;
3548
3548
  if (!handlers[parent.type]) {
3549
3549
  return;
3550
3550
  }
@@ -3558,20 +3558,20 @@ var require_group_single_chars_to_char_class = __commonJS({
3558
3558
  return charset.get(key);
3559
3559
  })
3560
3560
  };
3561
- handlers[parent.type](path2.getParent(), characterClass);
3561
+ handlers[parent.type](path3.getParent(), characterClass);
3562
3562
  }
3563
3563
  };
3564
3564
  var handlers = {
3565
- RegExp: function RegExp2(path2, characterClass) {
3566
- var node = path2.node;
3565
+ RegExp: function RegExp2(path3, characterClass) {
3566
+ var node = path3.node;
3567
3567
  node.body = characterClass;
3568
3568
  },
3569
- Group: function Group(path2, characterClass) {
3570
- var node = path2.node;
3569
+ Group: function Group(path3, characterClass) {
3570
+ var node = path3.node;
3571
3571
  if (node.capturing) {
3572
3572
  node.expression = characterClass;
3573
3573
  } else {
3574
- path2.replace(characterClass);
3574
+ path3.replace(characterClass);
3575
3575
  }
3576
3576
  }
3577
3577
  };
@@ -3605,16 +3605,16 @@ var require_remove_empty_group_transform = __commonJS({
3605
3605
  "../../node_modules/.pnpm/regexp-tree@0.1.27/node_modules/regexp-tree/dist/optimizer/transforms/remove-empty-group-transform.js"(exports2, module2) {
3606
3606
  "use strict";
3607
3607
  module2.exports = {
3608
- Group: function Group(path2) {
3609
- var node = path2.node, parent = path2.parent;
3610
- var childPath = path2.getChild();
3608
+ Group: function Group(path3) {
3609
+ var node = path3.node, parent = path3.parent;
3610
+ var childPath = path3.getChild();
3611
3611
  if (node.capturing || childPath) {
3612
3612
  return;
3613
3613
  }
3614
3614
  if (parent.type === "Repetition") {
3615
- path2.getParent().replace(node);
3615
+ path3.getParent().replace(node);
3616
3616
  } else if (parent.type !== "RegExp") {
3617
- path2.remove();
3617
+ path3.remove();
3618
3618
  }
3619
3619
  }
3620
3620
  };
@@ -3636,13 +3636,13 @@ var require_ungroup_transform = __commonJS({
3636
3636
  }
3637
3637
  }
3638
3638
  module2.exports = {
3639
- Group: function Group(path2) {
3640
- var node = path2.node, parent = path2.parent;
3641
- var childPath = path2.getChild();
3639
+ Group: function Group(path3) {
3640
+ var node = path3.node, parent = path3.parent;
3641
+ var childPath = path3.getChild();
3642
3642
  if (node.capturing || !childPath) {
3643
3643
  return;
3644
3644
  }
3645
- if (!hasAppropriateSiblings(path2)) {
3645
+ if (!hasAppropriateSiblings(path3)) {
3646
3646
  return;
3647
3647
  }
3648
3648
  if (childPath.node.type === "Disjunction" && parent.type !== "RegExp") {
@@ -3652,20 +3652,20 @@ var require_ungroup_transform = __commonJS({
3652
3652
  return;
3653
3653
  }
3654
3654
  if (childPath.node.type === "Alternative") {
3655
- var parentPath = path2.getParent();
3655
+ var parentPath = path3.getParent();
3656
3656
  if (parentPath.node.type === "Alternative") {
3657
3657
  parentPath.replace({
3658
3658
  type: "Alternative",
3659
- expressions: [].concat(_toConsumableArray(parent.expressions.slice(0, path2.index)), _toConsumableArray(childPath.node.expressions), _toConsumableArray(parent.expressions.slice(path2.index + 1)))
3659
+ expressions: [].concat(_toConsumableArray(parent.expressions.slice(0, path3.index)), _toConsumableArray(childPath.node.expressions), _toConsumableArray(parent.expressions.slice(path3.index + 1)))
3660
3660
  });
3661
3661
  }
3662
3662
  } else {
3663
- path2.replace(childPath.node);
3663
+ path3.replace(childPath.node);
3664
3664
  }
3665
3665
  }
3666
3666
  };
3667
- function hasAppropriateSiblings(path2) {
3668
- var parent = path2.parent, index = path2.index;
3667
+ function hasAppropriateSiblings(path3) {
3668
+ var parent = path3.parent, index = path3.index;
3669
3669
  if (parent.type !== "Alternative") {
3670
3670
  return true;
3671
3671
  }
@@ -3702,22 +3702,22 @@ var require_combine_repeating_patterns_transform = __commonJS({
3702
3702
  var _require = require_utils();
3703
3703
  var increaseQuantifierByOne = _require.increaseQuantifierByOne;
3704
3704
  module2.exports = {
3705
- Alternative: function Alternative(path2) {
3706
- var node = path2.node;
3705
+ Alternative: function Alternative(path3) {
3706
+ var node = path3.node;
3707
3707
  var index = 1;
3708
3708
  while (index < node.expressions.length) {
3709
- var child = path2.getChild(index);
3710
- index = Math.max(1, combineRepeatingPatternLeft(path2, child, index));
3709
+ var child = path3.getChild(index);
3710
+ index = Math.max(1, combineRepeatingPatternLeft(path3, child, index));
3711
3711
  if (index >= node.expressions.length) {
3712
3712
  break;
3713
3713
  }
3714
- child = path2.getChild(index);
3715
- index = Math.max(1, combineWithPreviousRepetition(path2, child, index));
3714
+ child = path3.getChild(index);
3715
+ index = Math.max(1, combineWithPreviousRepetition(path3, child, index));
3716
3716
  if (index >= node.expressions.length) {
3717
3717
  break;
3718
3718
  }
3719
- child = path2.getChild(index);
3720
- index = Math.max(1, combineRepetitionWithPrevious(path2, child, index));
3719
+ child = path3.getChild(index);
3720
+ index = Math.max(1, combineRepetitionWithPrevious(path3, child, index));
3721
3721
  index++;
3722
3722
  }
3723
3723
  }
@@ -5758,14 +5758,17 @@ var require_safe_regex = __commonJS({
5758
5758
  // src/index.ts
5759
5759
  var index_exports = {};
5760
5760
  __export(index_exports, {
5761
+ ApprovedSpecsValidator: () => ApprovedSpecsValidator,
5761
5762
  ClientGenerator: () => ClientGenerator,
5762
5763
  CollectionProviderGenerator: () => CollectionProviderGenerator,
5764
+ EndpointFilter: () => EndpointFilter,
5763
5765
  OpenAPICollectionProvider: () => OpenAPICollectionProvider,
5764
5766
  OpenAPIParser: () => OpenAPIParser,
5765
5767
  OpenAPIPlugin: () => OpenAPIPlugin,
5766
5768
  SchemaResolver: () => SchemaResolver,
5767
5769
  TypeGenerator: () => TypeGenerator,
5768
5770
  ZodSchemaGenerator: () => ZodSchemaGenerator,
5771
+ createOpenAPIFetcher: () => createOpenAPIFetcher,
5769
5772
  createOpenAPIPlugin: () => createOpenAPIPlugin
5770
5773
  });
5771
5774
  module.exports = __toCommonJS(index_exports);
@@ -5863,8 +5866,8 @@ var SchemaResolver = class {
5863
5866
  * @param responseCode - Response code (default: '200')
5864
5867
  * @returns Schema object or undefined
5865
5868
  */
5866
- getResponseSchema(path2, method, responseCode = "200") {
5867
- const pathItem = this.document.paths?.[path2];
5869
+ getResponseSchema(path3, method, responseCode = "200") {
5870
+ const pathItem = this.document.paths?.[path3];
5868
5871
  if (!pathItem) return void 0;
5869
5872
  const operation = pathItem[method];
5870
5873
  if (!operation?.responses) return void 0;
@@ -5890,14 +5893,14 @@ var SchemaResolver = class {
5890
5893
  */
5891
5894
  getAllEndpoints() {
5892
5895
  const endpoints = [];
5893
- for (const [path2, pathItem] of Object.entries(this.document.paths || {})) {
5896
+ for (const [path3, pathItem] of Object.entries(this.document.paths || {})) {
5894
5897
  if (!pathItem) continue;
5895
5898
  const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
5896
5899
  for (const method of methods) {
5897
5900
  const operation = pathItem[method];
5898
5901
  if (operation) {
5899
5902
  endpoints.push({
5900
- path: path2,
5903
+ path: path3,
5901
5904
  method,
5902
5905
  operationId: operation.operationId,
5903
5906
  summary: operation.summary,
@@ -5921,20 +5924,31 @@ var ZodSchemaGenerator = class {
5921
5924
  * @returns Zod schema code as string
5922
5925
  */
5923
5926
  generate(schema, options = {}) {
5924
- const { schemaName = "GeneratedSchema" } = options;
5927
+ const { schemaName = "GeneratedSchema", bare = false } = options;
5925
5928
  const zodSchemaCode = this.schemaToZod(schema);
5926
- return `import { z } from 'zod';
5927
- import isSafe from 'safe-regex';
5928
-
5929
- export const ${schemaName} = ${zodSchemaCode};
5929
+ const body = `export const ${schemaName} = ${zodSchemaCode};
5930
5930
 
5931
5931
  export type ${schemaName}Type = z.infer<typeof ${schemaName}>;
5932
5932
  `;
5933
+ if (bare) {
5934
+ return body;
5935
+ }
5936
+ return `import { z } from 'zod';
5937
+ import isSafe from 'safe-regex';
5938
+
5939
+ ${body}`;
5933
5940
  }
5934
5941
  /**
5935
5942
  * Convert OpenAPI schema to Zod schema code
5936
5943
  */
5937
5944
  schemaToZod(schema, depth = 0) {
5945
+ if ("$ref" in schema && schema.$ref) {
5946
+ const refPath = schema.$ref;
5947
+ console.warn(
5948
+ `\u26A0\uFE0F Unresolved $ref encountered: ${refPath} \u2014 generating z.unknown()`
5949
+ );
5950
+ return `z.unknown() /* unresolved $ref: ${refPath} */`;
5951
+ }
5938
5952
  const isNullable = "nullable" in schema && schema.nullable === true;
5939
5953
  let baseSchema = this.schemaTypeToZod(schema);
5940
5954
  if (schema.description) {
@@ -5963,6 +5977,12 @@ export type ${schemaName}Type = z.infer<typeof ${schemaName}>;
5963
5977
  return this.handleComposition(schema.oneOf, "discriminatedUnion");
5964
5978
  }
5965
5979
  const type = schema.type;
5980
+ if (!type && schema.properties) {
5981
+ return this.objectToZod(schema);
5982
+ }
5983
+ if (!type && schema.additionalProperties !== void 0) {
5984
+ return this.objectToZod(schema);
5985
+ }
5966
5986
  switch (type) {
5967
5987
  case "string":
5968
5988
  return this.stringToZod(schema);
@@ -6217,7 +6237,8 @@ var CollectionProviderGenerator = class {
6217
6237
  const {
6218
6238
  providerName = this.generateProviderName(config),
6219
6239
  baseUrl = this.getBaseUrl(),
6220
- auth
6240
+ auth,
6241
+ bare = false
6221
6242
  } = options;
6222
6243
  const { endpoint, slugField, method = "get", name } = config;
6223
6244
  const collectionName = name || this.sanitizePath(endpoint);
@@ -6227,9 +6248,7 @@ var CollectionProviderGenerator = class {
6227
6248
  throw new Error(`No response schema found for ${method.toUpperCase()} ${endpoint}`);
6228
6249
  }
6229
6250
  const isArray = schema.type === "array";
6230
- const itemSchema = isArray ? schema.items : schema;
6231
6251
  const schemaName = `${this.capitalize(collectionName)}Schema`;
6232
- const typeName = `${this.capitalize(collectionName)}`;
6233
6252
  return this.generateProviderCode({
6234
6253
  providerName,
6235
6254
  collectionName,
@@ -6239,8 +6258,8 @@ var CollectionProviderGenerator = class {
6239
6258
  baseUrl,
6240
6259
  auth,
6241
6260
  schemaName,
6242
- typeName,
6243
- isArray
6261
+ isArray,
6262
+ bare
6244
6263
  });
6245
6264
  }
6246
6265
  /**
@@ -6256,14 +6275,17 @@ var CollectionProviderGenerator = class {
6256
6275
  baseUrl,
6257
6276
  auth,
6258
6277
  schemaName,
6259
- typeName,
6260
- isArray
6278
+ isArray,
6279
+ bare
6261
6280
  } = params;
6262
6281
  const authHeader = this.generateAuthHeader(auth);
6263
- return `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
6264
- import { ${schemaName} } from './schemas';
6282
+ const arraySchemaName = `${schemaName.replace(/Schema$/, "")}ArraySchema`;
6283
+ const validationSchema = isArray ? arraySchemaName : schemaName;
6284
+ const imports = bare ? "" : `import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
6285
+ import { ${isArray ? arraySchemaName : schemaName} } from './schemas';
6265
6286
 
6266
- /**
6287
+ `;
6288
+ return `${imports}/**
6267
6289
  * CollectionProvider for ${collectionName}
6268
6290
  *
6269
6291
  * Generated from OpenAPI endpoint: ${method.toUpperCase()} ${endpoint}
@@ -6314,7 +6336,7 @@ export class ${providerName} implements CollectionProvider {
6314
6336
  const data = await response.json();
6315
6337
 
6316
6338
  // Validate response with Zod schema
6317
- const validated = ${isArray ? schemaName : `[${schemaName}]`}.parse(data);
6339
+ const validated = ${validationSchema}.parse(data);
6318
6340
 
6319
6341
  // Convert to CollectionItem format
6320
6342
  return ${isArray ? "validated" : "[validated]"}.map((item: any) => this.toCollectionItem(item));
@@ -6425,8 +6447,8 @@ export class ${providerName} implements CollectionProvider {
6425
6447
  /**
6426
6448
  * Sanitize path to valid identifier
6427
6449
  */
6428
- sanitizePath(path2) {
6429
- return path2.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
6450
+ sanitizePath(path3) {
6451
+ return path3.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
6430
6452
  }
6431
6453
  /**
6432
6454
  * Capitalize string
@@ -6451,10 +6473,12 @@ var ClientGenerator = class {
6451
6473
  this.resolver = new SchemaResolver(document);
6452
6474
  this.schemaMapping = schemaMapping;
6453
6475
  this.requiredSchemas = /* @__PURE__ */ new Set();
6476
+ this.generatedRequestSchemas = /* @__PURE__ */ new Set();
6454
6477
  }
6455
6478
  resolver;
6456
6479
  schemaMapping;
6457
6480
  requiredSchemas;
6481
+ generatedRequestSchemas;
6458
6482
  /**
6459
6483
  * Generate typed API client code from OpenAPI document
6460
6484
  */
@@ -6472,6 +6496,7 @@ var ClientGenerator = class {
6472
6496
  throw new Error("No endpoints found in OpenAPI document");
6473
6497
  }
6474
6498
  this.requiredSchemas.clear();
6499
+ this.generatedRequestSchemas.clear();
6475
6500
  let code = this.generateImports(!!schemaMapping, validateResponses);
6476
6501
  code += "\n";
6477
6502
  if (schemaMapping) {
@@ -6546,6 +6571,7 @@ var ClientGenerator = class {
6546
6571
  code += `export const ${schemaName} = ${schemaCode};
6547
6572
 
6548
6573
  `;
6574
+ this.generatedRequestSchemas.add(schemaName);
6549
6575
  }
6550
6576
  }
6551
6577
  return code;
@@ -6855,10 +6881,10 @@ ${paramSchemas.join(",\n")}
6855
6881
  continue;
6856
6882
  }
6857
6883
  const typeName = this.getOperationTypeName(endpoint);
6858
- if (mapping.requestSchema) {
6859
- this.addRequiredSchema(mapping.requestSchema);
6860
- code += `export type ${typeName}Request = z.infer<typeof ${mapping.requestSchema}>;
6861
- `;
6884
+ const requestSchemaName = mapping.requestSchema || typeName + "RequestSchema";
6885
+ if (this.generatedRequestSchemas.has(requestSchemaName)) {
6886
+ this.addRequiredSchema(requestSchemaName);
6887
+ code += "export type " + typeName + "Request = z.infer<typeof " + requestSchemaName + ">;\n";
6862
6888
  }
6863
6889
  this.addRequiredSchema(mapping.responseSchema);
6864
6890
  code += `export type ${typeName}Response = z.infer<typeof schemas.${mapping.responseSchema}>;
@@ -7197,7 +7223,7 @@ ${paramSchemas.join(",\n")}
7197
7223
  * Generate a method for an endpoint with Zod validation (NEW)
7198
7224
  */
7199
7225
  generateMethodWithValidation(endpoint, includeJsDoc, schemaMapping) {
7200
- const { operation, path: path2, method } = endpoint;
7226
+ const { operation, path: path3, method } = endpoint;
7201
7227
  const methodName = this.getMethodName(endpoint);
7202
7228
  const typeName = this.getOperationTypeName(endpoint);
7203
7229
  const operationId = endpoint.operationId || methodName;
@@ -7257,7 +7283,7 @@ ${paramSchemas.join(",\n")}
7257
7283
  if (pathParams.length > 0) {
7258
7284
  code += ` // Build URL with path parameters
7259
7285
  `;
7260
- code += ` let url = this.baseUrl + '${path2}'
7286
+ code += ` let url = this.baseUrl + '${path3}'
7261
7287
  `;
7262
7288
  for (const param of pathParams) {
7263
7289
  code += ` .replace('{${param.name}}', encodeURIComponent(String(request.path.${param.name})))
@@ -7267,7 +7293,7 @@ ${paramSchemas.join(",\n")}
7267
7293
 
7268
7294
  `;
7269
7295
  } else {
7270
- code += ` const url = this.baseUrl + '${path2}';
7296
+ code += ` const url = this.baseUrl + '${path3}';
7271
7297
 
7272
7298
  `;
7273
7299
  }
@@ -7279,7 +7305,7 @@ ${paramSchemas.join(",\n")}
7279
7305
  code += ` if (request.query) {
7280
7306
  `;
7281
7307
  for (const param of queryParams) {
7282
- code += ` if (request.query.${param.name} !== undefined) {
7308
+ code += ` if (request.query.${param.name} != null) {
7283
7309
  `;
7284
7310
  code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
7285
7311
  `;
@@ -7305,7 +7331,7 @@ ${paramSchemas.join(",\n")}
7305
7331
  code += ` method: '${method.toUpperCase()}',
7306
7332
  `;
7307
7333
  if (hasBody) {
7308
- code += ` body: request.body ? JSON.stringify(request.body) : undefined,
7334
+ code += ` body: request.body ? JSON.stringify(request.body) : null,
7309
7335
  `;
7310
7336
  }
7311
7337
  if (headerParams.length > 0) {
@@ -7363,7 +7389,7 @@ ${paramSchemas.join(",\n")}
7363
7389
  * Generate a method for an endpoint (LEGACY)
7364
7390
  */
7365
7391
  generateMethodLegacy(endpoint, includeJsDoc) {
7366
- const { operation, path: path2, method } = endpoint;
7392
+ const { operation, path: path3, method } = endpoint;
7367
7393
  const methodName = this.getMethodName(endpoint);
7368
7394
  const typeName = this.getOperationTypeName(endpoint);
7369
7395
  const params = this.getOperationParameters(operation);
@@ -7399,7 +7425,7 @@ ${paramSchemas.join(",\n")}
7399
7425
  if (pathParams.length > 0) {
7400
7426
  code += ` // Build URL with path parameters
7401
7427
  `;
7402
- code += ` let url = this.baseUrl + '${path2}'
7428
+ code += ` let url = this.baseUrl + '${path3}'
7403
7429
  `;
7404
7430
  for (const param of pathParams) {
7405
7431
  code += ` .replace('{${param.name}}', encodeURIComponent(String(request.path.${param.name})))
@@ -7409,7 +7435,7 @@ ${paramSchemas.join(",\n")}
7409
7435
 
7410
7436
  `;
7411
7437
  } else {
7412
- code += ` const url = this.baseUrl + '${path2}';
7438
+ code += ` const url = this.baseUrl + '${path3}';
7413
7439
 
7414
7440
  `;
7415
7441
  }
@@ -7422,7 +7448,7 @@ ${paramSchemas.join(",\n")}
7422
7448
  code += ` if (request.query) {
7423
7449
  `;
7424
7450
  for (const param of queryParams) {
7425
- code += ` if (request.query.${param.name} !== undefined) {
7451
+ code += ` if (request.query.${param.name} != null) {
7426
7452
  `;
7427
7453
  code += ` searchParams.append('${param.name}', String(request.query.${param.name}));
7428
7454
  `;
@@ -7446,7 +7472,7 @@ ${paramSchemas.join(",\n")}
7446
7472
  code += ` method: '${method.toUpperCase()}',
7447
7473
  `;
7448
7474
  if (hasBody) {
7449
- code += ` body: request.body ? JSON.stringify(request.body) : undefined,
7475
+ code += ` body: request.body ? JSON.stringify(request.body) : null,
7450
7476
  `;
7451
7477
  }
7452
7478
  const headerParams = params.filter((p) => p.in === "header");
@@ -7493,7 +7519,7 @@ ${paramSchemas.join(",\n")}
7493
7519
  const errorBody = await response.text().catch(() => null);
7494
7520
 
7495
7521
  // \u2705 SECURITY FIX: Safe JSON parsing with fallback
7496
- let parsedError: unknown = undefined;
7522
+ let parsedError: unknown;
7497
7523
  if (errorBody) {
7498
7524
  try {
7499
7525
  parsedError = JSON.parse(errorBody);
@@ -7515,7 +7541,7 @@ ${paramSchemas.join(",\n")}
7515
7541
 
7516
7542
  // Handle empty responses (204 No Content, etc.)
7517
7543
  if (response.status === 204 || response.headers.get('content-length') === '0') {
7518
- return undefined as T;
7544
+ return null as unknown as T;
7519
7545
  }
7520
7546
 
7521
7547
  // \u2705 SECURITY FIX: Validate Content-Type before parsing JSON
@@ -7541,14 +7567,14 @@ ${paramSchemas.join(",\n")}
7541
7567
  */
7542
7568
  getAllEndpoints() {
7543
7569
  const endpoints = [];
7544
- for (const [path2, pathItem] of Object.entries(this.document.paths || {})) {
7570
+ for (const [path3, pathItem] of Object.entries(this.document.paths || {})) {
7545
7571
  if (!pathItem) continue;
7546
7572
  const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
7547
7573
  for (const method of methods) {
7548
7574
  const operation = pathItem[method];
7549
7575
  if (operation) {
7550
7576
  endpoints.push({
7551
- path: path2,
7577
+ path: path3,
7552
7578
  method,
7553
7579
  operationId: operation.operationId,
7554
7580
  summary: operation.summary,
@@ -7558,7 +7584,32 @@ ${paramSchemas.join(",\n")}
7558
7584
  }
7559
7585
  }
7560
7586
  }
7561
- return endpoints;
7587
+ return this.deduplicateOperationIds(endpoints);
7588
+ }
7589
+ /**
7590
+ * Deduplicate colliding operationIds by appending a path-based suffix.
7591
+ *
7592
+ * Real-world specs (e.g. SAM.gov) reuse the same operationId across
7593
+ * versioned paths. We make each one unique so the generated client
7594
+ * has no duplicate method names.
7595
+ */
7596
+ deduplicateOperationIds(endpoints) {
7597
+ const seen = /* @__PURE__ */ new Map();
7598
+ return endpoints.map((ep) => {
7599
+ if (!ep.operationId) return ep;
7600
+ const count = seen.get(ep.operationId) ?? 0;
7601
+ seen.set(ep.operationId, count + 1);
7602
+ if (count === 0) return ep;
7603
+ const suffix = this.sanitizePath(ep.path);
7604
+ return { ...ep, operationId: `${ep.operationId}_${suffix}` };
7605
+ });
7606
+ }
7607
+ /**
7608
+ * Turn an API path into a valid, readable identifier suffix.
7609
+ * e.g. '/entity-information/v4/download-entities' -> 'entityInformationV4DownloadEntities'
7610
+ */
7611
+ sanitizePath(path3) {
7612
+ return path3.split("/").filter((seg) => seg && !seg.startsWith("{")).join("-").replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()).replace(/^[A-Z]/, (chr) => chr.toLowerCase());
7562
7613
  }
7563
7614
  /**
7564
7615
  * Get parameters for an operation
@@ -7654,12 +7705,556 @@ var OpenAPICollectionProvider = class {
7654
7705
  };
7655
7706
 
7656
7707
  // src/prebuild/OpenAPIPlugin.ts
7708
+ var import_fs3 = __toESM(require("fs"));
7709
+ var import_path2 = __toESM(require("path"));
7710
+
7711
+ // src/utils/EndpointFilter.ts
7712
+ var EndpointFilter = class {
7713
+ include;
7714
+ exclude;
7715
+ constructor(filter) {
7716
+ if (filter?.include !== void 0 && filter.include.length > 0) {
7717
+ this.include = filter.include;
7718
+ } else {
7719
+ this.include = ["/**"];
7720
+ }
7721
+ this.exclude = filter?.exclude ?? [];
7722
+ }
7723
+ /**
7724
+ * Check if a path should be included in code generation.
7725
+ *
7726
+ * Logic:
7727
+ * 1. If path matches any exclude pattern → excluded
7728
+ * 2. If path matches any include pattern → included
7729
+ * 3. Otherwise → excluded (default deny)
7730
+ */
7731
+ matches(path3) {
7732
+ const normalizedPath = path3.startsWith("/") ? path3 : `/${path3}`;
7733
+ for (const pattern of this.exclude) {
7734
+ if (this.matchPattern(normalizedPath, pattern)) {
7735
+ return false;
7736
+ }
7737
+ }
7738
+ for (const pattern of this.include) {
7739
+ if (this.matchPattern(normalizedPath, pattern)) {
7740
+ return true;
7741
+ }
7742
+ }
7743
+ return false;
7744
+ }
7745
+ /**
7746
+ * Match path against a pattern.
7747
+ */
7748
+ matchPattern(path3, pattern) {
7749
+ const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
7750
+ if (normalizedPattern === "/") {
7751
+ return true;
7752
+ }
7753
+ const regex = this.globToRegex(normalizedPattern);
7754
+ if (regex.test(path3)) {
7755
+ return true;
7756
+ }
7757
+ if (!normalizedPattern.endsWith("*")) {
7758
+ const patternSegments = normalizedPattern.split("/");
7759
+ const pathSegments = path3.split("/");
7760
+ if (pathSegments.length >= patternSegments.length) {
7761
+ let matches = true;
7762
+ for (let i = 0; i < patternSegments.length; i++) {
7763
+ const pSeg = patternSegments[i];
7764
+ if (pSeg === "*" || pSeg === "**") {
7765
+ continue;
7766
+ }
7767
+ if (pSeg.startsWith("{") && pSeg.endsWith("}")) {
7768
+ continue;
7769
+ }
7770
+ if (pSeg !== pathSegments[i]) {
7771
+ matches = false;
7772
+ break;
7773
+ }
7774
+ }
7775
+ if (matches) {
7776
+ return true;
7777
+ }
7778
+ }
7779
+ }
7780
+ return false;
7781
+ }
7782
+ /**
7783
+ * Convert a glob-like pattern to a RegExp.
7784
+ *
7785
+ * Pattern semantics:
7786
+ * - `*` = single path segment (no slashes), minimum 1 character
7787
+ * - `**` = multiple path segments (includes slashes), zero or more segments
7788
+ * - `{param}` = single path segment parameter
7789
+ * - Exact match segments are literal strings
7790
+ */
7791
+ globToRegex(pattern) {
7792
+ let result = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
7793
+ result = result.replace(/\*\*/g, "\u27EADOUBLESTAR\u27EB");
7794
+ result = result.replace(/\*/g, "\u27EASTAR\u27EB");
7795
+ if (result.startsWith("\u27EADOUBLESTAR\u27EB")) {
7796
+ result = ".*" + result.slice("\u27EADOUBLESTAR\u27EB".length);
7797
+ } else if (result.endsWith("\u27EADOUBLESTAR\u27EB") && result.endsWith("/\u27EADOUBLESTAR\u27EB")) {
7798
+ result = result.slice(0, -"\u27EADOUBLESTAR\u27EB".length - 1) + "(?:/.*)?";
7799
+ } else {
7800
+ result = result.replace(/⟪DOUBLESTAR⟫/g, "(?:/.*)?");
7801
+ }
7802
+ result = result.replace(/⟪STAR⟫/g, "[^/]+");
7803
+ result = result.replace(/\{[^}]+\}/g, "[^/]+");
7804
+ return new RegExp(`^${result}$`);
7805
+ }
7806
+ };
7807
+
7808
+ // src/utils/ApprovedSpecsValidator.ts
7809
+ var import_crypto = __toESM(require("crypto"));
7657
7810
  var import_fs = __toESM(require("fs"));
7811
+ var import_https = __toESM(require("https"));
7812
+ var import_http = __toESM(require("http"));
7813
+ var import_url = require("url");
7658
7814
  var import_path = __toESM(require("path"));
7815
+ var import_fs2 = require("fs");
7816
+ var import_net = require("net");
7817
+ var ApprovedSpecsValidator = class {
7818
+ allowlist;
7819
+ cache = /* @__PURE__ */ new Map();
7820
+ skipHashVerification;
7821
+ ALLOWED_DIRS;
7822
+ MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
7823
+ // 10MB - prevents memory exhaustion attacks
7824
+ /**
7825
+ * Create a new ApprovedSpecsValidator
7826
+ *
7827
+ * @param config - Security configuration from stackwright.yml
7828
+ * @param skipHashVerification - Skip hash check (for testing/development)
7829
+ */
7830
+ constructor(config, skipHashVerification = false) {
7831
+ this.allowlist = config.allowlist || [];
7832
+ this.skipHashVerification = skipHashVerification;
7833
+ this.ALLOWED_DIRS = this.buildAllowedDirs();
7834
+ }
7835
+ /**
7836
+ * Build list of allowed directories for path traversal prevention.
7837
+ * Defaults to cwd, specs subdir, and .stackwright cache dir.
7838
+ */
7839
+ buildAllowedDirs() {
7840
+ const cwd = process.cwd();
7841
+ return [cwd, import_path.default.join(cwd, "specs"), import_path.default.join(cwd, ".stackwright")];
7842
+ }
7843
+ /**
7844
+ * Validate that a file path is within allowed directories (path traversal prevention).
7845
+ * Uses realpathSync to resolve symlinks and prevent symlink traversal attacks.
7846
+ *
7847
+ * @param filePath - File path to validate
7848
+ * @returns true if path is allowed, false otherwise
7849
+ */
7850
+ isPathAllowed(filePath) {
7851
+ try {
7852
+ const realPath = (0, import_fs2.realpathSync)(filePath);
7853
+ return this.ALLOWED_DIRS.some((dir) => {
7854
+ const realDir = (0, import_fs2.realpathSync)(dir);
7855
+ return realPath.startsWith(realDir + import_path.default.sep) || realPath === realDir;
7856
+ });
7857
+ } catch {
7858
+ return false;
7859
+ }
7860
+ }
7861
+ /**
7862
+ * Check if a URL/path is a path traversal attempt.
7863
+ * This is checked BEFORE the allowlist to prevent bypassing path security.
7864
+ *
7865
+ * @param specUrl - URL or path to check
7866
+ * @returns true if this is a path traversal attempt
7867
+ */
7868
+ isPathTraversalAttempt(specUrl) {
7869
+ if (specUrl.startsWith("file://")) {
7870
+ return true;
7871
+ }
7872
+ if (import_fs.default.existsSync(specUrl)) {
7873
+ return !this.isPathAllowed(specUrl);
7874
+ }
7875
+ if (!specUrl.startsWith("http://") && !specUrl.startsWith("https://")) {
7876
+ if (specUrl.includes("../") || specUrl.includes("..\\")) {
7877
+ return true;
7878
+ }
7879
+ }
7880
+ return false;
7881
+ }
7882
+ /**
7883
+ * Validate that a hex string is a valid SHA-256 hash (64 hex characters).
7884
+ *
7885
+ * @param hash - String to validate
7886
+ * @returns true if valid SHA-256 hex format
7887
+ */
7888
+ isValidHex(hash) {
7889
+ return /^[a-fA-F0-9]{64}$/.test(hash);
7890
+ }
7891
+ /**
7892
+ * Validate that a redirect URL is safe (SSRF protection).
7893
+ * Blocks:
7894
+ * - HTTPS → HTTP downgrades
7895
+ * - Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 127.x.x.x)
7896
+ * - Localhost and loopback addresses
7897
+ * - Cloud metadata endpoints
7898
+ * - IPv6 private/link-local addresses
7899
+ *
7900
+ * @param location - Redirect location URL
7901
+ * @param originalProtocol - Protocol of the original request
7902
+ * @returns true if redirect is safe, false if it should be blocked
7903
+ */
7904
+ isRedirectSafe(location, originalProtocol) {
7905
+ try {
7906
+ const redirectUrl = new import_url.URL(location);
7907
+ if (redirectUrl.protocol === "http:" && originalProtocol === "https:") {
7908
+ return false;
7909
+ }
7910
+ const blockedPatterns = [
7911
+ "localhost",
7912
+ "127.0.0.1",
7913
+ "::1",
7914
+ "0.0.0.0",
7915
+ "169.254.169.254",
7916
+ // AWS/GCP/Azure metadata
7917
+ ".metadata.google.internal",
7918
+ // GCP metadata
7919
+ "metadata.google.internal",
7920
+ "metadata.internal"
7921
+ ];
7922
+ for (const pattern of blockedPatterns) {
7923
+ if (redirectUrl.hostname.includes(pattern)) {
7924
+ return false;
7925
+ }
7926
+ }
7927
+ const ipv4Match = redirectUrl.hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
7928
+ if (ipv4Match) {
7929
+ const octets = ipv4Match.slice(1, 5).map(Number);
7930
+ const [first, second] = octets;
7931
+ if (first === 10) {
7932
+ return false;
7933
+ }
7934
+ if (first === 172 && second >= 16 && second <= 31) {
7935
+ return false;
7936
+ }
7937
+ if (first === 192 && second === 168) {
7938
+ return false;
7939
+ }
7940
+ if (first === 127) {
7941
+ return false;
7942
+ }
7943
+ }
7944
+ const ipVersion = (0, import_net.isIP)(redirectUrl.hostname);
7945
+ if (ipVersion === 6) {
7946
+ const blockedPrefixes = [
7947
+ "::1",
7948
+ // Loopback
7949
+ "fe80:",
7950
+ // Link-local
7951
+ "fc00:",
7952
+ // Unique local
7953
+ "fd",
7954
+ // Unique local (short form)
7955
+ "::ffff:"
7956
+ // IPv4-mapped
7957
+ ];
7958
+ if (blockedPrefixes.some((p) => redirectUrl.hostname.startsWith(p))) {
7959
+ return false;
7960
+ }
7961
+ }
7962
+ return true;
7963
+ } catch {
7964
+ return false;
7965
+ }
7966
+ }
7967
+ /**
7968
+ * Atomically check if path is allowed and read content if so.
7969
+ * Prevents TOCTOU race conditions by combining existence check,
7970
+ * symlink resolution, path validation, and file read in a single operation.
7971
+ *
7972
+ * @param filePath - File path to check and read
7973
+ * @returns Object with content if successful, or error message
7974
+ */
7975
+ readAllowedFile(filePath) {
7976
+ try {
7977
+ import_fs.default.lstatSync(filePath);
7978
+ const realPath = import_fs.default.realpathSync(filePath);
7979
+ if (!this.isPathAllowed(realPath)) {
7980
+ return { error: "File path outside allowed directories (path traversal blocked)" };
7981
+ }
7982
+ return { content: import_fs.default.readFileSync(filePath, "utf8") };
7983
+ } catch (e) {
7984
+ const err = e;
7985
+ if (err.code === "ENOENT") {
7986
+ return { error: "File not found" };
7987
+ }
7988
+ return { error: String(e) };
7989
+ }
7990
+ }
7991
+ /**
7992
+ * Check if security enforcement is enabled
7993
+ */
7994
+ isEnabled() {
7995
+ return this.allowlist.length > 0;
7996
+ }
7997
+ /**
7998
+ * Get the allowlist count
7999
+ */
8000
+ getAllowlistCount() {
8001
+ return this.allowlist.length;
8002
+ }
8003
+ /**
8004
+ * Validate that a spec is on the approved list and matches expected hash.
8005
+ *
8006
+ * @param specUrl - URL or file path to the spec to validate
8007
+ * @returns ValidationResult indicating if the spec is approved
8008
+ */
8009
+ async validate(specUrl) {
8010
+ if (this.isPathTraversalAttempt(specUrl)) {
8011
+ return {
8012
+ valid: false,
8013
+ errorCode: "DOWNLOAD_FAILED",
8014
+ specUrl,
8015
+ error: "File path outside allowed directories (path traversal blocked)"
8016
+ };
8017
+ }
8018
+ const approved = this.allowlist.find((a) => this.urlsMatch(a.url, specUrl));
8019
+ if (!approved) {
8020
+ return {
8021
+ valid: false,
8022
+ errorCode: "SPEC_NOT_ON_ALLOWLIST",
8023
+ specUrl,
8024
+ error: this.formatAllowlistError(specUrl)
8025
+ };
8026
+ }
8027
+ if (this.skipHashVerification) {
8028
+ return { valid: true, specUrl };
8029
+ }
8030
+ const hashResult = await this.getHash(specUrl);
8031
+ if (hashResult.error) {
8032
+ return {
8033
+ valid: false,
8034
+ errorCode: "DOWNLOAD_FAILED",
8035
+ specUrl,
8036
+ error: `Failed to fetch spec '${specUrl}': ${hashResult.error}`
8037
+ };
8038
+ }
8039
+ if (!this.isValidHex(hashResult.hash)) {
8040
+ return {
8041
+ valid: false,
8042
+ errorCode: "DOWNLOAD_FAILED",
8043
+ specUrl,
8044
+ error: "Invalid hash format returned from download"
8045
+ };
8046
+ }
8047
+ if (!this.isValidHex(approved.sha256)) {
8048
+ return {
8049
+ valid: false,
8050
+ errorCode: "DOWNLOAD_FAILED",
8051
+ specUrl,
8052
+ error: "Invalid hash format in approved spec configuration"
8053
+ };
8054
+ }
8055
+ if (!import_crypto.default.timingSafeEqual(
8056
+ Buffer.from(hashResult.hash, "hex"),
8057
+ Buffer.from(approved.sha256, "hex")
8058
+ )) {
8059
+ return {
8060
+ valid: false,
8061
+ errorCode: "SPEC_MODIFIED",
8062
+ specUrl,
8063
+ error: this.formatHashMismatchError(approved, hashResult.hash)
8064
+ };
8065
+ }
8066
+ return { valid: true, specUrl };
8067
+ }
8068
+ /**
8069
+ * Validate multiple specs at once (batch validation).
8070
+ * Fails fast on first error.
8071
+ *
8072
+ * @param specUrls - Array of URLs/paths to validate
8073
+ * @returns Map of URL to ValidationResult
8074
+ */
8075
+ async validateAll(specUrls) {
8076
+ const results = /* @__PURE__ */ new Map();
8077
+ for (const url of specUrls) {
8078
+ const result = await this.validate(url);
8079
+ results.set(url, result);
8080
+ if (!result.valid) {
8081
+ return results;
8082
+ }
8083
+ }
8084
+ return results;
8085
+ }
8086
+ /**
8087
+ * Validate all specs and return all results (non-fail-fast).
8088
+ *
8089
+ * @param specUrls - Array of URLs/paths to validate
8090
+ * @returns Map of URL to ValidationResult
8091
+ */
8092
+ async validateAllComplete(specUrls) {
8093
+ const results = /* @__PURE__ */ new Map();
8094
+ for (const url of specUrls) {
8095
+ const result = await this.validate(url);
8096
+ results.set(url, result);
8097
+ }
8098
+ return results;
8099
+ }
8100
+ /**
8101
+ * Get content hash from cache or download.
8102
+ */
8103
+ async getHash(url) {
8104
+ const cached = this.cache.get(url);
8105
+ if (cached) {
8106
+ return { hash: cached.hash };
8107
+ }
8108
+ const contentResult = await this.download(url);
8109
+ if (contentResult.error) {
8110
+ return { error: contentResult.error };
8111
+ }
8112
+ const content = contentResult.content;
8113
+ const hash = import_crypto.default.createHash("sha256").update(content).digest("hex");
8114
+ this.cache.set(url, { content, hash });
8115
+ return { hash };
8116
+ }
8117
+ /**
8118
+ * Download content from URL or file.
8119
+ * Includes path traversal and SSRF protection.
8120
+ */
8121
+ async download(url, originalProtocol = "") {
8122
+ if (import_fs.default.existsSync(url)) {
8123
+ return this.readAllowedFile(url);
8124
+ }
8125
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
8126
+ return { error: `Invalid URL protocol. Expected http:// or https://, got: ${url}` };
8127
+ }
8128
+ const parsed = new import_url.URL(url);
8129
+ const requestProtocol = originalProtocol || parsed.protocol;
8130
+ return new Promise((resolve) => {
8131
+ const client = url.startsWith("https") ? import_https.default : import_http.default;
8132
+ const req = client.get(url, { timeout: 3e4 }, (res) => {
8133
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
8134
+ const location = res.headers.location;
8135
+ if (!this.isRedirectSafe(location, requestProtocol)) {
8136
+ resolve({ error: `Unsafe redirect target blocked: ${location}` });
8137
+ return;
8138
+ }
8139
+ this.download(location, requestProtocol).then(resolve);
8140
+ return;
8141
+ }
8142
+ if (res.statusCode !== 200) {
8143
+ resolve({ error: `HTTP ${res.statusCode}` });
8144
+ return;
8145
+ }
8146
+ let data = "";
8147
+ let size = 0;
8148
+ res.on("data", (chunk) => {
8149
+ size += chunk.length;
8150
+ if (size > this.MAX_RESPONSE_SIZE) {
8151
+ req.destroy();
8152
+ resolve({ error: `Response too large (>${this.MAX_RESPONSE_SIZE / 1024 / 1024}MB)` });
8153
+ return;
8154
+ }
8155
+ data += chunk;
8156
+ });
8157
+ res.on("end", () => resolve({ content: data }));
8158
+ res.on("error", (e) => resolve({ error: String(e) }));
8159
+ });
8160
+ req.on("error", (e) => resolve({ error: String(e) }));
8161
+ req.on("timeout", () => {
8162
+ req.destroy();
8163
+ resolve({ error: "Request timeout (30s)" });
8164
+ });
8165
+ });
8166
+ }
8167
+ /**
8168
+ * Check if two URLs match (handles trailing slashes, case sensitivity, etc.).
8169
+ * Strips credentials and hash to prevent bypass via @ symbol.
8170
+ */
8171
+ urlsMatch(url1, url2) {
8172
+ const normalize = (u) => {
8173
+ try {
8174
+ const parsed = new import_url.URL(u);
8175
+ const normalized = `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
8176
+ return normalized.replace(/\/$/, "").toLowerCase();
8177
+ } catch {
8178
+ return u.replace(/\/$/, "").replace(/\\/g, "/").toLowerCase();
8179
+ }
8180
+ };
8181
+ return normalize(url1) === normalize(url2);
8182
+ }
8183
+ /**
8184
+ * Format error message for spec not on allowlist
8185
+ */
8186
+ formatAllowlistError(specUrl) {
8187
+ const lines = [`Spec '${specUrl}' is not on the approved list.`];
8188
+ if (this.allowlist.length === 0) {
8189
+ lines.push("");
8190
+ lines.push("No approved specs are configured.");
8191
+ lines.push("Add approved specs to stackwright.yml under prebuild.security.allowlist");
8192
+ } else {
8193
+ lines.push("");
8194
+ lines.push("Allowed specs:");
8195
+ for (const spec of this.allowlist) {
8196
+ lines.push(` \u2022 ${spec.name}`);
8197
+ lines.push(` ${spec.url}`);
8198
+ }
8199
+ }
8200
+ return lines.join("\n");
8201
+ }
8202
+ /**
8203
+ * Format error message for hash mismatch
8204
+ */
8205
+ formatHashMismatchError(approved, actualHash) {
8206
+ const lines = [
8207
+ `Spec '${approved.url}' has been modified since approval.`,
8208
+ "",
8209
+ "Expected hash (approved): " + approved.sha256,
8210
+ "Actual hash (current): " + actualHash,
8211
+ "",
8212
+ "This may indicate:",
8213
+ " \u2022 The spec has been updated on the server",
8214
+ " \u2022 Network corruption during download",
8215
+ " \u2022 A man-in-the-middle attack",
8216
+ "",
8217
+ "If this is expected, update the sha256 in stackwright.yml."
8218
+ ];
8219
+ return lines.join("\n");
8220
+ }
8221
+ /**
8222
+ * Clear the download cache.
8223
+ * Useful for long-running processes.
8224
+ */
8225
+ clearCache() {
8226
+ this.cache.clear();
8227
+ }
8228
+ };
8229
+
8230
+ // src/prebuild/OpenAPIPlugin.ts
7659
8231
  var OpenAPIPlugin = class {
7660
8232
  name = "@stackwright-pro/openapi";
7661
8233
  async beforeBuild(context) {
7662
8234
  const { siteConfig, projectRoot } = context;
8235
+ const prebuildConfig = siteConfig.prebuild || {};
8236
+ const securityConfig = prebuildConfig.security;
8237
+ let validator = null;
8238
+ if (securityConfig?.enabled) {
8239
+ const skipApprovedSpecs = process.argv.includes("--skip-approved-specs");
8240
+ if (skipApprovedSpecs) {
8241
+ const isProductionBuild = process.env.NODE_ENV === "production" || process.env.CI === "true";
8242
+ if (isProductionBuild) {
8243
+ console.error("\n\u274C FATAL: --skip-approved-specs not allowed in production/CI builds\n");
8244
+ throw new Error(
8245
+ "SECURITY_CONFIG_VIOLATION: Cannot use --skip-approved-specs in production or CI environments"
8246
+ );
8247
+ }
8248
+ console.log(
8249
+ "\n[@stackwright-pro/openapi] \u26A0\uFE0F Approved-specs enforcement DISABLED (--skip-approved-specs)\n"
8250
+ );
8251
+ console.log(" This is ONLY allowed in development mode.\n");
8252
+ } else {
8253
+ validator = new ApprovedSpecsValidator(securityConfig);
8254
+ console.log("\n[@stackwright-pro/openapi] \u{1F512} Approved-specs enforcement ENABLED");
8255
+ console.log(` Approved specs: ${validator.getAllowlistCount()}`);
8256
+ }
8257
+ }
7663
8258
  const integrations = siteConfig.integrations;
7664
8259
  if (!integrations || !Array.isArray(integrations)) {
7665
8260
  return;
@@ -7670,6 +8265,17 @@ var OpenAPIPlugin = class {
7670
8265
  if (openAPIIntegrations.length === 0) {
7671
8266
  return;
7672
8267
  }
8268
+ if (validator) {
8269
+ const specUrls = openAPIIntegrations.map((i) => i.spec);
8270
+ const results = await validator.validateAll(specUrls);
8271
+ for (const [url, result] of results) {
8272
+ if (!result.valid) {
8273
+ this.printSecurityRejection(result);
8274
+ throw new Error(`SPEC_NOT_APPROVED: ${result.error}`);
8275
+ }
8276
+ console.log(` \u2713 Approved: ${url}`);
8277
+ }
8278
+ }
7673
8279
  console.log(
7674
8280
  `
7675
8281
  [@stackwright-pro/openapi] Processing ${openAPIIntegrations.length} integration(s)...`
@@ -7679,23 +8285,74 @@ var OpenAPIPlugin = class {
7679
8285
  }
7680
8286
  console.log("[@stackwright-pro/openapi] Generation complete\n");
7681
8287
  }
8288
+ /**
8289
+ * Print a security rejection error in a formatted box
8290
+ */
8291
+ printSecurityRejection(result) {
8292
+ const lines = [];
8293
+ lines.push(
8294
+ "\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"
8295
+ );
8296
+ lines.push(
8297
+ "\u2551 \u{1F512} SECURITY REJECTED \u2551"
8298
+ );
8299
+ 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");
8300
+ lines.push(`\u2551 Error: ${(result.errorCode || "UNKNOWN").padEnd(73)} \u2551`);
8301
+ 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");
8302
+ const errorLines = (result.error || "Unknown error").split("\n");
8303
+ const maxLen = 76;
8304
+ for (const line of errorLines) {
8305
+ if (line.length <= maxLen) {
8306
+ lines.push(`\u2551 ${line.padEnd(maxLen)} \u2551`);
8307
+ } else {
8308
+ let remaining = line;
8309
+ while (remaining.length > 0) {
8310
+ lines.push(`\u2551 ${remaining.substring(0, maxLen).padEnd(maxLen)} \u2551`);
8311
+ remaining = remaining.substring(maxLen);
8312
+ }
8313
+ }
8314
+ }
8315
+ lines.push(
8316
+ "\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"
8317
+ );
8318
+ for (const line of lines) {
8319
+ console.error(line);
8320
+ }
8321
+ }
7682
8322
  async processIntegration(config, projectRoot) {
7683
- const { name, spec, auth, collections } = config;
8323
+ const { name, spec, auth, mockUrl, collections, endpoints } = config;
7684
8324
  console.log(` - Processing integration: ${name}`);
7685
- const specPath = spec.startsWith("http") ? spec : import_path.default.resolve(projectRoot, spec);
8325
+ const specPath = spec.startsWith("http") ? spec : import_path2.default.resolve(projectRoot, spec);
7686
8326
  const parser = new OpenAPIParser();
7687
8327
  const { document } = await parser.parse(specPath);
7688
- const outputDir = import_path.default.join(projectRoot, "src", "generated", name);
7689
- import_fs.default.mkdirSync(outputDir, { recursive: true });
7690
- const schemaMapping = await this.generateSchemas(document, collections || [], outputDir, name);
8328
+ if (mockUrl) {
8329
+ const originalServer = document.servers?.[0]?.url || "unknown";
8330
+ document.servers = [{ url: mockUrl }];
8331
+ console.log(` > Using mock URL: ${mockUrl} (was: ${originalServer})`);
8332
+ }
8333
+ const endpointFilter = new EndpointFilter(endpoints);
8334
+ if (endpoints && (endpoints.include?.length || endpoints.exclude?.length)) {
8335
+ const includeStr = endpoints.include?.join(", ") || "/**";
8336
+ const excludeStr = endpoints.exclude?.length ? ` (exclude: ${endpoints.exclude.join(", ")})` : "";
8337
+ console.log(` > Filter: include [${includeStr}]${excludeStr}`);
8338
+ }
8339
+ const outputDir = import_path2.default.join(projectRoot, "src", "generated", name);
8340
+ import_fs3.default.mkdirSync(outputDir, { recursive: true });
8341
+ const schemaMapping = await this.generateSchemas(
8342
+ document,
8343
+ collections || [],
8344
+ outputDir,
8345
+ name,
8346
+ endpointFilter
8347
+ );
7691
8348
  await this.generateTypes(document, collections || [], outputDir, name);
7692
8349
  if (collections && collections.length > 0) {
7693
8350
  await this.generateProvider(document, config, outputDir, name);
7694
8351
  }
7695
- await this.generateClient(document, outputDir, name, schemaMapping);
7696
- console.log(` \u2713 Generated code in src/generated/${name}/`);
8352
+ await this.generateClient(document, outputDir, name, schemaMapping, endpointFilter);
8353
+ console.log(` > Generated code in src/generated/${name}/`);
7697
8354
  }
7698
- async generateSchemas(document, collections, outputDir, integrationName) {
8355
+ async generateSchemas(document, collections, outputDir, integrationName, endpointFilter) {
7699
8356
  const resolver = new SchemaResolver(document);
7700
8357
  const zodGenerator = new ZodSchemaGenerator();
7701
8358
  let schemasCode = `/**
@@ -7715,14 +8372,14 @@ import { z } from 'zod';
7715
8372
  const method = "get";
7716
8373
  const schema = resolver.getResponseSchema(endpoint, method);
7717
8374
  if (!schema) {
7718
- console.warn(` \u26A0 No schema found for ${method.toUpperCase()} ${endpoint}`);
8375
+ console.warn(` > No schema found for ${method.toUpperCase()} ${endpoint}`);
7719
8376
  continue;
7720
8377
  }
7721
8378
  const collectionName = this.sanitizeName(endpoint);
7722
8379
  const schemaName = `${this.capitalize(collectionName)}Schema`;
7723
8380
  const isArray = schema.type === "array";
7724
8381
  const itemSchema = isArray ? schema.items : schema;
7725
- const zodCode = zodGenerator.generate(itemSchema, { schemaName });
8382
+ const zodCode = zodGenerator.generate(itemSchema, { schemaName, bare: true });
7726
8383
  schemasCode += zodCode + "\n";
7727
8384
  if (isArray) {
7728
8385
  schemasCode += `export const ${this.capitalize(collectionName)}ArraySchema = z.array(${schemaName});
@@ -7731,22 +8388,27 @@ import { z } from 'zod';
7731
8388
  }
7732
8389
  }
7733
8390
  }
7734
- schemasCode += "\n// Response schemas for all endpoints\n";
8391
+ schemasCode += "\n// Response schemas for filtered endpoints\n";
7735
8392
  const generatedSchemas = /* @__PURE__ */ new Set();
8393
+ let filteredCount = 0;
7736
8394
  const paths = document.paths || {};
7737
- for (const [path2, pathItem] of Object.entries(paths)) {
8395
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
8396
+ if (!endpointFilter.matches(pathStr)) {
8397
+ filteredCount++;
8398
+ continue;
8399
+ }
7738
8400
  const methods = ["get", "post", "put", "patch", "delete"];
7739
8401
  for (const method of methods) {
7740
8402
  const operation = pathItem[method];
7741
8403
  if (!operation) continue;
7742
- const opId = operation.operationId || this.generateOperationId(path2, method);
8404
+ const opId = operation.operationId || this.generateOperationId(pathStr, method);
7743
8405
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
7744
8406
  if (generatedSchemas.has(responseSchemaName)) {
7745
8407
  continue;
7746
8408
  }
7747
- const responseSchema = resolver.getResponseSchema(path2, method);
8409
+ const responseSchema = resolver.getResponseSchema(pathStr, method);
7748
8410
  if (!responseSchema) {
7749
- console.warn(` \u26A0 No response schema for ${method.toUpperCase()} ${path2}`);
8411
+ console.warn(` > No response schema for ${method.toUpperCase()} ${pathStr}`);
7750
8412
  continue;
7751
8413
  }
7752
8414
  const isArray = responseSchema.type === "array";
@@ -7761,32 +8423,44 @@ import { z } from 'zod';
7761
8423
  `;
7762
8424
  generatedSchemas.add(responseSchemaName);
7763
8425
  } else if (isArray) {
7764
- const zodCode = zodGenerator.generate(responseSchema, { schemaName: responseSchemaName });
8426
+ const zodCode = zodGenerator.generate(responseSchema, {
8427
+ schemaName: responseSchemaName,
8428
+ bare: true
8429
+ });
7765
8430
  schemasCode += zodCode + "\n";
7766
8431
  generatedSchemas.add(responseSchemaName);
7767
8432
  } else {
7768
- const zodCode = zodGenerator.generate(responseSchema, { schemaName: responseSchemaName });
8433
+ const zodCode = zodGenerator.generate(responseSchema, {
8434
+ schemaName: responseSchemaName,
8435
+ bare: true
8436
+ });
7769
8437
  schemasCode += zodCode + "\n";
7770
8438
  generatedSchemas.add(responseSchemaName);
7771
8439
  }
7772
8440
  }
7773
8441
  }
8442
+ if (filteredCount > 0) {
8443
+ console.log(` > Skipped ${filteredCount} paths (filtered)`);
8444
+ }
7774
8445
  const schemaMapping = {};
7775
- for (const [path2, pathItem] of Object.entries(paths)) {
8446
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
8447
+ if (!endpointFilter.matches(pathStr)) {
8448
+ continue;
8449
+ }
7776
8450
  const methods = ["get", "post", "put", "patch", "delete"];
7777
8451
  for (const method of methods) {
7778
8452
  const operation = pathItem[method];
7779
8453
  if (!operation) continue;
7780
- const opId = operation.operationId || this.generateOperationId(path2, method);
8454
+ const opId = operation.operationId || this.generateOperationId(pathStr, method);
7781
8455
  const responseSchemaName = `${this.getOperationTypeName(opId)}ResponseSchema`;
7782
8456
  schemaMapping[opId] = {
7783
8457
  requestSchema: null,
7784
- // Will be added when we generate request schemas
8458
+ // ClientGenerator handles request schema generation + type inference
7785
8459
  responseSchema: responseSchemaName
7786
8460
  };
7787
8461
  }
7788
8462
  }
7789
- import_fs.default.writeFileSync(import_path.default.join(outputDir, "schemas.ts"), schemasCode);
8463
+ import_fs3.default.writeFileSync(import_path2.default.join(outputDir, "schemas.ts"), schemasCode);
7790
8464
  return schemaMapping;
7791
8465
  }
7792
8466
  async generateTypes(document, collections, outputDir, integrationName) {
@@ -7811,7 +8485,7 @@ import type * as schemas from './schemas';
7811
8485
  `;
7812
8486
  }
7813
8487
  }
7814
- import_fs.default.writeFileSync(import_path.default.join(outputDir, "types.ts"), typesCode);
8488
+ import_fs3.default.writeFileSync(import_path2.default.join(outputDir, "types.ts"), typesCode);
7815
8489
  }
7816
8490
  async generateProvider(document, config, outputDir, integrationName) {
7817
8491
  const generator = new CollectionProviderGenerator(document);
@@ -7819,22 +8493,15 @@ import type * as schemas from './schemas';
7819
8493
  if (!collections || collections.length === 0) {
7820
8494
  return;
7821
8495
  }
7822
- let providerCode = `/**
7823
- * Generated CollectionProvider from OpenAPI spec
7824
- * Integration: ${integrationName}
7825
- *
7826
- * DO NOT EDIT - This file is auto-generated by @stackwright-pro/openapi
7827
- * Regenerate by running: pnpm prebuild
7828
- */
7829
-
7830
- `;
8496
+ const schemaImports = /* @__PURE__ */ new Set();
8497
+ const classBlocks = [];
7831
8498
  for (const collection of collections) {
7832
8499
  const collectionConfig = {
7833
8500
  endpoint: collection.endpoint,
7834
8501
  slugField: collection.slug_field,
7835
8502
  filters: collection.filters
7836
8503
  };
7837
- let providerOptions = {};
8504
+ let providerOptions = { bare: true };
7838
8505
  if (auth) {
7839
8506
  if (auth.type === "bearer" || auth.type === "apiKey") {
7840
8507
  providerOptions.auth = {
@@ -7843,16 +8510,50 @@ import type * as schemas from './schemas';
7843
8510
  };
7844
8511
  } else if (auth.type === "oauth2") {
7845
8512
  console.warn(
7846
- ` \u26A0 OAuth2 auth not yet supported for ${collection.endpoint} (coming soon)`
8513
+ ` > OAuth2 auth not yet supported for ${collection.endpoint} (coming soon)`
7847
8514
  );
7848
8515
  }
7849
8516
  }
7850
8517
  const code = generator.generate(collectionConfig, providerOptions);
7851
- providerCode += code + "\n";
8518
+ classBlocks.push(code);
8519
+ const collectionName = this.sanitizeName(collection.endpoint);
8520
+ const resolver = new SchemaResolver(document);
8521
+ const schema = resolver.getResponseSchema(collection.endpoint, "get");
8522
+ const isArray = schema?.type === "array";
8523
+ if (isArray) {
8524
+ schemaImports.add(`${this.capitalize(collectionName)}ArraySchema`);
8525
+ } else {
8526
+ schemaImports.add(`${this.capitalize(collectionName)}Schema`);
8527
+ }
7852
8528
  }
7853
- import_fs.default.writeFileSync(import_path.default.join(outputDir, "provider.ts"), providerCode);
8529
+ let providerCode = `/**
8530
+ * Generated CollectionProvider from OpenAPI spec
8531
+ * Integration: ${integrationName}
8532
+ *
8533
+ * DO NOT EDIT - This file is auto-generated by @stackwright-pro/openapi
8534
+ * Regenerate by running: pnpm prebuild
8535
+ */
8536
+
8537
+ import type { CollectionProvider, CollectionItem } from '@stackwright/collections';
8538
+ import { ${Array.from(schemaImports).join(", ")} } from './schemas';
8539
+
8540
+ `;
8541
+ providerCode += classBlocks.join("\n");
8542
+ import_fs3.default.writeFileSync(import_path2.default.join(outputDir, "provider.ts"), providerCode);
7854
8543
  }
7855
- async generateClient(document, outputDir, integrationName, schemaMapping) {
8544
+ async generateClient(document, outputDir, integrationName, schemaMapping, endpointFilter) {
8545
+ const paths = document.paths || {};
8546
+ let filteredEndpoints = 0;
8547
+ for (const pathStr of Object.keys(paths)) {
8548
+ if (!endpointFilter.matches(pathStr)) {
8549
+ filteredEndpoints++;
8550
+ }
8551
+ }
8552
+ if (filteredEndpoints > 0) {
8553
+ console.log(
8554
+ ` > Generating client for ${Object.keys(paths).length - filteredEndpoints} endpoints (${filteredEndpoints} filtered)`
8555
+ );
8556
+ }
7856
8557
  const generator = new ClientGenerator(document);
7857
8558
  const className = `${this.capitalize(integrationName)}Client`;
7858
8559
  const clientCode = generator.generate({
@@ -7862,7 +8563,7 @@ import type * as schemas from './schemas';
7862
8563
  validateResponses: true,
7863
8564
  strictValidation: false
7864
8565
  });
7865
- import_fs.default.writeFileSync(import_path.default.join(outputDir, "client.ts"), clientCode);
8566
+ import_fs3.default.writeFileSync(import_path2.default.join(outputDir, "client.ts"), clientCode);
7866
8567
  }
7867
8568
  extractComponentName(ref) {
7868
8569
  const parts = ref.split("/");
@@ -7871,20 +8572,20 @@ import type * as schemas from './schemas';
7871
8572
  getOperationTypeName(operationId) {
7872
8573
  return operationId.charAt(0).toUpperCase() + operationId.slice(1);
7873
8574
  }
7874
- generateOperationId(path2, method) {
7875
- const sanitized = this.sanitizeName(path2);
8575
+ generateOperationId(pathStr, method) {
8576
+ const sanitized = this.sanitizeName(pathStr);
7876
8577
  return `${method}${this.capitalize(sanitized)}`;
7877
8578
  }
7878
- getResponseSchemaName(path2, method) {
7879
- const sanitized = this.sanitizeName(path2);
8579
+ getResponseSchemaName(pathStr, method) {
8580
+ const sanitized = this.sanitizeName(pathStr);
7880
8581
  const baseName = this.capitalize(sanitized);
7881
- if (method === "get" && !path2.includes("{")) {
8582
+ if (method === "get" && !pathStr.includes("{")) {
7882
8583
  return `${baseName}ArraySchema`;
7883
8584
  }
7884
8585
  return `${baseName}Schema`;
7885
8586
  }
7886
- sanitizeName(path2) {
7887
- return path2.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
8587
+ sanitizeName(pathStr) {
8588
+ return pathStr.replace(/^\//, "").replace(/\//g, "-").replace(/[{}:]/g, "").replace(/-+/g, "-");
7888
8589
  }
7889
8590
  capitalize(str) {
7890
8591
  return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -7893,15 +8594,138 @@ import type * as schemas from './schemas';
7893
8594
  function createOpenAPIPlugin() {
7894
8595
  return new OpenAPIPlugin();
7895
8596
  }
8597
+
8598
+ // src/sources/openapi.ts
8599
+ var BLOCKED_HOST_PATTERNS = [
8600
+ /^localhost$/i,
8601
+ /^127\./,
8602
+ // Loopback (127.0.0.0/8)
8603
+ /^10\./,
8604
+ // Class A private (10.0.0.0/8)
8605
+ /^172\.(1[6-9]|2[0-9]|3[0-1])\./,
8606
+ // Class B private (172.16.0.0/12)
8607
+ /^192\.168\./,
8608
+ // Class C private (192.168.0.0/16)
8609
+ /^169\.254\./,
8610
+ // Link-local (169.254.0.0/16) - AWS/GCP metadata
8611
+ /^0\./,
8612
+ // Current network (0.0.0.0/8)
8613
+ /^::1$/,
8614
+ // IPv6 loopback (no brackets)
8615
+ /^\[::1\]$/i,
8616
+ // IPv6 loopback (with brackets)
8617
+ /^[fF][cCdD][0-9a-fA-F]{2}:/,
8618
+ // IPv6 private (fc00::/7)
8619
+ /^169\.254\.169\.254$/i,
8620
+ // AWS metadata endpoint
8621
+ /^metadata\.googleapis\.com$/i,
8622
+ // GCP metadata endpoint
8623
+ /^100\.100\.100\.200$/
8624
+ // Alibaba Cloud metadata
8625
+ ];
8626
+ var ALLOWED_PROTOCOLS = ["http:", "https:"];
8627
+ function validateFetchUrl(baseUrl) {
8628
+ let url;
8629
+ try {
8630
+ url = new URL(baseUrl);
8631
+ } catch {
8632
+ throw new Error("SSRF Prevention: baseUrl must be a valid absolute URL");
8633
+ }
8634
+ if (!ALLOWED_PROTOCOLS.includes(url.protocol)) {
8635
+ throw new Error(
8636
+ "SSRF Prevention: Only HTTP and HTTPS protocols are allowed, got " + url.protocol
8637
+ );
8638
+ }
8639
+ const hostname = url.hostname.toLowerCase();
8640
+ for (const pattern of BLOCKED_HOST_PATTERNS) {
8641
+ if (pattern.test(hostname)) {
8642
+ throw new Error("SSRF Prevention: Blocked internal network address: " + hostname);
8643
+ }
8644
+ }
8645
+ const ip = url.hostname;
8646
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip)) {
8647
+ const octets = ip.split(".").map(Number);
8648
+ if (octets[0] === 127) {
8649
+ throw new Error("SSRF Prevention: Blocked loopback address: " + ip);
8650
+ }
8651
+ if (octets[0] === 10) {
8652
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8653
+ }
8654
+ if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
8655
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8656
+ }
8657
+ if (octets[0] === 192 && octets[1] === 168) {
8658
+ throw new Error("SSRF Prevention: Blocked private address: " + ip);
8659
+ }
8660
+ if (octets[0] === 169 && octets[1] === 254) {
8661
+ throw new Error("SSRF Prevention: Blocked link-local address: " + ip);
8662
+ }
8663
+ if (octets[0] === 0) {
8664
+ throw new Error("SSRF Prevention: Blocked current network address: " + ip);
8665
+ }
8666
+ }
8667
+ return url;
8668
+ }
8669
+ function validateEndpoint(endpoint) {
8670
+ if (endpoint.includes("..")) {
8671
+ throw new Error("SSRF Prevention: Path traversal detected in endpoint");
8672
+ }
8673
+ if (endpoint.includes("\\")) {
8674
+ throw new Error("SSRF Prevention: Backslash detected in endpoint");
8675
+ }
8676
+ }
8677
+ function createOpenAPIFetcher(config) {
8678
+ const { baseUrl, endpoint, method = "get", params, body, headers = {}, auth, schema } = config;
8679
+ const validatedBaseUrl = validateFetchUrl(baseUrl);
8680
+ validateEndpoint(endpoint);
8681
+ return async () => {
8682
+ const url = new URL(endpoint, validatedBaseUrl.toString());
8683
+ if (params) {
8684
+ Object.entries(params).forEach(([key, value]) => {
8685
+ url.searchParams.append(key, String(value));
8686
+ });
8687
+ }
8688
+ const requestHeaders = {
8689
+ "Content-Type": "application/json",
8690
+ Accept: "application/json",
8691
+ ...headers
8692
+ };
8693
+ if (auth) {
8694
+ if (auth.type === "bearer" && auth.token) {
8695
+ requestHeaders["Authorization"] = `Bearer ${auth.token}`;
8696
+ } else if (auth.type === "apiKey" && auth.apiKey) {
8697
+ requestHeaders[auth.headerName ?? "X-API-Key"] = auth.apiKey;
8698
+ }
8699
+ }
8700
+ const supportsBody = ["post", "put", "patch", "delete"].includes(method);
8701
+ const response = await fetch(url.toString(), {
8702
+ method: method.toUpperCase(),
8703
+ headers: requestHeaders,
8704
+ body: supportsBody && body ? JSON.stringify(body) : void 0
8705
+ });
8706
+ if (!response.ok) {
8707
+ const safeStatus = response.status;
8708
+ throw new Error("API error: " + safeStatus);
8709
+ }
8710
+ const result = await response.json();
8711
+ if (schema) {
8712
+ return schema.parse(result);
8713
+ }
8714
+ return result;
8715
+ };
8716
+ }
7896
8717
  // Annotate the CommonJS export names for ESM import in node:
7897
8718
  0 && (module.exports = {
8719
+ ApprovedSpecsValidator,
7898
8720
  ClientGenerator,
7899
8721
  CollectionProviderGenerator,
8722
+ EndpointFilter,
7900
8723
  OpenAPICollectionProvider,
7901
8724
  OpenAPIParser,
7902
8725
  OpenAPIPlugin,
7903
8726
  SchemaResolver,
7904
8727
  TypeGenerator,
7905
8728
  ZodSchemaGenerator,
8729
+ createOpenAPIFetcher,
7906
8730
  createOpenAPIPlugin
7907
8731
  });