diff --git a/.github/workflows/release-snap.yml b/.github/workflows/release-snap.yml index b71e88486..16a8d2e86 100644 --- a/.github/workflows/release-snap.yml +++ b/.github/workflows/release-snap.yml @@ -30,6 +30,7 @@ jobs: run: | npm run build:bruno-query npm run build:graphql-docs + npm run build:web npm run build:electron:snap - name: Install Snapcraft diff --git a/docs/readme/readme_de.md b/docs/readme/readme_de.md index a0ef031d7..371672b4c 100644 --- a/docs/readme/readme_de.md +++ b/docs/readme/readme_de.md @@ -1,5 +1,5 @@
- + ### 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](assets/images/landing-2.png)

+![bruno](/assets/images/landing-2.png)

### Einsatz auf verschiedensten Plattformen 🖥️ -![bruno](assets/images/run-anywhere.png)

+![bruno](/assets/images/run-anywhere.png)

### Zusammenarbeiten mit Git 👩‍💻🧑‍💻 oder eine Versionskontrolle Deiner Wahl -![bruno](assets/images/version-control.png)

+![bruno](/assets/images/version-control.png)

### 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 -Bitte [hier](publishing.md) für mehr Informationen lesen. +Bitte [hier](/publishing.md) für mehr Informationen lesen. ### Mitmachen 👩‍💻🧑‍💻 @@ -90,4 +90,4 @@ Das Logo stammt von [OpenMoji](https://openmoji.org/library/emoji-1F436/). Lizen ### Lizenz 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/docs/readme/readme_fr.md b/docs/readme/readme_fr.md index 97b327bfa..9c3fdd6cd 100644 --- a/docs/readme/readme_fr.md +++ b/docs/readme/readme_fr.md @@ -1,5 +1,5 @@
- + ### 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](assets/images/landing-2.png)

+![bruno](/assets/images/landing-2.png)

### Fonctionne sur de multiples platformes 🖥️ -![bruno](assets/images/run-anywhere.png)

+![bruno](/assets/images/run-anywhere.png)

### Collaborer via Git 👩‍💻🧑‍💻 Ou n'importe quel système de gestion de sources -![bruno](assets/images/version-control.png)

+![bruno](/assets/images/version-control.png)

### 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 -Veuillez regarder [ici](publishing.md) pour plus d'information. +Veuillez regarder [ici](/publishing.md) pour plus d'information. ### Contribuer 👩‍💻🧑‍💻 @@ -91,4 +91,4 @@ Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) ### Licence 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/docs/readme/readme_ru.md b/docs/readme/readme_ru.md index 423ab33b1..ddbe8135c 100644 --- a/docs/readme/readme_ru.md +++ b/docs/readme/readme_ru.md @@ -1,5 +1,5 @@
- + ### Bruno - IDE с открытым исходным кодом для изучения и тестирования API. @@ -18,17 +18,17 @@ Bruno хранит ваши коллекции непосредственно в Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269) -![bruno](assets/images/landing-2.png)

+![bruno](/assets/images/landing-2.png)

### Работа на нескольких платформах 🖥️ -![bruno](assets/images/run-anywhere.png)

+![bruno](/assets/images/run-anywhere.png)

### Совместная работа через Git 👩‍💻🧑‍💻 Или другая система контроля версий по вашему выбору -![bruno](assets/images/version-control.png)

+![bruno](/assets/images/version-control.png)

### Важные ссылки 📌 @@ -74,4 +74,4 @@ Bruno работает только в автономном режиме. Доб ### Лицензия 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/docs/readme/readme_tr.md b/docs/readme/readme_tr.md index 74bd8aa50..b04738505 100644 --- a/docs/readme/readme_tr.md +++ b/docs/readme/readme_tr.md @@ -1,5 +1,5 @@
- + ### 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](assets/images/landing-2.png)

+![bruno](/assets/images/landing-2.png)

### Birden fazla platformda çalıştırın 🖥️ -![bruno](assets/images/run-anywhere.png)

+![bruno](/assets/images/run-anywhere.png)

### Git üzerinden işbirliği yapın 👩‍💻🧑‍💻 Veya seçtiğiniz herhangi bir sürüm kontrol sistemi -![bruno](assets/images/version-control.png)

+![bruno](/assets/images/version-control.png)

### Önemli Bağlantılar 📌 @@ -75,4 +75,4 @@ Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek ### Lisans 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/docs/readme/readme_ua.md b/docs/readme/readme_ua.md index 9b0eb7a0f..cb0ccb9ee 100644 --- a/docs/readme/readme_ua.md +++ b/docs/readme/readme_ua.md @@ -1,5 +1,5 @@
- + ### Bruno - IDE із відкритим кодом для тестування та дослідження API @@ -18,17 +18,17 @@ Bruno зберігає ваші колекції напряму у теці на Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269) -![bruno](assets/images/landing-2.png)

+![bruno](/assets/images/landing-2.png)

### Кросплатформенність 🖥️ -![bruno](assets/images/run-anywhere.png)

+![bruno](/assets/images/run-anywhere.png)

### Спільна робота через Git 👩‍💻🧑‍💻 Або будь-яку іншу систему контролю версій на ваш вибір -![bruno](assets/images/version-control.png)

+![bruno](/assets/images/version-control.png)

### Важливі посилання 📌 @@ -75,4 +75,4 @@ Bruno є повністю автономним. Немає жодних план ### Ліцензія 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/package-lock.json b/package-lock.json index 79f3de50f..cf7a7d95f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12178,6 +12178,14 @@ "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": { "version": "2.0.5", "dev": true, @@ -14515,6 +14523,11 @@ "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": { "version": "1.0.4", "license": "MIT", @@ -16470,6 +16483,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "^0.2.6", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", @@ -20540,6 +20554,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "*", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", @@ -25603,6 +25618,14 @@ "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": { "version": "2.0.5", "dev": true @@ -27036,6 +27059,11 @@ "version": "3.0.0", "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": { "version": "1.0.4", "requires": { diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index c3f82569f..77ff96b67 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -42,6 +42,7 @@ "mousetrap": "^1.6.5", "nanoid": "3.3.4", "next": "12.3.3", + "parse-curl": "^0.2.6", "path": "^0.12.7", "platform": "^1.3.6", "posthog-node": "^2.1.0", diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index b1a115c9e..0bfd6fdd3 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -146,6 +146,9 @@ export default class CodeEditor extends React.Component { } render() { + if (this.editor) { + this.editor.refresh(); + } return ( return (
{isSchemaLoading && } - {!isSchemaLoading && schema && } - {!isSchemaLoading && !schema && } + {!isSchemaLoading && schema && } + {!isSchemaLoading && !schema && } Schema
); diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 2e54b56ad..f6bcdfeb0 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; +import { getRequestFromCurlCommand } from 'utils/curl'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -21,7 +22,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: '', requestType: 'http-request', requestUrl: '', - requestMethod: 'GET' + requestMethod: 'GET', + curlCommand: '' }, validationSchema: Yup.object({ requestName: Yup.string() @@ -35,6 +37,14 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const trimmedValue = value ? value.trim().toLowerCase() : ''; 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) => { @@ -61,6 +71,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { onClose(); }) .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 { dispatch( newHttpRequest({ @@ -124,9 +150,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { + + + + -
+ {formik.values.requestType !== 'from-curl' ? ( + <> +
+ -
- - -
-
- formik.setFieldValue('requestMethod', val)} - /> -
-
- +
+
+ formik.setFieldValue('requestMethod', val)} + /> +
+
+ +
+
+ {formik.touched.requestUrl && formik.errors.requestUrl ? ( +
{formik.errors.requestUrl}
+ ) : null}
+ + ) : ( +
+ + + {formik.touched.curlCommand && formik.errors.curlCommand ? ( +
{formik.errors.curlCommand}
+ ) : null}
- {formik.touched.requestUrl && formik.errors.requestUrl ? ( -
{formik.errors.requestUrl}
- ) : null} -
+ )} diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index a6df7141c..69be75689 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -105,7 +105,7 @@ const Sidebar = () => { Star
-
v0.27.2
+
v1.0.1
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 20a68b80e..2ef107954 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -568,7 +568,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di }; 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) => { const state = getState(); @@ -591,9 +591,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => { request: { method: requestMethod, url: requestUrl, - headers: [], + headers: headers ?? [], params, - body: { + body: body ?? { mode: 'none', json: null, text: null, diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index 78016de6f..6074dbdcf 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -10,7 +10,7 @@ if (!SERVER_RENDERED) { const pathFoundInVariables = (path, obj) => { const value = get(obj, path); - return isString(value); + return value !== undefined; }; export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js new file mode 100644 index 000000000..30080e467 --- /dev/null +++ b/packages/bruno-app/src/utils/curl/index.js @@ -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; + } +}; diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index bc7f475e6..136bef94f 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -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) => { 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 || []; each(item, (i) => { @@ -31,7 +66,7 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { }; brunoParent.items.push(brunoFolderItem); if (i.item && i.item.length) { - importPostmanV2CollectionItem(brunoFolderItem, i.item); + importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth); } } else { if (i.request) { @@ -49,6 +84,12 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { request: { url: url, method: i.request.method, + auth: { + mode: 'none', + basic: null, + bearer: null, + awsv4: null + }, headers: [], params: [], 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) => { brunoRequestItem.request.headers.push({ 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) => { brunoRequestItem.request.params.push({ uid: uuid(), @@ -183,7 +260,7 @@ const importPostmanV2Collection = (collection) => { environments: [] }; - importPostmanV2CollectionItem(brunoCollection, collection.item); + importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth); return brunoCollection; }; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 47753da5a..5f334ac87 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.27.2", + "version": "v1.0.1", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 348d7818c..00ce034e8 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -587,6 +587,7 @@ const registerNetworkIpc = (mainWindow) => { scriptingConfig ); + interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars); const axiosInstance = await configureRequest( collection.uid, preparedRequest, diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 6e3f123c1..d38849f69 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -1,5 +1,6 @@ -const { get, each, filter } = require('lodash'); +const { get, each, filter, forOwn, extend } = require('lodash'); const decomment = require('decomment'); +const FormData = require('form-data'); // Authentication // A request can override the collection auth with another auth diff --git a/readme.md b/readme.md index 1486ad25d..96a9d7c88 100644 --- a/readme.md +++ b/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. -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) +📢 Watch our recent talk at India FOSS 3.0 Conference [here](https://www.youtube.com/watch?v=7bSMFpbcPiY) + ![bruno](assets/images/landing-2.png)

+### 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 🖥️ ![bruno](assets/images/run-anywhere.png)

@@ -75,7 +103,7 @@ Even if you are not able to make contributions via code, please don't hesitate t ### Stay in touch 🌐 -[Twitter](https://twitter.com/use_bruno)
+[𝕏 (Twitter)](https://twitter.com/use_bruno)
[Website](https://www.usebruno.com)
[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno)