Compare commits

..

14 Commits

Author SHA1 Message Date
Anoop M D
a18e5e5b94 Feat/safe mode quickjs (#2825)
* feat: testing quickjs sanbox

* feat: testing quickjs sanbox

* feat: testing quickjs sanbox
2024-08-13 19:06:17 +05:30
lohit
bde2c57a23 feat: safe mode updates (#2813)
* feat: safe mode updates
* feat: safe-mode updates
2024-08-12 16:52:59 +05:30
Anoop M D
9955b8796e feat: safe mode updates 2024-08-12 14:10:52 +05:30
Anoop M D
3b3fa8a856 feat: safe mode updates 2024-08-12 13:10:21 +05:30
Anoop M D
c687856a9e feat: safe mode updates 2024-08-12 12:28:41 +05:30
Anoop M D
033bba1805 feat: safe mode updates 2024-08-12 12:27:32 +05:30
Anoop M D
4fcb6f0980 feat: safe mode updates 2024-08-11 23:14:08 +05:30
Anoop M D
e4d2e5c1cf feat: safe mode updates 2024-08-11 19:22:48 +05:30
Anoop M D
969fe8e3cf feat: safe mode updates 2024-08-11 18:19:15 +05:30
Anoop M D
6d01c46d50 feat: safe mode updates 2024-08-11 18:02:05 +05:30
Anoop M D
751c7aa16d feat: safe mode updates 2024-08-11 16:11:12 +05:30
Anoop M D
b2baa1e48d feat: safe mode updates 2024-08-10 21:59:13 +05:30
Anoop M D
f834eb4425 feat: safe mode updates 2024-08-10 21:30:50 +05:30
lohit
26f8dd7a7b Safe Mode using isolated-vm (#2511)
feat: safe mode using isolated vm
2024-08-10 16:05:57 +05:30
161 changed files with 7687 additions and 4610 deletions

View File

@@ -25,7 +25,12 @@ jobs:
run: |
npm run build --workspace=packages/bruno-common
npm run build --workspace=packages/bruno-query
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
# rebuild isolated-vm for bruno-js
- name: Rebuild bruno-js isolated-vm
run: |
cd packages/bruno-js/node_modules/isolated-vm
npm run rebuild || true
# tests
- name: Test Package bruno-js
@@ -64,7 +69,12 @@ jobs:
run: |
npm run build --workspace=packages/bruno-query
npm run build --workspace=packages/bruno-common
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
# rebuild isolated-vm for bruno-js
- name: Rebuild bruno-js isolated-vm
run: |
cd packages/bruno-js/node_modules/isolated-vm
npm run rebuild || true
- name: Run tests
run: |

2
.nvmrc
View File

@@ -1 +1 @@
v20.9.0
v20.9.0

View File

@@ -34,11 +34,10 @@ Libraries we use
- Schema Validation - Yup
- Request Client - axios
- Filesystem Watcher - chokidar
- i18n - i18next
### Dependencies
You would need [Node v20.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
You would need [Node v18.x or the latest LTS version](https://nodejs.org/en/) and npm 8.x. We use npm workspaces in the project
## Development

View File

@@ -37,7 +37,7 @@ Bruno 基于 NextJs 和 React 构建。我们使用 Electron 来封装桌面版
### 依赖项
您需要 [Node v20.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我们在这个项目中也使用 npm 工作区_npm workspaces_
您需要 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我们在这个项目中也使用 npm 工作区_npm workspaces_
## 开发

View File

@@ -37,7 +37,7 @@ Bibliotheken die wir benutzen
### Abhängigkeiten
Du benötigst [Node v20.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
Du benötigst [Node v18.x oder die neuste LTS Version](https://nodejs.org/en/) und npm 8.x. Wir benutzen npm workspaces in dem Projekt.
### Lass uns coden

View File

@@ -37,7 +37,7 @@ Librerías que utilizamos:
### Dependencias
Necesitarás [Node v20.x o la última versión LTS](https://nodejs.org/es) y npm 8.x. Ten en cuenta que utilizamos espacios de trabajo de npm en el proyecto.
Necesitarás [Node v18.x o la última versión LTS](https://nodejs.org/es) y npm 8.x. Ten en cuenta que utilizamos espacios de trabajo de npm en el proyecto.
## Desarrollo

View File

@@ -37,7 +37,7 @@ Les librairies que nous utilisons :
### Dépendances
Vous aurez besoin de [Node v20.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
Vous aurez besoin de [Node v18.x ou la dernière version LTS](https://nodejs.org/en/) et npm 8.x. Nous utilisons aussi les espaces de travail npm (_npm workspaces_) dans ce projet.
## Développement

View File

@@ -37,7 +37,7 @@ Libraries जिनका हम उपयोग करते हैं
### निर्भरताएँ
आपको [Node v20.x या नवीनतम LTS संस्करण](https://nodejs.org/en/) और npm 8.x की आवश्यकता होगी। हम प्रोजेक्ट में npm वर्कस्पेस का उपयोग करते हैं
आपको [Node v18.x या नवीनतम LTS संस्करण](https://nodejs.org/en/) और npm 8.x की आवश्यकता होगी। हम प्रोजेक्ट में npm वर्कस्पेस का उपयोग करते हैं
## डेवलपमेंट

View File

@@ -37,7 +37,7 @@ Le librerie che utilizziamo sono:
### Dependences
Hai bisogno di [Node v20.x o dell'ultima versione LTS](https://nodejs.org/en/) di npm 8.x. Utilizziamo gli spazi di lavoro npm (_npm workspaces_) in questo progetto.
Hai bisogno di [Node v18.x o dell'ultima versione LTS](https://nodejs.org/en/) di npm 8.x. Utilizziamo gli spazi di lavoro npm (_npm workspaces_) in questo progetto.
### Iniziamo a codificare

View File

@@ -37,7 +37,7 @@ Bruno は Next.js と React で作られています。デスクトップアプ
### 依存関係
[Node v20.x もしくは最新の LTS バージョン](https://nodejs.org/en/)と npm 8.x が必要です。プロジェクトに npm ワークスペースを使用しています。
[Node v18.x もしくは最新の LTS バージョン](https://nodejs.org/en/)と npm 8.x が必要です。プロジェクトに npm ワークスペースを使用しています。
## 開発

View File

@@ -37,7 +37,7 @@ Bruno는 Next.js와 React로 구축되었습니다. 또한, (로컬 컬렉션을
### 의존성
[Node v20.x 혹은 최신 LTS version](https://nodejs.org/en/)과 npm 8.x 버전이 필요합니다. 우리는 이 프로젝트에서 npm workspaces를 사용합니다.
[Node v18.x 혹은 최신 LTS version](https://nodejs.org/en/)과 npm 8.x 버전이 필요합니다. 우리는 이 프로젝트에서 npm workspaces를 사용합니다.
## 개발

View File

@@ -37,7 +37,7 @@ Biblioteki, których używamy
### Zależności
Będziesz potrzebować [Node v20.x lub najnowszej wersji LTS](https://nodejs.org/en/) oraz npm 8.x. W projekcie używamy npm workspaces
Będziesz potrzebować [Node v18.x lub najnowszej wersji LTS](https://nodejs.org/en/) oraz npm 8.x. W projekcie używamy npm workspaces
## Rozwój

View File

@@ -37,7 +37,7 @@ Bibliotecas que utilizamos:
### Dependências
Você precisará do [Node v20.x (ou da versão LTS mais recente)](https://nodejs.org/en/) e do npm na versão 8.x. Nós utilizamos npm workspaces no projeto.
Você precisará do [Node v18.x (ou da versão LTS mais recente)](https://nodejs.org/en/) e do npm na versão 8.x. Nós utilizamos npm workspaces no projeto.
## Desenvolvimento

View File

@@ -37,7 +37,7 @@ Bibliotecile pe care le folosim
### Dependențele
Veți avea nevoie de [Node v20.x sau cea mai recentă versiune LTS](https://nodejs.org/en/) și npm 8.x. Noi folosim spații de lucru npm în proiect
Veți avea nevoie de [Node v18.x sau cea mai recentă versiune LTS](https://nodejs.org/en/) și npm 8.x. Noi folosim spații de lucru npm în proiect
## Dezvoltarea

View File

@@ -37,7 +37,7 @@ Bruno построен с использованием Next.js и React. Мы т
### Зависимости
Вам потребуется [Node v20.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm
### Приступим к коду

View File

@@ -37,7 +37,7 @@ Kullandığımız kütüphaneler
### Bağımlılıklar
[Node v20.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz
[Node v18.x veya en son LTS sürümüne](https://nodejs.org/en/) ve npm 8.x'e ihtiyacınız olacaktır. Projede npm çalışma alanlarını kullanıyoruz
## Gelişim

View File

@@ -37,7 +37,7 @@ Bruno побудований на Next.js та React. Також для деск
### Залежності
Вам знадобиться [Node v20.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті
### Починаєм писати код

View File

@@ -37,7 +37,7 @@ Bruno 使用 Next.js 和 React 構建。我們使用 Electron 來封裝及發佈
### 依賴關係
您需要使用 [Node v20.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我們在這個專案中使用 npm 工作區_npm workspaces_
您需要使用 [Node v18.x 或最新的 LTS 版本](https://nodejs.org/en/) 和 npm 8.x。我們在這個專案中使用 npm 工作區_npm workspaces_
## 開發

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| **العربية**
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
برونو هو عميل API جديد ومبتكر، يهدف إلى ثورة الحالة الحالية التي يمثلها برنامج Postman وأدوات مماثلة هناك.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
ব্রুনো হল একটি নতুন এবং উদ্ভাবনী API ক্লায়েন্ট, যার লক্ষ্য পোস্টম্যান এবং অনুরূপ সরঞ্জাম দ্বারা প্রতিনিধিত্ব করা স্থিতাবস্থায় বিপ্লব ঘটানো।

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno 是一款全新且创新的 API 客户端,旨在颠覆 Postman 和其他类似工具。

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll.

View File

@@ -27,8 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno es un cliente de APIs nuevo e innovador, creado con el objetivo de revolucionar el panorama actual representado por Postman y otras herramientas similares.
Bruno almacena tus colecciones directamente en una carpeta de tu sistema de archivos. Usamos un lenguaje de marcado de texto plano, llamado Bru, para guardar información sobre las peticiones a tus APIs.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _statu quo_ que représentent Postman et les autres outils.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno è un nuovo ed innovativo API client, mirato a rivoluzionare lo status quo rappresentato da Postman e strumenti simili disponibili.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| **日本語**
| [ქართული](./readme_ka.md)
Bruno は革新的な API クライアントです。Postman を代表する API クライアントツールの現状に一石を投じることを目指しています。

View File

@@ -1,176 +0,0 @@
<br />
<img src="../../assets/images/logo-transparent.png" width="80"/>
### ბრუნო - ღია წყაროების IDE API-ების შესწავლისა და ტესტირებისათვის.
[![GitHub version](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno)
[![CI](https://github.com/usebruno/bruno/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
[![Commit Activity](https://img.shields.io/github/commit-activity/m/usebruno/bruno)](https://github.com/usebruno/bruno/pulse)
[![X](https://img.shields.io/twitter/follow/use_bruno?style=social&logo=x)](https://twitter.com/use_bruno)
[![Website](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com)
[![Download](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads)
[English](../../readme.md)
| [Українська](./readme_ua.md)
| [Русский](./readme_ru.md)
| [Türkçe](./readme_tr.md)
| [Deutsch](./readme_de.md)
| [Français](./readme_fr.md)
| [Português (BR)](./readme_pt_br.md)
| [한국어](./readme_kr.md)
| [বাংলা](./readme_bn.md)
| [Español](./readme_es.md)
| [Italiano](./readme_it.md)
| [Română](./readme_ro.md)
| [Polski](./readme_pl.md)
| [简体中文](./readme_cn.md)
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| **ქართული**
ბრუნო არის ახალი და ინოვაციური API კლიენტი, რომელიც მიზნად ისახავს პოსტმანისა და მსგავსი ინსტრუმენტების არსებული მდგომარეობის რევოლუციას.
ბრუნო თქვენი კოლექციების შენახვას უშუალოდ თქვენს ფაილური სისტემის ერთ-ერთ საქაღალოში ახდენს. ჩვენ ვხმარობთ უბრალო ტექსტურ მარკაპ ენის, Bru-ს, API მოთხოვნების შესახებ ინფორმაციის შენახვისთვის.
თქვენ შეგიძლიათ გამოიყენოთ Git ან ნებისმიერი ვერსიის კონტროლის სისტემა თქვენი API კოლექციების გასაზიარებლად.
ბრუნო მხოლოდ ოფლაინ რეჟიმში მუშაობს. ბრუნოში ღრუბლური სინქრონიზაციის დამატების გეგმები არ არის. ჩვენ ვაფასებთ თქვენი მონაცემების პრივატობას და creemos, რომ ისინი თქვენს მოწყობილობაში უნდა დარჩეს. წაიკითხეთ ჩვენი გრძელვადიანი ხედვა [აქ](https://github.com/usebruno/bruno/discussions/269)
[დამატებით ბრუნო](https://www.usebruno.com/downloads)
📢 შეიტყვეთ ჩვენი უახლესი საუბრის შესახებ India FOSS 3.0 კონფერენციაზე [აქ](https://www.youtube.com/watch?v=7bSMFpbcPiY)
![bruno](../../assets/images/landing-2.png) <br /><br />
### ოქროს გამოცემა ✨
მთავარი ფუნქციების უმეტესობა უფასოა და ღია წყაროა. ჩვენ ვცდილობთ ჰარმონიული ბალანსის დაცვას [ღია წყაროების პრინციპებსა და მდგრადობას შორის](https://github.com/usebruno/bruno/discussions/269)
თქვენ შეგიძლიათ შეიძინოთ [ოქროს გამოცემა](https://www.usebruno.com/pricing) ერთჯერადი გადახდით **19 დოლარად**! <br/>
### ინსტალაცია
ბრუნო ხელმისაწვდომია როგორც ბინარული ჩამოტვირთვა [ჩვენ的网站上](https://www.usebruno.com/downloads) Mac-ის, Windows-ისა და Linux-ისთვის.
თქვენ ასევე შეგიძლიათ დააინსტალიროთ ბრუნო პაკეტის მენეჯერების საშუალებით, როგორიცაა Homebrew, Chocolatey, Scoop, Snap, Flatpak და Apt.
```sh
# Mac-ზე Homebrew-ს საშუალებით
brew install bruno
# Windows-ზე Chocolatey-ს საშუალებით
choco install bruno
# Windows-ზე Scoop-ის საშუალებით
scoop bucket add extras
scoop install bruno
# Windows-ზე winget-ის საშუალებით
winget install Bruno.Bruno
# Linux-ზე Snap-ის საშუალებით
snap install bruno
# Linux-ზე Flatpak-ის საშუალებით
flatpak install com.usebruno.Bruno
# Linux-ზე 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 [arch=amd64 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
```
### პლატფორმებს შორის მუშაობა 🖥️
![bruno](../../assets/images/run-anywhere.png) <br /><br />
### თანამშრომლობა Git-ის საშუალებით 👩‍💻🧑‍💻
ან ნებისმიერი ვერსიის კონტროლის სისტემის საშუალებით
![bruno](../../assets/images/version-control.png) <br /><br />
### სპონსორები
#### ოქროს სპონსორები
<img src="../../assets/images/sponsors/samagata.png" width="150"/>
#### ვერცხლის სპონსორები
<img src="../../assets/images/sponsors/commit-company.png" width="70"/>
#### ბრინჯის სპონსორები
<a href="https://zuplo.link/bruno">
<img src="../../assets/images/sponsors/zuplo.png" width="120"/>
</a>
### მნიშვნელოვანი ბმულები 📌
- [ჩვენი გრძელვადიანი ხედვა](https://github.com/usebruno/bruno/discussions/269)
- [გეგმა](https://github.com/usebruno/bruno/discussions/384)
- [დოკუმენტაცია](https://docs.usebruno.com)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno)
- [ვებსაიტი](https://www.usebruno.com)
- [ფასები](https://www.usebruno.com/pricing)
- [დამატება](https://www.usebruno.com/downloads)
- [GitHub სპონსორები](https://github.com/sponsors/helloanoop).
### ვიტრინა 🎥
- [მოწონებები](https://github.com/usebruno/bruno/discussions/343)
- [მეცნიერების ჰაბი](https://github.com/usebruno/bruno/discussions/386)
- [Scriptmania](https://github.com/usebruno/bruno/discussions/385)
### მხარდაჭერა ❤️
თუ გიყვართ ბრუნო და გინდათ მხარი დაუჭიროთ ჩვენს ღია წყაროების მუშაობას, გაითვალისწინეთ ჩვენი დახმარება [GitHub სპონსორების საშუალებით](https://github.com/sponsors/helloanoop).
### გააზიარეთ მოწმობები 📣
თუ ბრუნო დაგეხმარათ თქვენს სამუშაოში და გუნდებში, გთხოვთ, არ დაგავიწყდეთ ჩვენი [მოწონებების გაზიარება ჩვენს GitHub განხილვაში](https://github.com/usebruno/bruno/discussions/343)
### ახალი პაკეტის მენეჯერებში გამოქვეყნება
იხილეთ [აქ](../../publishing.md) მეტი ინფორმაციისათვის.
### დაინტერესდით 🌐
[𝕎 (Twitter)](https://twitter.com/use_bruno) <br />
[ვებსაიტი](https://www.usebruno.com) <br />
[Discord](https://discord.com/invite/KgcZUncpjq) <br />
[LinkedIn](https://www.linkedin.com/company/usebruno)
### სავაჭრო ნიშანი
**სახელი**
`ბრუნო` არის სავაჭრო ნიშანი, რომელსაც ფლობს [ანუპ მ. დ.](https://www.helloanoop.com/)
**ლოგო**
ლოგო არის [OpenMoji](https://openmoji.org/library/emoji-1F436/) სურათებიდან. ლიცენზია: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
### თანამშრომლობა 👩‍💻🧑‍💻
მიხარია, რომ დაინტერესებული ხართ ბრუნოს გაუმჯობესებით. გთხოვთ, გადახედეთ [თანამშრომლობის სახელმძღვანელოს](../../contributing.md)
თუნდაც ვერ მოახერხოთ კოდის საშუალებით კონტრიბუცია, ნუ ინანებთ პრობლემების და ფუნქციის მოთხოვნების ჩაწერას, რომლებიც უნდა განხორციელდეს თქვენი შემთხვევის გადასაჭრელად.
### ავტორები
<div align="center">
<a href="https://github.com/usebruno/bruno/graphs/contributors">
<img src="https://contrib.rocks/image?repo=usebruno/bruno" />
</a>
</div>
### ლიცენზია 📄
[MIT](../../license.md)

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno는 새롭고 혁신적인 API 클라이언트로, Postman과 유사한 툴들을 혁신하는 것을 목표로 합니다.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno to nowy i innowacyjny klient API, którego celem jest zrewolucjonizowanie status quo reprezentowanego przez narzędzia takie jak Postman.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno é um novo e inovador cliente de API, com o objetivo de revolucionar o status quo representado por ferramentas como o Postman e outras semelhantes.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno este un client API nou și inovativ, care vizează să revoluționeze status quo-ul reprezentat de Postman și alte instrumente similare.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir.

View File

@@ -27,7 +27,6 @@
| [正體中文](./readme_zhtw.md)
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.

View File

@@ -27,7 +27,6 @@
| **正體中文**
| [العربية](./readme_ar.md)
| [日本語](./readme_ja.md)
| [ქართული](./readme_ka.md)
Bruno 是一個全新且有創新性的 API 用戶端,目的在徹底改變以 Postman 和其他類似工具的現況。

8834
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,17 +20,14 @@
"@jest/globals": "^29.2.0",
"@playwright/test": "^1.27.1",
"@types/jest": "^29.5.11",
"concurrently": "^8.2.2",
"fs-extra": "^11.1.1",
"husky": "^8.0.3",
"jest": "^29.2.0",
"pretty-quick": "^3.1.3",
"randomstring": "^1.2.2",
"rimraf": "^6.0.1",
"ts-jest": "^29.0.5"
},
"scripts": {
"dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"",
"dev:web": "npm run dev --workspace=packages/bruno-app",
"build:web": "npm run build --workspace=packages/bruno-app",
"prettier:web": "npm run prettier --workspace=packages/bruno-app",
@@ -51,10 +48,13 @@
"prepare": "husky install"
},
"overrides": {
"rollup":"3.29.4"
"rollup": "3.2.5"
},
"dependencies": {
"isolated-vm": "^5.0.0",
"json-bigint": "^1.0.0",
"lossless-json": "^4.0.1"
"lossless-json": "^4.0.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0"
}
}

View File

@@ -12,7 +12,6 @@
"prettier": "prettier --write \"./src/**/*.{js,jsx,json,ts,tsx}\""
},
"dependencies": {
"@fontsource/inter": "^5.0.15",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16",
@@ -36,8 +35,7 @@
"graphiql": "^1.5.9",
"graphql": "^16.6.0",
"graphql-request": "^3.7.0",
"httpsnippet": "^3.0.6",
"i18next": "^23.14.0",
"httpsnippet": "^3.0.1",
"idb": "^7.0.0",
"immer": "^9.0.15",
"jsesc": "^3.0.2",
@@ -49,7 +47,6 @@
"know-your-http-well": "^0.5.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.2",
"markdown-it-replace-link": "^1.2.0",
"mousetrap": "^1.6.5",
"nanoid": "3.3.4",
"next": "12.3.3",
@@ -67,7 +64,6 @@
"react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-hot-toast": "^2.4.0",
"react-i18next": "^15.0.1",
"react-inspector": "^6.0.2",
"react-pdf": "^7.5.1",
"react-redux": "^7.2.6",

View File

@@ -14,24 +14,6 @@ const StyledWrapper = styled.div`
background: #d2d7db;
}
.CodeMirror-dialog {
overflow: visible;
}
#search-results-count {
display: inline-block;
position: absolute;
top: calc(100% + 1px);
right: 0;
border-width: 0 0 1px 1px;
border-style: solid;
border-color: ${(props) => props.theme.codemirror.border};
padding: 0.1em 0.8em;
background-color: ${(props) => props.theme.codemirror.bg};
color: rgb(102, 102, 102);
white-space: nowrap;
}
textarea.cm-editor {
position: relative;
}

View File

@@ -66,10 +66,7 @@ if (!SERVER_RENDERED) {
'bru.getVar(key)',
'bru.setVar(key,value)',
'bru.deleteVar(key)',
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()'
'bru.getRequestVar(key)',
'bru.sleep(ms)'
'bru.setNextRequest(requestName)'
];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor();
@@ -112,7 +109,6 @@ export default class CodeEditor extends React.Component {
// unnecessary updates during the update lifecycle.
this.cachedValue = props.value || '';
this.variables = {};
this.searchResultsCountElementId = 'search-results-count';
this.lintOptions = {
esversion: 11,
@@ -159,16 +155,8 @@ export default class CodeEditor extends React.Component {
this.props.onSave();
}
},
'Cmd-F': (cm) => {
cm.execCommand('findPersistent');
this._bindSearchHandler();
this._appendSearchResultsCount();
},
'Ctrl-F': (cm) => {
cm.execCommand('findPersistent');
this._bindSearchHandler();
this._appendSearchResultsCount();
},
'Cmd-F': 'findPersistent',
'Ctrl-F': 'findPersistent',
'Cmd-H': 'replace',
'Ctrl-H': 'replace',
Tab: function (cm) {
@@ -320,8 +308,6 @@ export default class CodeEditor extends React.Component {
this.editor.off('change', this._onEdit);
this.editor = null;
}
this._unbindSearchHandler();
}
render() {
@@ -358,62 +344,4 @@ export default class CodeEditor extends React.Component {
}
}
};
/**
* Bind handler to search input to count number of search results
*/
_bindSearchHandler = () => {
const searchInput = document.querySelector('.CodeMirror-search-field');
if (searchInput) {
searchInput.addEventListener('input', this._countSearchResults);
}
};
/**
* Unbind handler to search input to count number of search results
*/
_unbindSearchHandler = () => {
const searchInput = document.querySelector('.CodeMirror-search-field');
if (searchInput) {
searchInput.removeEventListener('input', this._countSearchResults);
}
};
/**
* Append search results count to search dialog
*/
_appendSearchResultsCount = () => {
const dialog = document.querySelector('.CodeMirror-dialog.CodeMirror-dialog-top');
if (dialog) {
const searchResultsCount = document.createElement('span');
searchResultsCount.id = this.searchResultsCountElementId;
dialog.appendChild(searchResultsCount);
this._countSearchResults();
}
};
/**
* Count search results and update state
*/
_countSearchResults = () => {
let count = 0;
const searchInput = document.querySelector('.CodeMirror-search-field');
if (searchInput && searchInput.value.length > 0) {
const text = new RegExp(searchInput.value, 'gi');
const matches = this.editor.getValue().match(text);
count = matches ? matches.length : 0;
}
const searchResultsCountElement = document.querySelector(`#${this.searchResultsCountElementId}`);
if (searchResultsCountElement) {
searchResultsCountElement.innerText = `${count} results`;
}
};
}

View File

@@ -48,7 +48,7 @@ const Docs = ({ collection }) => {
font={get(preferences, 'font.codeFont', 'default')}
/>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
<Markdown onDoubleClick={toggleViewMode} content={docs} />
)}
</StyledWrapper>
);

View File

@@ -53,7 +53,7 @@ const Documentation = ({ item, collection }) => {
mode="application/text"
/>
) : (
<Markdown collectionPath={collection.pathname} onDoubleClick={toggleViewMode} content={docs} />
<Markdown onDoubleClick={toggleViewMode} content={docs} />
)}
</StyledWrapper>
);

View File

@@ -40,15 +40,10 @@ const Wrapper = styled.div`
color: ${(props) => props.theme.dropdown.iconColor};
}
&:hover:not(:disabled) {
&:hover {
background-color: ${(props) => props.theme.dropdown.hoverBg};
}
&:disabled {
cursor: not-allowed;
color: gray;
}
&.border-top {
border-top: solid 1px ${(props) => props.theme.dropdown.separator};
}

View File

@@ -44,8 +44,8 @@ const Script = ({ collection, folder }) => {
<div className="text-xs mb-4 text-muted">
Pre and post-request scripts that will run before and after any request inside this folder is sent.
</div>
<div className="flex flex-col flex-1 mt-2 gap-y-2">
<div className="title text-xs">Pre Request</div>
<div className="flex-1 mt-2">
<div className="mb-1 title text-xs">Pre Request</div>
<CodeEditor
collection={collection}
value={requestScript || ''}
@@ -56,8 +56,8 @@ const Script = ({ collection, folder }) => {
font={get(preferences, 'font.codeFont', 'default')}
/>
</div>
<div className="flex flex-col flex-1 mt-2 gap-y-2">
<div className="title text-xs">Post Response</div>
<div className="flex-1 mt-6">
<div className="mt-1 mb-1 title text-xs">Post Response</div>
<CodeEditor
collection={collection}
value={responseScript || ''}

View File

@@ -19,8 +19,8 @@ const FolderSettings = ({ collection, folder }) => {
const setTab = (tab) => {
dispatch(
updatedFolderSettingsSelectedTab({
collectionUid: collection?.uid,
folderUid: folder?.uid,
collectionUid: collection.uid,
folderUid: folder.uid,
tab
})
);
@@ -50,7 +50,7 @@ const FolderSettings = ({ collection, folder }) => {
};
return (
<StyledWrapper className="flex flex-col h-full">
<StyledWrapper>
<div className="flex flex-col h-full relative px-4 py-4">
<div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>

View File

@@ -1,16 +0,0 @@
import React from 'react';
const DotIcon = ({ width }) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width}
viewBox="0 0 24 24" strokeWidth="1.5"
stroke="currentColor" fill="none" strokeLinecap="round" strokeLinejoin="round"
className='inline-block'
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 7a5 5 0 1 1 -4.995 5.217l-.005 -.217l.005 -.217a5 5 0 0 1 4.995 -4.783z" stroke-width="0" fill="currentColor" />
</svg>
);
};
export default DotIcon;

View File

@@ -69,7 +69,6 @@ const StyledMarkdownBodyWrapper = styled.div`
pre {
background: ${(props) => props.theme.sidebar.bg};
color: ${(props) => props.theme.text};
}
table {

View File

@@ -1,15 +1,10 @@
import MarkdownIt from 'markdown-it';
import * as MarkdownItReplaceLink from 'markdown-it-replace-link';
import StyledWrapper from './StyledWrapper';
import React from 'react';
const Markdown = ({ collectionPath, onDoubleClick, content }) => {
const markdownItOptions = {
replaceLink: function (link, env) {
return link.replace(/^\./, collectionPath);
}
};
const md = new MarkdownIt();
const Markdown = ({ onDoubleClick, content }) => {
const handleOnClick = (event) => {
const target = event.target;
if (target.tagName === 'A') {
@@ -28,8 +23,6 @@ const Markdown = ({ collectionPath, onDoubleClick, content }) => {
}
};
const md = new MarkdownIt(markdownItOptions).use(MarkdownItReplaceLink);
const htmlFromMarkdown = md.render(content || '');
return (

View File

@@ -17,7 +17,7 @@ const StyledWrapper = styled.div`
overflow: hidden !important;
${'' /* padding-bottom: 50px !important; */}
position: relative;
display: block;
display: contents;
margin: 0px;
padding: 0px;
}

View File

@@ -1,42 +1,39 @@
import React from 'react';
import { IconSpeakerphone, IconBrandTwitter, IconBrandGithub, IconBrandDiscord, IconBook } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
import { useTranslation } from 'react-i18next';
const Support = () => {
const { t } = useTranslation();
return (
<StyledWrapper>
<div className="rows">
<div className="mt-2">
<a href="https://docs.usebruno.com" target="_blank" className="flex items-end">
<IconBook size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.DOCUMENTATION')}</span>
<span className="label ml-2">Documentation</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="flex items-end">
<IconSpeakerphone size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.REPORT_ISSUES')}</span>
<span className="label ml-2">Report Issues</span>
</a>
</div>
<div className="mt-2">
<a href="https://discord.com/invite/KgcZUncpjq" target="_blank" className="flex items-end">
<IconBrandDiscord size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.DISCORD')}</span>
<span className="label ml-2">Discord</span>
</a>
</div>
<div className="mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-end">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.GITHUB')}</span>
<span className="label ml-2">GitHub</span>
</a>
</div>
<div className="mt-2">
<a href="https://twitter.com/use_bruno" target="_blank" className="flex items-end">
<IconBrandTwitter size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.TWITTER')}</span>
<span className="label ml-2">Twitter</span>
</a>
</div>
</div>

View File

@@ -1,4 +1,7 @@
import React from 'react';
import { useTheme } from 'providers/Theme/index';
import darkTheme from 'themes/dark';
import lightTheme from 'themes/light';
/**
* Assertion operators
@@ -78,10 +81,16 @@ const AssertionOperator = ({ operator, onChange }) => {
}
};
const { storedTheme } = useTheme();
return (
<select value={operator} onChange={handleChange} className="mousetrap">
{operators.map((operator) => (
<option key={operator} value={operator}>
<option
style={{ backgroundColor: storedTheme === 'dark' ? darkTheme.bg : lightTheme.bg }}
key={operator}
value={operator}
>
{getLabel(operator)}
</option>
))}

View File

@@ -55,9 +55,6 @@ const Wrapper = styled.div`
position: relative;
top: 1px;
}
option {
background-color: ${(props) => props.theme.bg};
}
`;
export default Wrapper;

View File

@@ -23,10 +23,6 @@ const StyledWrapper = styled.div`
color: ${(props) => props.theme.tabs.active.color} !important;
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
}
.content-indicator {
color: ${(props) => props.theme.text}
}
}
}
`;

View File

@@ -7,7 +7,7 @@ import RequestHeaders from 'components/RequestPane/RequestHeaders';
import RequestBody from 'components/RequestPane/RequestBody';
import RequestBodyMode from 'components/RequestPane/RequestBody/RequestBodyMode';
import Auth from 'components/RequestPane/Auth';
import DotIcon from 'components/Icons/Dot';
import AuthMode from 'components/RequestPane/Auth/AuthMode';
import Vars from 'components/RequestPane/Vars';
import Assertions from 'components/RequestPane/Assertions';
import Script from 'components/RequestPane/Script';
@@ -16,12 +16,6 @@ import StyledWrapper from './StyledWrapper';
import { find, get } from 'lodash';
import Documentation from 'components/Documentation/index';
const ContentIndicator = () => {
return <sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
};
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs);
@@ -88,17 +82,12 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const isMultipleContentTab = ['params', 'script', 'vars', 'auth', 'docs'].includes(focusedTab.requestPaneTab);
// get the length of active params, headers, asserts and vars as well as the contents of the body, tests and script
const getPropertyFromDraftOrRequest = (propertyKey) =>
item.draft ? get(item, `draft.${propertyKey}`, []) : get(item, propertyKey, []);
const params = getPropertyFromDraftOrRequest('request.params');
const body = getPropertyFromDraftOrRequest('request.body');
const headers = getPropertyFromDraftOrRequest('request.headers');
const script = getPropertyFromDraftOrRequest('request.script');
const assertions = getPropertyFromDraftOrRequest('request.assertions');
const tests = getPropertyFromDraftOrRequest('request.tests');
const requestVars = getPropertyFromDraftOrRequest('request.vars.req');
const responseVars = getPropertyFromDraftOrRequest('request.vars.res');
// get the length of active params, headers, asserts and vars
const params = item.draft ? get(item, 'draft.request.params', []) : get(item, 'request.params', []);
const headers = item.draft ? get(item, 'draft.request.headers', []) : get(item, 'request.headers', []);
const assertions = item.draft ? get(item, 'draft.request.assertions', []) : get(item, 'request.assertions', []);
const requestVars = item.draft ? get(item, 'draft.request.vars.req', []) : get(item, 'request.vars.req', []);
const responseVars = item.draft ? get(item, 'draft.request.vars.res', []) : get(item, 'request.vars.res', []);
const activeParamsLength = params.filter((param) => param.enabled).length;
const activeHeadersLength = headers.filter((header) => header.enabled).length;
@@ -116,11 +105,10 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
<div className={getTabClassname('body')} role="tab" onClick={() => selectTab('body')}>
Body
{body.mode !== 'none' && <ContentIndicator />}
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
{activeHeadersLength > 0 && <sup className="ml-[.125rem] font-medium">{activeHeadersLength}</sup>}
{activeHeadersLength > 0 && <sup className="ml-1 font-medium">{activeHeadersLength}</sup>}
</div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth
@@ -131,7 +119,6 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
<div className={getTabClassname('script')} role="tab" onClick={() => selectTab('script')}>
Script
{(script.req || script.res) && <ContentIndicator />}
</div>
<div className={getTabClassname('assert')} role="tab" onClick={() => selectTab('assert')}>
Assert
@@ -139,7 +126,6 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div>
<div className={getTabClassname('tests')} role="tab" onClick={() => selectTab('tests')}>
Tests
{tests && <ContentIndicator />}
</div>
<div className={getTabClassname('docs')} role="tab" onClick={() => selectTab('docs')}>
Docs

View File

@@ -1,7 +1,7 @@
import React from 'react';
import get from 'lodash/get';
import cloneDeep from 'lodash/cloneDeep';
import Tooltip from 'components/Tooltip';
import has from 'lodash/has';
import { IconTrash } from '@tabler/icons';
import { useDispatch } from 'react-redux';
import { useTheme } from 'providers/Theme';
@@ -103,7 +103,7 @@ const QueryParams = ({ item, collection }) => {
return (
<StyledWrapper className="w-full flex flex-col">
<div className="flex-1 mt-2">
<div className="mb-2 title text-xs">Query</div>
<div className="mb-1 title text-xs">Query</div>
<table>
<thead>
<tr>
@@ -173,22 +173,7 @@ const QueryParams = ({ item, collection }) => {
<button className="btn-add-param text-link pr-2 py-3 mt-2 select-none" onClick={handleAddQueryParam}>
+&nbsp;<span>Add Param</span>
</button>
<div className="mb-2 title text-xs flex items-stretch">
<span>Path</span>
<Tooltip
text={`
<div>
Path variables are automatically added whenever the
<code className="font-mono mx-2">:name</code>
template is used in the URL. <br/> For example:
<code className="font-mono mx-2">
https://example.com/v1/users/<span>:id</span>
</code>
</div>
`}
tooltipId="path-param-tooltip"
/>
</div>
<div className="mb-1 title text-xs">Path</div>
<table>
<thead>
<tr>
@@ -239,11 +224,6 @@ const QueryParams = ({ item, collection }) => {
: null}
</tbody>
</table>
{!(pathParams && pathParams.length) ?
<div className="title pr-2 py-3 mt-2 text-xs">
</div>
: null}
</div>
</StyledWrapper>
);

View File

@@ -74,7 +74,7 @@ const QueryUrl = ({ item, collection, handleRun }) => {
/>
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
<div
className="tooltip mx-3"
className="tooltip mr-3"
onClick={(e) => {
e.stopPropagation();
if (!item.draft) return;

View File

@@ -30,7 +30,7 @@ const RequestNotFound = ({ itemUid }) => {
return (
<div className="mt-6 px-6">
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700">
<div className="p-4 bg-orange-100 border-l-4 border-yellow-500 text-yellow-700 bg-yellow-100 p-4">
<div>Request no longer exists.</div>
<div className="mt-2">
This can happen when the .bru file associated with this request was deleted on your filesystem.

View File

@@ -18,7 +18,7 @@ import CollectionSettings from 'components/CollectionSettings';
import { DocExplorer } from '@usebruno/graphql-docs';
import StyledWrapper from './StyledWrapper';
import SecuritySettings from 'components/SecuritySettings';
import SecuritySettings from 'components/SecuritySettings/index';
import FolderSettings from 'components/FolderSettings';
const MIN_LEFT_PANE_WIDTH = 300;

View File

@@ -1,4 +1,4 @@
import React, { useState, useRef, Fragment } from 'react';
import React, { useState } from 'react';
import get from 'lodash/get';
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@@ -12,18 +12,12 @@ import ConfirmRequestClose from './ConfirmRequestClose';
import RequestTabNotFound from './RequestTabNotFound';
import SpecialTab from './SpecialTab';
import StyledWrapper from './StyledWrapper';
import Dropdown from 'components/Dropdown';
import CloneCollectionItem from 'components/Sidebar/Collections/Collection/CollectionItem/CloneCollectionItem/index';
import NewRequest from 'components/Sidebar/NewRequest/index';
const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUid }) => {
const RequestTab = ({ tab, collection, folderUid }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const [showConfirmClose, setShowConfirmClose] = useState(false);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const handleCloseClick = (event) => {
event.stopPropagation();
event.preventDefault();
@@ -34,19 +28,6 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
);
};
const handleRightClick = (_event) => {
const menuDropdown = dropdownTippyRef.current;
if (!menuDropdown) {
return;
}
if (menuDropdown.state.isShown) {
menuDropdown.hide();
} else {
menuDropdown.show();
}
};
const handleMouseUp = (e) => {
if (e.button === 1) {
e.stopPropagation();
@@ -62,9 +43,43 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
const getMethodColor = (method = '') => {
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
return theme.request.methods[method.toLocaleLowerCase()];
};
let color = '';
method = method.toLocaleLowerCase();
switch (method) {
case 'get': {
color = theme.request.methods.get;
break;
}
case 'post': {
color = theme.request.methods.post;
break;
}
case 'put': {
color = theme.request.methods.put;
break;
}
case 'delete': {
color = theme.request.methods.delete;
break;
}
case 'patch': {
color = theme.request.methods.patch;
break;
}
case 'options': {
color = theme.request.methods.options;
break;
}
case 'head': {
color = theme.request.methods.head;
break;
}
}
return color;
};
const folder = folderUid ? findItemInCollection(collection, folderUid) : null;
if (['collection-settings', 'folder-settings', 'variables', 'collection-runner', 'security-settings'].includes(tab.type)) {
return (
@@ -128,7 +143,6 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
)}
<div
className="flex items-baseline tab-label pl-2"
onContextMenu={handleRightClick}
onMouseUp={(e) => {
if (!item.draft) return handleMouseUp(e);
@@ -145,15 +159,6 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
<span className="ml-1 tab-name" title={item.name}>
{item.name}
</span>
<RequestTabMenu
onDropdownCreate={onDropdownCreate}
tabIndex={tabIndex}
collectionRequestTabs={collectionRequestTabs}
tabItem={item}
collection={collection}
dropdownTippyRef={dropdownTippyRef}
dispatch={dispatch}
/>
</div>
<div
className="flex px-2 close-icon-container"
@@ -190,124 +195,4 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
);
};
function RequestTabMenu({ onDropdownCreate, collectionRequestTabs, tabIndex, collection, dropdownTippyRef, dispatch }) {
const [showCloneRequestModal, setShowCloneRequestModal] = useState(false);
const [showAddNewRequestModal, setShowAddNewRequestModal] = useState(false);
const totalTabs = collectionRequestTabs.length || 0;
const currentTabUid = collectionRequestTabs[tabIndex]?.uid;
const currentTabItem = findItemInCollection(collection, currentTabUid);
const hasLeftTabs = tabIndex !== 0;
const hasRightTabs = totalTabs > tabIndex + 1;
const hasOtherTabs = totalTabs > 1;
async function handleCloseTab(event, tabUid) {
event.stopPropagation();
dropdownTippyRef.current.hide();
if (!tabUid) {
return;
}
try {
const item = findItemInCollection(collection, tabUid);
// silently save unsaved changes before closing the tab
if (item.draft) {
await dispatch(saveRequest(item.uid, collection.uid, true));
}
dispatch(closeTabs({ tabUids: [tabUid] }));
} catch (err) {}
}
function handleCloseOtherTabs(event) {
dropdownTippyRef.current.hide();
const otherTabs = collectionRequestTabs.filter((_, index) => index !== tabIndex);
otherTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
function handleCloseTabsToTheLeft(event) {
dropdownTippyRef.current.hide();
const leftTabs = collectionRequestTabs.filter((_, index) => index < tabIndex);
leftTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
function handleCloseTabsToTheRight(event) {
dropdownTippyRef.current.hide();
const rightTabs = collectionRequestTabs.filter((_, index) => index > tabIndex);
rightTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
function handleCloseSavedTabs(event) {
event.stopPropagation();
const savedTabs = collection.items.filter((item) => !item.draft);
const savedTabIds = savedTabs.map((item) => item.uid) || [];
dispatch(closeTabs({ tabUids: savedTabIds }));
}
function handleCloseAllTabs(event) {
collectionRequestTabs.forEach((tab) => handleCloseTab(event, tab.uid));
}
return (
<Fragment>
{showAddNewRequestModal && (
<NewRequest collection={collection} onClose={() => setShowAddNewRequestModal(false)} />
)}
{showCloneRequestModal && (
<CloneCollectionItem
item={currentTabItem}
collection={collection}
onClose={() => setShowCloneRequestModal(false)}
/>
)}
<Dropdown onCreate={onDropdownCreate} icon={<span></span>} placement="bottom-start">
<button
className="dropdown-item w-full"
onClick={() => {
dropdownTippyRef.current.hide();
setShowAddNewRequestModal(true);
}}
>
New Request
</button>
<button
className="dropdown-item w-full"
onClick={() => {
dropdownTippyRef.current.hide();
setShowCloneRequestModal(true);
}}
>
Clone Request
</button>
<button className="dropdown-item w-full" onClick={(e) => handleCloseTab(e, currentTabUid)}>
Close
</button>
<button disabled={!hasOtherTabs} className="dropdown-item w-full" onClick={handleCloseOtherTabs}>
Close Others
</button>
<button disabled={!hasLeftTabs} className="dropdown-item w-full" onClick={handleCloseTabsToTheLeft}>
Close to the Left
</button>
<button disabled={!hasRightTabs} className="dropdown-item w-full" onClick={handleCloseTabsToTheRight}>
Close to the Right
</button>
<button className="dropdown-item w-full" onClick={handleCloseSavedTabs}>
Close Saved
</button>
<button className="dropdown-item w-full" onClick={handleCloseAllTabs}>
Close All
</button>
</Dropdown>
</Fragment>
);
}
export default RequestTab;

View File

@@ -7,6 +7,7 @@ const Wrapper = styled.div`
padding: 0;
margin: 0;
display: flex;
position: relative;
overflow: scroll;
&::-webkit-scrollbar {

View File

@@ -110,14 +110,7 @@ const RequestTabs = () => {
role="tab"
onClick={() => handleClick(tab)}
>
<RequestTab
collectionRequestTabs={collectionRequestTabs}
tabIndex={index}
key={tab.uid}
tab={tab}
collection={activeCollection}
folderUid={tab.folderUid}
/>
<RequestTab key={tab.uid} tab={tab} collection={activeCollection} folderUid={tab.folderUid} />
</li>
);
})

View File

@@ -46,7 +46,7 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
return (
<div
className={
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2 pointer-events-none'
'response-filter absolute bottom-2 w-full justify-end right-0 flex flex-row items-center gap-2 py-4 px-2'
}
>
{tooltipText && !isExpanded && <ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />}
@@ -61,11 +61,11 @@ const QueryResultFilter = ({ filter, onChange, mode }) => {
autoCapitalize="off"
spellCheck="false"
className={`block ml-14 p-2 py-1 sm:text-sm transition-all duration-200 ease-in-out border border-gray-300 rounded-md ${
isExpanded ? 'w-full opacity-100 pointer-events-auto' : 'w-[0] opacity-0'
isExpanded ? 'w-full opacity-100' : 'w-[0] opacity-0'
}`}
onChange={onChange}
/>
<div className="text-gray-500 sm:text-sm cursor-pointer pointer-events-auto" id="request-filter-icon" onClick={handleFilterClick}>
<div className="text-gray-500 sm:text-sm cursor-pointer" id="request-filter-icon" onClick={handleFilterClick}>
{isExpanded ? <IconX size={20} strokeWidth={1.5} /> : <IconFilter size={20} strokeWidth={1.5} />}
</div>
</div>

View File

@@ -17,6 +17,8 @@ const Timeline = ({ request, response }) => {
});
});
let requestData = safeStringifyJSON(request.data);
return (
<StyledWrapper className="pb-4 w-full">
<div>
@@ -31,10 +33,9 @@ const Timeline = ({ request, response }) => {
);
})}
{request.data ? (
{requestData ? (
<pre className="line request">
<span className="arrow">{'>'}</span> data{' '}
<pre className="text-sm flex flex-wrap whitespace-break-spaces">{request.data}</pre>
<span className="arrow">{'>'}</span> data {requestData}
</pre>
) : null}
</div>

View File

@@ -97,8 +97,6 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
});
};
const responseHeadersCount = typeof response.headers === 'object' ? Object.entries(response.headers).length : 0;
return (
<StyledWrapper className="flex flex-col h-full relative">
<div className="flex flex-wrap items-center pl-3 pr-4 tabs" role="tablist">
@@ -107,7 +105,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
</div>
<div className={getTabClassname('headers')} role="tab" onClick={() => selectTab('headers')}>
Headers
{responseHeadersCount > 0 && <sup className="ml-1 font-medium">{responseHeadersCount}</sup>}
{response.headers?.length > 0 && <sup className="ml-1 font-medium">{response.headers.length}</sup>}
</div>
<div className={getTabClassname('timeline')} role="tab" onClick={() => selectTab('timeline')}>
Timeline

View File

@@ -6,7 +6,7 @@ import Portal from 'components/Portal';
import Modal from 'components/Modal';
import StyledWrapper from './StyledWrapper';
const JsSandboxModeModal = ({ collection }) => {
const JsSandboxModeModal = ({ collection, onClose }) => {
const dispatch = useDispatch();
const [jsSandboxMode, setJsSandboxMode] = useState(collection?.securityConfig?.jsSandboxMode || 'safe');
@@ -22,6 +22,7 @@ const JsSandboxModeModal = ({ collection }) => {
)
.then(() => {
toast.success('Sandbox mode updated successfully');
onClose();
})
.catch((err) => console.log(err) && toast.error('Failed to update sandbox mode'));
};
@@ -64,7 +65,7 @@ const JsSandboxModeModal = ({ collection }) => {
<span className='beta-tag'>BETA</span>
</label>
<p className='text-sm text-muted mt-1'>
JavaScript code is executed in a secure sandbox and cannot access your filesystem or execute system commands.
JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands.
</p>
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
@@ -86,7 +87,7 @@ const JsSandboxModeModal = ({ collection }) => {
JavaScript code has access to the filesystem, can execute system commands and access sensitive information.
</p>
<small className='text-muted mt-6'>
* SAFE mode has been introduced v1.26 onwards and is in beta. Please report any issues on github.
* SAFE mode has been introduced v1.25 onwards and is in beta. Please report any issues on github.
</small>
</div>
</StyledWrapper>

View File

@@ -50,7 +50,7 @@ const SecuritySettings = ({ collection }) => {
<span className='beta-tag'>BETA</span>
</label>
<p className='text-sm text-muted mt-1'>
JavaScript code is executed in a secure sandbox and cannot access your filesystem or execute system commands.
JavaScript code is executed in a secure sandbox and cannot excess your filesystem or execute system commands.
</p>
<label htmlFor="developer" className="flex flex-row gap-2 mt-6 cursor-pointer">
@@ -76,7 +76,7 @@ const SecuritySettings = ({ collection }) => {
Save
</button>
<small className='text-muted mt-6'>
* SAFE mode has been introduced v1.26 onwards and is in beta. Please report any issues on github.
* SAFE mode has been introduced v1.25 onwards and is in beta. Please report any issues on github.
</small>
</div>
</StyledWrapper>

View File

@@ -23,9 +23,7 @@ const RequestMethod = ({ item }) => {
return (
<StyledWrapper>
<div className={getClassname(item.request.method)}>
<span className="uppercase">
{item.request.method.length > 5 ? item.request.method.substring(0, 3) : item.request.method}
</span>
<span className="uppercase">{item.request.method}</span>
</div>
</StyledWrapper>
);

View File

@@ -189,28 +189,16 @@ const CollectionItem = ({ item, collection, searchText }) => {
toast.error('URL is required');
}
};
const viewFolderSettings = () => {
if (isItemAFolder(item)) {
if (itemIsOpenedInTabs(item, tabs)) {
dispatch(
focusTab({
uid: item.uid
})
);
return;
}
dispatch(
addTab({
uid: item.uid,
collectionUid: collection.uid,
type: 'folder-settings'
})
);
return;
}
dispatch(
addTab({
uid: uuid(),
collectionUid: collection.uid,
folderUid: item.uid,
type: 'folder-settings'
})
);
};
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));

View File

@@ -115,7 +115,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName, trans
collectionLocation: Yup.string()
.min(1, 'must be at least 1 character')
.max(500, 'must be 500 characters or less')
.required('Location is required')
.required('name is required')
}),
onSubmit: (values) => {
handleSubmit(values.collectionLocation);
@@ -124,9 +124,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName, trans
const browse = () => {
dispatch(browseDirectory())
.then((dirPath) => {
if (typeof dirPath === 'string' && dirPath.length > 0) {
formik.setFieldValue('collectionLocation', dirPath);
}
formik.setFieldValue('collectionLocation', dirPath);
})
.catch((error) => {
formik.setFieldValue('collectionLocation', '');

View File

@@ -109,8 +109,7 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
collectionUid: collection.uid,
itemUid: item ? item.uid : null,
headers: request.headers,
body: request.body,
auth: request.auth
body: request.body
})
)
.then(() => onClose())

View File

@@ -129,7 +129,7 @@ const Sidebar = () => {
Star
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.26.2</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.24.0</div>
</div>
</div>
</div>

View File

@@ -174,7 +174,7 @@ class SingleLineEditor extends Component {
render() {
return (
<div className="flex flex-row justify-between w-full overflow-x-auto">
<div className="flex flex-row justify-between w-full">
<StyledWrapper ref={this.editorRef} className="single-line-editor grow" />
{this.secretEye(this.props.isSecret)}
</div>

View File

@@ -13,6 +13,7 @@ const Tooltip = ({ text, tooltipId }) => {
fill="currentColor"
className="inline-block ml-2 cursor-pointer"
viewBox="0 0 16 16"
style={{ marginTop: 1 }}
>
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z" />

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { openCollection, importCollection } from 'providers/ReduxStore/slices/collections/actions';
import { IconBrandGithub, IconPlus, IconDownload, IconFolders, IconSpeakerphone, IconBook } from '@tabler/icons';
@@ -13,7 +12,6 @@ import StyledWrapper from './StyledWrapper';
const Welcome = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const [importedCollection, setImportedCollection] = useState(null);
const [importedTranslationLog, setImportedTranslationLog] = useState({});
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
@@ -22,7 +20,7 @@ const Welcome = () => {
const handleOpenCollection = () => {
dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error(t('WELCOME.COLLECTION_OPEN_ERROR'))
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
);
};
@@ -40,12 +38,12 @@ const Welcome = () => {
.then(() => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
toast.success(t('WELCOME.COLLECTION_IMPORT_SUCCESS'));
toast.success('Collection imported successfully');
})
.catch((err) => {
setImportCollectionLocationModalOpen(false);
console.error(err);
toast.error(t('WELCOME.COLLECTION_IMPORT_ERROR'));
toast.error('An error occurred while importing the collection. Check the logs for more information.');
});
};
@@ -68,45 +66,46 @@ const Welcome = () => {
<Bruno width={50} />
</div>
<div className="text-xl font-semibold select-none">bruno</div>
<div className="mt-4">{t('WELCOME.ABOUT_BRUNO')}</div>
<div className="mt-4">Opensource IDE for exploring and testing APIs</div>
<div className="uppercase font-semibold heading mt-10">{t('COMMON.COLLECTIONS')}</div>
<div className="uppercase font-semibold heading mt-10">Collections</div>
<div className="mt-4 flex items-center collection-options select-none">
<div className="flex items-center" onClick={() => setCreateCollectionModalOpen(true)}>
<IconPlus size={18} strokeWidth={2} />
<span className="label ml-2" id="create-collection">
{t('WELCOME.CREATE_COLLECTION')}
Create Collection
</span>
</div>
<div className="flex items-center ml-6" onClick={handleOpenCollection}>
<IconFolders size={18} strokeWidth={2} />
<span className="label ml-2">{t('WELCOME.OPEN_COLLECTION')}</span>
<span className="label ml-2">Open Collection</span>
</div>
<div className="flex items-center ml-6" onClick={() => setImportCollectionModalOpen(true)}>
<IconDownload size={18} strokeWidth={2} />
<span className="label ml-2" id="import-collection">
{t('WELCOME.IMPORT_COLLECTION')}
Import Collection
</span>
</div>
</div>
<div className="uppercase font-semibold heading mt-10 pt-6">{t('WELCOME.LINKS')}</div>
<div className="uppercase font-semibold heading mt-10 pt-6">Links</div>
<div className="mt-4 flex flex-col collection-options select-none">
<div className="flex items-center mt-2">
<a href="https://docs.usebruno.com" target="_blank" className="inline-flex items-center">
<IconBook size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.DOCUMENTATION')}</span>
<span className="label ml-2">Documentation</span>
</a>
</div>
<div className="flex items-center mt-2">
<a href="https://github.com/usebruno/bruno/issues" target="_blank" className="inline-flex items-center">
<IconSpeakerphone size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.REPORT_ISSUES')}</span>
<span className="label ml-2">Report Issues</span>
</a>
</div>
<div className="flex items-center mt-2">
<a href="https://github.com/usebruno/bruno" target="_blank" className="flex items-center">
<IconBrandGithub size={18} strokeWidth={2} />
<span className="label ml-2">{t('COMMON.GITHUB')}</span>
<span className="label ml-2">GitHub</span>
</a>
</div>
</div>

View File

@@ -1,24 +0,0 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translationEn from './translation/en.json';
const resources = {
en: {
translation: translationEn,
},
};
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: 'en', // Use "en" as the default language. "cimode" can be used to debug / show translation placeholder
ns: 'translation', // Use translation as the default Namespace that will be loaded by default
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;

View File

@@ -1,20 +0,0 @@
{
"COMMON": {
"COLLECTIONS": "Collections",
"DOCUMENTATION": "Documentation",
"REPORT_ISSUES": "Report Issues",
"GITHUB": "GitHub",
"DISCORD": "Discord",
"TWITTER": "Twitter"
},
"WELCOME": {
"ABOUT_BRUNO": "Opensource IDE for exploring and testing APIs",
"LINKS": "Links",
"CREATE_COLLECTION": "Create Collection",
"OPEN_COLLECTION": "Open Collection",
"IMPORT_COLLECTION": "Import Collection",
"COLLECTION_IMPORT_SUCCESS": "Collection imported successfully",
"COLLECTION_IMPORT_ERROR": "An error occurred while importing the collection. Check the logs for more information.",
"COLLECTION_OPEN_ERROR": "An error occurred while opening the collection"
}
}

View File

@@ -14,15 +14,6 @@ import 'codemirror/lib/codemirror.css';
import 'graphiql/graphiql.min.css';
import 'react-tooltip/dist/react-tooltip.css';
import '@usebruno/graphql-docs/dist/esm/index.css';
import '@fontsource/inter/100.css';
import '@fontsource/inter/200.css';
import '@fontsource/inter/300.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css';
import '@fontsource/inter/900.css';
function SafeHydrate({ children }) {
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;

View File

@@ -30,7 +30,12 @@ export default class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<body id="bruno-app-body">
<Main />
<NextScript />

View File

@@ -1,7 +1,6 @@
import Head from 'next/head';
import Bruno from './Bruno';
import GlobalStyle from '../globalStyles';
import '../i18n';
export default function Home() {
return (

View File

@@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start',
properties: {
os: platformLib.os.family,
version: '1.26.2'
version: '1.24.0'
}
});
};

View File

@@ -154,31 +154,6 @@ export const HotkeysProvider = (props) => {
};
}, [activeTabUid]);
// close all tabs
useEffect(() => {
Mousetrap.bind(['command+shift+w', 'ctrl+shift+w'], (e) => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if (activeTab) {
const collection = findCollectionByUid(collections, activeTab.collectionUid);
if (collection) {
const tabUids = tabs.filter((tab) => tab.collectionUid === collection.uid).map((tab) => tab.uid);
dispatch(
closeTabs({
tabUids: tabUids
})
);
}
}
return false; // this stops the event bubbling
});
return () => {
Mousetrap.unbind(['command+shift+w', 'ctrl+shift+w']);
};
}, [activeTabUid, tabs, collections, dispatch]);
return (
<HotkeysContext.Provider {...props} value="hotkey">
{showSaveRequestModal && (

View File

@@ -40,7 +40,7 @@ import {
import { each } from 'lodash';
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
import { resolveRequestFilename } from 'utils/common/platform';
import { parsePathParams, parseQueryParams, splitOnFirst } from 'utils/url/index';
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
import { name } from 'file-loader';
@@ -374,7 +374,6 @@ export const newFolder = (folderName, collectionUid, itemUid) => (dispatch, getS
});
};
// rename item
export const renameItem = (newName, itemUid, collectionUid) => (dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
@@ -700,7 +699,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di
};
export const newHttpRequest = (params) => (dispatch, getState) => {
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body, auth } = params;
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body } = params;
return new Promise((resolve, reject) => {
const state = getState();
@@ -710,20 +709,11 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
}
const parts = splitOnFirst(requestUrl, '?');
const queryParams = parseQueryParams(parts[1]);
each(queryParams, (urlParam) => {
const params = parseQueryParams(parts[1]);
each(params, (urlParam) => {
urlParam.enabled = true;
urlParam.type = 'query';
});
const pathParams = parsePathParams(requestUrl);
each(pathParams, (pathParm) => {
pathParams.enabled = true;
pathParm.type = 'path';
});
const params = [...queryParams, ...pathParams];
const item = {
uid: uuid(),
type: requestType,
@@ -741,9 +731,6 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
sparql: null,
multipartForm: null,
formUrlEncoded: null
},
auth: auth ?? {
mode: 'none'
}
}
};

View File

@@ -41,7 +41,7 @@ export const tabsSlice = createSlice({
requestPaneTab: action.payload.requestPaneTab || 'params',
responsePaneTab: 'response',
type: action.payload.type || 'request',
...(action.payload.uid ? { folderUid: action.payload.uid } : {})
...(action.payload.folderUid ? { folderUid: action.payload.folderUid } : {})
});
state.activeTabUid = action.payload.uid;
},

View File

@@ -50,11 +50,7 @@ const createPostData = (body) => {
mimeType: contentType,
params: body[body.mode]
.filter((param) => param.enabled)
.map((param) => ({
name: param.name,
value: param.value,
...(param.type === 'file' && { fileName: param.value })
}))
.map((param) => ({ name: param.name, value: param.value }))
};
} else {
return {

View File

@@ -3,7 +3,7 @@ import filter from 'lodash/filter';
import find from 'lodash/find';
export const doesRequestMatchSearchText = (request, searchText = '') => {
return request?.name?.toLowerCase().includes(searchText.toLowerCase());
return request.name.toLowerCase().includes(searchText.toLowerCase());
};
export const doesFolderHaveItemsMatchSearchText = (item, searchText = '') => {

View File

@@ -123,7 +123,7 @@ const curlToJson = (curlCommand) => {
request.urlWithoutQuery = 'http://' + request.urlWithoutQuery;
}
requestJson.url = request.urlWithoutQuery;
requestJson.url = request.urlWithoutQuery.replace(/\/$/, '');
requestJson.raw_url = request.url;
requestJson.method = request.method;
@@ -160,15 +160,14 @@ const curlToJson = (curlCommand) => {
}
if (request.auth) {
if(request.auth.mode === 'basic'){
requestJson.auth = {
mode: 'basic',
basic: {
username: repr(request.auth.basic?.username),
password: repr(request.auth.basic?.password)
}
}
}
const splitAuth = request.auth.split(':');
const user = splitAuth[0] || '';
const password = splitAuth[1] || '';
requestJson.auth = {
user: repr(user),
password: repr(password)
};
}
return Object.keys(requestJson).length ? requestJson : {};

View File

@@ -75,15 +75,4 @@ describe('curlToJson', () => {
}
});
});
it('should return and parse a simple curl command with a trailing slash', () => {
const curlCommand = 'curl https://www.usebruno.com/';
const result = curlToJson(curlCommand);
expect(result).toEqual({
url: 'https://www.usebruno.com/',
raw_url: 'https://www.usebruno.com/',
method: 'get'
});
});
});

View File

@@ -56,8 +56,7 @@ export const getRequestFromCurlCommand = (curlCommand) => {
url: request.url,
method: request.method,
body,
headers: headers,
auth: request.auth
headers: headers
};
} catch (error) {
console.error(error);

View File

@@ -36,8 +36,7 @@ const parseCurlCommand = (curlCommand) => {
boolean: ['I', 'head', 'compressed', 'L', 'k', 'silent', 's', 'G', 'get'],
alias: {
H: 'header',
A: 'user-agent',
u: 'user'
A: 'user-agent'
}
});
@@ -188,21 +187,10 @@ const parseCurlCommand = (curlCommand) => {
}
urlObject.search = null; // Clean out the search/query portion.
let urlWithoutQuery = URL.format(urlObject);
let urlHost = urlObject?.host;
if (!url?.includes(`${urlHost}/`)) {
if (urlWithoutQuery && urlHost) {
const [beforeHost, afterHost] = urlWithoutQuery.split(urlHost);
urlWithoutQuery = beforeHost + urlHost + afterHost?.slice(1);
}
}
const request = {
url,
urlWithoutQuery
url: url,
urlWithoutQuery: URL.format(urlObject)
};
if (compressed) {
request.compressed = true;
}
@@ -238,19 +226,12 @@ const parseCurlCommand = (curlCommand) => {
request.data = parsedArguments['data-urlencode'];
}
if (parsedArguments.user && typeof parsedArguments.user === 'string') {
const basicAuth = parsedArguments.user.split(':')
const username = basicAuth[0] || ''
const password = basicAuth[1] || ''
request.auth = {
mode: 'basic',
basic: {
username,
password
}
}
if (parsedArguments.u) {
request.auth = parsedArguments.u;
}
if (parsedArguments.user) {
request.auth = parsedArguments.user;
}
if (Array.isArray(request.data)) {
request.dataArray = request.data;
request.data = request.data.join('&');

View File

@@ -12,7 +12,6 @@ export const exportCollection = (collection) => {
const generateInfoSection = () => {
return {
name: collection.name,
description: collection.root?.docs,
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
};
};
@@ -138,11 +137,6 @@ export const exportCollection = (collection) => {
}
}
};
case 'graphql':
return {
mode: 'graphql',
graphql: body.graphql
};
}
};
@@ -207,8 +201,6 @@ export const exportCollection = (collection) => {
const requestObject = {
method: itemRequest.method,
header: generateHeaders(itemRequest.headers),
auth: generateAuth(itemRequest.auth),
description: itemRequest.docs,
url: {
raw: itemRequest.url,
host: generateHost(itemRequest.url),

View File

@@ -59,15 +59,12 @@ const transformOpenapiRequestItem = (request) => {
operationName = `${request.method} ${request.path}`;
}
// replace OpenAPI links in path by Bruno variables
let path = request.path.replace(/{([a-zA-Z]+)}/g, `{{${_operationObject.operationId}_$1}}`);
const brunoRequestItem = {
uid: uuid(),
name: operationName,
type: 'http-request',
request: {
url: ensureUrl(request.global.server + '/' + path),
url: ensureUrl(request.global.server + '/' + request.path),
method: request.method.toUpperCase(),
auth: {
mode: 'none',
@@ -84,9 +81,6 @@ const transformOpenapiRequestItem = (request) => {
xml: null,
formUrlEncoded: [],
multipartForm: []
},
script: {
res: null
}
}
};
@@ -201,26 +195,6 @@ const transformOpenapiRequestItem = (request) => {
}
}
// build the extraction scripts from responses that have links
// https://swagger.io/docs/specification/links/
let script = [];
each(_operationObject.responses || [], (response, responseStatus) => {
if (Object.hasOwn(response, 'links')) {
// only extract if the status code matches the response
script.push(`if (res.status === ${responseStatus}) {`);
each(response.links, (link) => {
each(link.parameters || [], (expression, parameter) => {
let value = openAPIRuntimeExpressionToScript(expression);
script.push(` bru.setVar('${link.operationId}_${parameter}', ${value});`);
});
});
script.push(`}`);
}
});
if (script.length > 0) {
brunoRequestItem.request.script.res = script.join('\n');
}
return brunoRequestItem;
};
@@ -331,18 +305,6 @@ const getSecurity = (apiSpec) => {
};
};
const openAPIRuntimeExpressionToScript = (expression) => {
// see https://swagger.io/docs/specification/links/#runtime-expressions
if (expression === '$response.body') {
return 'res.body';
} else if (expression.startsWith('$response.body#')) {
let pointer = expression.substring(15);
// could use https://www.npmjs.com/package/json-pointer for better support
return `res.body${pointer.replace('/', '.')}`;
}
return expression;
};
const parseOpenApiCollection = (data) => {
const brunoCollection = {
name: '',
@@ -386,7 +348,7 @@ const parseOpenApiCollection = (data) => {
.map(([method, operationObject]) => {
return {
method: method,
path: path.replace(/{([^}]+)}/g, ':$1'), // Replace placeholders enclosed in curly braces with colons
path: path,
operationObject: operationObject,
global: {
server: baseUrl,

View File

@@ -113,8 +113,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
xml: null,
formUrlEncoded: [],
multipartForm: []
},
docs: i.request.description
}
}
};
/* struct of translation log

View File

@@ -44,8 +44,7 @@ export const parsePathParams = (url) => {
try {
uri = new URL(uri);
} catch (e) {
// URL is non-parsable, is it incomplete? Ignore.
return [];
throw e;
}
let paths = uri.pathname.split('/');

View File

@@ -14,7 +14,7 @@
"url": "git+https://github.com/usebruno/bruno.git"
},
"scripts": {
"test": "node --experimental-vm-modules $(npx which jest)"
"test": "jest"
},
"files": [
"src",

View File

@@ -190,10 +190,6 @@ const getFolderRoot = (dir) => {
return collectionBruToJson(content);
};
const getJsSandboxRuntime = (sandbox) => {
return sandbox === 'safe' ? 'quickjs' : 'vm2';
};
const builder = async (yargs) => {
yargs
.option('r', {
@@ -219,11 +215,6 @@ const builder = async (yargs) => {
describe: 'Overwrite a single environment variable, multiple usages possible',
type: 'string'
})
.option('sandbox', {
describe: 'Javscript sandbox to use; available sandboxes are "developer" (default) or "safe"',
default: 'developer',
type: 'string'
})
.option('output', {
alias: 'o',
describe: 'Path to write file results to',
@@ -241,7 +232,7 @@ const builder = async (yargs) => {
})
.option('tests-only', {
type: 'boolean',
description: 'Only run requests that have a test or active assertion'
description: 'Only run requests that have a test'
})
.option('bail', {
type: 'boolean',
@@ -291,7 +282,6 @@ const handler = async function (argv) {
r: recursive,
output: outputPath,
format,
sandbox,
testsOnly,
bail
} = argv;
@@ -461,7 +451,6 @@ const handler = async function (argv) {
}
}
const runtime = getJsSandboxRuntime(sandbox);
let currentRequestIndex = 0;
let nJumps = 0; // count the number of jumps to avoid infinite loops
while (currentRequestIndex < bruJsons.length) {
@@ -477,8 +466,7 @@ const handler = async function (argv) {
envVars,
processEnvVars,
brunoConfig,
collectionRoot,
runtime
collectionRoot
);
results.push({

View File

@@ -21,10 +21,6 @@ const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'
const path = require('path');
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const onConsoleLog = (type, args) => {
console[type](...args);
};
const runSingleRequest = async function (
filename,
bruJson,
@@ -33,8 +29,7 @@ const runSingleRequest = async function (
envVariables,
processEnvVars,
brunoConfig,
collectionRoot,
runtime
collectionRoot
) {
try {
let request;
@@ -43,7 +38,11 @@ const runSingleRequest = async function (
request = prepareRequest(bruJson.request, collectionRoot);
const scriptingConfig = get(brunoConfig, 'scripts', {});
scriptingConfig.runtime = runtime;
// todo: allow to override from cli args
// we will default to vm2 (developer-mode) for 1.x version for backward compatibility
// 2.x will default to isolated-vm (safe mode)
scriptingConfig.runtime = 'isolated-vm';
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
@@ -87,7 +86,7 @@ const runSingleRequest = async function (
envVariables,
runtimeVariables,
collectionPath,
onConsoleLog,
null,
processEnvVars,
scriptingConfig
);

View File

@@ -22,13 +22,13 @@
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^9.0.2",
"rollup":"3.29.4",
"rollup": "3.2.5",
"rollup-plugin-dts": "^5.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.8.4"
},
"overrides": {
"rollup":"3.29.4"
"rollup": "3.2.5"
}
}

View File

@@ -294,7 +294,7 @@ describe('interpolate - recursive', () => {
const result = interpolate(inputString, inputObject);
expect(result).toBe('{{recursion3}}');
expect(result).toBe('{{recursion2}}');
});
it('should replace repetead placeholders with 1 level of recursion with values from the object', () => {
@@ -335,22 +335,4 @@ describe('interpolate - recursive', () => {
expect(result).toBe(new Array(24).fill('repetead4').join(' '));
});
it('should replace mutiple interdependent variables in the same input string', () => {
const inputString = `{
"x": "{{v2}} {{v1}}"
}`;
const inputObject = {
foo: 'bar',
v1: '{{foo}}',
v2: '{{bar}}',
bar: 'baz'
};
const result = interpolate(inputString, inputObject);
expect(result).toBe(`{
"x": "baz bar"
}`);
});
});

View File

@@ -27,41 +27,32 @@ const interpolate = (str: string, obj: Record<string, any>): string => {
const replace = (
str: string,
flattenedObj: Record<string, any>,
visited = new Set<string>(),
visited = new Set<String>(),
results = new Map<string, string>()
): string => {
let resultStr = str;
let matchFound = true;
const patternRegex = /\{\{([^}]+)\}\}/g;
while (matchFound) {
const patternRegex = /\{\{([^}]+)\}\}/g;
matchFound = false;
resultStr = resultStr.replace(patternRegex, (match, placeholder) => {
const replacement = flattenedObj[placeholder];
return str.replace(patternRegex, (match, placeholder) => {
const replacement = flattenedObj[placeholder];
if (results.has(match)) {
return results.get(match);
}
if (patternRegex.test(replacement) && !visited.has(match)) {
visited.add(match);
const result = replace(replacement, flattenedObj, visited, results);
results.set(match, result);
matchFound = true;
return result;
}
if (results.has(match)) {
return results.get(match);
}
if (patternRegex.test(replacement) && !visited.has(match)) {
visited.add(match);
const result = replacement !== undefined ? replacement : match;
const result = replace(replacement, flattenedObj, visited, results);
results.set(match, result);
matchFound = true;
return result;
});
}
}
return resultStr;
visited.add(match);
const result = replacement !== undefined ? replacement : match;
results.set(match, result);
return result;
});
};
export default interpolate;

View File

@@ -1,5 +1,5 @@
{
"version": "v1.26.2",
"version": "v1.24.0",
"name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com",
@@ -8,7 +8,7 @@
"author": "Anoop M D <anoop.md1421@gmail.com> (https://helloanoop.com/)",
"scripts": {
"clean": "rimraf dist",
"dev": "electron .",
"dev": "electron --no-node-snapshot .",
"dist:mac": "electron-builder --mac --config electron-builder-config.js",
"dist:win": "electron-builder --win --config electron-builder-config.js",
"dist:linux": "electron-builder --linux AppImage --config electron-builder-config.js",
@@ -16,7 +16,7 @@
"dist:rpm": "electron-builder --linux rpm --config electron-builder-config.js",
"dist:snap": "electron-builder --linux snap --config electron-builder-config.js",
"pack": "electron-builder --dir",
"test": "node --experimental-vm-modules $(npx which jest)"
"test": "jest"
},
"jest": {
"modulePaths": ["node_modules"]
@@ -64,7 +64,8 @@
"dmg-license": "^1.0.11"
},
"devDependencies": {
"electron": "31.2.1",
"electron-builder": "23.0.2"
"electron": "23.3.3",
"electron-builder": "23.0.2",
"electron-icon-maker": "^0.0.5"
}
}

Some files were not shown because too many files have changed in this diff Show More