@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.d.mts +331 -2
- package/dist/index.d.ts +331 -2
- package/dist/index.js +1025 -201
- package/dist/index.mjs +1022 -201
- package/package.json +1 -1
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(
|
|
117
|
-
var 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
|
-
|
|
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(
|
|
173
|
-
var 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(
|
|
182
|
-
var 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
|
|
2025
|
-
|
|
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(
|
|
2098
|
-
return JSON.stringify(this.node, jsonSkipLoc) === JSON.stringify(
|
|
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
|
|
2149
|
+
var path3 = NodePath2.registry.get(node);
|
|
2150
2150
|
if (parentPath !== null) {
|
|
2151
|
-
|
|
2152
|
-
|
|
2151
|
+
path3.parentPath = parentPath;
|
|
2152
|
+
path3.parent = path3.parentPath.node;
|
|
2153
2153
|
}
|
|
2154
2154
|
if (prop !== null) {
|
|
2155
|
-
|
|
2155
|
+
path3.property = prop;
|
|
2156
2156
|
}
|
|
2157
2157
|
if (index >= 0) {
|
|
2158
|
-
|
|
2158
|
+
path3.index = index;
|
|
2159
2159
|
}
|
|
2160
|
-
return
|
|
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(
|
|
2641
|
-
var 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(
|
|
2664
|
-
var node =
|
|
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
|
-
|
|
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(
|
|
2723
|
-
var node =
|
|
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(
|
|
2789
|
-
var 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 =
|
|
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(
|
|
2874
|
-
var node =
|
|
2875
|
-
if (parent.type !== "Alternative" || !
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
2940
|
-
var node =
|
|
2939
|
+
Quantifier: function Quantifier(path3) {
|
|
2940
|
+
var node = path3.node;
|
|
2941
2941
|
if (node.kind !== "Range") {
|
|
2942
2942
|
return;
|
|
2943
2943
|
}
|
|
2944
|
-
rewriteOpenZero(
|
|
2945
|
-
rewriteOpenOne(
|
|
2946
|
-
rewriteExactOne(
|
|
2944
|
+
rewriteOpenZero(path3);
|
|
2945
|
+
rewriteOpenOne(path3);
|
|
2946
|
+
rewriteExactOne(path3);
|
|
2947
2947
|
}
|
|
2948
2948
|
};
|
|
2949
|
-
function rewriteOpenZero(
|
|
2950
|
-
var 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(
|
|
2958
|
-
var 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(
|
|
2966
|
-
var 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
|
-
|
|
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(
|
|
2981
|
-
var node =
|
|
2980
|
+
ClassRange: function ClassRange(path3) {
|
|
2981
|
+
var node = path3.node;
|
|
2982
2982
|
if (node.from.codePoint === node.to.codePoint) {
|
|
2983
|
-
|
|
2983
|
+
path3.replace(node.from);
|
|
2984
2984
|
} else if (node.from.codePoint === node.to.codePoint - 1) {
|
|
2985
|
-
|
|
2986
|
-
|
|
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(
|
|
3015
|
-
rewriteNumberRanges(
|
|
3016
|
-
rewriteWordRanges(
|
|
3017
|
-
rewriteWhitespaceRanges(
|
|
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(
|
|
3021
|
-
var node =
|
|
3020
|
+
function rewriteNumberRanges(path3) {
|
|
3021
|
+
var node = path3.node;
|
|
3022
3022
|
node.expressions.forEach(function(expression, i) {
|
|
3023
3023
|
if (isFullNumberRange(expression)) {
|
|
3024
|
-
|
|
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(
|
|
3033
|
-
var 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 =
|
|
3042
|
+
numberPath = path3.getChild(i);
|
|
3043
3043
|
} else if (isLowerCaseRange(expression)) {
|
|
3044
|
-
lowerCasePath =
|
|
3044
|
+
lowerCasePath = path3.getChild(i);
|
|
3045
3045
|
} else if (isUpperCaseRange(expression)) {
|
|
3046
|
-
upperCasePath =
|
|
3046
|
+
upperCasePath = path3.getChild(i);
|
|
3047
3047
|
} else if (isUnderscore(expression)) {
|
|
3048
|
-
underscorePath =
|
|
3048
|
+
underscorePath = path3.getChild(i);
|
|
3049
3049
|
} else if (hasIFlag && hasUFlag && isCodePoint(expression, 383)) {
|
|
3050
|
-
u017fPath =
|
|
3050
|
+
u017fPath = path3.getChild(i);
|
|
3051
3051
|
} else if (hasIFlag && hasUFlag && isCodePoint(expression, 8490)) {
|
|
3052
|
-
u212aPath =
|
|
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(
|
|
3090
|
-
var 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
|
-
}) ?
|
|
3108
|
-
}).filter(Boolean).forEach(function(
|
|
3109
|
-
return
|
|
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(
|
|
3143
|
-
var node =
|
|
3144
|
-
if (node.expressions.length !== 1 || !hasAppropriateSiblings(
|
|
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
|
-
|
|
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(
|
|
3174
|
-
var parent =
|
|
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(
|
|
3206
|
-
var node =
|
|
3205
|
+
Char: function Char(path3) {
|
|
3206
|
+
var node = path3.node;
|
|
3207
3207
|
if (!node.escaped) {
|
|
3208
3208
|
return;
|
|
3209
3209
|
}
|
|
3210
|
-
if (shouldUnescape(
|
|
3210
|
+
if (shouldUnescape(path3, this._hasXFlag)) {
|
|
3211
3211
|
delete node.escaped;
|
|
3212
3212
|
}
|
|
3213
3213
|
}
|
|
3214
3214
|
};
|
|
3215
|
-
function shouldUnescape(
|
|
3216
|
-
var value =
|
|
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(
|
|
3306
|
-
var 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(
|
|
3525
|
-
var 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
|
-
|
|
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(
|
|
3547
|
-
var node =
|
|
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](
|
|
3561
|
+
handlers[parent.type](path3.getParent(), characterClass);
|
|
3562
3562
|
}
|
|
3563
3563
|
};
|
|
3564
3564
|
var handlers = {
|
|
3565
|
-
RegExp: function RegExp2(
|
|
3566
|
-
var node =
|
|
3565
|
+
RegExp: function RegExp2(path3, characterClass) {
|
|
3566
|
+
var node = path3.node;
|
|
3567
3567
|
node.body = characterClass;
|
|
3568
3568
|
},
|
|
3569
|
-
Group: function Group(
|
|
3570
|
-
var 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
|
-
|
|
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(
|
|
3609
|
-
var node =
|
|
3610
|
-
var childPath =
|
|
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
|
-
|
|
3615
|
+
path3.getParent().replace(node);
|
|
3616
3616
|
} else if (parent.type !== "RegExp") {
|
|
3617
|
-
|
|
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(
|
|
3640
|
-
var node =
|
|
3641
|
-
var childPath =
|
|
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(
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
3663
|
+
path3.replace(childPath.node);
|
|
3664
3664
|
}
|
|
3665
3665
|
}
|
|
3666
3666
|
};
|
|
3667
|
-
function hasAppropriateSiblings(
|
|
3668
|
-
var parent =
|
|
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(
|
|
3706
|
-
var 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 =
|
|
3710
|
-
index = Math.max(1, combineRepeatingPatternLeft(
|
|
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 =
|
|
3715
|
-
index = Math.max(1, combineWithPreviousRepetition(
|
|
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 =
|
|
3720
|
-
index = Math.max(1, combineRepetitionWithPrevious(
|
|
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(
|
|
5867
|
-
const pathItem = this.document.paths?.[
|
|
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 [
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
6243
|
-
|
|
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
|
-
|
|
6260
|
-
|
|
6278
|
+
isArray,
|
|
6279
|
+
bare
|
|
6261
6280
|
} = params;
|
|
6262
6281
|
const authHeader = this.generateAuthHeader(auth);
|
|
6263
|
-
|
|
6264
|
-
|
|
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 = ${
|
|
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(
|
|
6429
|
-
return
|
|
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
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
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:
|
|
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 + '${
|
|
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 + '${
|
|
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}
|
|
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) :
|
|
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:
|
|
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 + '${
|
|
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 + '${
|
|
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}
|
|
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) :
|
|
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
|
|
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
|
|
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 [
|
|
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:
|
|
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 :
|
|
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
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
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(`
|
|
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(`
|
|
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
|
|
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 [
|
|
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(
|
|
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(
|
|
8409
|
+
const responseSchema = resolver.getResponseSchema(pathStr, method);
|
|
7748
8410
|
if (!responseSchema) {
|
|
7749
|
-
console.warn(`
|
|
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, {
|
|
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, {
|
|
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 [
|
|
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(
|
|
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
|
-
//
|
|
8458
|
+
// ClientGenerator handles request schema generation + type inference
|
|
7785
8459
|
responseSchema: responseSchemaName
|
|
7786
8460
|
};
|
|
7787
8461
|
}
|
|
7788
8462
|
}
|
|
7789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7823
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
7875
|
-
const sanitized = this.sanitizeName(
|
|
8575
|
+
generateOperationId(pathStr, method) {
|
|
8576
|
+
const sanitized = this.sanitizeName(pathStr);
|
|
7876
8577
|
return `${method}${this.capitalize(sanitized)}`;
|
|
7877
8578
|
}
|
|
7878
|
-
getResponseSchemaName(
|
|
7879
|
-
const sanitized = this.sanitizeName(
|
|
8579
|
+
getResponseSchemaName(pathStr, method) {
|
|
8580
|
+
const sanitized = this.sanitizeName(pathStr);
|
|
7880
8581
|
const baseName = this.capitalize(sanitized);
|
|
7881
|
-
if (method === "get" && !
|
|
8582
|
+
if (method === "get" && !pathStr.includes("{")) {
|
|
7882
8583
|
return `${baseName}ArraySchema`;
|
|
7883
8584
|
}
|
|
7884
8585
|
return `${baseName}Schema`;
|
|
7885
8586
|
}
|
|
7886
|
-
sanitizeName(
|
|
7887
|
-
return
|
|
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
|
});
|