mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-26 01:44:05 +01:00
Merge branch 'main' into feature/add-bru-setNextRequest
This commit is contained in:
commit
db0de68987
1
.github/workflows/release-snap.yml
vendored
1
.github/workflows/release-snap.yml
vendored
@ -30,6 +30,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npm run build:bruno-query
|
npm run build:bruno-query
|
||||||
npm run build:graphql-docs
|
npm run build:graphql-docs
|
||||||
|
npm run build:web
|
||||||
npm run build:electron:snap
|
npm run build:electron:snap
|
||||||
|
|
||||||
- name: Install Snapcraft
|
- name: Install Snapcraft
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<img src="assets/images/logo-transparent.png" width="80"/>
|
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
### Bruno - Opensource IDE zum Erkunden und Testen von APIs.
|
### Bruno - Opensource IDE zum Erkunden und Testen von APIs.
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ Du kannst Git oder eine andere Versionskontrolle deiner Wahl verwenden, um an de
|
|||||||
|
|
||||||
Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno eine Cloud-Synchronisation hinzuzufügen. Wir schätzen den Schutz Deiner Daten und glauben, dass sie auf Deinem Gerät bleiben sollten. Lies unsere Langzeit-Vision [hier](https://github.com/usebruno/bruno/discussions/269).
|
Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno eine Cloud-Synchronisation hinzuzufügen. Wir schätzen den Schutz Deiner Daten und glauben, dass sie auf Deinem Gerät bleiben sollten. Lies unsere Langzeit-Vision [hier](https://github.com/usebruno/bruno/discussions/269).
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](/assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
### Einsatz auf verschiedensten Plattformen 🖥️
|
### Einsatz auf verschiedensten Plattformen 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](/assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
### Zusammenarbeiten mit Git 👩💻🧑💻
|
### Zusammenarbeiten mit Git 👩💻🧑💻
|
||||||
|
|
||||||
oder eine Versionskontrolle Deiner Wahl
|
oder eine Versionskontrolle Deiner Wahl
|
||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](/assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Wichtige Links 📌
|
### Wichtige Links 📌
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ Wenn Bruno Dir bei Deiner Arbeit und in Deinen Teams geholfen hat, vergiss bitte
|
|||||||
|
|
||||||
### Veröffentlichung in neuen Paketmanagern
|
### Veröffentlichung in neuen Paketmanagern
|
||||||
|
|
||||||
Bitte [hier](publishing.md) für mehr Informationen lesen.
|
Bitte [hier](/publishing.md) für mehr Informationen lesen.
|
||||||
|
|
||||||
### Mitmachen 👩💻🧑💻
|
### Mitmachen 👩💻🧑💻
|
||||||
|
|
||||||
@ -90,4 +90,4 @@ Das Logo stammt von [OpenMoji](https://openmoji.org/library/emoji-1F436/). Lizen
|
|||||||
|
|
||||||
### Lizenz 📄
|
### Lizenz 📄
|
||||||
|
|
||||||
[MIT](license.md)
|
[MIT](/license.md)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<img src="assets/images/logo-transparent.png" width="80"/>
|
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
### Bruno - IDE Opensource pour explorer et tester des APIs.
|
### Bruno - IDE Opensource pour explorer et tester des APIs.
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler d
|
|||||||
|
|
||||||
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
|
Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269).
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](/assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
### Fonctionne sur de multiples platformes 🖥️
|
### Fonctionne sur de multiples platformes 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](/assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
### Collaborer via Git 👩💻🧑💻
|
### Collaborer via Git 👩💻🧑💻
|
||||||
|
|
||||||
Ou n'importe quel système de gestion de sources
|
Ou n'importe quel système de gestion de sources
|
||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](/assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Liens importants 📌
|
### Liens importants 📌
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de pen
|
|||||||
|
|
||||||
### Publier Bruno sur un nouveau gestionnaire de paquets
|
### Publier Bruno sur un nouveau gestionnaire de paquets
|
||||||
|
|
||||||
Veuillez regarder [ici](publishing.md) pour plus d'information.
|
Veuillez regarder [ici](/publishing.md) pour plus d'information.
|
||||||
|
|
||||||
### Contribuer 👩💻🧑💻
|
### Contribuer 👩💻🧑💻
|
||||||
|
|
||||||
@ -91,4 +91,4 @@ Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
|
|||||||
|
|
||||||
### Licence 📄
|
### Licence 📄
|
||||||
|
|
||||||
[MIT](license.md)
|
[MIT](/license.md)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<img src="assets/images/logo-transparent.png" width="80"/>
|
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
|
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ Bruno хранит ваши коллекции непосредственно в
|
|||||||
|
|
||||||
Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269)
|
Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](/assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
### Работа на нескольких платформах 🖥️
|
### Работа на нескольких платформах 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](/assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
### Совместная работа через Git 👩💻🧑💻
|
### Совместная работа через Git 👩💻🧑💻
|
||||||
|
|
||||||
Или другая система контроля версий по вашему выбору
|
Или другая система контроля версий по вашему выбору
|
||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](/assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Важные ссылки 📌
|
### Важные ссылки 📌
|
||||||
|
|
||||||
@ -74,4 +74,4 @@ Bruno работает только в автономном режиме. Доб
|
|||||||
|
|
||||||
### Лицензия 📄
|
### Лицензия 📄
|
||||||
|
|
||||||
[MIT](license.md)
|
[MIT](/license.md)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<img src="assets/images/logo-transparent.png" width="80"/>
|
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE.
|
### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE.
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ API koleksiyonlarınız üzerinde işbirliği yapmak için git veya seçtiğiniz
|
|||||||
|
|
||||||
Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269)
|
Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](/assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
### Birden fazla platformda çalıştırın 🖥️
|
### Birden fazla platformda çalıştırın 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](/assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
### Git üzerinden işbirliği yapın 👩💻🧑💻
|
### Git üzerinden işbirliği yapın 👩💻🧑💻
|
||||||
|
|
||||||
Veya seçtiğiniz herhangi bir sürüm kontrol sistemi
|
Veya seçtiğiniz herhangi bir sürüm kontrol sistemi
|
||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](/assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Önemli Bağlantılar 📌
|
### Önemli Bağlantılar 📌
|
||||||
|
|
||||||
@ -75,4 +75,4 @@ Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek
|
|||||||
|
|
||||||
### Lisans 📄
|
### Lisans 📄
|
||||||
|
|
||||||
[MIT](license.md)
|
[MIT](/license.md)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<br />
|
<br />
|
||||||
<img src="assets/images/logo-transparent.png" width="80"/>
|
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||||
|
|
||||||
### Bruno - IDE із відкритим кодом для тестування та дослідження API
|
### Bruno - IDE із відкритим кодом для тестування та дослідження API
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ Bruno зберігає ваші колекції напряму у теці на
|
|||||||
|
|
||||||
Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269)
|
Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](/assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
### Кросплатформенність 🖥️
|
### Кросплатформенність 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](/assets/images/run-anywhere.png) <br /><br />
|
||||||
|
|
||||||
### Спільна робота через Git 👩💻🧑💻
|
### Спільна робота через Git 👩💻🧑💻
|
||||||
|
|
||||||
Або будь-яку іншу систему контролю версій на ваш вибір
|
Або будь-яку іншу систему контролю версій на ваш вибір
|
||||||
|
|
||||||
![bruno](assets/images/version-control.png) <br /><br />
|
![bruno](/assets/images/version-control.png) <br /><br />
|
||||||
|
|
||||||
### Важливі посилання 📌
|
### Важливі посилання 📌
|
||||||
|
|
||||||
@ -75,4 +75,4 @@ Bruno є повністю автономним. Немає жодних план
|
|||||||
|
|
||||||
### Ліцензія 📄
|
### Ліцензія 📄
|
||||||
|
|
||||||
[MIT](license.md)
|
[MIT](/license.md)
|
||||||
|
28
package-lock.json
generated
28
package-lock.json
generated
@ -12178,6 +12178,14 @@
|
|||||||
"xml2js": "^0.4.5"
|
"xml2js": "^0.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-curl": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-curl/-/parse-curl-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-ENhXeIxG4A6wFvYSU87b0o3Tp6U+Wup069GYhXCn0ZP/E7evfvomTSM/MeEOs3QTyuye+u2r7fFRj6nX0q9kQA==",
|
||||||
|
"dependencies": {
|
||||||
|
"shellwords": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parse-headers": {
|
"node_modules/parse-headers": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -14515,6 +14523,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shellwords": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww=="
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -16470,6 +16483,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "^0.2.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
@ -20540,6 +20554,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "*",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
@ -25603,6 +25618,14 @@
|
|||||||
"xml2js": "^0.4.5"
|
"xml2js": "^0.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"parse-curl": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-curl/-/parse-curl-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-ENhXeIxG4A6wFvYSU87b0o3Tp6U+Wup069GYhXCn0ZP/E7evfvomTSM/MeEOs3QTyuye+u2r7fFRj6nX0q9kQA==",
|
||||||
|
"requires": {
|
||||||
|
"shellwords": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"parse-headers": {
|
"parse-headers": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"dev": true
|
"dev": true
|
||||||
@ -27036,6 +27059,11 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"shellwords": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww=="
|
||||||
|
},
|
||||||
"side-channel": {
|
"side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"next": "12.3.3",
|
"next": "12.3.3",
|
||||||
|
"parse-curl": "^0.2.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"platform": "^1.3.6",
|
"platform": "^1.3.6",
|
||||||
"posthog-node": "^2.1.0",
|
"posthog-node": "^2.1.0",
|
||||||
|
@ -146,6 +146,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.refresh();
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, forwardRef } from 'react';
|
import React, { useEffect, useRef, forwardRef } from 'react';
|
||||||
import useGraphqlSchema from './useGraphqlSchema';
|
import useGraphqlSchema from './useGraphqlSchema';
|
||||||
import { IconBook, IconDownload, IconLoader2, IconCheckmark } from '@tabler/icons';
|
import { IconBook, IconDownload, IconLoader2, IconRefresh } from '@tabler/icons';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { findEnvironmentInCollection } from 'utils/collections';
|
import { findEnvironmentInCollection } from 'utils/collections';
|
||||||
import Dropdown from '../../Dropdown';
|
import Dropdown from '../../Dropdown';
|
||||||
@ -30,8 +30,8 @@ const GraphQLSchemaActions = ({ item, collection, onSchemaLoad, toggleDocs }) =>
|
|||||||
return (
|
return (
|
||||||
<div ref={ref} className="dropdown-icon cursor-pointer flex hover:underline ml-2">
|
<div ref={ref} className="dropdown-icon cursor-pointer flex hover:underline ml-2">
|
||||||
{isSchemaLoading && <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />}
|
{isSchemaLoading && <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />}
|
||||||
{!isSchemaLoading && schema && <IconDownload size={18} strokeWidth={1.5} />}
|
{!isSchemaLoading && schema && <IconRefresh size={18} strokeWidth={1.5} />}
|
||||||
{!isSchemaLoading && !schema && <IconCheckmark size={18} strokeWidth={1.5} />}
|
{!isSchemaLoading && !schema && <IconDownload size={18} strokeWidth={1.5} />}
|
||||||
<span className="ml-1">Schema</span>
|
<span className="ml-1">Schema</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs';
|
|||||||
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
|
import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector';
|
||||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { getRequestFromCurlCommand } from 'utils/curl';
|
||||||
|
|
||||||
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -21,7 +22,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
requestName: '',
|
requestName: '',
|
||||||
requestType: 'http-request',
|
requestType: 'http-request',
|
||||||
requestUrl: '',
|
requestUrl: '',
|
||||||
requestMethod: 'GET'
|
requestMethod: 'GET',
|
||||||
|
curlCommand: ''
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
requestName: Yup.string()
|
requestName: Yup.string()
|
||||||
@ -35,6 +37,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
const trimmedValue = value ? value.trim().toLowerCase() : '';
|
const trimmedValue = value ? value.trim().toLowerCase() : '';
|
||||||
return !['collection', 'folder'].includes(trimmedValue);
|
return !['collection', 'folder'].includes(trimmedValue);
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
curlCommand: Yup.string()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
|
.required('curlCommand is required')
|
||||||
|
.test({
|
||||||
|
name: 'curlCommand',
|
||||||
|
message: `Invalid cURL Command`,
|
||||||
|
test: (value) => getRequestFromCurlCommand(value) !== null
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
@ -61,6 +71,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
onClose();
|
onClose();
|
||||||
})
|
})
|
||||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||||
|
} else if (values.requestType === 'from-curl') {
|
||||||
|
const request = getRequestFromCurlCommand(values.curlCommand);
|
||||||
|
dispatch(
|
||||||
|
newHttpRequest({
|
||||||
|
requestName: values.requestName,
|
||||||
|
requestType: 'http-request',
|
||||||
|
requestUrl: request.url,
|
||||||
|
requestMethod: request.method,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item ? item.uid : null,
|
||||||
|
headers: request.headers,
|
||||||
|
body: request.body
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => onClose())
|
||||||
|
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
||||||
} else {
|
} else {
|
||||||
dispatch(
|
dispatch(
|
||||||
newHttpRequest({
|
newHttpRequest({
|
||||||
@ -124,9 +150,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
|
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
|
||||||
GraphQL
|
GraphQL
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="from-curl"
|
||||||
|
className="cursor-pointer ml-auto"
|
||||||
|
type="radio"
|
||||||
|
name="requestType"
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value="from-curl"
|
||||||
|
checked={formik.values.requestType === 'from-curl'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label htmlFor="from-curl" className="ml-1 cursor-pointer select-none">
|
||||||
|
From cURL
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label htmlFor="requestName" className="block font-semibold">
|
<label htmlFor="requestName" className="block font-semibold">
|
||||||
Name
|
Name
|
||||||
@ -148,38 +187,58 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
<div className="text-red-500">{formik.errors.requestName}</div>
|
<div className="text-red-500">{formik.errors.requestName}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{formik.values.requestType !== 'from-curl' ? (
|
||||||
|
<>
|
||||||
|
<div className="mt-4">
|
||||||
|
<label htmlFor="request-url" className="block font-semibold">
|
||||||
|
URL
|
||||||
|
</label>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="flex items-center mt-2 ">
|
||||||
<label htmlFor="request-url" className="block font-semibold">
|
<div className="flex items-center h-full method-selector-container">
|
||||||
URL
|
<HttpMethodSelector
|
||||||
</label>
|
method={formik.values.requestMethod}
|
||||||
|
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
|
||||||
<div className="flex items-center mt-2 ">
|
/>
|
||||||
<div className="flex items-center h-full method-selector-container">
|
</div>
|
||||||
<HttpMethodSelector
|
<div className="flex items-center flex-grow input-container h-full">
|
||||||
method={formik.values.requestMethod}
|
<input
|
||||||
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
|
id="request-url"
|
||||||
/>
|
type="text"
|
||||||
</div>
|
name="requestUrl"
|
||||||
<div className="flex items-center flex-grow input-container h-full">
|
className="px-3 w-full "
|
||||||
<input
|
autoComplete="off"
|
||||||
id="request-url"
|
autoCorrect="off"
|
||||||
type="text"
|
autoCapitalize="off"
|
||||||
name="requestUrl"
|
spellCheck="false"
|
||||||
className="px-3 w-full "
|
onChange={formik.handleChange}
|
||||||
autoComplete="off"
|
value={formik.values.requestUrl || ''}
|
||||||
autoCorrect="off"
|
/>
|
||||||
autoCapitalize="off"
|
</div>
|
||||||
spellCheck="false"
|
</div>
|
||||||
onChange={formik.handleChange}
|
{formik.touched.requestUrl && formik.errors.requestUrl ? (
|
||||||
value={formik.values.requestUrl || ''}
|
<div className="text-red-500">{formik.errors.requestUrl}</div>
|
||||||
/>
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="mt-4">
|
||||||
|
<label htmlFor="request-url" className="block font-semibold">
|
||||||
|
cURL Command
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="curlCommand"
|
||||||
|
placeholder="Enter cURL request here.."
|
||||||
|
className="block textbox w-full mt-4"
|
||||||
|
style={{ resize: 'none' }}
|
||||||
|
value={formik.values.curlCommand}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
></textarea>
|
||||||
|
{formik.touched.curlCommand && formik.errors.curlCommand ? (
|
||||||
|
<div className="text-red-500">{formik.errors.curlCommand}</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{formik.touched.requestUrl && formik.errors.requestUrl ? (
|
)}
|
||||||
<div className="text-red-500">{formik.errors.requestUrl}</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -105,7 +105,7 @@ const Sidebar = () => {
|
|||||||
Star
|
Star
|
||||||
</GitHubButton>
|
</GitHubButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.27.2</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.0.1</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -568,7 +568,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newHttpRequest = (params) => (dispatch, getState) => {
|
export const newHttpRequest = (params) => (dispatch, getState) => {
|
||||||
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid } = params;
|
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body } = params;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -591,9 +591,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
request: {
|
request: {
|
||||||
method: requestMethod,
|
method: requestMethod,
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
headers: [],
|
headers: headers ?? [],
|
||||||
params,
|
params,
|
||||||
body: {
|
body: body ?? {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
json: null,
|
json: null,
|
||||||
text: null,
|
text: null,
|
||||||
|
@ -10,7 +10,7 @@ if (!SERVER_RENDERED) {
|
|||||||
|
|
||||||
const pathFoundInVariables = (path, obj) => {
|
const pathFoundInVariables = (path, obj) => {
|
||||||
const value = get(obj, path);
|
const value = get(obj, path);
|
||||||
return isString(value);
|
return value !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => {
|
export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => {
|
||||||
|
60
packages/bruno-app/src/utils/curl/index.js
Normal file
60
packages/bruno-app/src/utils/curl/index.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import * as parse from 'parse-curl';
|
||||||
|
import { BrunoError } from 'utils/common/error';
|
||||||
|
import { parseQueryParams } from 'utils/url';
|
||||||
|
|
||||||
|
export const getRequestFromCurlCommand = (command) => {
|
||||||
|
const parseFormData = (parsedBody) => {
|
||||||
|
parseQueryParams(parsedBody);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const request = parse(command);
|
||||||
|
const parsedHeader = request?.header;
|
||||||
|
const headers =
|
||||||
|
parsedHeader && Object.keys(parsedHeader).map((key) => ({ name: key, value: parsedHeader[key], enabled: true }));
|
||||||
|
|
||||||
|
const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type');
|
||||||
|
const body = {
|
||||||
|
mode: 'none',
|
||||||
|
json: null,
|
||||||
|
text: null,
|
||||||
|
xml: null,
|
||||||
|
sparql: null,
|
||||||
|
multipartForm: null,
|
||||||
|
formUrlEncoded: null
|
||||||
|
};
|
||||||
|
const parsedBody = request?.body;
|
||||||
|
if (parsedBody && contentType) {
|
||||||
|
switch (contentType.value.toLowerCase()) {
|
||||||
|
case 'application/json':
|
||||||
|
body.mode = 'json';
|
||||||
|
body.json = parsedBody;
|
||||||
|
break;
|
||||||
|
case 'text/xml':
|
||||||
|
body.mode = 'xml';
|
||||||
|
body.xml = parsedBody;
|
||||||
|
break;
|
||||||
|
case 'application/x-www-form-urlencoded':
|
||||||
|
body.mode = 'formUrlEncoded';
|
||||||
|
body.formUrlEncoded = parseFormData(parsedBody);
|
||||||
|
break;
|
||||||
|
case 'multipart/form-data':
|
||||||
|
body.mode = 'multipartForm';
|
||||||
|
body.multipartForm = parsedBody;
|
||||||
|
break;
|
||||||
|
case 'text/plain':
|
||||||
|
default:
|
||||||
|
body.mode = 'text';
|
||||||
|
body.text = parsedBody;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
body,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
@ -14,11 +14,46 @@ const readFile = (files) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseGraphQLRequest = (graphqlSource) => {
|
||||||
|
try {
|
||||||
|
let queryResultObject = {
|
||||||
|
query: '',
|
||||||
|
variables: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof graphqlSource === 'string') {
|
||||||
|
graphqlSource = JSON.parse(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphqlSource.hasOwnProperty('variables') && graphqlSource.variables !== '') {
|
||||||
|
queryResultObject.variables = graphqlSource.variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphqlSource.hasOwnProperty('query') && graphqlSource.query !== '') {
|
||||||
|
queryResultObject.query = graphqlSource.query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryResultObject;
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
query: '',
|
||||||
|
variables: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isItemAFolder = (item) => {
|
const isItemAFolder = (item) => {
|
||||||
return !item.request;
|
return !item.request;
|
||||||
};
|
};
|
||||||
|
|
||||||
const importPostmanV2CollectionItem = (brunoParent, item) => {
|
const convertV21Auth = (array) => {
|
||||||
|
return array.reduce((accumulator, currentValue) => {
|
||||||
|
accumulator[currentValue.key] = currentValue.value;
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => {
|
||||||
brunoParent.items = brunoParent.items || [];
|
brunoParent.items = brunoParent.items || [];
|
||||||
|
|
||||||
each(item, (i) => {
|
each(item, (i) => {
|
||||||
@ -31,7 +66,7 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
};
|
};
|
||||||
brunoParent.items.push(brunoFolderItem);
|
brunoParent.items.push(brunoFolderItem);
|
||||||
if (i.item && i.item.length) {
|
if (i.item && i.item.length) {
|
||||||
importPostmanV2CollectionItem(brunoFolderItem, i.item);
|
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i.request) {
|
if (i.request) {
|
||||||
@ -49,6 +84,12 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
request: {
|
request: {
|
||||||
url: url,
|
url: url,
|
||||||
method: i.request.method,
|
method: i.request.method,
|
||||||
|
auth: {
|
||||||
|
mode: 'none',
|
||||||
|
basic: null,
|
||||||
|
bearer: null,
|
||||||
|
awsv4: null
|
||||||
|
},
|
||||||
headers: [],
|
headers: [],
|
||||||
params: [],
|
params: [],
|
||||||
body: {
|
body: {
|
||||||
@ -133,6 +174,12 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bodyMode === 'graphql') {
|
||||||
|
brunoRequestItem.type = 'graphql-request';
|
||||||
|
brunoRequestItem.request.body.mode = 'graphql';
|
||||||
|
brunoRequestItem.request.body.graphql = parseGraphQLRequest(i.request.body.graphql);
|
||||||
|
}
|
||||||
|
|
||||||
each(i.request.header, (header) => {
|
each(i.request.header, (header) => {
|
||||||
brunoRequestItem.request.headers.push({
|
brunoRequestItem.request.headers.push({
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
@ -143,6 +190,36 @@ const importPostmanV2CollectionItem = (brunoParent, item) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auth = i.request.auth ?? parentAuth;
|
||||||
|
if (auth?.[auth.type] && auth.type !== 'noauth') {
|
||||||
|
let authValues = auth[auth.type];
|
||||||
|
if (Array.isArray(authValues)) {
|
||||||
|
authValues = convertV21Auth(authValues);
|
||||||
|
}
|
||||||
|
if (auth.type === 'basic') {
|
||||||
|
brunoRequestItem.request.auth.mode = 'basic';
|
||||||
|
brunoRequestItem.request.auth.basic = {
|
||||||
|
username: authValues.username,
|
||||||
|
password: authValues.password
|
||||||
|
};
|
||||||
|
} else if (auth.type === 'bearer') {
|
||||||
|
brunoRequestItem.request.auth.mode = 'bearer';
|
||||||
|
brunoRequestItem.request.auth.bearer = {
|
||||||
|
token: authValues.token
|
||||||
|
};
|
||||||
|
} else if (auth.type === 'awsv4') {
|
||||||
|
brunoRequestItem.request.auth.mode = 'awsv4';
|
||||||
|
brunoRequestItem.request.auth.awsv4 = {
|
||||||
|
accessKeyId: authValues.accessKey,
|
||||||
|
secretAccessKey: authValues.secretKey,
|
||||||
|
sessionToken: authValues.sessionToken,
|
||||||
|
service: authValues.service,
|
||||||
|
region: authValues.region,
|
||||||
|
profileName: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
each(get(i, 'request.url.query'), (param) => {
|
each(get(i, 'request.url.query'), (param) => {
|
||||||
brunoRequestItem.request.params.push({
|
brunoRequestItem.request.params.push({
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
@ -183,7 +260,7 @@ const importPostmanV2Collection = (collection) => {
|
|||||||
environments: []
|
environments: []
|
||||||
};
|
};
|
||||||
|
|
||||||
importPostmanV2CollectionItem(brunoCollection, collection.item);
|
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth);
|
||||||
|
|
||||||
return brunoCollection;
|
return brunoCollection;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "v0.27.2",
|
"version": "v1.0.1",
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||||
"homepage": "https://www.usebruno.com",
|
"homepage": "https://www.usebruno.com",
|
||||||
|
@ -587,6 +587,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
scriptingConfig
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars);
|
||||||
const axiosInstance = await configureRequest(
|
const axiosInstance = await configureRequest(
|
||||||
collection.uid,
|
collection.uid,
|
||||||
preparedRequest,
|
preparedRequest,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter, forOwn, extend } = require('lodash');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
// A request can override the collection auth with another auth
|
// A request can override the collection auth with another auth
|
||||||
|
32
readme.md
32
readme.md
@ -16,12 +16,40 @@ Bruno is a new and innovative API client, aimed at revolutionizing the status qu
|
|||||||
|
|
||||||
Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests.
|
Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests.
|
||||||
|
|
||||||
You can use git or any version control of your choice to collaborate over your API collections.
|
You can use Git or any version control of your choice to collaborate over your API collections.
|
||||||
|
|
||||||
Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We value your data privacy and believe it should stay on your device. Read our long-term vision [here](https://github.com/usebruno/bruno/discussions/269)
|
Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We value your data privacy and believe it should stay on your device. Read our long-term vision [here](https://github.com/usebruno/bruno/discussions/269)
|
||||||
|
|
||||||
|
📢 Watch our recent talk at India FOSS 3.0 Conference [here](https://www.youtube.com/watch?v=7bSMFpbcPiY)
|
||||||
|
|
||||||
![bruno](assets/images/landing-2.png) <br /><br />
|
![bruno](assets/images/landing-2.png) <br /><br />
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Bruno is available as binary download [on our website](https://www.usebruno.com/downloads) for Mac, Windows and Linux.
|
||||||
|
|
||||||
|
You can also install Bruno via package managers like Homebrew, Chocolatey, Snap and Apt.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# On Mac via Homebrew
|
||||||
|
brew install bruno
|
||||||
|
|
||||||
|
# On Windows via Chocolatey
|
||||||
|
choco install bruno
|
||||||
|
|
||||||
|
# On Linux via Snap
|
||||||
|
snap install bruno
|
||||||
|
|
||||||
|
# On Linux via Apt
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266
|
||||||
|
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install bruno
|
||||||
|
```
|
||||||
|
|
||||||
### Run across multiple platforms 🖥️
|
### Run across multiple platforms 🖥️
|
||||||
|
|
||||||
![bruno](assets/images/run-anywhere.png) <br /><br />
|
![bruno](assets/images/run-anywhere.png) <br /><br />
|
||||||
@ -75,7 +103,7 @@ Even if you are not able to make contributions via code, please don't hesitate t
|
|||||||
|
|
||||||
### Stay in touch 🌐
|
### Stay in touch 🌐
|
||||||
|
|
||||||
[Twitter](https://twitter.com/use_bruno) <br />
|
[𝕏 (Twitter)](https://twitter.com/use_bruno) <br />
|
||||||
[Website](https://www.usebruno.com) <br />
|
[Website](https://www.usebruno.com) <br />
|
||||||
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
|
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
|
||||||
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
[LinkedIn](https://www.linkedin.com/company/usebruno)
|
||||||
|
Loading…
Reference in New Issue
Block a user