diff --git a/.github/ISSUE_TEMPLATE/BugReport.yaml b/.github/ISSUE_TEMPLATE/BugReport.yaml
index 4b6da7871..f525bb7e6 100644
--- a/.github/ISSUE_TEMPLATE/BugReport.yaml
+++ b/.github/ISSUE_TEMPLATE/BugReport.yaml
@@ -6,26 +6,58 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
+
+ Before submitting, please make sure you've searched existing issues:
+ 👉 [Search existing issues](https://github.com/usebruno/bruno/issues?q=is%3Aissue)
+
- type: checkboxes
attributes:
label: 'I have checked the following:'
options:
- - label: I use the newest version of bruno.
- required: true
- - label: I've searched existing issues and found nothing related to my issue.
+ - label: "I have searched existing issues and found nothing related to my issue."
required: true
+
+ - type: checkboxes
+ attributes:
+ label: 'This bug is:'
+ options:
+ - label: making Bruno unusable for me
+ required: false
+ - label: slowing me down but I'm able to continue working
+ required: false
+ - label: annoying
+ required: false
+
+ - type: input
+ attributes:
+ label: Bruno version
+ description: Please specify the version of Bruno you are using in which the issue occurs.
+ placeholder: 1.38.1
+ validations:
+ required: true
+
+ - type: input
+ attributes:
+ label: Operating System
+ description: Information about the operating system the issue occurs on.
+ placeholder: Windows 11 26100.3037 / macOS 15.1 (24B83) / Linux 6.13.1
+ validations:
+ required: true
+
- type: textarea
attributes:
label: Describe the bug
- description: A clear and concise description of the bug.
+ description: A clear and concise description of the bug and how it's effecting your work along with steps to reproduce.
validations:
required: true
+
- type: textarea
attributes:
label: .bru file to reproduce the bug
- description: Attach your .bru file here that can reqroduce the problem.
+ description: Attach your .bru file here that can reproduce the problem.
validations:
required: false
+
- type: textarea
attributes:
label: Screenshots/Live demo link
diff --git a/.github/ISSUE_TEMPLATE/FeatureRequest.yaml b/.github/ISSUE_TEMPLATE/FeatureRequest.yaml
index 3a3997beb..161e56e9c 100644
--- a/.github/ISSUE_TEMPLATE/FeatureRequest.yaml
+++ b/.github/ISSUE_TEMPLATE/FeatureRequest.yaml
@@ -8,13 +8,23 @@ body:
options:
- label: I've searched existing issues and found nothing related to my issue.
required: true
+ - type: checkboxes
+ attributes:
+ label: 'This feature'
+ options:
+ - label: blocks me from using Bruno
+ required: false
+ - label: would improve my quality of life in Bruno
+ required: false
+ - label: is something I've never seen an API client do before
+ required: false
- type: markdown
attributes:
value: |
Suggest an idea for this project.
- type: textarea
attributes:
- label: Describe the feature you want to add
+ label: Describe the feature you want to add, and how it would change your usage of Bruno
description: A clear and concise description of the feature you want to be added.
validations:
required: true
@@ -23,4 +33,4 @@ body:
label: Mockups or Images of the feature
description: Add some images to support your feature.
validations:
- required: true
+ required: false
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c029b0224..d218fc65a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -5,14 +5,13 @@ on:
pull_request:
branches: [main]
-permissions:
- contents: read
-
jobs:
unit-test:
name: Unit Tests
timeout-minutes: 60
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -55,6 +54,7 @@ jobs:
permissions:
checks: write
pull-requests: write
+ contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
diff --git a/package-lock.json b/package-lock.json
index f53c786a9..8d63dedff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,7 +50,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -787,7 +786,6 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
@@ -818,7 +816,6 @@
"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,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -836,7 +833,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/@babel/generator": {
@@ -1116,7 +1112,6 @@
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.9",
@@ -7083,7 +7078,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": {
@@ -7096,7 +7090,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": "*",
@@ -7107,7 +7100,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": {
@@ -7199,6 +7191,13 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/verror": {
"version": "1.10.10",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz",
@@ -8586,7 +8585,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true,
"license": "ISC"
},
"node_modules/boolean": {
@@ -9417,6 +9415,229 @@
"node": "*"
}
},
+ "node_modules/cheerio": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
+ "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
+ "license": "MIT",
+ "dependencies": {
+ "cheerio-select": "^2.1.0",
+ "dom-serializer": "^2.0.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.1.0",
+ "encoding-sniffer": "^0.2.0",
+ "htmlparser2": "^9.1.0",
+ "parse5": "^7.1.2",
+ "parse5-htmlparser2-tree-adapter": "^7.0.0",
+ "parse5-parser-stream": "^7.1.2",
+ "undici": "^6.19.5",
+ "whatwg-mimetype": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18.17"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-select": "^5.1.0",
+ "css-what": "^6.1.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cheerio-select/node_modules/css-select": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+ "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cheerio-select/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/cheerio-select/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/cheerio-select/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/cheerio/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/cheerio/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/cheerio/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/cheerio/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/cheerio/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/cheerio/node_modules/htmlparser2": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
+ "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
+ }
+ },
+ "node_modules/cheerio/node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -10115,7 +10336,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
"license": "MIT"
},
"node_modules/cookie": {
@@ -10583,7 +10803,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
@@ -11256,6 +11475,15 @@
"domelementtype": "1"
}
},
+ "node_modules/dompurify": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
+ "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/domutils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
@@ -11718,13 +11946,25 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
+ "node_modules/encoding-sniffer": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
+ "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "^0.6.3",
+ "whatwg-encoding": "^3.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
+ }
+ },
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -12739,7 +12979,6 @@
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -17377,7 +17616,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
@@ -17782,6 +18020,106 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/parse5-htmlparser2-tree-adapter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
+ "license": "MIT",
+ "dependencies": {
+ "domhandler": "^5.0.3",
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-parser-stream": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
+ "license": "MIT",
+ "dependencies": {
+ "parse5": "^7.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/parse5-parser-stream/node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/parse5-parser-stream/node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -21170,7 +21508,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
- "dev": true,
"license": "ISC"
},
"node_modules/scheduler": {
@@ -23213,7 +23550,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",
@@ -23229,6 +23566,15 @@
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"license": "MIT"
},
+ "node_modules/undici": {
+ "version": "6.21.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
+ "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.17"
+ }
+ },
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
@@ -23757,6 +24103,27 @@
"node": ">=10.13.0"
}
},
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -23898,6 +24265,28 @@
"node": ">= 16"
}
},
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xml2js/node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
@@ -24019,7 +24408,7 @@
},
"packages/bruno-app": {
"name": "@usebruno/app",
- "version": "0.3.0",
+ "version": "1.39.0",
"dependencies": {
"@babel/preset-env": "^7.26.0",
"@fontsource/inter": "^5.0.15",
@@ -24034,6 +24423,7 @@
"codemirror": "5.65.2",
"codemirror-graphql": "2.1.1",
"cookie": "0.7.1",
+ "dompurify": "^3.2.4",
"escape-html": "^1.0.3",
"file": "^0.2.2",
"file-dialog": "^0.0.8",
@@ -24043,7 +24433,7 @@
"graphiql": "3.7.1",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
- "httpsnippet": "^3.0.6",
+ "httpsnippet": "^3.0.9",
"i18next": "24.1.2",
"idb": "^7.0.0",
"immer": "^9.0.15",
@@ -24078,6 +24468,7 @@
"react-redux": "^7.2.9",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
+ "semver": "^7.7.1",
"strip-json-comments": "^5.0.1",
"styled-components": "^5.3.3",
"system": "^2.0.1",
@@ -24135,6 +24526,17 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "packages/bruno-app/node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"packages/bruno-cli": {
"name": "@usebruno/cli",
"version": "1.16.0",
@@ -24365,6 +24767,7 @@
"btoa": "^1.2.1",
"chai": "^4.3.7",
"chai-string": "^1.5.0",
+ "cheerio": "^1.0.0",
"crypto-js": "^4.1.1",
"json-query": "^2.2.2",
"lodash": "^4.17.21",
@@ -24374,7 +24777,8 @@
"node-vault": "^0.10.2",
"path": "^0.12.7",
"quickjs-emscripten": "^0.29.2",
- "uuid": "^9.0.0"
+ "uuid": "^9.0.0",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^23.0.2",
diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json
index 7203bb816..f3e6fe292 100644
--- a/packages/bruno-app/package.json
+++ b/packages/bruno-app/package.json
@@ -1,6 +1,6 @@
{
"name": "@usebruno/app",
- "version": "0.3.0",
+ "version": "1.39.0",
"private": true,
"scripts": {
"dev": "rsbuild dev",
@@ -24,6 +24,7 @@
"codemirror": "5.65.2",
"codemirror-graphql": "2.1.1",
"cookie": "0.7.1",
+ "dompurify": "^3.2.4",
"escape-html": "^1.0.3",
"file": "^0.2.2",
"file-dialog": "^0.0.8",
@@ -68,6 +69,7 @@
"react-redux": "^7.2.9",
"react-tooltip": "^5.5.2",
"sass": "^1.46.0",
+ "semver": "^7.7.1",
"strip-json-comments": "^5.0.1",
"styled-components": "^5.3.3",
"system": "^2.0.1",
diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js
index 2f9ca9cdd..ed198086d 100644
--- a/packages/bruno-app/src/components/CodeEditor/index.js
+++ b/packages/bruno-app/src/components/CodeEditor/index.js
@@ -83,7 +83,7 @@ if (!SERVER_RENDERED) {
'bru.runner',
'bru.runner.setNextRequest(requestName)',
'bru.runner.skipRequest()',
- 'bru.runner.stopExecution()',
+ 'bru.runner.stopExecution()'
];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor();
@@ -174,11 +174,21 @@ export default class CodeEditor extends React.Component {
}
},
'Cmd-F': (cm) => {
+ if (this._isSearchOpen()) {
+ // replace the older search component with the new one
+ const search = document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top');
+ search && search.remove();
+ }
cm.execCommand('findPersistent');
this._bindSearchHandler();
this._appendSearchResultsCount();
},
'Ctrl-F': (cm) => {
+ if (this._isSearchOpen()) {
+ // replace the older search component with the new one
+ const search = document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top');
+ search && search.remove();
+ }
cm.execCommand('findPersistent');
this._bindSearchHandler();
this._appendSearchResultsCount();
@@ -365,6 +375,10 @@ export default class CodeEditor extends React.Component {
}
};
+ _isSearchOpen = () => {
+ return document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top');
+ };
+
/**
* Bind handler to search input to count number of search results
*/
diff --git a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js
index afe08bcba..4c3130e3d 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Docs/StyledWrapper.js
@@ -1,11 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
- div.CodeMirror {
- .CodeMirror-scroll {
- padding-bottom: 0px;
- }
- }
+
.editing-mode {
cursor: pointer;
}
diff --git a/packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js b/packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js
index 86bf2308f..a83850e91 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Overview/Info/index.js
@@ -1,10 +1,14 @@
-import React from 'react';
+import React from "react";
import { getTotalRequestCountInCollection } from 'utils/collections/';
-import { IconFolder, IconFileOff, IconWorld, IconApi } from '@tabler/icons';
+import { IconFolder, IconWorld, IconApi, IconClock } from '@tabler/icons';
+import { areItemsLoading, getItemsLoadStats } from "utils/collections/index";
const Info = ({ collection }) => {
const totalRequestsInCollection = getTotalRequestCountInCollection(collection);
+ const isCollectionLoading = areItemsLoading(collection);
+ const { loading: itemsLoadingCount, total: totalItems } = getItemsLoadStats(collection);
+
return (
@@ -42,8 +46,10 @@ const Info = ({ collection }) => {
Requests
-
- {totalRequestsInCollection} request{totalRequestsInCollection !== 1 ? 's' : ''} in collection
+
+ {
+ isCollectionLoading? `${totalItems - itemsLoadingCount} out of ${totalItems} requests in the collection loaded` : `${totalRequestsInCollection} request${totalRequestsInCollection !== 1 ? 's' : ''} in collection`
+ }
@@ -53,4 +59,4 @@ const Info = ({ collection }) => {
);
};
-export default Info;
+export default Info;
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/CollectionSettings/Overview/RequestsNotLoaded/index.js b/packages/bruno-app/src/components/CollectionSettings/Overview/RequestsNotLoaded/index.js
index c15b36cd8..4c7406580 100644
--- a/packages/bruno-app/src/components/CollectionSettings/Overview/RequestsNotLoaded/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/Overview/RequestsNotLoaded/index.js
@@ -2,8 +2,15 @@ import React from 'react';
import { flattenItems } from "utils/collections";
import { IconAlertTriangle } from '@tabler/icons';
import StyledWrapper from "./StyledWrapper";
+import { useDispatch, useSelector } from 'react-redux';
+import { isItemARequest, itemIsOpenedInTabs } from 'utils/tabs/index';
+import { getDefaultRequestPaneTab } from 'utils/collections/index';
+import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
+import { hideHomePage } from 'providers/ReduxStore/slices/app';
const RequestsNotLoaded = ({ collection }) => {
+ const dispatch = useDispatch();
+ const tabs = useSelector((state) => state.tabs.tabs);
const flattenedItems = flattenItems(collection.items);
const itemsFailedLoading = flattenedItems?.filter(item => item?.partial && !item?.loading);
@@ -11,6 +18,29 @@ const RequestsNotLoaded = ({ collection }) => {
return null;
}
+ const handleRequestClick = (item) => e => {
+ e.preventDefault();
+ if (isItemARequest(item)) {
+ dispatch(hideHomePage());
+ if (itemIsOpenedInTabs(item, tabs)) {
+ dispatch(
+ focusTab({
+ uid: item.uid
+ })
+ );
+ return;
+ }
+ dispatch(
+ addTab({
+ uid: item.uid,
+ collectionUid: collection.uid,
+ requestPaneTab: getDefaultRequestPaneTab(item)
+ })
+ );
+ return;
+ }
+ }
+
return (
@@ -31,7 +61,7 @@ const RequestsNotLoaded = ({ collection }) => {
{flattenedItems?.map((item, index) => (
item?.partial && !item?.loading ? (
-
+
|
{item?.pathname?.split(`${collection?.pathname}/`)?.[1]}
|
diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js
index 105a92642..bb48cbdc0 100644
--- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js
+++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js
@@ -104,18 +104,15 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
) : (
diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js
index 187a91a68..07dcf1419 100644
--- a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js
+++ b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js
@@ -154,7 +154,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
- {getTabPanel(focusedTab.requestPaneTab)}
+ {getTabPanel(focusedTab.requestPaneTab)}
);
};
diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
index 91fea0134..eaac6f204 100644
--- a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
+++ b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
@@ -49,7 +49,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
-
+ <>
+ >
);
};
diff --git a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js
index 777280eb0..3f7f7ef01 100644
--- a/packages/bruno-app/src/components/RequestPane/QueryParams/index.js
+++ b/packages/bruno-app/src/components/RequestPane/QueryParams/index.js
@@ -176,8 +176,7 @@ const QueryParams = ({ item, collection }) => {
Path
-
Path variables are automatically added whenever the
:name
@@ -186,9 +185,7 @@ const QueryParams = ({ item, collection }) => {
https://example.com/v1/users/:id
- `}
- infotipId="path-param-InfoTip"
- />
+
diff --git a/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js
deleted file mode 100644
index 9f7583222..000000000
--- a/packages/bruno-app/src/components/RequestPane/Tests/StyledWrapper.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import styled from 'styled-components';
-
-const StyledWrapper = styled.div`
- div.CodeMirror {
- /* todo: find a better way */
- height: calc(100vh - 220px);
- }
-`;
-
-export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/RequestPane/Tests/index.js b/packages/bruno-app/src/components/RequestPane/Tests/index.js
index c781d34d5..d0d19c283 100644
--- a/packages/bruno-app/src/components/RequestPane/Tests/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Tests/index.js
@@ -5,7 +5,6 @@ import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
import { useTheme } from 'providers/Theme';
-import StyledWrapper from './StyledWrapper';
const Tests = ({ item, collection }) => {
const dispatch = useDispatch();
@@ -28,19 +27,17 @@ const Tests = ({ item, collection }) => {
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
return (
-
-
-
+
);
};
diff --git a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js
index c073135d3..cd3f83797 100644
--- a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js
+++ b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js
@@ -98,7 +98,7 @@ const VarsTable = ({ item, collection, vars, varType }) => {
) : (
Expr
-
+
), accessor: 'value', width: '46%' },
{ name: '', accessor: '', width: '14%' }
diff --git a/packages/bruno-app/src/components/RequestTabPanel/RequestNotLoaded/index.js b/packages/bruno-app/src/components/RequestTabPanel/RequestNotLoaded/index.js
index 1a951b624..7908dfc09 100644
--- a/packages/bruno-app/src/components/RequestTabPanel/RequestNotLoaded/index.js
+++ b/packages/bruno-app/src/components/RequestTabPanel/RequestNotLoaded/index.js
@@ -1,4 +1,4 @@
-import { IconLoader2, IconFile } from '@tabler/icons';
+import { IconLoader2, IconFile, IconAlertTriangle } from '@tabler/icons';
import { loadRequest, loadRequestViaWorker } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
@@ -15,65 +15,59 @@ const RequestNotLoaded = ({ collection, item }) => {
return
-
+
File Info
-
+
-
+
-
+
Size:
{item?.size?.toFixed?.(2)} MB
{!item?.error && (
- <>
-
-
- Due to its large size, this request wasn't loaded automatically.
+
+
+
+ The request wasn't loaded due to its large size. Please try again with the following options:
-
-
-
-
- May cause the app to freeze temporarily while it runs.
-
-
-
+
-
- Runs in background.
-
-
+
(Runs in background)
- >
+
+
+
(May cause the app to freeze temporarily while it runs)
+
+
)}
{item?.loading && (
<>
-
+
Loading...
diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
index 8ca76b15e..447523fdb 100644
--- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js
@@ -35,7 +35,7 @@ const CollectionToolBar = ({ collection }) => {
const viewCollectionSettings = () => {
dispatch(
addTab({
- uid: uuid(),
+ uid: collection.uid,
collectionUid: collection.uid,
type: 'collection-settings'
})
diff --git a/packages/bruno-app/src/components/RunnerResults/index.jsx b/packages/bruno-app/src/components/RunnerResults/index.jsx
index 4381c86b1..e8bf153a4 100644
--- a/packages/bruno-app/src/components/RunnerResults/index.jsx
+++ b/packages/bruno-app/src/components/RunnerResults/index.jsx
@@ -11,14 +11,14 @@ import ResponsePane from './ResponsePane';
import StyledWrapper from './StyledWrapper';
import { areItemsLoading } from 'utils/collections';
-const getRelativePath = (fullPath, pathname) => {
+const getDisplayName = (fullPath, pathname, name) => {
// convert to unix style path
fullPath = slash(fullPath);
pathname = slash(pathname);
let relativePath = path.relative(fullPath, pathname);
- const { dir, name } = path.parse(relativePath);
- return path.join(dir, name);
+ const { dir } = path.parse(relativePath);
+ return [dir, name].filter(i => i).join('/');
};
export default function RunnerResults({ collection }) {
@@ -58,7 +58,7 @@ export default function RunnerResults({ collection }) {
type: info.type,
filename: info.filename,
pathname: info.pathname,
- relativePath: getRelativePath(collection.pathname, info.pathname)
+ displayName: getDisplayName(collection.pathname, info.pathname, info.name)
};
if (newItem.status !== 'error' && newItem.status !== 'skipped') {
if (newItem.testResults) {
@@ -186,7 +186,7 @@ export default function RunnerResults({ collection }) {
- {item.relativePath}
+ {item.displayName}
{item.status !== 'error' && item.status !== 'skipped' && item.status !== 'completed' ? (
@@ -266,7 +266,7 @@ export default function RunnerResults({ collection }) {
- {selectedItem.relativePath}
+ {selectedItem.displayName}
{selectedItem.testStatus === 'pass' ? (
@@ -275,7 +275,6 @@ export default function RunnerResults({ collection }) {
)}
- {/*
{selectedItem.relativePath}
*/}
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js
index 41d3e5ff2..1b048e731 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CloneCollection/index.js
@@ -127,7 +127,7 @@ const CloneCollection = ({ onClose, collection }) => {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
index 3e426eb7f..a08fac3b7 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
@@ -32,6 +32,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isSidebarDragging = useSelector((state) => state.app.isDragging);
const dispatch = useDispatch();
+ const collectionItemRef = useRef(null);
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false);
@@ -45,28 +46,31 @@ const CollectionItem = ({ item, collection, searchText }) => {
const itemIsCollapsed = hasSearchText ? false : item.collapsed;
const [{ isDragging }, drag] = useDrag({
- type: `COLLECTION_ITEM_${collection.uid}`,
+ type: `collection-item-${collection.uid}`,
item: item,
collect: (monitor) => ({
isDragging: monitor.isDragging()
- })
+ }),
+ options: {
+ dropEffect: "move"
+ }
});
const [{ isOver }, drop] = useDrop({
- accept: `COLLECTION_ITEM_${collection.uid}`,
+ accept: `collection-item-${collection.uid}`,
drop: (draggedItem) => {
- if (draggedItem.uid !== item.uid) {
- dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
- }
+ dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
},
canDrop: (draggedItem) => {
return draggedItem.uid !== item.uid;
},
collect: (monitor) => ({
- isOver: monitor.isOver()
- })
+ isOver: monitor.isOver(),
+ }),
});
+ drag(drop(collectionItemRef));
+
const dropdownTippyRef = useRef();
const MenuIcon = forwardRef((props, ref) => {
return (
@@ -255,7 +259,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
{generateCodeItemModalOpen && (
setGenerateCodeItemModalOpen(false)} />
)}
- drag(drop(node))}>
+
{indents && indents.length
? indents.map((i) => {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
index b8e0d21fd..5c06cc42a 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
@@ -12,6 +12,17 @@ const Wrapper = styled.div`
transform: rotateZ(90deg);
}
+ &.item-hovered {
+ background: ${(props) => props.theme.sidebar.collection.item.hoverBg};
+ .collection-actions {
+ .dropdown {
+ div[aria-expanded='false'] {
+ visibility: visible;
+ }
+ }
+ }
+ }
+
.collection-actions {
.dropdown {
div[aria-expanded='true'] {
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
index 3fe00c686..f21f25ac3 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
@@ -2,11 +2,11 @@ import React, { useState, forwardRef, useRef, useEffect } from 'react';
import classnames from 'classnames';
import { uuid } from 'utils/common';
import filter from 'lodash/filter';
-import { useDrop } from 'react-dnd';
+import { useDrop, useDrag } from 'react-dnd';
import { IconChevronRight, IconDots, IconLoader2 } from '@tabler/icons';
import Dropdown from 'components/Dropdown';
import { collapseCollection } from 'providers/ReduxStore/slices/collections';
-import { mountCollection, moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
+import { mountCollection, moveItemToRootOfCollection, moveCollectionAndPersist } from 'providers/ReduxStore/slices/collections/actions';
import { useDispatch, useSelector } from 'react-redux';
import { addTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs';
import NewRequest from 'components/Sidebar/NewRequest';
@@ -33,6 +33,7 @@ const Collection = ({ collection, searchText }) => {
const tabs = useSelector((state) => state.tabs.tabs);
const dispatch = useDispatch();
const isLoading = areItemsLoading(collection);
+ const collectionRef = useRef(null);
const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
@@ -117,33 +118,58 @@ const Collection = ({ collection, searchText }) => {
const viewCollectionSettings = () => {
dispatch(
addTab({
- uid: uuid(),
+ uid: collection.uid,
collectionUid: collection.uid,
type: 'collection-settings'
})
);
};
+ const isCollectionItem = (itemType) => {
+ return itemType.startsWith('collection-item');
+ };
+
+ const [{ isDragging }, drag] = useDrag({
+ type: "collection",
+ item: collection,
+ collect: (monitor) => ({
+ isDragging: monitor.isDragging(),
+ }),
+ options: {
+ dropEffect: "move"
+ }
+ });
+
const [{ isOver }, drop] = useDrop({
- accept: `COLLECTION_ITEM_${collection.uid}`,
- drop: (draggedItem) => {
- dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
+ accept: ["collection", `collection-item-${collection.uid}`],
+ drop: (draggedItem, monitor) => {
+ const itemType = monitor.getItemType();
+ if (isCollectionItem(itemType)) {
+ dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid))
+ } else {
+ dispatch(moveCollectionAndPersist({draggedItem, targetItem: collection}));
+ }
},
canDrop: (draggedItem) => {
- // todo need to make sure that draggedItem belongs to the collection
- return true;
+ return draggedItem.uid !== collection.uid;
},
collect: (monitor) => ({
- isOver: monitor.isOver()
- })
+ isOver: monitor.isOver(),
+ }),
});
+ drag(drop(collectionRef));
+
if (searchText && searchText.length) {
if (!doesCollectionHaveItemsMatchingSearchText(collection, searchText)) {
return null;
}
}
+ const collectionRowClassName = classnames('flex py-1 collection-name items-center', {
+ 'item-hovered': isOver
+ });
+
// we need to sort request items by seq property
const sortRequestItems = (items = []) => {
return items.sort((a, b) => a.seq - b.seq);
@@ -173,7 +199,9 @@ const Collection = ({ collection, searchText }) => {
{showCloneCollectionModalOpen && (
setShowCloneCollectionModalOpen(false)} />
)}
-
+
{
{collections && collections.length
? collections.map((c) => {
return (
-
-
-
+
);
})
: null}
diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js
index 6f05207d2..17f31f1f8 100644
--- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js
+++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js
@@ -120,7 +120,7 @@ const CreateCollection = ({ onClose }) => {
diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js
index 50e19c22e..9f476e3c8 100644
--- a/packages/bruno-app/src/components/Sidebar/index.js
+++ b/packages/bruno-app/src/components/Sidebar/index.js
@@ -5,6 +5,7 @@ import Preferences from 'components/Preferences';
import Cookies from 'components/Cookies';
import ToolHint from 'components/ToolHint';
import GoldenEdition from './GoldenEdition';
+import { useApp } from 'providers/App';
import { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
@@ -20,7 +21,7 @@ const Sidebar = () => {
const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth);
const preferencesOpen = useSelector((state) => state.app.showPreferences);
const [goldenEditionOpen, setGoldenEditionOpen] = useState(false);
-
+ const { version } = useApp();
const [asideWidth, setAsideWidth] = useState(leftSidebarWidth);
const [cookiesOpen, setCookiesOpen] = useState(false);
@@ -184,7 +185,7 @@ const Sidebar = () => {
Star
*/}
-
v1.36.1
+
v{version}
diff --git a/packages/bruno-app/src/components/ToolHint/index.js b/packages/bruno-app/src/components/ToolHint/index.js
index b8799dd69..3d559625e 100644
--- a/packages/bruno-app/src/components/ToolHint/index.js
+++ b/packages/bruno-app/src/components/ToolHint/index.js
@@ -34,7 +34,7 @@ const ToolHint = ({
-
+
+
+
);
}
diff --git a/packages/bruno-app/src/providers/App/index.js b/packages/bruno-app/src/providers/App/index.js
index 7664ae03e..b06d1d3a8 100644
--- a/packages/bruno-app/src/providers/App/index.js
+++ b/packages/bruno-app/src/providers/App/index.js
@@ -6,11 +6,12 @@ import ConfirmAppClose from './ConfirmAppClose';
import useIpcEvents from './useIpcEvents';
import useTelemetry from './useTelemetry';
import StyledWrapper from './StyledWrapper';
+import { version } from '../../../package.json';
export const AppContext = React.createContext();
export const AppProvider = (props) => {
- useTelemetry();
+ useTelemetry({ version });
useIpcEvents();
const dispatch = useDispatch();
@@ -37,7 +38,7 @@ export const AppProvider = (props) => {
}, []);
return (
-
+
{props.children}
@@ -46,4 +47,12 @@ export const AppProvider = (props) => {
);
};
+export const useApp = () => {
+ const context = React.useContext(AppContext);
+ if (!context) {
+ throw new Error('useApp must be used within an AppProvider');
+ }
+ return context;
+};
+
export default AppProvider;
diff --git a/packages/bruno-app/src/providers/App/useTelemetry.js b/packages/bruno-app/src/providers/App/useTelemetry.js
index 6b64e1279..712a6efb7 100644
--- a/packages/bruno-app/src/providers/App/useTelemetry.js
+++ b/packages/bruno-app/src/providers/App/useTelemetry.js
@@ -42,7 +42,7 @@ const getAnonymousTrackingId = () => {
return id;
};
-const trackStart = () => {
+const trackStart = (version) => {
if (isPlaywrightTestRunning()) {
return;
}
@@ -58,16 +58,18 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
- version: '1.38.1'
+ version: version
}
});
};
-const useTelemetry = () => {
+const useTelemetry = ({ version }) => {
useEffect(() => {
- trackStart();
- setInterval(trackStart, 24 * 60 * 60 * 1000);
- }, []);
+ if (posthogApiKey && posthogApiKey.length) {
+ trackStart(version);
+ setInterval(trackStart, 24 * 60 * 60 * 1000);
+ }
+ }, [posthogApiKey]);
};
export default useTelemetry;
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index 7de849eea..c2532b3fd 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -32,6 +32,7 @@ import {
selectEnvironment as _selectEnvironment,
sortCollections as _sortCollections,
updateCollectionMountStatus,
+ moveCollection,
requestCancelled,
resetRunResults,
responseReceived,
@@ -1151,6 +1152,22 @@ export const importCollection = (collection, collectionLocation) => (dispatch, g
});
};
+export const moveCollectionAndPersist = ({ draggedItem, targetItem }) => (dispatch, getState) => {
+ dispatch(moveCollection({ draggedItem, targetItem }));
+
+ return new Promise((resolve, reject) => {
+ const { ipcRenderer } = window;
+ const state = getState();
+
+ const collectionPaths = state.collections.collections.map((collection) => collection.pathname);
+
+ ipcRenderer
+ .invoke('renderer:update-collection-paths', collectionPaths)
+ .then(resolve)
+ .catch(reject);
+ });
+};
+
export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
index 3ae0fa4e5..905576a2f 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
@@ -1,5 +1,5 @@
import { uuid } from 'utils/common';
-import { find, map, forOwn, concat, filter, each, cloneDeep, get, set } from 'lodash';
+import { find, map, forOwn, concat, filter, each, cloneDeep, get, set, findIndex } from 'lodash';
import { createSlice } from '@reduxjs/toolkit';
import {
addDepth,
@@ -59,7 +59,9 @@ export const collectionsSlice = createSlice({
updateCollectionMountStatus: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
- collection.mountStatus = action.payload.mountStatus;
+ if (action.payload.mountStatus) {
+ collection.mountStatus = action.payload.mountStatus;
+ }
}
},
setCollectionSecurityConfig: (state, action) => {
@@ -100,6 +102,12 @@ export const collectionsSlice = createSlice({
break;
}
},
+ moveCollection: (state, action) => {
+ const { draggedItem, targetItem } = action.payload;
+ state.collections = state.collections.filter((i) => i.uid !== draggedItem.uid); // Remove dragged item
+ const targetItemIndex = state.collections.findIndex((i) => i.uid === targetItem.uid); // Find target item
+ state.collections.splice(targetItemIndex, 0, draggedItem); // Insert dragged-item above target-item
+ },
updateLastAction: (state, action) => {
const { collectionUid, lastAction } = action.payload;
const collection = findCollectionByUid(state.collections, collectionUid);
@@ -1761,6 +1769,12 @@ export const collectionsSlice = createSlice({
// we don't want to lose the draft in this case
if (areItemsTheSameExceptSeqUpdate(item, file.data)) {
item.seq = file.data.seq;
+ if (item?.draft) {
+ item.draft.seq = file.data.seq;
+ }
+ if (item?.draft && areItemsTheSameExceptSeqUpdate(item?.draft, file.data)) {
+ item.draft = null;
+ }
} else {
item.name = file.data.name;
item.type = file.data.type;
@@ -2082,7 +2096,8 @@ export const {
runFolderEvent,
resetCollectionRunner,
updateRequestDocs,
- updateFolderDocs
+ updateFolderDocs,
+ moveCollection
} = collectionsSlice.actions;
export default collectionsSlice.reducer;
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js
index ca6c232d8..062f367ca 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js
@@ -1,7 +1,7 @@
import toast from 'react-hot-toast';
import { createSlice } from '@reduxjs/toolkit';
import { getAppInstallDate } from 'utils/common/platform';
-
+import semver from 'semver';
const getReadNotificationIds = () => {
try {
let readNotificationIdsString = window.localStorage.getItem('bruno.notifications.read');
@@ -27,6 +27,26 @@ const initialState = {
readNotificationIds: getReadNotificationIds() || []
};
+export const filterNotificationsByVersion = (notifications, currentVersion) => {
+ try {
+ if (!notifications) return [];
+
+ if (!currentVersion) return notifications;
+
+ return notifications.filter(notification => {
+ const { minVersion, maxVersion } = notification;
+ if (!minVersion && !maxVersion) return true;
+ if (!minVersion) return semver.lte(currentVersion, maxVersion);
+ if (!maxVersion) return semver.gte(currentVersion, minVersion);
+
+ return semver.gte(currentVersion, minVersion) && semver.lte(currentVersion, maxVersion);
+ });
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+};
+
export const notificationSlice = createSlice({
name: 'notifications',
initialState,
@@ -86,13 +106,14 @@ export const notificationSlice = createSlice({
export const { setNotifications, setFetchingStatus, markNotificationAsRead, markAllNotificationsAsRead } =
notificationSlice.actions;
-export const fetchNotifications = () => (dispatch, getState) => {
+export const fetchNotifications = ({currentVersion}) => (dispatch, getState) => {
return new Promise((resolve) => {
const { ipcRenderer } = window;
dispatch(setFetchingStatus(true));
ipcRenderer
.invoke('renderer:fetch-notifications')
.then((notifications) => {
+ notifications = filterNotificationsByVersion(notifications, currentVersion);
dispatch(setNotifications({ notifications }));
dispatch(setFetchingStatus(false));
resolve(notifications);
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js
new file mode 100644
index 000000000..80e84d9bd
--- /dev/null
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js
@@ -0,0 +1,133 @@
+const { filterNotificationsByVersion } = require('./notifications');
+
+describe('filterNotificationsByVersion - basic', () => {
+ it('should filter notifications by version', () => {
+ const notifications = [{ minVersion: '1.0.0', maxVersion: '1.1.0' }];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([{ minVersion: '1.0.0', maxVersion: '1.1.0' }]);
+ });
+
+ it('should gracefully handle no notifications', () => {
+ const notifications = [];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([]);
+ });
+
+ it('should gracefully handle notifications are undefined', () => {
+ const notifications = undefined;
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([]);
+ });
+
+ it('should gracefully handle scenario when no current version is provided', () => {
+ const notifications = [{ minVersion: '1.0.0', maxVersion: '1.1.0' }];
+ const filteredNotifications = filterNotificationsByVersion(notifications);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+
+ it('should gracefully handle scenario minVersion is undefined', () => {
+ const notifications = [{ minVersion: undefined, maxVersion: '1.1.0' }];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+
+ it('should gracefully handle scenario maxVersion is undefined', () => {
+ const notifications = [{ minVersion: '1.0.0', maxVersion: undefined }];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+
+ it('should gracefully handle scenario minVersion and maxVersion are undefined', () => {
+ const notifications = [{ minVersion: undefined, maxVersion: undefined }];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+});
+
+describe('filterNotificationsByVersion - semver', () => {
+ it('should filter out notifications outside version range', () => {
+ const notifications = [
+ { minVersion: '1.0.0', maxVersion: '1.1.0' }, // should be included
+ { minVersion: '2.0.0', maxVersion: '2.1.0' }, // should be filtered out
+ { minVersion: '0.5.0', maxVersion: '0.9.0' } // should be filtered out
+ ];
+ const currentVersion = '1.0.5';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([
+ { minVersion: '1.0.0', maxVersion: '1.1.0' }
+ ]);
+ });
+
+ it('should handle mixed valid and invalid version ranges', () => {
+ const notifications = [
+ { minVersion: '1.0.0', maxVersion: '2.0.0' }, // should be included
+ { minVersion: '3.0.0', maxVersion: '4.0.0' }, // should be filtered out
+ { minVersion: '1.5.0', maxVersion: '1.8.0' }, // should be included
+ { minVersion: '0.1.0', maxVersion: '0.5.0' } // should be filtered out
+ ];
+ const currentVersion = '1.6.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([
+ { minVersion: '1.0.0', maxVersion: '2.0.0' },
+ { minVersion: '1.5.0', maxVersion: '1.8.0' }
+ ]);
+ });
+
+ it('should handle edge cases of version ranges', () => {
+ const notifications = [
+ { minVersion: '1.0.0', maxVersion: '1.0.0' }, // should be included
+ { minVersion: '1.0.1', maxVersion: '2.0.0' }, // should be filtered out
+ { minVersion: '0.9.9', maxVersion: '1.0.0' } // should be included
+ ];
+ const currentVersion = '1.0.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([
+ { minVersion: '1.0.0', maxVersion: '1.0.0' },
+ { minVersion: '0.9.9', maxVersion: '1.0.0' }
+ ]);
+ });
+});
+
+describe('filterNotificationsByVersion - undefined version bounds', () => {
+ it('should include notifications when minVersion is undefined and current version is below maxVersion', () => {
+ const notifications = [
+ { minVersion: undefined, maxVersion: '2.0.0' }
+ ];
+ const currentVersion = '1.5.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+
+ it('should exclude notifications when minVersion is undefined and current version is above maxVersion', () => {
+ const notifications = [
+ { minVersion: undefined, maxVersion: '2.0.0' }
+ ];
+ const currentVersion = '2.1.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([]);
+ });
+
+ it('should include notifications when maxVersion is undefined and current version is above minVersion', () => {
+ const notifications = [
+ { minVersion: '1.0.0', maxVersion: undefined }
+ ];
+ const currentVersion = '2.0.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual(notifications);
+ });
+
+ it('should exclude notifications when maxVersion is undefined and current version is below minVersion', () => {
+ const notifications = [
+ { minVersion: '1.0.0', maxVersion: undefined }
+ ];
+ const currentVersion = '0.9.0';
+ const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion);
+ expect(filteredNotifications).toEqual([]);
+ });
+});
\ No newline at end of file
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
index 91b39b33d..219655d70 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js
@@ -11,24 +11,35 @@ const initialState = {
activeTabUid: null
};
+const tabTypeAlreadyExists = (tabs, collectionUid, type) => {
+ return find(tabs, (tab) => tab.collectionUid === collectionUid && tab.type === type);
+};
+
export const tabsSlice = createSlice({
name: 'tabs',
initialState,
reducers: {
addTab: (state, action) => {
const { uid, collectionUid, type, requestPaneTab, preview } = action.payload;
-
- const existingTab = find(state.tabs, (tab) => tab.uid === uid);
-
- if (existingTab) {
- state.activeTabUid = existingTab.uid;
- return;
- }
const nonReplaceableTabTypes = [
"variables",
"collection-runner",
"security-settings",
];
+
+ const existingTab = find(state.tabs, (tab) => tab.uid === uid);
+ if (existingTab) {
+ state.activeTabUid = existingTab.uid;
+ return;
+ }
+
+ if (nonReplaceableTabTypes.includes(type)) {
+ const existingTab = tabTypeAlreadyExists(state.tabs, collectionUid, type);
+ if (existingTab) {
+ state.activeTabUid = existingTab.uid;
+ return;
+ }
+ }
const lastTab = state.tabs[state.tabs.length - 1];
if (state.tabs.length > 0 && lastTab.preview) {
@@ -39,7 +50,9 @@ export const tabsSlice = createSlice({
requestPaneTab: requestPaneTab || 'params',
responsePaneTab: 'response',
type: type || 'request',
- preview: true,
+ preview: preview !== undefined
+ ? preview
+ : !nonReplaceableTabTypes.includes(type),
...(uid ? { folderUid: uid } : {})
}
diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js
index eb53cfb48..3ac612c62 100644
--- a/packages/bruno-app/src/utils/collections/index.js
+++ b/packages/bruno-app/src/utils/collections/index.js
@@ -1,13 +1,4 @@
-import get from 'lodash/get';
-import each from 'lodash/each';
-import find from 'lodash/find';
-import findIndex from 'lodash/findIndex';
-import isString from 'lodash/isString';
-import map from 'lodash/map';
-import filter from 'lodash/filter';
-import sortBy from 'lodash/sortBy';
-import isEqual from 'lodash/isEqual';
-import cloneDeep from 'lodash/cloneDeep';
+import {cloneDeep, isEqual, sortBy, filter, map, isString, findIndex, find, each, get } from 'lodash';
import { uuid } from 'utils/common';
import path from 'path';
import slash from 'utils/common/slash';
@@ -146,6 +137,20 @@ export const areItemsLoading = (folder) => {
}, false);
}
+export const getItemsLoadStats = (folder) => {
+ let loadingCount = 0;
+ let flattenedItems = flattenItems(folder.items);
+ flattenedItems?.forEach(i => {
+ if(i?.loading) {
+ loadingCount += 1;
+ }
+ });
+ return {
+ loading: loadingCount,
+ total: flattenedItems?.length
+ };
+}
+
export const moveCollectionItem = (collection, draggedItem, targetItem) => {
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js
index 89ab15c7e..e4acd9ba6 100644
--- a/packages/bruno-app/src/utils/importers/postman-collection.js
+++ b/packages/bruno-app/src/utils/importers/postman-collection.js
@@ -181,6 +181,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
brunoParent.items = brunoParent.items || [];
const folderMap = {};
const requestMap = {};
+ const requestMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE']
each(item, (i) => {
if (isItemAFolder(i)) {
@@ -230,6 +231,11 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
} else {
if (i.request) {
+ if(!requestMethods.includes(i?.request?.method.toUpperCase())){
+ console.warn("Unexpected request.method")
+ return;
+ }
+
const baseRequestName = i.name;
let requestName = baseRequestName;
let count = 1;
diff --git a/packages/bruno-app/src/utils/importers/translators/postman_translation.js b/packages/bruno-app/src/utils/importers/translators/postman_translation.js
index b386d719e..5d6573c16 100644
--- a/packages/bruno-app/src/utils/importers/translators/postman_translation.js
+++ b/packages/bruno-app/src/utils/importers/translators/postman_translation.js
@@ -24,6 +24,10 @@ const replacements = {
'postman\\.setEnvironmentVariable\\(': 'bru.setEnvVar(',
'postman\\.getEnvironmentVariable\\(': 'bru.getEnvVar(',
'postman\\.clearEnvironmentVariable\\(': 'bru.deleteEnvVar(',
+ 'pm\\.execution\\.skipRequest\\(\\)': 'bru.runner.skipRequest()',
+ 'pm\\.execution\\.skipRequest': 'bru.runner.skipRequest',
+ 'pm\\.execution\\.setNextRequest\\(null\\)': 'bru.runner.stopExecution()',
+ 'pm\\.execution\\.setNextRequest\\(\'null\'\\)': 'bru.runner.stopExecution()',
};
const extendedReplacements = Object.keys(replacements).reduce((acc, key) => {
@@ -50,7 +54,7 @@ export const postmanTranslation = (script, logCallback) => {
}
if (modifiedScript.includes('pm.') || modifiedScript.includes('postman.')) {
modifiedScript = modifiedScript.replace(/^(.*(pm\.|postman\.).*)$/gm, '// $1');
- logCallback?.();
+ //logCallback?.();
}
return modifiedScript;
} catch (e) {
diff --git a/packages/bruno-electron/src/bru/workers/scripts/bru-to-json.js b/packages/bruno-electron/src/bru/workers/scripts/bru-to-json.js
index c1bbb44e7..92086c4b6 100644
--- a/packages/bruno-electron/src/bru/workers/scripts/bru-to-json.js
+++ b/packages/bruno-electron/src/bru/workers/scripts/bru-to-json.js
@@ -1,14 +1,16 @@
-const { workerData, parentPort } = require('worker_threads');
+const { parentPort } = require('worker_threads');
const {
bruToJsonV2,
} = require('@usebruno/lang');
-try {
- const bru = workerData;
- const json = bruToJsonV2(bru);
- parentPort.postMessage(json);
-}
-catch(error) {
- console.error(error);
- parentPort.postMessage({ error: error?.message });
-}
\ No newline at end of file
+parentPort.on('message', (workerData) => {
+ try {
+ const bru = workerData;
+ const json = bruToJsonV2(bru);
+ parentPort.postMessage(json);
+ }
+ catch(error) {
+ console.error(error);
+ parentPort.postMessage({ error: error?.message });
+ }
+});
\ No newline at end of file
diff --git a/packages/bruno-electron/src/bru/workers/scripts/json-to-bru.js b/packages/bruno-electron/src/bru/workers/scripts/json-to-bru.js
index e08be60b9..c2a4f88e4 100644
--- a/packages/bruno-electron/src/bru/workers/scripts/json-to-bru.js
+++ b/packages/bruno-electron/src/bru/workers/scripts/json-to-bru.js
@@ -1,13 +1,16 @@
-const { workerData, parentPort } = require('worker_threads');
+const { parentPort } = require('worker_threads');
const {
jsonToBruV2,
} = require('@usebruno/lang');
-try {
- const json = workerData;
- const bru = jsonToBruV2(json);
- parentPort.postMessage(bru);
-}
-catch(error) {
- console.error(error);
- parentPort.postMessage({ error: error?.message });
-}
\ No newline at end of file
+
+parentPort.on('message', (workerData) => {
+ try {
+ const json = workerData;
+ const bru = jsonToBruV2(json);
+ parentPort.postMessage(bru);
+ }
+ catch(error) {
+ console.error(error);
+ parentPort.postMessage({ error: error?.message });
+ }
+});
\ No newline at end of file
diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js
index 4b6494b2f..522df6c68 100644
--- a/packages/bruno-electron/src/index.js
+++ b/packages/bruno-electron/src/index.js
@@ -30,9 +30,9 @@ const lastOpenedCollections = new LastOpenedCollections();
// Reference: https://content-security-policy.com/
const contentSecurityPolicy = [
"default-src 'self'",
- "script-src * 'unsafe-inline' 'unsafe-eval'",
- "connect-src * 'unsafe-inline'",
+ "connect-src 'self' https://*.posthog.com",
"font-src 'self' https:",
+ "frame-src data:",
// this has been commented out to make oauth2 work
// "form-action 'none'",
// we make an exception and allow http for images so that
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index 07f15926f..43a3a1283 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -20,7 +20,6 @@ const {
normalizeWslPath,
normalizeAndResolvePath,
safeToRename,
- sanitizeCollectionName,
isWindowsOS,
isValidFilename,
hasSubDirectories,
@@ -41,9 +40,9 @@ const collectionSecurityStore = new CollectionSecurityStore();
const uiStateSnapshotStore = new UiStateSnapshotStore();
// size and file count limits to determine whether the bru files in the collection should be loaded asynchronously or not.
-const MAX_COLLECTION_SIZE_IN_MB = 5;
-const MAX_SINGLE_FILE_SIZE_IN_COLLECTION_IN_MB = 2;
-const MAX_COLLECTION_FILES_COUNT = 100;
+const MAX_COLLECTION_SIZE_IN_MB = 20;
+const MAX_SINGLE_FILE_SIZE_IN_COLLECTION_IN_MB = 5;
+const MAX_COLLECTION_FILES_COUNT = 2000;
const envHasSecrets = (environment = {}) => {
const secrets = _.filter(environment.variables, (v) => v.secret);
@@ -76,7 +75,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
async (event, collectionName, collectionFolderName, collectionLocation) => {
try {
collectionFolderName = sanitizeDirectoryName(collectionFolderName);
- collectionName = sanitizeCollectionName(collectionName);
const dirPath = path.join(collectionLocation, collectionFolderName);
if (fs.existsSync(dirPath)) {
const files = fs.readdirSync(dirPath);
@@ -118,7 +116,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
ipcMain.handle(
'renderer:clone-collection',
async (event, collectionName, collectionFolderName, collectionLocation, previousPath) => {
- collectionFolderName = sanitizeCollectionName(collectionFolderName);
+ collectionFolderName = sanitizeDirectoryName(collectionFolderName);
const dirPath = path.join(collectionLocation, collectionFolderName);
if (fs.existsSync(dirPath)) {
throw new Error(`collection: ${dirPath} already exists`);
@@ -168,7 +166,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
// rename collection
ipcMain.handle('renderer:rename-collection', async (event, newName, collectionPathname) => {
try {
- newName = sanitizeCollectionName(newName);
const brunoJsonFilePath = path.join(collectionPathname, 'bruno.json');
const content = fs.readFileSync(brunoJsonFilePath, 'utf8');
const json = JSON.parse(content);
@@ -519,9 +516,13 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
}
});
+ ipcMain.handle('renderer:update-collection-paths', async (_, collectionPaths) => {
+ lastOpenedCollections.update(collectionPaths);
+ })
+
ipcMain.handle('renderer:import-collection', async (event, collection, collectionLocation) => {
try {
- let collectionName = sanitizeCollectionName(collection.name);
+ let collectionName = sanitizeDirectoryName(collection.name);
let collectionPath = path.join(collectionLocation, collectionName);
if (fs.existsSync(collectionPath)) {
diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js
index 3418f4694..2271a3d33 100644
--- a/packages/bruno-electron/src/ipc/network/index.js
+++ b/packages/bruno-electron/src/ipc/network/index.js
@@ -384,8 +384,8 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
// Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
- const charsetValue = charsetMatch?.[1] || 'utf-8';
- const dataBuffer = Buffer.isBuffer(response.data) ? response.data : Buffer.from(response.data);
+ const charsetValue = charsetMatch?.[1];
+ const dataBuffer = Buffer.from(response.data);
// Overwrite the original data for backwards compatibility
let data;
if (iconv.encodingExists(charsetValue)) {
@@ -407,25 +407,6 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
console.log('Failed to parse response data as JSON');
}
- // Handle Buffer responses that contain JSON
- if (Buffer.isBuffer(response.data)) {
- try {
- const decodedString = response.data.toString('utf-8');
- const parsedData = JSON.parse(decodedString);
-
- if (parsedData && parsedData.type === "Buffer" && Array.isArray(parsedData.data)) {
- data = Buffer.from(parsedData.data).toString('utf-8');
- if (!disableParsingResponseJson) {
- data = JSON.parse(data);
- }
- } else {
- data = parsedData;
- }
- } catch {
- console.error('Failed to parse Buffer data as JSON');
- }
- }
-
return { data, dataBuffer };
};
diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
index 2b46327af..0b8e300ed 100644
--- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js
+++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js
@@ -65,7 +65,11 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
const contentType = getContentType(request.headers);
- if (contentType.includes('json')) {
+ /*
+ We explicitly avoid interpolating buffer values because the file content is read as a buffer object in raw body mode.
+ Even if the selected file's content type is JSON, this prevents the buffer object from being interpolated.
+ */
+ if (contentType.includes('json') && !Buffer.isBuffer(request.data)) {
if (typeof request.data === 'string') {
if (request.data.length) {
request.data = _interpolate(request.data);
diff --git a/packages/bruno-electron/src/store/last-opened-collections.js b/packages/bruno-electron/src/store/last-opened-collections.js
index 546b73b57..72452eef3 100644
--- a/packages/bruno-electron/src/store/last-opened-collections.js
+++ b/packages/bruno-electron/src/store/last-opened-collections.js
@@ -16,18 +16,20 @@ class LastOpenedCollections {
}
add(collectionPath) {
- const collections = this.store.get('lastOpenedCollections') || [];
+ const collections = this.getAll();
- if (isDirectory(collectionPath)) {
- if (!collections.includes(collectionPath)) {
- collections.push(collectionPath);
- this.store.set('lastOpenedCollections', collections);
- }
+ if (isDirectory(collectionPath) && !collections.includes(collectionPath)) {
+ collections.push(collectionPath);
+ this.store.set('lastOpenedCollections', collections);
}
}
+ update(collectionPaths) {
+ this.store.set('lastOpenedCollections', collectionPaths);
+ }
+
remove(collectionPath) {
- let collections = this.store.get('lastOpenedCollections') || [];
+ let collections = this.getAll();
if (collections.includes(collectionPath)) {
collections = _.filter(collections, (c) => c !== collectionPath);
@@ -36,7 +38,7 @@ class LastOpenedCollections {
}
removeAll() {
- return this.store.set('lastOpenedCollections', []);
+ this.store.set('lastOpenedCollections', []);
}
}
diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js
index 0a849f055..a9c8597e7 100644
--- a/packages/bruno-electron/src/utils/filesystem.js
+++ b/packages/bruno-electron/src/utils/filesystem.js
@@ -154,10 +154,6 @@ const searchForBruFiles = (dir) => {
return searchForFiles(dir, '.bru');
};
-const sanitizeCollectionName = (name) => {
- return name.trim();
-}
-
const sanitizeDirectoryName = (name) => {
return name.replace(/[<>:"/\\|?*\x00-\x1F]+/g, '-').trim();
};
@@ -267,7 +263,6 @@ module.exports = {
searchForFiles,
searchForBruFiles,
sanitizeDirectoryName,
- sanitizeCollectionName,
isWindowsOS,
safeToRename,
isValidFilename,
diff --git a/packages/bruno-electron/src/workers/index.js b/packages/bruno-electron/src/workers/index.js
index 04836e9fc..d1d1a1b74 100644
--- a/packages/bruno-electron/src/workers/index.js
+++ b/packages/bruno-electron/src/workers/index.js
@@ -4,8 +4,18 @@ class WorkerQueue {
constructor() {
this.queue = [];
this.isProcessing = false;
+ this.workers = {};
}
+ async getWorkerForScriptPath(scriptPath) {
+ if (!this.workers) this.workers = {};
+ let worker = this.workers[scriptPath];
+ if (!worker || worker.threadId === -1) {
+ this.workers[scriptPath] = worker = new Worker(scriptPath);
+ }
+ return worker;
+ }
+
async enqueue(task) {
const { priority, scriptPath, data } = task;
@@ -36,22 +46,20 @@ class WorkerQueue {
}
async runWorker({ scriptPath, data }) {
- return new Promise((resolve, reject) => {
- const worker = new Worker(scriptPath, { workerData: data });
+ return new Promise(async (resolve, reject) => {
+ let worker = await this.getWorkerForScriptPath(scriptPath);
+ worker.postMessage(data);
worker.on('message', (data) => {
if (data?.error) {
reject(new Error(data?.error));
}
resolve(data);
- worker.terminate();
});
worker.on('error', (error) => {
reject(error);
- worker.terminate();
});
worker.on('exit', (code) => {
reject(new Error(`stopped with ${code} exit code`));
- worker.terminate();
});
});
}
diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json
index ad400ab58..1f1cee55a 100644
--- a/packages/bruno-js/package.json
+++ b/packages/bruno-js/package.json
@@ -25,6 +25,7 @@
"btoa": "^1.2.1",
"chai": "^4.3.7",
"chai-string": "^1.5.0",
+ "cheerio": "^1.0.0",
"crypto-js": "^4.1.1",
"json-query": "^2.2.2",
"lodash": "^4.17.21",
@@ -34,7 +35,8 @@
"node-vault": "^0.10.2",
"path": "^0.12.7",
"quickjs-emscripten": "^0.29.2",
- "uuid": "^9.0.0"
+ "uuid": "^9.0.0",
+ "xml2js": "^0.6.2"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^23.0.2",
diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js
index dcde3f27c..758b574e8 100644
--- a/packages/bruno-js/src/runtime/script-runtime.js
+++ b/packages/bruno-js/src/runtime/script-runtime.js
@@ -28,6 +28,8 @@ const fetch = require('node-fetch');
const chai = require('chai');
const CryptoJS = require('crypto-js');
const NodeVault = require('node-vault');
+const xml2js = require('xml2js');
+const cheerio = require('cheerio');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
class ScriptRuntime {
@@ -145,6 +147,8 @@ class ScriptRuntime {
chai,
'node-fetch': fetch,
'crypto-js': CryptoJS,
+ 'xml2js': xml2js,
+ cheerio,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault
diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js
index 71db9d83e..e2d1f4865 100644
--- a/packages/bruno-js/src/runtime/test-runtime.js
+++ b/packages/bruno-js/src/runtime/test-runtime.js
@@ -30,6 +30,8 @@ const axios = require('axios');
const fetch = require('node-fetch');
const CryptoJS = require('crypto-js');
const NodeVault = require('node-vault');
+const xml2js = require('xml2js');
+const cheerio = require('cheerio');
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
const getResultsSummary = (results) => {
@@ -205,6 +207,8 @@ class TestRuntime {
chai,
'node-fetch': fetch,
'crypto-js': CryptoJS,
+ 'xml2js': xml2js,
+ cheerio,
...whitelistedModules,
fs: allowScriptFilesystemAccess ? fs : undefined,
'node-vault': NodeVault
diff --git a/packages/bruno-tests/collection/ping-another-one.bru b/packages/bruno-tests/collection/ping-another-one.bru
deleted file mode 100644
index 84c1412a8..000000000
--- a/packages/bruno-tests/collection/ping-another-one.bru
+++ /dev/null
@@ -1,15 +0,0 @@
-meta {
- name: ping-another-one
- type: http
- seq: 2
-}
-
-get {
- url: {{host}}/ping
- body: none
- auth: none
-}
-
-script:pre-request {
- throw new Error('this should not execute in a collection run');
-}
diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru
new file mode 100644
index 000000000..07aad76b2
--- /dev/null
+++ b/packages/bruno-tests/collection/scripting/inbuilt modules/cheerio/cheerio.bru
@@ -0,0 +1,42 @@
+meta {
+ name: cheerio
+ type: http
+ seq: 1
+}
+
+post {
+ url: https://echo.usebruno.com
+ body: text
+ auth: none
+}
+
+body:text {
+ Hello Bruno!
+}
+
+script:pre-request {
+ const cheerio = require('cheerio');
+
+ const $ = cheerio.load('Hello world
');
+
+ $('h2.title').text('Hello there!');
+ $('h2').addClass('welcome');
+
+ bru.setVar("cheerio-test-html", $.html());
+}
+
+tests {
+ const cheerio = require('cheerio');
+
+ test("cheerio html - from scripts", function() {
+ const expected = 'Hello there!
';
+ const html = bru.getVar('cheerio-test-html');
+ expect(html).to.eql(expected);
+ });
+
+ test("cheerio html - from tests", function() {
+ const expected = 'Hello Bruno!
';
+ const $ = cheerio.load(res.body);
+ expect($.html()).to.eql(expected);
+ });
+}
diff --git a/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru b/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru
new file mode 100644
index 000000000..db8748ec3
--- /dev/null
+++ b/packages/bruno-tests/collection/scripting/inbuilt modules/xml2js/xml2js.bru
@@ -0,0 +1,41 @@
+meta {
+ name: xml2js
+ type: http
+ seq: 1
+}
+
+get {
+ url: {{host}}/ping
+ body: none
+ auth: none
+}
+
+script:pre-request {
+ var parseString = require('xml2js').parseString;
+ var xml = "Hello xml2js!"
+ parseString(xml, function (err, result) {
+ bru.setVar("xml2js-test-result", result);
+ });
+}
+
+tests {
+ var parseString = require('xml2js').parseString;
+
+ test("xml2js parseString in scripts", function() {
+ const expected = {
+ root: 'Hello xml2js!'
+ };
+ const result = bru.getVar('xml2js-test-result');
+ expect(result).to.eql(expected);
+ });
+
+ test("xml2js parseString in tests", async function() {
+ var xml = "Hello inside test!"
+ const expected = {
+ root: 'Hello inside test!'
+ };
+ parseString(xml, function (err, result) {
+ expect(result).to.eql(expected);
+ });
+ });
+}
diff --git a/scripts/build-electron.js b/scripts/build-electron.js
index 9825c3a09..ab44dcbdf 100644
--- a/scripts/build-electron.js
+++ b/scripts/build-electron.js
@@ -78,14 +78,14 @@ async function main() {
console.log('The directory has been created successfully!');
// Copy build
- await copyFolderIfExists('packages/bruno-app/out', 'packages/bruno-electron/web');
+ await copyFolderIfExists('packages/bruno-app/dist', 'packages/bruno-electron/web');
// Change paths in next
const files = await fs.readdir('packages/bruno-electron/web');
for (const file of files) {
if (file.endsWith('.html')) {
let content = await fs.readFile(`packages/bruno-electron/web/${file}`, 'utf8');
- content = content.replace(/\/_next\//g, '_next/');
+ content = content.replace(/\/static/g, './static');
await fs.writeFile(`packages/bruno-electron/web/${file}`, content);
}
}