Merge branch 'main' into feature/add-bru-setNextRequest

This commit is contained in:
Martin Hoecker 2023-11-01 22:59:39 +01:00
commit db0de68987
No known key found for this signature in database
GPG Key ID: DD1434854AB52DD0
20 changed files with 332 additions and 73 deletions

View File

@ -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

View File

@ -1,5 +1,5 @@
<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.
@ -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) <br /><br />
![bruno](/assets/images/landing-2.png) <br /><br />
### Einsatz auf verschiedensten Plattformen 🖥️
![bruno](assets/images/run-anywhere.png) <br /><br />
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Zusammenarbeiten mit Git 👩‍💻🧑‍💻
oder eine Versionskontrolle Deiner Wahl
![bruno](assets/images/version-control.png) <br /><br />
![bruno](/assets/images/version-control.png) <br /><br />
### 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)

View File

@ -1,5 +1,5 @@
<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.
@ -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) <br /><br />
![bruno](/assets/images/landing-2.png) <br /><br />
### Fonctionne sur de multiples platformes 🖥️
![bruno](assets/images/run-anywhere.png) <br /><br />
![bruno](/assets/images/run-anywhere.png) <br /><br />
### Collaborer via Git 👩‍💻🧑‍💻
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 📌
@ -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)

View File

@ -1,5 +1,5 @@
<br />
<img src="assets/images/logo-transparent.png" width="80"/>
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE с открытым исходным кодом для изучения и тестирования API.
@ -18,17 +18,17 @@ Bruno хранит ваши коллекции непосредственно в
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 👩‍💻🧑‍💻
Или другая система контроля версий по вашему выбору
![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)

View File

@ -1,5 +1,5 @@
<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.
@ -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) <br /><br />
![bruno](/assets/images/landing-2.png) <br /><br />
### 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 👩‍💻🧑‍💻
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 📌
@ -75,4 +75,4 @@ Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek
### Lisans 📄
[MIT](license.md)
[MIT](/license.md)

View File

@ -1,5 +1,5 @@
<br />
<img src="assets/images/logo-transparent.png" width="80"/>
<img src="../../assets/images/logo-transparent.png" width="80"/>
### Bruno - IDE із відкритим кодом для тестування та дослідження API
@ -18,17 +18,17 @@ 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 👩‍💻🧑‍💻
Або будь-яку іншу систему контролю версій на ваш вибір
![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
View File

@ -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": {

View File

@ -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",

View File

@ -146,6 +146,9 @@ export default class CodeEditor extends React.Component {
}
render() {
if (this.editor) {
this.editor.refresh();
}
return (
<StyledWrapper
className="h-full w-full"

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef, forwardRef } from 'react';
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 { findEnvironmentInCollection } from 'utils/collections';
import Dropdown from '../../Dropdown';
@ -30,8 +30,8 @@ const GraphQLSchemaActions = ({ item, collection, onSchemaLoad, toggleDocs }) =>
return (
<div ref={ref} className="dropdown-icon cursor-pointer flex hover:underline ml-2">
{isSchemaLoading && <IconLoader2 className="animate-spin" size={18} strokeWidth={1.5} />}
{!isSchemaLoading && schema && <IconDownload size={18} strokeWidth={1.5} />}
{!isSchemaLoading && !schema && <IconCheckmark size={18} strokeWidth={1.5} />}
{!isSchemaLoading && schema && <IconRefresh size={18} strokeWidth={1.5} />}
{!isSchemaLoading && !schema && <IconDownload size={18} strokeWidth={1.5} />}
<span className="ml-1">Schema</span>
</div>
);

View File

@ -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 }) => {
<label htmlFor="graphql-request" className="ml-1 cursor-pointer select-none">
GraphQL
</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 className="mt-4">
<label htmlFor="requestName" className="block font-semibold">
Name
@ -148,38 +187,58 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
<div className="text-red-500">{formik.errors.requestName}</div>
) : null}
</div>
{formik.values.requestType !== 'from-curl' ? (
<>
<div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">
URL
</label>
<div className="mt-4">
<label htmlFor="request-url" className="block font-semibold">
URL
</label>
<div className="flex items-center mt-2 ">
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
/>
</div>
<div className="flex items-center flex-grow input-container h-full">
<input
id="request-url"
type="text"
name="requestUrl"
className="px-3 w-full "
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
/>
<div className="flex items-center mt-2 ">
<div className="flex items-center h-full method-selector-container">
<HttpMethodSelector
method={formik.values.requestMethod}
onMethodSelect={(val) => formik.setFieldValue('requestMethod', val)}
/>
</div>
<div className="flex items-center flex-grow input-container h-full">
<input
id="request-url"
type="text"
name="requestUrl"
className="px-3 w-full "
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.requestUrl || ''}
/>
</div>
</div>
{formik.touched.requestUrl && formik.errors.requestUrl ? (
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</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>
{formik.touched.requestUrl && formik.errors.requestUrl ? (
<div className="text-red-500">{formik.errors.requestUrl}</div>
) : null}
</div>
)}
</form>
</Modal>
</StyledWrapper>

View File

@ -105,7 +105,7 @@ const Sidebar = () => {
Star
</GitHubButton>
</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>

View File

@ -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,

View File

@ -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) => {

View 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;
}
};

View File

@ -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;
};

View File

@ -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",

View File

@ -587,6 +587,7 @@ const registerNetworkIpc = (mainWindow) => {
scriptingConfig
);
interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars);
const axiosInstance = await configureRequest(
collection.uid,
preparedRequest,

View File

@ -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

View File

@ -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) <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 🖥️
![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 🌐
[Twitter](https://twitter.com/use_bruno) <br />
[𝕏 (Twitter)](https://twitter.com/use_bruno) <br />
[Website](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)