mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 01:48:33 +00:00
Compare commits
14 Commits
fix/json-s
...
feat/safe-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a18e5e5b94 | ||
|
|
bde2c57a23 | ||
|
|
9955b8796e | ||
|
|
3b3fa8a856 | ||
|
|
c687856a9e | ||
|
|
033bba1805 | ||
|
|
4fcb6f0980 | ||
|
|
e4d2e5c1cf | ||
|
|
969fe8e3cf | ||
|
|
6d01c46d50 | ||
|
|
751c7aa16d | ||
|
|
b2baa1e48d | ||
|
|
f834eb4425 | ||
|
|
26f8dd7a7b |
14
.github/workflows/tests.yml
vendored
14
.github/workflows/tests.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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_)。
|
||||
|
||||
## 开发
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 वर्कस्पेस का उपयोग करते हैं
|
||||
|
||||
## डेवलपमेंट
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ワークスペースを使用しています。
|
||||
|
||||
## 開発
|
||||
|
||||
|
||||
@@ -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를 사용합니다.
|
||||
|
||||
## 개발
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
### Приступим к коду
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 в цьому проекті
|
||||
|
||||
### Починаєм писати код
|
||||
|
||||
|
||||
@@ -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_)。
|
||||
|
||||
## 開發
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| **العربية**
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
برونو هو عميل API جديد ومبتكر، يهدف إلى ثورة الحالة الحالية التي يمثلها برنامج Postman وأدوات مماثلة هناك.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
ব্রুনো হল একটি নতুন এবং উদ্ভাবনী API ক্লায়েন্ট, যার লক্ষ্য পোস্টম্যান এবং অনুরূপ সরঞ্জাম দ্বারা প্রতিনিধিত্ব করা স্থিতাবস্থায় বিপ্লব ঘটানো।
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno 是一款全新且创新的 API 客户端,旨在颠覆 Postman 和其他类似工具。
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| **日本語**
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno は革新的な API クライアントです。Postman を代表する API クライアントツールの現状に一石を投じることを目指しています。
|
||||
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
<br />
|
||||
<img src="../../assets/images/logo-transparent.png" width="80"/>
|
||||
|
||||
### ბრუნო - ღია წყაროების IDE API-ების შესწავლისა და ტესტირებისათვის.
|
||||
|
||||
[](https://badge.fury.io/gh/usebruno%bruno)
|
||||
[](https://github.com/usebruno/bruno/actions/workflows/tests.yml)
|
||||
[](https://github.com/usebruno/bruno/pulse)
|
||||
[](https://twitter.com/use_bruno)
|
||||
[](https://www.usebruno.com)
|
||||
[](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)
|
||||
|
||||
 <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
|
||||
```
|
||||
|
||||
### პლატფორმებს შორის მუშაობა 🖥️
|
||||
|
||||
 <br /><br />
|
||||
|
||||
### თანამშრომლობა Git-ის საშუალებით 👩💻🧑💻
|
||||
|
||||
ან ნებისმიერი ვერსიის კონტროლის სისტემის საშუალებით
|
||||
|
||||
 <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)
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno는 새롭고 혁신적인 API 클라이언트로, Postman과 유사한 툴들을 혁신하는 것을 목표로 합니다.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| [正體中文](./readme_zhtw.md)
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman.
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
| **正體中文**
|
||||
| [العربية](./readme_ar.md)
|
||||
| [日本語](./readme_ja.md)
|
||||
| [ქართული](./readme_ka.md)
|
||||
|
||||
Bruno 是一個全新且有創新性的 API 用戶端,目的在徹底改變以 Postman 和其他類似工具的現況。
|
||||
|
||||
|
||||
8834
package-lock.json
generated
8834
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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 || ''}
|
||||
|
||||
@@ -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')}>
|
||||
|
||||
@@ -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;
|
||||
@@ -69,7 +69,6 @@ const StyledMarkdownBodyWrapper = styled.div`
|
||||
|
||||
pre {
|
||||
background: ${(props) => props.theme.sidebar.bg};
|
||||
color: ${(props) => props.theme.text};
|
||||
}
|
||||
|
||||
table {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -55,9 +55,6 @@ const Wrapper = styled.div`
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
option {
|
||||
background-color: ${(props) => props.theme.bg};
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
+ <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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,6 +7,7 @@ const Wrapper = styled.div`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: scroll;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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', '');
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Head from 'next/head';
|
||||
import Bruno from './Bruno';
|
||||
import GlobalStyle from '../globalStyles';
|
||||
import '../i18n';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
@@ -60,7 +60,7 @@ const trackStart = () => {
|
||||
event: 'start',
|
||||
properties: {
|
||||
os: platformLib.os.family,
|
||||
version: '1.26.2'
|
||||
version: '1.24.0'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = '') => {
|
||||
|
||||
@@ -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 : {};
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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('&');
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -113,8 +113,7 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
|
||||
xml: null,
|
||||
formUrlEncoded: [],
|
||||
multipartForm: []
|
||||
},
|
||||
docs: i.request.description
|
||||
}
|
||||
}
|
||||
};
|
||||
/* struct of translation log
|
||||
|
||||
@@ -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('/');
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user