mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-23 04:35:40 +00:00
feat: faster collection mount via file cache (Beta) (#8222)
This commit is contained in:
committed by
GitHub
parent
d1ebf578b2
commit
683d487181
213
package-lock.json
generated
213
package-lock.json
generated
@@ -5027,7 +5027,7 @@
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
|
||||
"integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.25.9",
|
||||
@@ -5045,7 +5045,7 @@
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
|
||||
"integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.22.6",
|
||||
@@ -5062,7 +5062,7 @@
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -5080,7 +5080,7 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
@@ -5160,7 +5160,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz",
|
||||
"integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.25.9",
|
||||
@@ -5235,7 +5235,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz",
|
||||
"integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.25.9",
|
||||
@@ -5278,7 +5278,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz",
|
||||
"integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -5295,7 +5295,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz",
|
||||
"integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5311,7 +5311,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
|
||||
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5327,7 +5327,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz",
|
||||
"integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -5345,7 +5345,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz",
|
||||
"integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -5380,7 +5380,7 @@
|
||||
"version": "7.21.0-placeholder-for-preset-env.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
|
||||
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -5479,7 +5479,7 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
|
||||
"integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5495,7 +5495,7 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
|
||||
"integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5677,7 +5677,7 @@
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
|
||||
"integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
|
||||
@@ -5694,7 +5694,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz",
|
||||
"integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5710,7 +5710,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz",
|
||||
"integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -5728,7 +5728,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz",
|
||||
"integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.25.9",
|
||||
@@ -5746,7 +5746,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz",
|
||||
"integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5762,7 +5762,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz",
|
||||
"integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5794,7 +5794,7 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz",
|
||||
"integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.25.9",
|
||||
@@ -5811,7 +5811,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz",
|
||||
"integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.25.9",
|
||||
@@ -5832,7 +5832,7 @@
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -5842,7 +5842,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz",
|
||||
"integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -5859,7 +5859,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz",
|
||||
"integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5875,7 +5875,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -5892,7 +5892,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz",
|
||||
"integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5908,7 +5908,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -5925,7 +5925,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
|
||||
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5941,7 +5941,7 @@
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz",
|
||||
"integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5957,7 +5957,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz",
|
||||
"integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -5989,7 +5989,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz",
|
||||
"integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -6006,7 +6006,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz",
|
||||
"integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.25.9",
|
||||
@@ -6024,7 +6024,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz",
|
||||
"integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6040,7 +6040,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz",
|
||||
"integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6056,7 +6056,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz",
|
||||
"integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6072,7 +6072,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz",
|
||||
"integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6088,7 +6088,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz",
|
||||
"integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.9",
|
||||
@@ -6121,7 +6121,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz",
|
||||
"integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.9",
|
||||
@@ -6140,7 +6140,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz",
|
||||
"integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.9",
|
||||
@@ -6157,7 +6157,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -6174,7 +6174,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz",
|
||||
"integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6205,7 +6205,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz",
|
||||
"integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6221,7 +6221,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz",
|
||||
"integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.25.9",
|
||||
@@ -6239,7 +6239,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz",
|
||||
"integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -6256,7 +6256,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz",
|
||||
"integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6288,7 +6288,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz",
|
||||
"integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6320,7 +6320,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz",
|
||||
"integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.25.9",
|
||||
@@ -6338,7 +6338,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz",
|
||||
"integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6423,7 +6423,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz",
|
||||
"integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -6440,7 +6440,7 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz",
|
||||
"integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -6457,7 +6457,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz",
|
||||
"integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6473,7 +6473,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz",
|
||||
"integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6489,7 +6489,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz",
|
||||
"integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
@@ -6506,7 +6506,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6522,7 +6522,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz",
|
||||
"integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6538,7 +6538,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz",
|
||||
"integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6573,7 +6573,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz",
|
||||
"integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
@@ -6589,7 +6589,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -6606,7 +6606,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -6623,7 +6623,7 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz",
|
||||
"integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.25.9",
|
||||
@@ -6640,7 +6640,7 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz",
|
||||
"integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.26.0",
|
||||
@@ -6741,7 +6741,7 @@
|
||||
"version": "0.1.6-no-external-plugins",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
|
||||
"integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
@@ -12553,7 +12553,6 @@
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
@@ -12573,7 +12572,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -12586,7 +12584,6 @@
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
@@ -12601,7 +12598,6 @@
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
@@ -12682,7 +12678,6 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
@@ -12981,7 +12976,6 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
@@ -13004,7 +12998,6 @@
|
||||
"version": "12.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "*",
|
||||
@@ -13015,7 +13008,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
@@ -14507,7 +14499,6 @@
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
@@ -14933,7 +14924,7 @@
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
|
||||
"integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.6",
|
||||
@@ -14948,7 +14939,7 @@
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
|
||||
"integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.2",
|
||||
@@ -14962,7 +14953,7 @@
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
|
||||
"integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.3"
|
||||
@@ -16819,7 +16810,7 @@
|
||||
"version": "3.39.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz",
|
||||
"integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.24.2"
|
||||
@@ -18049,7 +18040,6 @@
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-converter": {
|
||||
@@ -21623,7 +21613,7 @@
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
@@ -24038,7 +24028,6 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
@@ -25663,7 +25652,7 @@
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
@@ -28056,14 +28045,14 @@
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regenerate-unicode-properties": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
|
||||
"integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerate": "^1.4.2"
|
||||
@@ -28076,7 +28065,7 @@
|
||||
"version": "0.15.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
||||
"integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.4"
|
||||
@@ -28086,7 +28075,7 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
|
||||
"integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerate": "^1.4.2",
|
||||
@@ -28104,14 +28093,14 @@
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
|
||||
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/regjsparser": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
|
||||
"integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"jsesc": "~3.0.2"
|
||||
@@ -28124,7 +28113,7 @@
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
@@ -28316,7 +28305,7 @@
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
@@ -30573,7 +30562,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -31767,7 +31756,7 @@
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -31828,7 +31817,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
|
||||
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -31838,7 +31827,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
|
||||
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unicode-canonical-property-names-ecmascript": "^2.0.0",
|
||||
@@ -31852,7 +31841,7 @@
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
|
||||
"integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -31862,7 +31851,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
|
||||
"integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -32605,6 +32594,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/workerpool": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.2.tgz",
|
||||
"integrity": "sha512-8PCeZlCwu0+8hXruze1ahYNsY+M0LOCmbmySZ9BWWqWIXP9TAXa6FZCxACTDL/0j47pFcC4xW98Gr8nAC5oymg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -35226,6 +35221,7 @@
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"uuid": "^10.0.0",
|
||||
"workerpool": "10.0.2",
|
||||
"yup": "^0.32.11",
|
||||
"zod": "^4.1.8"
|
||||
},
|
||||
@@ -36157,6 +36153,25 @@
|
||||
"ohm-js": "^16.6.0"
|
||||
}
|
||||
},
|
||||
"packages/bruno-pool": {
|
||||
"name": "@usebruno/pool",
|
||||
"version": "0.1.0",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@usebruno/filestore": "0.1.0",
|
||||
"workerpool": "10.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "23.0.2",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@rollup/plugin-typescript": "12.1.2",
|
||||
"@types/node": "^24.1.0",
|
||||
"rollup": "3.30.0",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
},
|
||||
"packages/bruno-query": {
|
||||
"name": "@usebruno/query",
|
||||
"version": "0.1.0",
|
||||
@@ -36385,6 +36400,16 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"packages/bruno-storage": {
|
||||
"name": "@usebruno/storage",
|
||||
"version": "0.1.0",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.1.0",
|
||||
"typescript": "5.4.5"
|
||||
}
|
||||
},
|
||||
"packages/bruno-tests": {
|
||||
"name": "@usebruno/tests",
|
||||
"version": "0.0.1",
|
||||
|
||||
@@ -2,11 +2,85 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
|
||||
form.bruno-form {
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.cache-section-title {
|
||||
text-transform: uppercase;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
letter-spacing: 0.05em;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.cache-item {
|
||||
border: 1px solid ${(props) => props.theme.border.border1};
|
||||
border-radius: ${(props) => props.theme.border.radius.md};
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cache-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1rem;
|
||||
gap: 1rem;
|
||||
background: ${(props) => props.theme.background.surface0};
|
||||
border-bottom: 1px solid ${(props) => props.theme.border.border1};
|
||||
}
|
||||
|
||||
.cache-item-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.cache-item-title {
|
||||
font-size: ${(props) => props.theme.font.size.md};
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.beta-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 6px;
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
background: ${(props) => props.theme.status.info.background};
|
||||
color: ${(props) => props.theme.status.info.text};
|
||||
}
|
||||
|
||||
.cache-item-body {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 0.875rem 1rem;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.cache-item-body-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cache-item-description {
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.cache-item-size {
|
||||
font-size: ${(props) => props.theme.font.size.base};
|
||||
color: ${(props) => props.theme.colors.text.subtext2};
|
||||
margin: 0.5rem 0 0 0;
|
||||
}
|
||||
|
||||
.cache-item-size strong {
|
||||
font-weight: 600;
|
||||
color: ${(props) => props.theme.text};
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,120 +1,147 @@
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
savePreferences,
|
||||
clearHttpHttpsAgentCache
|
||||
} from 'providers/ReduxStore/slices/app';
|
||||
import { savePreferences, clearHttpHttpsAgentCache } from 'providers/ReduxStore/slices/app';
|
||||
import toast from 'react-hot-toast';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import * as Yup from 'yup';
|
||||
import debounce from 'lodash/debounce';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const cacheSchema = Yup.object().shape({
|
||||
sslSession: Yup.object({
|
||||
enabled: Yup.boolean()
|
||||
})
|
||||
});
|
||||
import { IconEraser } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import ToggleSwitch from 'components/ToggleSwitch';
|
||||
import ActionIcon from 'ui/ActionIcon';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { formatSize } from 'utils/common';
|
||||
|
||||
const Cache = () => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const dispatch = useDispatch();
|
||||
const { theme } = useTheme();
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
const handleSave = useCallback(
|
||||
(newCachePreferences) => {
|
||||
dispatch(
|
||||
savePreferences({
|
||||
...preferences,
|
||||
cache: newCachePreferences
|
||||
})
|
||||
).catch(() => toast.error('Failed to update cache preferences'));
|
||||
},
|
||||
[dispatch, preferences]
|
||||
);
|
||||
const fileCacheEnabled = get(preferences, 'cache.file.enabled', false);
|
||||
const sslSessionEnabled = get(preferences, 'cache.sslSession.enabled', false);
|
||||
|
||||
const handleSaveRef = useRef(handleSave);
|
||||
handleSaveRef.current = handleSave;
|
||||
const [fileCacheSize, setFileCacheSize] = useState(null);
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
sslSession: {
|
||||
enabled: get(preferences, 'cache.sslSession.enabled', false)
|
||||
}
|
||||
},
|
||||
validationSchema: cacheSchema,
|
||||
onSubmit: async (values) => {
|
||||
try {
|
||||
const newPreferences = await cacheSchema.validate(values, { abortEarly: true });
|
||||
handleSave(newPreferences);
|
||||
} catch (error) {
|
||||
console.error('Cache preferences validation error:', error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const debouncedSave = useCallback(
|
||||
debounce((values) => {
|
||||
cacheSchema
|
||||
.validate(values, { abortEarly: true })
|
||||
.then((validatedValues) => handleSaveRef.current(validatedValues))
|
||||
.catch(() => {});
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
const refreshFileCacheSize = useCallback(() => {
|
||||
if (!ipcRenderer) return;
|
||||
ipcRenderer
|
||||
.invoke('renderer:get-file-cache-size')
|
||||
.then((size) => setFileCacheSize(size))
|
||||
.catch(() => setFileCacheSize(null));
|
||||
}, [ipcRenderer]);
|
||||
|
||||
useEffect(() => {
|
||||
if (formik.dirty && formik.isValid) {
|
||||
debouncedSave(formik.values);
|
||||
}
|
||||
return () => {
|
||||
debouncedSave.flush();
|
||||
};
|
||||
}, [formik.values, formik.dirty, formik.isValid, debouncedSave]);
|
||||
refreshFileCacheSize();
|
||||
}, [refreshFileCacheSize, fileCacheEnabled]);
|
||||
|
||||
const handleAgentCachingChange = (e) => {
|
||||
formik.handleChange(e);
|
||||
// Immediately evict all cached agents when caching is disabled
|
||||
if (!e.target.checked) {
|
||||
const persist = (next) => {
|
||||
dispatch(savePreferences({ ...preferences, cache: next })).catch(() => {
|
||||
toast.error('Failed to update cache preferences');
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleFileCache = () => {
|
||||
persist({
|
||||
...preferences.cache,
|
||||
file: { enabled: !fileCacheEnabled }
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleSslSession = () => {
|
||||
const next = !sslSessionEnabled;
|
||||
persist({
|
||||
...preferences.cache,
|
||||
sslSession: { enabled: next }
|
||||
});
|
||||
if (!next) {
|
||||
dispatch(clearHttpHttpsAgentCache()).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetCache = () => {
|
||||
const handleClearFileCache = () => {
|
||||
if (!ipcRenderer) return;
|
||||
ipcRenderer
|
||||
.invoke('renderer:clear-file-cache')
|
||||
.then((size) => {
|
||||
setFileCacheSize(size);
|
||||
toast.success('File cache cleared');
|
||||
})
|
||||
.catch(() => toast.error('Failed to clear file cache'));
|
||||
};
|
||||
|
||||
const handleClearSslSession = () => {
|
||||
dispatch(clearHttpHttpsAgentCache())
|
||||
.then(() => toast.success('ssl session cache cleared'))
|
||||
.catch(() => toast.error('Failed to clear ssl session cache'));
|
||||
.then(() => toast.success('SSL session cache cleared'))
|
||||
.catch(() => toast.error('Failed to clear SSL session cache'));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div className="section-title mt-6 mb-3">Cache SSL Session</div>
|
||||
<div className="cache-section-title">Cache</div>
|
||||
|
||||
<div className="flex items-center my-2">
|
||||
<input
|
||||
id="sslSession.enabled"
|
||||
type="checkbox"
|
||||
name="sslSession.enabled"
|
||||
checked={formik.values.sslSession.enabled}
|
||||
onChange={handleAgentCachingChange}
|
||||
className="mousetrap mr-0"
|
||||
<div className="cache-item">
|
||||
<div className="cache-item-header">
|
||||
<div className="cache-item-title-group">
|
||||
<span className="cache-item-title">File cache</span>
|
||||
<span className="beta-badge">Beta</span>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
data-testid="cache.file.enabled"
|
||||
isOn={fileCacheEnabled}
|
||||
handleToggle={handleToggleFileCache}
|
||||
size="2xs"
|
||||
activeColor={theme.primary.solid}
|
||||
/>
|
||||
<label className="block ml-2 select-none" htmlFor="sslSession.enabled">
|
||||
Enable SSL session caching
|
||||
</label>
|
||||
</div>
|
||||
<div className="text-xs mt-1 ml-6 opacity-70">
|
||||
Reuses TLS sessions and connections across requests for faster handshakes. Disable to create a fresh connection for every
|
||||
request.
|
||||
<div className="cache-item-body">
|
||||
<div className="cache-item-body-text">
|
||||
<p className="cache-item-description">
|
||||
Loads your workspace faster by caching opened collections. Bruno refreshes the cache when your collection
|
||||
changes. Clearing it won't affect your original files.
|
||||
</p>
|
||||
<p className="cache-item-size">
|
||||
Cache size <strong>{fileCacheSize == null ? '—' : formatSize(fileCacheSize)}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<ActionIcon
|
||||
label="Clear cache"
|
||||
onClick={handleClearFileCache}
|
||||
disabled={!fileCacheSize}
|
||||
colorOnHover={theme.colors.text.danger}
|
||||
>
|
||||
<IconEraser size={16} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<button type="button" className="text-link cursor-pointer hover:underline" onClick={handleResetCache}>
|
||||
Clear
|
||||
</button>
|
||||
<div className="cache-item">
|
||||
<div className="cache-item-header">
|
||||
<div className="cache-item-title-group">
|
||||
<span className="cache-item-title">SSL session cache</span>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
data-testid="sslSession.enabled"
|
||||
isOn={sslSessionEnabled}
|
||||
handleToggle={handleToggleSslSession}
|
||||
size="2xs"
|
||||
activeColor={theme.primary.solid}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div className="cache-item-body">
|
||||
<div className="cache-item-body-text">
|
||||
<p className="cache-item-description">
|
||||
Reuses TLS sessions and connections across requests for faster handshakes. Disable to create a fresh
|
||||
connection for every request.
|
||||
</p>
|
||||
</div>
|
||||
<ActionIcon
|
||||
label="Clear cache"
|
||||
onClick={handleClearSslSession}
|
||||
colorOnHover={theme.colors.text.danger}
|
||||
>
|
||||
<IconEraser size={16} strokeWidth={1.5} />
|
||||
</ActionIcon>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ import toast from 'react-hot-toast';
|
||||
import { useDispatch, useStore } from 'react-redux';
|
||||
import { isElectron } from 'utils/common/platform';
|
||||
import { globalEnvironmentsUpdateEvent, updateGlobalEnvironments } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { collectionAddOauth2CredentialsByUrl, collectionClearOauth2CredentialsByCredentialsId, updateCollectionLoadingState } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { collectionAddOauth2CredentialsByUrl, collectionClearOauth2CredentialsByCredentialsId, updateCollectionLoadingState, collectionLoadedFromTree } from 'providers/ReduxStore/slices/collections/index';
|
||||
import { addLog } from 'providers/ReduxStore/slices/logs';
|
||||
import { loadNotifications } from 'providers/ReduxStore/slices/notifications';
|
||||
import { updateSystemResources } from 'providers/ReduxStore/slices/performance';
|
||||
@@ -348,7 +348,22 @@ const useIpcEvents = () => {
|
||||
dispatch(loadNotifications(notifications));
|
||||
});
|
||||
|
||||
const removeCollectionTreeLoadedListener = ipcRenderer.on('main:collection-tree-loaded', ({ collectionUid, tree }) => {
|
||||
dispatch(collectionLoadedFromTree({ collectionUid, tree }));
|
||||
});
|
||||
|
||||
const removeCollectionLoadingStateV2Listener = ipcRenderer.on('main:collection-loading-state-updated-v2', (val) => {
|
||||
dispatch(updateCollectionLoadingState(val));
|
||||
});
|
||||
|
||||
const removeBrunoConfigUpdateV2Listener = ipcRenderer.on('main:bruno-config-update-v2', (val) => {
|
||||
dispatch(brunoConfigUpdateEvent(val));
|
||||
});
|
||||
|
||||
return () => {
|
||||
removeCollectionTreeLoadedListener();
|
||||
removeCollectionLoadingStateV2Listener();
|
||||
removeBrunoConfigUpdateV2Listener();
|
||||
removeCollectionTreeUpdateListener();
|
||||
removeApiSpecTreeUpdateListener();
|
||||
removeOpenCollectionListener();
|
||||
|
||||
@@ -4,7 +4,7 @@ import filter from 'lodash/filter';
|
||||
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { removeTaskFromQueue } from 'providers/ReduxStore/slices/app';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { collectionAddFileEvent, collectionChangeFileEvent } from 'providers/ReduxStore/slices/collections';
|
||||
import { collectionAddFileEvent, collectionChangeFileEvent, collectionLoadedFromTree } from 'providers/ReduxStore/slices/collections';
|
||||
import { findCollectionByUid, findItemInCollectionByPathname, getDefaultRequestPaneTab, findItemInCollectionByItemUid } from 'utils/collections/index';
|
||||
import { taskTypes } from './utils';
|
||||
|
||||
@@ -25,31 +25,53 @@ taskMiddleware.startListening({
|
||||
|
||||
const openRequestTasks = filter(state.app.taskQueue, { type: taskTypes.OPEN_REQUEST });
|
||||
each(openRequestTasks, (task) => {
|
||||
if (collectionUid === task.collectionUid) {
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
if (collection && collection.mountStatus === 'mounted' && !collection.isLoading) {
|
||||
const item = findItemInCollectionByPathname(collection, task.itemPathname);
|
||||
if (item) {
|
||||
listenerApi.dispatch(
|
||||
addTab({
|
||||
uid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
type: item.type,
|
||||
pathname: item.pathname,
|
||||
requestPaneTab: getDefaultRequestPaneTab(item),
|
||||
preview: task?.preview ?? true,
|
||||
...(item.isTransient ? { isTransient: true } : {})
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if (collectionUid !== task.collectionUid) return;
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
if (!collection || collection.mountStatus !== 'mounted' || collection.isLoading) return;
|
||||
const item = findItemInCollectionByPathname(collection, task.itemPathname);
|
||||
if (!item) return;
|
||||
listenerApi.dispatch(
|
||||
addTab({
|
||||
uid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
type: item.type,
|
||||
pathname: item.pathname,
|
||||
requestPaneTab: getDefaultRequestPaneTab(item),
|
||||
preview: task?.preview ?? true,
|
||||
...(item.isTransient ? { isTransient: true } : {})
|
||||
})
|
||||
);
|
||||
listenerApi.dispatch(removeTaskFromQueue({ taskUid: task.uid }));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
listenerApi.dispatch(
|
||||
removeTaskFromQueue({
|
||||
taskUid: task.uid
|
||||
})
|
||||
);
|
||||
}
|
||||
// v2 tree push also acts as a signal for queued OPEN_REQUEST tasks.
|
||||
taskMiddleware.startListening({
|
||||
actionCreator: collectionLoadedFromTree,
|
||||
effect: (action, listenerApi) => {
|
||||
const state = listenerApi.getState();
|
||||
const collectionUid = get(action, 'payload.collectionUid');
|
||||
|
||||
const openRequestTasks = filter(state.app.taskQueue, { type: taskTypes.OPEN_REQUEST });
|
||||
each(openRequestTasks, (task) => {
|
||||
if (collectionUid !== task.collectionUid) return;
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
if (!collection || collection.mountStatus !== 'mounted' || collection.isLoading) return;
|
||||
const item = findItemInCollectionByPathname(collection, task.itemPathname);
|
||||
if (!item) return;
|
||||
listenerApi.dispatch(
|
||||
addTab({
|
||||
uid: item.uid,
|
||||
collectionUid: collection.uid,
|
||||
type: item.type,
|
||||
pathname: item.pathname,
|
||||
requestPaneTab: getDefaultRequestPaneTab(item),
|
||||
preview: task?.preview ?? true,
|
||||
...(item.isTransient ? { isTransient: true } : {})
|
||||
})
|
||||
);
|
||||
listenerApi.dispatch(removeTaskFromQueue({ taskUid: task.uid }));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3122,8 +3122,10 @@ export const mountCollection
|
||||
= ({ collectionUid, collectionPathname, brunoConfig, skipTabRestore = false, workspacePathname = null }) =>
|
||||
(dispatch, getState) => {
|
||||
dispatch(updateCollectionMountStatus({ collectionUid, mountStatus: 'mounting' }));
|
||||
const fileCacheEnabled = getState().app?.preferences?.cache?.file?.enabled;
|
||||
const channel = fileCacheEnabled ? 'renderer:mount-collection-v2' : 'renderer:mount-collection';
|
||||
return new Promise(async (resolve, reject) => {
|
||||
callIpc('renderer:mount-collection', { collectionUid, collectionPathname, brunoConfig })
|
||||
callIpc(channel, { collectionUid, collectionPathname, brunoConfig })
|
||||
.then(async (transientDirPath) => {
|
||||
dispatch(updateCollectionMountStatus({ collectionUid, mountStatus: 'mounted' }));
|
||||
dispatch(addTransientDirectory({ collectionUid, pathname: transientDirPath }));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseQueryParams, buildQueryString as stringifyQueryParams } from '@usebruno/common/utils';
|
||||
import { uuid } from 'utils/common';
|
||||
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, findIndex } from 'lodash';
|
||||
import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, findIndex, pick } from 'lodash';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { hexy as hexdump } from 'hexy';
|
||||
import {
|
||||
@@ -27,6 +27,69 @@ import { getCollectionEnvironmentPath } from 'utils/snapshot';
|
||||
import { getDataTypeFromValue } from '@usebruno/common/utils';
|
||||
import * as exampleReducers from './exampleReducers';
|
||||
|
||||
const FILE_DERIVED_REQUEST_FIELDS = [
|
||||
'name',
|
||||
'type',
|
||||
'seq',
|
||||
'tags',
|
||||
'request',
|
||||
'settings',
|
||||
'examples',
|
||||
'filename',
|
||||
'pathname',
|
||||
'partial',
|
||||
'loading',
|
||||
'size',
|
||||
'error',
|
||||
'isTransient'
|
||||
];
|
||||
|
||||
const FILE_DERIVED_FOLDER_FIELDS = [
|
||||
'name',
|
||||
'filename',
|
||||
'pathname',
|
||||
'seq',
|
||||
'type',
|
||||
'root'
|
||||
];
|
||||
|
||||
const mergeTreeItems = (existingItems, newItems) => {
|
||||
if (!Array.isArray(existingItems) || existingItems.length === 0) return newItems;
|
||||
const existingByUid = new Map();
|
||||
for (const item of existingItems) {
|
||||
if (item && item.uid) existingByUid.set(item.uid, item);
|
||||
}
|
||||
|
||||
return newItems.map((newItem) => {
|
||||
const existing = existingByUid.get(newItem.uid);
|
||||
if (!existing) return newItem;
|
||||
|
||||
if (newItem.type === 'folder') {
|
||||
const merged = { ...existing, ...pick(newItem, FILE_DERIVED_FOLDER_FIELDS) };
|
||||
merged.items = mergeTreeItems(existing.items, newItem.items || []);
|
||||
return merged;
|
||||
}
|
||||
|
||||
// seq-only change (reorder) — keep everything else, including the draft
|
||||
if (areItemsTheSameExceptSeqUpdate(existing, newItem)) {
|
||||
const merged = { ...existing, seq: newItem.seq };
|
||||
if (merged.draft) {
|
||||
merged.draft = { ...merged.draft, seq: newItem.seq };
|
||||
if (areItemsTheSameExceptSeqUpdate(merged.draft, newItem)) {
|
||||
merged.draft = null;
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
const merged = { ...existing, ...pick(newItem, FILE_DERIVED_REQUEST_FIELDS) };
|
||||
// only drop the draft if it matches what's on disk — user may still be typing
|
||||
const draftMatchesFile = existing.draft && areItemsTheSameExceptSeqUpdate(existing.draft, newItem);
|
||||
merged.draft = draftMatchesFile ? null : (existing.draft || null);
|
||||
return merged;
|
||||
});
|
||||
};
|
||||
|
||||
// gRPC status code meanings
|
||||
const grpcStatusCodes = {
|
||||
0: 'OK',
|
||||
@@ -3341,6 +3404,35 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
collectionLoadedFromTree: (state, action) => {
|
||||
const { collectionUid, tree } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
if (!collection) return;
|
||||
|
||||
collection.items = mergeTreeItems(collection.items, tree?.items || []);
|
||||
collection.environments = tree?.environments || [];
|
||||
if (tree?.root !== undefined) {
|
||||
collection.root = tree.root;
|
||||
}
|
||||
if (tree?.brunoConfig) {
|
||||
collection.brunoConfig = tree.brunoConfig;
|
||||
}
|
||||
const tempDirectory = state.tempDirectories?.[collectionUid];
|
||||
if (tempDirectory) {
|
||||
const annotateTransient = (items) => {
|
||||
for (const item of items) {
|
||||
if (item.pathname && item.pathname.startsWith(tempDirectory)) {
|
||||
item.isTransient = true;
|
||||
}
|
||||
if (item.type === 'folder' && Array.isArray(item.items)) {
|
||||
annotateTransient(item.items);
|
||||
}
|
||||
}
|
||||
};
|
||||
annotateTransient(collection.items);
|
||||
}
|
||||
addDepth(collection.items);
|
||||
},
|
||||
collectionAddOauth2CredentialsByUrl: (state, action) => {
|
||||
const { collectionUid, folderUid, itemUid, url, credentials, credentialsId, debugInfo, executionMode } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
@@ -3734,6 +3826,7 @@ export const {
|
||||
createCollection,
|
||||
updateCollectionMountStatus,
|
||||
updateCollectionLoadingState,
|
||||
collectionLoadedFromTree,
|
||||
setCollectionSecurityConfig,
|
||||
brunoConfigUpdateEvent,
|
||||
renameCollection,
|
||||
|
||||
@@ -1344,11 +1344,20 @@ export const mountScratchCollection = (workspaceUid) => {
|
||||
ignore: ['node_modules', '.git']
|
||||
};
|
||||
|
||||
await ipcRenderer.invoke('renderer:add-collection-watcher', {
|
||||
collectionPath: tempDirectoryPath,
|
||||
collectionUid: scratchCollectionUid,
|
||||
brunoConfig
|
||||
});
|
||||
const fileCacheEnabled = state.app?.preferences?.cache?.file?.enabled;
|
||||
if (fileCacheEnabled) {
|
||||
await ipcRenderer.invoke('renderer:mount-collection-v2', {
|
||||
collectionUid: scratchCollectionUid,
|
||||
collectionPathname: tempDirectoryPath,
|
||||
brunoConfig
|
||||
});
|
||||
} else {
|
||||
await ipcRenderer.invoke('renderer:add-collection-watcher', {
|
||||
collectionPath: tempDirectoryPath,
|
||||
collectionUid: scratchCollectionUid,
|
||||
brunoConfig
|
||||
});
|
||||
}
|
||||
|
||||
// Map scratch collection to workspace so getProcessEnvVars can resolve workspace .env values
|
||||
if (workspace.pathname) {
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"socks-proxy-agent": "^8.0.2",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"uuid": "^10.0.0",
|
||||
"workerpool": "10.0.2",
|
||||
"yup": "^0.32.11",
|
||||
"zod": "^4.1.8"
|
||||
},
|
||||
|
||||
@@ -33,6 +33,27 @@ const MAX_FILE_SIZE = 2.5 * 1024 * 1024;
|
||||
|
||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||
|
||||
// registered collections stage parsed data into the git-lite snapshot (no-op otherwise)
|
||||
const fileIndexByCollection = new Map();
|
||||
const stageToCache = (collectionPath, pathname, data) => {
|
||||
const index = fileIndexByCollection.get(collectionPath);
|
||||
if (!index) return;
|
||||
try {
|
||||
index.stageParsed(collectionPath, pathname, data);
|
||||
} catch (err) {
|
||||
console.error('[collection-watcher] cache stage failed for', pathname, err);
|
||||
}
|
||||
};
|
||||
const unstageFromCache = (collectionPath, pathname) => {
|
||||
const index = fileIndexByCollection.get(collectionPath);
|
||||
if (!index) return;
|
||||
try {
|
||||
index.unstagePath(collectionPath, pathname);
|
||||
} catch (err) {
|
||||
console.error('[collection-watcher] cache unstage failed for', pathname, err);
|
||||
}
|
||||
};
|
||||
|
||||
const isBrunoConfigFile = (pathname, collectionPath) => {
|
||||
const dirname = path.dirname(pathname);
|
||||
const basename = path.basename(pathname);
|
||||
@@ -107,6 +128,7 @@ const addEnvironmentFile = async (win, pathname, collectionUid, collectionPath)
|
||||
let content = fs.readFileSync(pathname, 'utf8');
|
||||
|
||||
file.data = await parseEnvironment(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
|
||||
// Extract name by removing the extension
|
||||
const ext = path.extname(basename);
|
||||
@@ -148,6 +170,7 @@ const changeEnvironmentFile = async (win, pathname, collectionUid, collectionPat
|
||||
const content = fs.readFileSync(pathname, 'utf8');
|
||||
|
||||
file.data = await parseEnvironment(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
|
||||
// Extract name by removing the extension
|
||||
const ext = path.extname(basename);
|
||||
@@ -203,6 +226,7 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
||||
try {
|
||||
const content = fs.readFileSync(pathname, 'utf8');
|
||||
let brunoConfig = JSON.parse(content);
|
||||
stageToCache(collectionPath, pathname, brunoConfig);
|
||||
|
||||
// Transform the config to add exists metadata for protobuf files and import paths
|
||||
brunoConfig = await transformBrunoConfigAfterRead(brunoConfig, collectionPath);
|
||||
@@ -249,6 +273,8 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
||||
}
|
||||
|
||||
file.data = collectionRoot;
|
||||
// cache the full parse result (collectionRoot + brunoConfig for yml)
|
||||
stageToCache(collectionPath, pathname, parsed);
|
||||
|
||||
hydrateCollectionRootWithUuid(file.data);
|
||||
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
@@ -288,6 +314,7 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
||||
let format = getCollectionFormat(collectionPath);
|
||||
let content = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = await parseFolder(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
|
||||
hydrateCollectionRootWithUuid(file.data);
|
||||
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
@@ -317,6 +344,7 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
||||
if (!useWorkerThread) {
|
||||
try {
|
||||
file.data = await parseRequest(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
file.partial = false;
|
||||
file.loading = false;
|
||||
file.size = sizeInMB(fileStats?.size);
|
||||
@@ -360,6 +388,7 @@ const add = async (win, pathname, collectionUid, collectionPath, useWorkerThread
|
||||
format,
|
||||
filename: pathname
|
||||
});
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
file.partial = false;
|
||||
file.loading = false;
|
||||
file.data.raw = content;
|
||||
@@ -429,6 +458,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
try {
|
||||
const content = fs.readFileSync(pathname, 'utf8');
|
||||
let brunoConfig = JSON.parse(content);
|
||||
stageToCache(collectionPath, pathname, brunoConfig);
|
||||
|
||||
// Transform the config to add file existence checks for protobuf files and import paths
|
||||
brunoConfig = await transformBrunoConfigAfterRead(brunoConfig, collectionPath);
|
||||
@@ -477,6 +507,8 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
}
|
||||
|
||||
file.data = collectionRoot;
|
||||
// cache the full parse result (collectionRoot + brunoConfig for yml)
|
||||
stageToCache(collectionPath, pathname, parsed);
|
||||
|
||||
hydrateCollectionRootWithUuid(file.data);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
@@ -516,6 +548,7 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
let format = getCollectionFormat(collectionPath);
|
||||
let content = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = await parseFolder(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
|
||||
hydrateCollectionRootWithUuid(file.data);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
@@ -541,9 +574,11 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
const fileStats = fs.statSync(pathname);
|
||||
|
||||
if (fileStats.size >= MAX_FILE_SIZE && format === 'bru') {
|
||||
// redacted parse — do not write the redacted data through to the cache
|
||||
file.data = await parseLargeRequestWithRedaction(content, 'bru');
|
||||
} else {
|
||||
file.data = await parseRequest(content, { format });
|
||||
stageToCache(collectionPath, pathname, file.data);
|
||||
}
|
||||
|
||||
file.data.raw = content;
|
||||
@@ -562,6 +597,8 @@ const unlink = (win, pathname, collectionUid, collectionPath) => {
|
||||
return;
|
||||
}
|
||||
console.log(`watcher unlink: ${pathname}`);
|
||||
// drop the file from the snapshot regardless of type (request/env/config/folder root)
|
||||
unstageFromCache(collectionPath, pathname);
|
||||
|
||||
if (isEnvironmentsFolder(pathname, collectionPath)) {
|
||||
return unlinkEnvironmentFile(win, pathname, collectionUid);
|
||||
@@ -729,11 +766,17 @@ class CollectionWatcher {
|
||||
delete this.loadingStates[collectionUid];
|
||||
}
|
||||
|
||||
addWatcher(win, watchPath, collectionUid, brunoConfig, forcePolling = false, useWorkerThread) {
|
||||
addWatcher(win, watchPath, collectionUid, brunoConfig, forcePolling = false, useWorkerThread, options = {}) {
|
||||
if (this.watchers[watchPath]) {
|
||||
this.watchers[watchPath].close();
|
||||
}
|
||||
|
||||
// v2 already loaded the tree from cache; skip startup scan and stage live edits
|
||||
const { ignoreInitial = false, fileIndex = null } = options;
|
||||
if (fileIndex) {
|
||||
fileIndexByCollection.set(watchPath, fileIndex);
|
||||
}
|
||||
|
||||
this.initializeLoadingState(collectionUid);
|
||||
|
||||
this.startCollectionDiscovery(win, collectionUid);
|
||||
@@ -746,7 +789,7 @@ class CollectionWatcher {
|
||||
|
||||
setTimeout(() => {
|
||||
const watcher = chokidar.watch(watchPath, {
|
||||
ignoreInitial: false,
|
||||
ignoreInitial,
|
||||
usePolling: isWSLPath(watchPath) || forcePolling ? true : false,
|
||||
ignored: (filepath) => {
|
||||
const normalizedPath = normalizeAndResolvePath(filepath);
|
||||
@@ -802,7 +845,7 @@ class CollectionWatcher {
|
||||
'Update your system config to allow more concurrently watched files with:',
|
||||
'"echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p"'
|
||||
);
|
||||
this.addWatcher(win, watchPath, collectionUid, brunoConfig, true, useWorkerThread);
|
||||
this.addWatcher(win, watchPath, collectionUid, brunoConfig, true, useWorkerThread, options);
|
||||
} else {
|
||||
console.error(`An error occurred in the watcher for: ${watchPath}`, error);
|
||||
}
|
||||
@@ -824,6 +867,8 @@ class CollectionWatcher {
|
||||
this.watchers[watchPath] = null;
|
||||
}
|
||||
|
||||
fileIndexByCollection.delete(watchPath);
|
||||
|
||||
dotEnvWatcher.removeCollectionWatcher(watchPath);
|
||||
|
||||
const tempDirectoryPath = this.tempDirectoryMap[watchPath];
|
||||
|
||||
@@ -46,6 +46,7 @@ const registerApiSpecIpc = require('./ipc/apiSpec');
|
||||
const registerGitIpc = require('./ipc/git');
|
||||
const registerOpenAPISyncIpc = require('./ipc/openapi-sync');
|
||||
const registerAiIpc = require('./ipc/ai');
|
||||
const { registerMountIpc } = require('./ipc/mount');
|
||||
const collectionWatcher = require('./app/collection-watcher');
|
||||
const WorkspaceWatcher = require('./app/workspace-watcher');
|
||||
const ApiSpecWatcher = require('./app/apiSpecsWatcher');
|
||||
@@ -477,6 +478,7 @@ app.on('ready', async () => {
|
||||
registerGitIpc(mainWindow);
|
||||
registerOpenAPISyncIpc(mainWindow);
|
||||
registerAiIpc(mainWindow);
|
||||
registerMountIpc();
|
||||
|
||||
// Internal delegator
|
||||
ipcMain.handle('main:cache-clear', async () => {
|
||||
@@ -505,6 +507,8 @@ app.on('before-quit', (event) => {
|
||||
]);
|
||||
} catch {}
|
||||
|
||||
try { await require('./ipc/mount').shutdown(); } catch {}
|
||||
|
||||
if (useSingleInstance && gotTheLock) {
|
||||
try { app.releaseSingleInstanceLock(); } catch {}
|
||||
}
|
||||
|
||||
@@ -1263,6 +1263,8 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
|
||||
}
|
||||
}
|
||||
|
||||
await require('./mount').unmount(collectionUid).catch(() => {});
|
||||
|
||||
// Clean up
|
||||
const { clearCollectionWorkspace } = require('../store/process-env');
|
||||
clearCollectionWorkspace(collectionUid);
|
||||
|
||||
35
packages/bruno-electron/src/ipc/mount.js
Normal file
35
packages/bruno-electron/src/ipc/mount.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { ipcMain, BrowserWindow } = require('electron');
|
||||
const { MountManager } = require('../services/mount');
|
||||
|
||||
const manager = new MountManager();
|
||||
|
||||
const registerMountIpc = () => {
|
||||
ipcMain.handle('renderer:get-file-cache-size', () => manager.getCacheSize());
|
||||
|
||||
ipcMain.handle('renderer:clear-file-cache', () => {
|
||||
manager.clearCache();
|
||||
return manager.getCacheSize();
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'renderer:mount-collection-v2',
|
||||
async (event, { collectionUid, collectionPathname, brunoConfig }) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
const send = (channel, payload) => {
|
||||
if (!win || win.isDestroyed?.()) return;
|
||||
win.webContents.send(channel, payload);
|
||||
};
|
||||
const emit = {
|
||||
tree: (tree) => send('main:collection-tree-loaded', { collectionUid, tree }),
|
||||
loading: (isLoading) => send('main:collection-loading-state-updated-v2', { collectionUid, isLoading }),
|
||||
config: (brunoConfig) => send('main:bruno-config-update-v2', { collectionUid, brunoConfig })
|
||||
};
|
||||
return manager.mount({ win, collectionPath: collectionPathname, collectionUid, brunoConfig, emit });
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const unmount = (collectionUid) => manager.unmount(collectionUid);
|
||||
const shutdown = () => manager.shutdown();
|
||||
|
||||
module.exports = { registerMountIpc, unmount, shutdown };
|
||||
188
packages/bruno-electron/src/services/mount/file-index.js
Normal file
188
packages/bruno-electron/src/services/mount/file-index.js
Normal file
@@ -0,0 +1,188 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Database } = require('../storage');
|
||||
const {
|
||||
hashFile,
|
||||
hashFileAsync,
|
||||
normalize,
|
||||
posixifyPath,
|
||||
idForAbsolutePath,
|
||||
resolveDenylist,
|
||||
isDenied,
|
||||
walk
|
||||
} = require('../../utils/mount');
|
||||
|
||||
const MIGRATIONS = [
|
||||
{
|
||||
version: 1,
|
||||
up: `
|
||||
CREATE TABLE IF NOT EXISTS file_index_entries (
|
||||
collection_path TEXT NOT NULL,
|
||||
relative_path TEXT NOT NULL,
|
||||
id TEXT NOT NULL,
|
||||
mtime INTEGER NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
PRIMARY KEY (collection_path, relative_path)
|
||||
) WITHOUT ROWID;
|
||||
CREATE INDEX IF NOT EXISTS idx_collection_path ON file_index_entries(collection_path);
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
class FileIndex {
|
||||
#db;
|
||||
#dbPath;
|
||||
|
||||
constructor({ dbPath } = {}) {
|
||||
this.#dbPath = dbPath || path.join(require('electron').app.getPath('userData'), 'mount-snapshots.db');
|
||||
this.#db = new Database({ path: this.#dbPath, migrations: MIGRATIONS, readBigInts: true });
|
||||
}
|
||||
|
||||
close() {
|
||||
this.#db.close();
|
||||
}
|
||||
|
||||
async status(collectionPath, options = {}) {
|
||||
const root = normalize(collectionPath);
|
||||
const stored = this.#loadStored(root);
|
||||
const denylist = resolveDenylist(options.denylist);
|
||||
const added = [];
|
||||
const updated = [];
|
||||
const removed = [];
|
||||
const seen = new Set();
|
||||
|
||||
const files = walk(root, denylist);
|
||||
const results = await Promise.all(files.map(async ({ relativePath, absolutePath }) => {
|
||||
const stat = await fs.promises.stat(absolutePath, { bigint: true });
|
||||
const mtime = stat.mtimeNs;
|
||||
const prior = stored.get(relativePath);
|
||||
|
||||
if (!prior) {
|
||||
const hash = await hashFileAsync(absolutePath);
|
||||
return { kind: 'added', entry: { relativePath, absolutePath, mtime, hash } };
|
||||
}
|
||||
if (prior.mtime === mtime) return { kind: 'unchanged', relativePath };
|
||||
const hash = await hashFileAsync(absolutePath);
|
||||
if (hash === prior.hash) return { kind: 'unchanged', relativePath };
|
||||
return { kind: 'updated', entry: { relativePath, absolutePath, mtime, hash, prevHash: prior.hash } };
|
||||
}));
|
||||
|
||||
for (const r of results) {
|
||||
if (r.kind === 'added') {
|
||||
added.push(r.entry);
|
||||
seen.add(r.entry.relativePath);
|
||||
} else if (r.kind === 'updated') {
|
||||
updated.push(r.entry);
|
||||
seen.add(r.entry.relativePath);
|
||||
} else {
|
||||
seen.add(r.relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [relativePath, row] of stored) {
|
||||
if (seen.has(relativePath)) continue;
|
||||
if (isDenied(posixifyPath(relativePath), denylist)) continue;
|
||||
removed.push({ relativePath, id: row.id, hash: row.hash });
|
||||
}
|
||||
|
||||
return { added, updated, removed };
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#db.exec('DELETE FROM file_index_entries');
|
||||
// VACUUM so the file actually shrinks after the DELETE
|
||||
this.#db.exec('VACUUM');
|
||||
}
|
||||
|
||||
get dbPath() {
|
||||
return this.#dbPath;
|
||||
}
|
||||
|
||||
entries(collectionPath) {
|
||||
const root = normalize(collectionPath);
|
||||
const rows = this.#db.all(
|
||||
'SELECT relative_path AS relativePath, data FROM file_index_entries WHERE collection_path = ?',
|
||||
root
|
||||
);
|
||||
const map = new Map();
|
||||
for (const row of rows) {
|
||||
map.set(row.relativePath, { data: JSON.parse(row.data) });
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
stage(collectionPath, entry) {
|
||||
const root = normalize(collectionPath);
|
||||
const { op, relativePath } = entry;
|
||||
|
||||
if (op === 'remove') {
|
||||
this.#db.run(
|
||||
'DELETE FROM file_index_entries WHERE collection_path = ? AND relative_path = ?',
|
||||
root,
|
||||
relativePath
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { mtime, hash, data } = entry;
|
||||
const id = idForAbsolutePath(path.join(root, relativePath));
|
||||
this.#db.run(
|
||||
`
|
||||
INSERT INTO file_index_entries (collection_path, relative_path, id, mtime, hash, data)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(collection_path, relative_path) DO UPDATE SET
|
||||
mtime = excluded.mtime,
|
||||
hash = excluded.hash,
|
||||
data = excluded.data
|
||||
`,
|
||||
root,
|
||||
relativePath,
|
||||
id,
|
||||
mtime,
|
||||
hash,
|
||||
JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
|
||||
stageParsed(collectionPath, absolutePath, data) {
|
||||
const root = normalize(collectionPath);
|
||||
const relativePath = path.relative(root, normalize(absolutePath));
|
||||
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) return;
|
||||
const stat = fs.statSync(absolutePath, { bigint: true });
|
||||
this.stage(root, {
|
||||
op: 'add',
|
||||
relativePath,
|
||||
mtime: stat.mtimeNs,
|
||||
hash: hashFile(absolutePath),
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
unstagePath(collectionPath, absolutePath) {
|
||||
const root = normalize(collectionPath);
|
||||
const relativePath = path.relative(root, normalize(absolutePath));
|
||||
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) return;
|
||||
this.stage(root, { op: 'remove', relativePath });
|
||||
}
|
||||
|
||||
transaction(callback) {
|
||||
return this.#db.transaction(callback);
|
||||
}
|
||||
|
||||
#loadStored(collectionPath) {
|
||||
const rows = this.#db.all(
|
||||
'SELECT relative_path AS relativePath, id, mtime, hash FROM file_index_entries WHERE collection_path = ?',
|
||||
collectionPath
|
||||
);
|
||||
const map = new Map();
|
||||
for (const row of rows) {
|
||||
map.set(row.relativePath, row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FileIndex
|
||||
};
|
||||
3
packages/bruno-electron/src/services/mount/index.js
Normal file
3
packages/bruno-electron/src/services/mount/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const { MountManager } = require('./manager');
|
||||
|
||||
module.exports = { MountManager };
|
||||
223
packages/bruno-electron/src/services/mount/manager.js
Normal file
223
packages/bruno-electron/src/services/mount/manager.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { JobType, getPool, destroyPool } = require('../pool');
|
||||
const { FileIndex } = require('./file-index');
|
||||
const { buildTree } = require('./tree-builder');
|
||||
const { defaultClassify, uidForSeed } = require('../../utils/mount');
|
||||
|
||||
// cold start only — collection-watcher handles live changes and writes through to the cache
|
||||
|
||||
let _envSecretsStore = null;
|
||||
const getEnvSecretsStore = () => {
|
||||
if (!_envSecretsStore) {
|
||||
const EnvironmentSecretsStore = require('../../store/env-secrets');
|
||||
_envSecretsStore = new EnvironmentSecretsStore();
|
||||
}
|
||||
return _envSecretsStore;
|
||||
};
|
||||
|
||||
const envHasSecrets = (env) => Array.isArray(env?.variables) && env.variables.some((v) => v.secret);
|
||||
|
||||
const hydrateEnvironments = (collectionPath, environments) => {
|
||||
if (!Array.isArray(environments)) return;
|
||||
const { decryptStringSafe } = require('../../utils/encryption');
|
||||
for (const env of environments) {
|
||||
if (!Array.isArray(env.variables)) continue;
|
||||
env.variables.forEach((variable, i) => {
|
||||
const key = variable.name || `index:${i}`;
|
||||
variable.uid = uidForSeed(`${env.uid}#var#${key}`);
|
||||
});
|
||||
if (!envHasSecrets(env)) continue;
|
||||
try {
|
||||
const envSecrets = getEnvSecretsStore().getEnvSecrets(collectionPath, env);
|
||||
for (const secret of envSecrets || []) {
|
||||
const variable = env.variables.find((v) => v.name === secret.name);
|
||||
if (variable && secret.value) {
|
||||
const decrypted = decryptStringSafe(secret.value);
|
||||
variable.value = decrypted.value;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[mount] env secret hydration failed', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sendTree = async (collectionUid, collectionPath, tree, emit) => {
|
||||
if (tree.brunoConfig) {
|
||||
try {
|
||||
const { transformBrunoConfigAfterRead } = require('../../utils/transformBrunoConfig');
|
||||
const { setBrunoConfig } = require('../../store/bruno-config');
|
||||
const transformed = await transformBrunoConfigAfterRead(tree.brunoConfig, collectionPath);
|
||||
tree.brunoConfig = transformed;
|
||||
setBrunoConfig(collectionUid, transformed);
|
||||
emit.config(transformed);
|
||||
} catch (err) {
|
||||
console.error(`[mount:${collectionUid}] brunoConfig transform failed:`, err);
|
||||
}
|
||||
}
|
||||
hydrateEnvironments(collectionPath, tree.environments);
|
||||
emit.tree(tree);
|
||||
};
|
||||
|
||||
const ensureTransientDirectory = () => {
|
||||
const base = path.join(require('electron').app.getPath('userData'), 'tmp', 'transient');
|
||||
if (!fs.existsSync(base)) fs.mkdirSync(base, { recursive: true });
|
||||
return fs.mkdtempSync(path.join(base, 'bruno-'));
|
||||
};
|
||||
|
||||
class MountManager {
|
||||
#index = null;
|
||||
#mounts = new Map();
|
||||
|
||||
async mount({ win, collectionPath, collectionUid, brunoConfig, emit }) {
|
||||
collectionPath = path.resolve(collectionPath);
|
||||
|
||||
if (this.#mounts.has(collectionUid)) {
|
||||
// renderer reload — pull fresh state from cache and re-emit
|
||||
const existing = this.#mounts.get(collectionUid);
|
||||
existing.win = win;
|
||||
existing.emit = emit;
|
||||
existing.brunoConfig = brunoConfig || existing.brunoConfig;
|
||||
existing.state = this.#getIndex().entries(existing.collectionPath);
|
||||
await this.#emitTree(collectionUid, existing);
|
||||
return existing.tempDirectoryPath;
|
||||
}
|
||||
|
||||
const tempDirectoryPath = ensureTransientDirectory();
|
||||
fs.writeFileSync(path.join(tempDirectoryPath, 'metadata.json'), JSON.stringify({ collectionPath }));
|
||||
|
||||
const entry = {
|
||||
state: new Map(),
|
||||
collectionPath,
|
||||
tempDirectoryPath,
|
||||
brunoConfig,
|
||||
win,
|
||||
emit
|
||||
};
|
||||
this.#mounts.set(collectionUid, entry);
|
||||
|
||||
entry.emit.loading(true);
|
||||
try {
|
||||
entry.state = this.#getIndex().entries(collectionPath);
|
||||
await this.#reconcile(entry);
|
||||
await this.#emitTree(collectionUid, entry);
|
||||
|
||||
// skip the startup walk (already done) and stage live edits into the cache
|
||||
const collectionWatcher = require('../../app/collection-watcher');
|
||||
collectionWatcher.addWatcher(entry.win, collectionPath, collectionUid, brunoConfig, false, false, {
|
||||
ignoreInitial: true,
|
||||
fileIndex: this.#getIndex()
|
||||
});
|
||||
collectionWatcher.addTempDirectoryWatcher(entry.win, tempDirectoryPath, collectionUid, collectionPath);
|
||||
} finally {
|
||||
entry.emit.loading(false);
|
||||
}
|
||||
return tempDirectoryPath;
|
||||
}
|
||||
|
||||
async unmount(collectionUid) {
|
||||
const entry = this.#mounts.get(collectionUid);
|
||||
if (!entry) return;
|
||||
this.#mounts.delete(collectionUid);
|
||||
const collectionWatcher = require('../../app/collection-watcher');
|
||||
try {
|
||||
collectionWatcher.removeWatcher(entry.collectionPath, entry.win, collectionUid);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
await Promise.all(
|
||||
Array.from(this.#mounts.keys()).map((uid) => this.unmount(uid).catch(() => {}))
|
||||
);
|
||||
await destroyPool().catch(() => {});
|
||||
if (this.#index) {
|
||||
this.#index.close();
|
||||
this.#index = null;
|
||||
}
|
||||
}
|
||||
|
||||
getCacheSize() {
|
||||
try {
|
||||
return fs.statSync(this.#getIndex().dbPath).size;
|
||||
} catch (err) {
|
||||
if (err && err.code === 'ENOENT') return 0;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this.#getIndex().clear();
|
||||
}
|
||||
|
||||
async #reconcile(entry) {
|
||||
const denylist = entry.brunoConfig?.ignore || [];
|
||||
const { added, updated, removed } = await this.#getIndex().status(entry.collectionPath, { denylist });
|
||||
|
||||
const toParse = [];
|
||||
for (const e of [...added, ...updated]) {
|
||||
const cls = defaultClassify(e.relativePath);
|
||||
if (!cls) continue;
|
||||
toParse.push({ relativePath: e.relativePath, format: cls.format, type: cls.type });
|
||||
}
|
||||
|
||||
const parsed = new Map();
|
||||
if (toParse.length > 0) {
|
||||
const pool = getPool();
|
||||
await Promise.allSettled(
|
||||
toParse.map(async (e) => {
|
||||
try {
|
||||
const result = await pool.run(JobType.ParseFile, {
|
||||
collectionPath: entry.collectionPath,
|
||||
relativePath: e.relativePath,
|
||||
format: e.format,
|
||||
type: e.type
|
||||
});
|
||||
parsed.set(e.relativePath, result);
|
||||
} catch (err) {
|
||||
parsed.set(e.relativePath, {
|
||||
relativePath: e.relativePath,
|
||||
error: { message: err.message, stack: err.stack }
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.#getIndex().transaction(() => {
|
||||
for (const e of toParse) {
|
||||
const result = parsed.get(e.relativePath);
|
||||
if (!result) continue;
|
||||
if (result.error) {
|
||||
entry.state.set(e.relativePath, { error: result.error });
|
||||
continue;
|
||||
}
|
||||
entry.state.set(e.relativePath, { data: result.data });
|
||||
this.#getIndex().stage(entry.collectionPath, {
|
||||
op: 'add',
|
||||
relativePath: e.relativePath,
|
||||
mtime: result.mtime,
|
||||
hash: result.hash,
|
||||
data: result.data
|
||||
});
|
||||
}
|
||||
for (const e of removed) {
|
||||
entry.state.delete(e.relativePath);
|
||||
this.#getIndex().stage(entry.collectionPath, { op: 'remove', relativePath: e.relativePath });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async #emitTree(collectionUid, entry) {
|
||||
const { getRequestUid } = require('../../cache/requestUids');
|
||||
const tree = buildTree(entry.collectionPath, entry.state, { uidFor: getRequestUid });
|
||||
await sendTree(collectionUid, entry.collectionPath, tree, entry.emit);
|
||||
}
|
||||
|
||||
#getIndex() {
|
||||
if (!this.#index) this.#index = new FileIndex({});
|
||||
return this.#index;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { MountManager };
|
||||
237
packages/bruno-electron/src/services/mount/tree-builder.js
Normal file
237
packages/bruno-electron/src/services/mount/tree-builder.js
Normal file
@@ -0,0 +1,237 @@
|
||||
const path = require('node:path');
|
||||
const {
|
||||
posixifyPath,
|
||||
idForAbsolutePath,
|
||||
uidForSeed,
|
||||
defaultClassify
|
||||
} = require('../../utils/mount');
|
||||
|
||||
const REQUEST_EXT_RE = /\.(bru|yml|yaml)$/i;
|
||||
const stripExt = (basename) => basename.replace(REQUEST_EXT_RE, '');
|
||||
|
||||
const isSeqValid = (seq) => Number.isFinite(seq) && Number.isInteger(seq) && seq > 0;
|
||||
|
||||
// lists that get deterministic uids; section tag keeps reorders within one list local
|
||||
const REQUEST_UID_PATHS = [
|
||||
['request.params', 'params'],
|
||||
['request.headers', 'headers'],
|
||||
['request.vars.req', 'vars.req'],
|
||||
['request.vars.res', 'vars.res'],
|
||||
['request.assertions', 'assertions'],
|
||||
['request.body.formUrlEncoded', 'body.formUrlEncoded'],
|
||||
['request.body.multipartForm', 'body.multipartForm'],
|
||||
['request.body.file', 'body.file']
|
||||
];
|
||||
|
||||
const EXAMPLE_UID_PATHS = [
|
||||
['request.params', 'params'],
|
||||
['request.headers', 'headers'],
|
||||
['request.body.formUrlEncoded', 'body.formUrlEncoded'],
|
||||
['request.body.multipartForm', 'body.multipartForm'],
|
||||
['request.body.file', 'body.file'],
|
||||
['response.headers', 'response.headers']
|
||||
];
|
||||
|
||||
const hydrateListAt = (root, dotPath, seed, section) => {
|
||||
let cur = root;
|
||||
for (const p of dotPath.split('.')) {
|
||||
if (!cur || typeof cur !== 'object') return;
|
||||
cur = cur[p];
|
||||
}
|
||||
if (!Array.isArray(cur)) return;
|
||||
cur.forEach((item, i) => {
|
||||
item.uid = uidForSeed(`${seed}#${section}#${i}`);
|
||||
});
|
||||
};
|
||||
|
||||
const hydrateRequestUuids = (data, parentUid, absolutePath) => {
|
||||
if (!data || typeof data !== 'object') return;
|
||||
const seed = posixifyPath(absolutePath);
|
||||
for (const [p, sec] of REQUEST_UID_PATHS) hydrateListAt(data, p, seed, sec);
|
||||
if (!Array.isArray(data.examples)) return;
|
||||
data.examples.forEach((example, i) => {
|
||||
const exSeed = `${seed}#example#${i}`;
|
||||
example.uid = uidForSeed(exSeed);
|
||||
if (parentUid) example.itemUid = parentUid;
|
||||
for (const [p, sec] of EXAMPLE_UID_PATHS) hydrateListAt(example, p, exSeed, sec);
|
||||
});
|
||||
};
|
||||
|
||||
// alpha by name, then any folder with a valid seq pins to that position (v1 quirk)
|
||||
const sortByNameThenSequence = (items) => {
|
||||
const sorted = [...items].sort((a, b) =>
|
||||
a.name && b.name ? a.name.localeCompare(b.name) : 0
|
||||
);
|
||||
const withoutSeq = sorted.filter((f) => !isSeqValid(f.seq));
|
||||
const withSeq = sorted.filter((f) => isSeqValid(f.seq)).sort((a, b) => a.seq - b.seq);
|
||||
|
||||
withSeq.forEach((item) => {
|
||||
const existing = withoutSeq[item.seq - 1];
|
||||
const collides = Array.isArray(existing)
|
||||
? existing[0]?.seq === item.seq
|
||||
: existing?.seq === item.seq;
|
||||
if (collides) {
|
||||
const group = Array.isArray(existing) ? [...existing, item] : [existing, item];
|
||||
withoutSeq.splice(item.seq - 1, 1, group);
|
||||
} else {
|
||||
withoutSeq.splice(item.seq - 1, 0, item);
|
||||
}
|
||||
});
|
||||
return withoutSeq.flat();
|
||||
};
|
||||
|
||||
const sortLevel = (items) => {
|
||||
const folders = items.filter((i) => i.type === 'folder');
|
||||
const requests = items.filter((i) => i.type !== 'folder');
|
||||
const sortedFolders = sortByNameThenSequence(folders);
|
||||
for (const f of sortedFolders) f.items = sortLevel(f.items);
|
||||
const sortedRequests = [...requests].sort((a, b) => (a.seq ?? 1) - (b.seq ?? 1));
|
||||
return [...sortedFolders, ...sortedRequests];
|
||||
};
|
||||
|
||||
const ensureFolder = (collectionPath, items, segments, uidFor) => {
|
||||
let cursor = items;
|
||||
let acc = '';
|
||||
let last = null;
|
||||
for (const seg of segments) {
|
||||
acc = acc ? path.join(acc, seg) : seg;
|
||||
const absolutePath = path.join(collectionPath, acc);
|
||||
let folder = cursor.find((i) => i.type === 'folder' && i.filename === seg);
|
||||
if (!folder) {
|
||||
folder = {
|
||||
uid: uidFor(absolutePath),
|
||||
name: seg,
|
||||
filename: seg,
|
||||
pathname: absolutePath,
|
||||
type: 'folder',
|
||||
collapsed: true,
|
||||
items: []
|
||||
};
|
||||
cursor.push(folder);
|
||||
}
|
||||
cursor = folder.items;
|
||||
last = folder;
|
||||
}
|
||||
return { cursor, folder: last };
|
||||
};
|
||||
|
||||
const buildRequestNode = (absolutePath, basename, entry, uidOverrides, uidFor) => {
|
||||
const uid = uidOverrides?.get(absolutePath) || uidFor(absolutePath);
|
||||
const data = entry.data || {};
|
||||
hydrateRequestUuids(data, uid, absolutePath);
|
||||
return {
|
||||
uid,
|
||||
name: data.name || stripExt(basename),
|
||||
type: data.type || 'http-request',
|
||||
seq: data.seq,
|
||||
tags: data.tags,
|
||||
request: data.request,
|
||||
settings: data.settings,
|
||||
examples: data.examples,
|
||||
filename: basename,
|
||||
pathname: absolutePath,
|
||||
draft: null,
|
||||
partial: false,
|
||||
loading: false,
|
||||
...(entry.error ? { error: entry.error, partial: true } : {})
|
||||
};
|
||||
};
|
||||
|
||||
const buildEnvironmentNode = (collectionPath, relativePath, entry, uidFor) => {
|
||||
const basename = path.basename(relativePath);
|
||||
const absolutePath = path.join(collectionPath, relativePath);
|
||||
const data = entry.data || {};
|
||||
return {
|
||||
uid: uidFor(absolutePath),
|
||||
name: stripExt(basename),
|
||||
variables: data.variables || [],
|
||||
...(entry.error ? { error: entry.error } : {})
|
||||
};
|
||||
};
|
||||
|
||||
const buildTree = (collectionPath, parserResults, options = {}) => {
|
||||
const uidOverrides = options.uidOverrides;
|
||||
const uidFor = options.uidFor || idForAbsolutePath;
|
||||
const transientEntries = options.transientEntries || [];
|
||||
|
||||
const tree = {
|
||||
pathname: collectionPath,
|
||||
brunoConfig: null,
|
||||
root: null,
|
||||
items: [],
|
||||
environments: []
|
||||
};
|
||||
|
||||
const folderRoots = new Map();
|
||||
const requests = [];
|
||||
|
||||
for (const [relativePath, entry] of parserResults) {
|
||||
const cls = defaultClassify(relativePath);
|
||||
if (!cls) continue;
|
||||
|
||||
if (cls.type === 'config') {
|
||||
tree.brunoConfig = entry.error ? null : entry.data;
|
||||
} else if (cls.type === 'collection') {
|
||||
if (!entry.error) {
|
||||
const data = entry.data;
|
||||
if (data && typeof data === 'object' && 'collectionRoot' in data && 'brunoConfig' in data) {
|
||||
hydrateRequestUuids(data.collectionRoot, null, collectionPath);
|
||||
tree.root = data.collectionRoot;
|
||||
tree.brunoConfig = data.brunoConfig;
|
||||
} else {
|
||||
hydrateRequestUuids(data, null, collectionPath);
|
||||
tree.root = data;
|
||||
}
|
||||
}
|
||||
} else if (cls.type === 'folder') {
|
||||
folderRoots.set(path.dirname(relativePath), entry);
|
||||
} else if (cls.type === 'environment') {
|
||||
tree.environments.push(buildEnvironmentNode(collectionPath, relativePath, entry, uidFor));
|
||||
} else {
|
||||
requests.push({ relativePath, entry });
|
||||
}
|
||||
}
|
||||
|
||||
for (const { relativePath, entry } of requests) {
|
||||
const segments = path.dirname(relativePath).split(path.sep).filter((s) => s && s !== '.');
|
||||
const { cursor } = ensureFolder(collectionPath, tree.items, segments, uidFor);
|
||||
cursor.push(buildRequestNode(
|
||||
path.join(collectionPath, relativePath),
|
||||
path.basename(relativePath),
|
||||
entry,
|
||||
uidOverrides,
|
||||
uidFor
|
||||
));
|
||||
}
|
||||
|
||||
for (const [dirRel, entry] of folderRoots) {
|
||||
const segments = dirRel.split(path.sep).filter((s) => s && s !== '.');
|
||||
const { folder } = ensureFolder(collectionPath, tree.items, segments, uidFor);
|
||||
if (!folder) continue;
|
||||
const meta = entry.data?.meta || {};
|
||||
if (meta.name) folder.name = meta.name;
|
||||
if (isSeqValid(meta.seq)) folder.seq = meta.seq;
|
||||
if (!entry.error) {
|
||||
hydrateRequestUuids(entry.data, folder.uid, folder.pathname);
|
||||
folder.root = entry.data;
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of transientEntries) {
|
||||
if (!t || !t.absolutePath) continue;
|
||||
const node = buildRequestNode(
|
||||
t.absolutePath,
|
||||
path.basename(t.absolutePath),
|
||||
t,
|
||||
uidOverrides,
|
||||
uidFor
|
||||
);
|
||||
node.isTransient = true;
|
||||
tree.items.push(node);
|
||||
}
|
||||
|
||||
tree.items = sortLevel(tree.items);
|
||||
return tree;
|
||||
};
|
||||
|
||||
module.exports = { buildTree };
|
||||
45
packages/bruno-electron/src/services/pool/index.js
Normal file
45
packages/bruno-electron/src/services/pool/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const workerpool = require('workerpool');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const JobType = Object.freeze({
|
||||
ParseFile: 'parse-file'
|
||||
});
|
||||
|
||||
const WORKER_FILE = path.join(__dirname, 'worker.js');
|
||||
|
||||
class Pool {
|
||||
#pool;
|
||||
|
||||
constructor({ size } = {}) {
|
||||
const workers = Math.max(1, size ?? os.availableParallelism());
|
||||
this.#pool = workerpool.pool(WORKER_FILE, {
|
||||
maxWorkers: workers,
|
||||
workerType: 'thread'
|
||||
});
|
||||
}
|
||||
|
||||
run(type, args) {
|
||||
return this.#pool.exec(type, [args]);
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
await this.#pool.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
let shared = null;
|
||||
|
||||
const getPool = (options) => {
|
||||
if (!shared) shared = new Pool(options);
|
||||
return shared;
|
||||
};
|
||||
|
||||
const destroyPool = async () => {
|
||||
if (!shared) return;
|
||||
const pool = shared;
|
||||
shared = null;
|
||||
await pool.destroy();
|
||||
};
|
||||
|
||||
module.exports = { Pool, getPool, destroyPool, JobType };
|
||||
34
packages/bruno-electron/src/services/pool/jobs/parse-file.js
Normal file
34
packages/bruno-electron/src/services/pool/jobs/parse-file.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const filestore = require('@usebruno/filestore');
|
||||
|
||||
const sha256 = (buf) => crypto.createHash('sha256').update(buf).digest('hex');
|
||||
|
||||
const parseContent = (content, format, type) => {
|
||||
if (type === 'config' || format === 'json') return JSON.parse(content);
|
||||
const options = { format };
|
||||
switch (type) {
|
||||
case 'request':
|
||||
return filestore.parseRequest(content, options);
|
||||
case 'collection':
|
||||
return filestore.parseCollection(content, options);
|
||||
case 'folder':
|
||||
return filestore.parseFolder(content, options);
|
||||
case 'environment':
|
||||
return filestore.parseEnvironment(content, options);
|
||||
default:
|
||||
throw new Error(`Unknown type: ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
const parseFile = ({ collectionPath, relativePath, format, type }) => {
|
||||
const absolutePath = path.join(collectionPath, relativePath);
|
||||
const buf = fs.readFileSync(absolutePath);
|
||||
const stat = fs.statSync(absolutePath, { bigint: true });
|
||||
const content = buf.toString('utf8');
|
||||
const data = parseContent(content, format, type);
|
||||
return { relativePath, mtime: stat.mtimeNs, hash: sha256(buf), data, format, type };
|
||||
};
|
||||
|
||||
module.exports = parseFile;
|
||||
7
packages/bruno-electron/src/services/pool/worker.js
Normal file
7
packages/bruno-electron/src/services/pool/worker.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const workerpool = require('workerpool');
|
||||
const parseFile = require('./jobs/parse-file');
|
||||
const { JobType } = require('./index');
|
||||
|
||||
workerpool.worker({
|
||||
[JobType.ParseFile]: parseFile
|
||||
});
|
||||
129
packages/bruno-electron/src/services/storage/index.js
Normal file
129
packages/bruno-electron/src/services/storage/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
|
||||
// Lazy SQLite connection. The db is opened on first use, pragmas are applied,
|
||||
// pending migrations are run (tracked via PRAGMA user_version), and prepared
|
||||
// statements are cached by their SQL text.
|
||||
class Database {
|
||||
#db = null;
|
||||
#path;
|
||||
#migrations;
|
||||
#pragmas;
|
||||
#readBigInts;
|
||||
#statements = new Map();
|
||||
|
||||
constructor({ path, migrations = [], pragmas, readBigInts = false }) {
|
||||
this.#path = path;
|
||||
this.#migrations = migrations;
|
||||
this.#pragmas = pragmas;
|
||||
this.#readBigInts = readBigInts;
|
||||
this.#validateMigrations();
|
||||
}
|
||||
|
||||
get path() {
|
||||
return typeof this.#path === 'function' ? this.#path() : this.#path;
|
||||
}
|
||||
|
||||
get schemaVersion() {
|
||||
const row = this.#connect().prepare('PRAGMA user_version').get();
|
||||
return row.user_version;
|
||||
}
|
||||
|
||||
setPath(p) {
|
||||
this.close();
|
||||
this.#path = p;
|
||||
}
|
||||
|
||||
prepare(sql) {
|
||||
const db = this.#connect();
|
||||
let stmt = this.#statements.get(sql);
|
||||
if (!stmt) {
|
||||
stmt = db.prepare(sql);
|
||||
if (this.#readBigInts) stmt.setReadBigInts(true);
|
||||
this.#statements.set(sql, stmt);
|
||||
}
|
||||
return stmt;
|
||||
}
|
||||
|
||||
run(sql, ...params) {
|
||||
return this.prepare(sql).run(...params);
|
||||
}
|
||||
|
||||
get(sql, ...params) {
|
||||
return this.prepare(sql).get(...params);
|
||||
}
|
||||
|
||||
all(sql, ...params) {
|
||||
return this.prepare(sql).all(...params);
|
||||
}
|
||||
|
||||
exec(sql) {
|
||||
this.#connect().exec(sql);
|
||||
}
|
||||
|
||||
transaction(fn) {
|
||||
const db = this.#connect();
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
const result = fn();
|
||||
db.exec('COMMIT');
|
||||
return result;
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (!this.#db) return;
|
||||
this.#statements.clear();
|
||||
this.#db.close();
|
||||
this.#db = null;
|
||||
}
|
||||
|
||||
#connect() {
|
||||
if (this.#db) return this.#db;
|
||||
const db = new DatabaseSync(this.path);
|
||||
if (this.#pragmas) {
|
||||
for (const [key, value] of Object.entries(this.#pragmas)) {
|
||||
db.exec(`PRAGMA ${key} = ${value};`);
|
||||
}
|
||||
}
|
||||
this.#migrate(db);
|
||||
this.#db = db;
|
||||
return db;
|
||||
}
|
||||
|
||||
#migrate(db) {
|
||||
if (!this.#migrations.length) return;
|
||||
const sorted = [...this.#migrations].sort((a, b) => a.version - b.version);
|
||||
let current = db.prepare('PRAGMA user_version').get().user_version;
|
||||
for (const migration of sorted) {
|
||||
if (migration.version <= current) continue;
|
||||
db.exec('BEGIN');
|
||||
try {
|
||||
db.exec(migration.up);
|
||||
db.exec(`PRAGMA user_version = ${migration.version}`);
|
||||
db.exec('COMMIT');
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
current = migration.version;
|
||||
}
|
||||
}
|
||||
|
||||
#validateMigrations() {
|
||||
const seen = new Set();
|
||||
for (const { version } of this.#migrations) {
|
||||
if (!Number.isInteger(version) || version < 1) {
|
||||
throw new Error(`Migration version must be a positive integer, got ${version}`);
|
||||
}
|
||||
if (seen.has(version)) {
|
||||
throw new Error(`Duplicate migration version ${version}`);
|
||||
}
|
||||
seen.add(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Database };
|
||||
@@ -68,6 +68,9 @@ const defaultPreferences = {
|
||||
cache: {
|
||||
sslSession: {
|
||||
enabled: false
|
||||
},
|
||||
file: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
ai: {
|
||||
@@ -145,6 +148,9 @@ const preferencesSchema = Yup.object().shape({
|
||||
cache: Yup.object({
|
||||
sslSession: Yup.object({
|
||||
enabled: Yup.boolean()
|
||||
}),
|
||||
file: Yup.object({
|
||||
enabled: Yup.boolean()
|
||||
})
|
||||
}).optional(),
|
||||
ai: Yup.object({
|
||||
@@ -352,6 +358,9 @@ const preferencesUtil = {
|
||||
isSslSessionCachingEnabled: () => {
|
||||
return get(getPreferences(), 'cache.sslSession.enabled', false);
|
||||
},
|
||||
isFileCacheEnabled: () => {
|
||||
return get(getPreferences(), 'cache.file.enabled', false);
|
||||
},
|
||||
hasLaunchedBefore: () => {
|
||||
return get(getPreferences(), 'onboarding.hasLaunchedBefore', false);
|
||||
},
|
||||
|
||||
92
packages/bruno-electron/src/utils/mount.js
Normal file
92
packages/bruno-electron/src/utils/mount.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const { posixifyPath } = require('./filesystem');
|
||||
|
||||
const DENY_DIRS = new Set(['node_modules', '.git', '.svn', '.hg', '.bruno']);
|
||||
const DEFAULT_DENYLIST = ['**/.DS_Store', '**/Thumbs.db'];
|
||||
|
||||
const sha256 = (input) => crypto.createHash('sha256').update(input).digest('hex');
|
||||
const hashFile = (absPath) => sha256(fs.readFileSync(absPath));
|
||||
const hashFileAsync = async (absPath) => sha256(await fs.promises.readFile(absPath));
|
||||
const normalize = (p) => path.resolve(p);
|
||||
const idForAbsolutePath = (absolutePath) => sha256(posixifyPath(absolutePath)).slice(0, 21);
|
||||
const uidForSeed = (seed) => sha256(seed).slice(0, 21);
|
||||
|
||||
const resolveDenylist = (patterns) => [...DEFAULT_DENYLIST, ...(patterns || [])];
|
||||
|
||||
const isDenied = (relativePathPosix, patterns) => {
|
||||
for (const pattern of patterns) {
|
||||
if (path.matchesGlob(relativePathPosix, pattern)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const walk = (root, denylist) => {
|
||||
const out = [];
|
||||
const visit = (absDir, relDir) => {
|
||||
const entries = fs.readdirSync(absDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const childAbs = path.join(absDir, entry.name);
|
||||
const childRel = relDir ? path.join(relDir, entry.name) : entry.name;
|
||||
if (entry.isDirectory()) {
|
||||
if (DENY_DIRS.has(entry.name)) continue;
|
||||
visit(childAbs, childRel);
|
||||
} else if (entry.isFile()) {
|
||||
if (isDenied(posixifyPath(childRel), denylist)) continue;
|
||||
out.push({ relativePath: childRel, absolutePath: childAbs });
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(root, '');
|
||||
return out;
|
||||
};
|
||||
|
||||
const COLLECTION_ROOT_BASENAMES = new Set(['collection.bru', 'collection.yml', 'opencollection.yml']);
|
||||
const FOLDER_ROOT_BASENAMES = new Set(['folder.bru', 'folder.yml']);
|
||||
const BRUNO_CONFIG_BASENAME = 'bruno.json';
|
||||
const ENVIRONMENTS_DIR = 'environments';
|
||||
|
||||
const defaultClassify = (relativePath) => {
|
||||
const basename = path.basename(relativePath);
|
||||
const dirname = path.dirname(relativePath);
|
||||
const segments = dirname === '.' || dirname === '' ? [] : dirname.split(path.sep).filter(Boolean);
|
||||
|
||||
if (basename === BRUNO_CONFIG_BASENAME && segments.length === 0) {
|
||||
return { format: 'json', type: 'config' };
|
||||
}
|
||||
|
||||
const ext = path.extname(basename).slice(1).toLowerCase();
|
||||
let format;
|
||||
if (ext === 'bru') format = 'bru';
|
||||
else if (ext === 'yml' || ext === 'yaml') format = 'yml';
|
||||
else return null;
|
||||
|
||||
if (COLLECTION_ROOT_BASENAMES.has(basename) && segments.length === 0) {
|
||||
return { format, type: 'collection' };
|
||||
}
|
||||
if (FOLDER_ROOT_BASENAMES.has(basename)) {
|
||||
return { format, type: 'folder' };
|
||||
}
|
||||
if (segments[0] === ENVIRONMENTS_DIR && segments.length === 1) {
|
||||
return { format, type: 'environment' };
|
||||
}
|
||||
return { format, type: 'request' };
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
COLLECTION_ROOT_BASENAMES,
|
||||
FOLDER_ROOT_BASENAMES,
|
||||
BRUNO_CONFIG_BASENAME,
|
||||
ENVIRONMENTS_DIR,
|
||||
hashFile,
|
||||
hashFileAsync,
|
||||
normalize,
|
||||
posixifyPath,
|
||||
idForAbsolutePath,
|
||||
uidForSeed,
|
||||
resolveDenylist,
|
||||
isDenied,
|
||||
walk,
|
||||
defaultClassify
|
||||
};
|
||||
@@ -78,13 +78,14 @@ test.describe('Preferences Tab Switch Persistence', () => {
|
||||
await page.getByRole('tab', { name: 'Cache' }).click();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Get the initial state of SSL session caching checkbox
|
||||
const sslSessionCheckbox = page.locator('#sslSession\\.enabled');
|
||||
await sslSessionCheckbox.waitFor({ state: 'visible' });
|
||||
// Get the initial state of SSL session caching toggle
|
||||
const sslSessionToggle = page.getByTestId('sslSession.enabled');
|
||||
const sslSessionCheckbox = sslSessionToggle.locator('input[type="checkbox"]');
|
||||
await sslSessionToggle.waitFor({ state: 'visible' });
|
||||
const initialChecked = await sslSessionCheckbox.isChecked();
|
||||
|
||||
// Toggle the checkbox
|
||||
await sslSessionCheckbox.click();
|
||||
await sslSessionToggle.click();
|
||||
|
||||
// Immediately switch to another tab
|
||||
await page.getByRole('tab', { name: 'Themes' }).click();
|
||||
@@ -92,14 +93,14 @@ test.describe('Preferences Tab Switch Persistence', () => {
|
||||
|
||||
// Switch back to Cache tab
|
||||
await page.getByRole('tab', { name: 'Cache' }).click();
|
||||
await sslSessionCheckbox.waitFor({ state: 'visible' });
|
||||
await sslSessionToggle.waitFor({ state: 'visible' });
|
||||
|
||||
// Verify the setting was persisted
|
||||
const newChecked = await sslSessionCheckbox.isChecked();
|
||||
expect(newChecked).toBe(!initialChecked);
|
||||
|
||||
// Restore original state
|
||||
await sslSessionCheckbox.click();
|
||||
await sslSessionToggle.click();
|
||||
await page.waitForTimeout(600);
|
||||
});
|
||||
|
||||
@@ -154,13 +155,14 @@ test.describe('Preferences Tab Switch Persistence', () => {
|
||||
await page.getByRole('tab', { name: 'Cache' }).click();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Get the initial state of SSL session caching checkbox
|
||||
const sslSessionCheckbox = page.locator('#sslSession\\.enabled');
|
||||
await sslSessionCheckbox.waitFor({ state: 'visible' });
|
||||
// Get the initial state of SSL session caching toggle
|
||||
const sslSessionToggle = page.getByTestId('sslSession.enabled');
|
||||
const sslSessionCheckbox = sslSessionToggle.locator('input[type="checkbox"]');
|
||||
await sslSessionToggle.waitFor({ state: 'visible' });
|
||||
const initialCacheState = await sslSessionCheckbox.isChecked();
|
||||
|
||||
// Toggle the checkbox
|
||||
await sslSessionCheckbox.click();
|
||||
await sslSessionToggle.click();
|
||||
|
||||
// Close preferences tab immediately
|
||||
const preferencesTab = page.locator('.request-tab').filter({ hasText: 'Preferences' });
|
||||
@@ -178,13 +180,13 @@ test.describe('Preferences Tab Switch Persistence', () => {
|
||||
// Navigate to Cache tab
|
||||
await page.getByRole('tab', { name: 'Cache' }).click();
|
||||
await page.waitForTimeout(300);
|
||||
await sslSessionCheckbox.waitFor({ state: 'visible' });
|
||||
await sslSessionToggle.waitFor({ state: 'visible' });
|
||||
|
||||
// Verify the setting was persisted
|
||||
expect(await sslSessionCheckbox.isChecked()).toBe(!initialCacheState);
|
||||
|
||||
// Restore original state
|
||||
await sslSessionCheckbox.click();
|
||||
await sslSessionToggle.click();
|
||||
await page.waitForTimeout(600);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user