diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 785378f9..b18db1b8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,3 +11,7 @@ - [ ] **Create an issue and link to the pull request.** Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests. + +### Publishing to New Package Managers + +Please see [here](../publishing.md) for more information. diff --git a/.github/workflows/release-snap.yml b/.github/workflows/release-snap.yml index b71e8848..16a8d2e8 100644 --- a/.github/workflows/release-snap.yml +++ b/.github/workflows/release-snap.yml @@ -30,6 +30,7 @@ jobs: run: | npm run build:bruno-query npm run build:graphql-docs + npm run build:web npm run build:electron:snap - name: Install Snapcraft diff --git a/contributing.md b/contributing.md index 966a6134..9e918a44 100644 --- a/contributing.md +++ b/contributing.md @@ -1,12 +1,12 @@ -**English** | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) +**English** | [Українська](docs/contributing/contributing_ua.md) | [Русский](docs/contributing/contributing_ru.md) | [Türkçe](docs/contributing/contributing_tr.md) | [Deutsch](docs/contributing/contributing_de.md) | [Français](docs/contributing/contributing_fr.md) | [Português (BR)](docs/contributing/contributing_pt_br.md) | [বাংলা](docs/contributing/contributing_bn.md) | [Español](docs/contributing/contributing_es.md) -## Lets make bruno better, together !! +## Let's make bruno better, together !! -I am happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer. +We are happy that you are looking to improve bruno. Below are the guidelines to get started bringing up bruno on your computer. ### Technology Stack -Bruno is built using NextJs and React. We also use electron to ship a desktop version (that supports local collections) +Bruno is built using Next.js and React. We also use electron to ship a desktop version (that supports local collections) Libraries we use @@ -23,9 +23,59 @@ Libraries we use 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 -### Lets start coding +## Development -Please reference [development.md](docs/development.md) for instructions on running the local development environment. +Bruno is being developed as a desktop app. You need to load the app by running the Next.js app in one terminal and then run the electron app in another terminal. + +### Dependencies + +- NodeJS v18 + +### Local Development + +```bash +# use nodejs 18 version +nvm use + +# install deps +npm i --legacy-peer-deps + +# build graphql docs +npm run build:graphql-docs + +# build bruno query +npm run build:bruno-query + +# run next app (terminal 1) +npm run dev:web + +# run electron app (terminal 2) +npm run dev:electron +``` + +### Troubleshooting + +You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app. + +```shell +# Delete node_modules in sub-directories +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Delete package-lock in sub-directories +find . -type f -name "package-lock.json" -delete +``` + +### Testing + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` ### Raising Pull Request @@ -33,5 +83,5 @@ Please reference [development.md](docs/development.md) for instructions on runni - Please follow the format of creating branches - feature/[feature name]: This branch should contain changes for a specific feature - Example: feature/dark-mode - - bugfix/[bug name]: This branch should container only bug fixes for a specific bug + - bugfix/[bug name]: This branch should contain only bug fixes for a specific bug - Example bugfix/bug-1 diff --git a/contributing_ru.md b/contributing_ru.md deleted file mode 100644 index 6636004b..00000000 --- a/contributing_ru.md +++ /dev/null @@ -1,37 +0,0 @@ -[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** - -## Давайте вместе сделаем Бруно лучше!!! - -Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере. - -### Стек - -Bruno построен с использованием NextJs и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции ) - -Библиотеки, которые мы используем - -- CSS - Tailwind -- Редакторы кода - Codemirror -- Управление состоянием - Redux -- Иконки - Tabler Icons -- Формы - formik -- Валидация схем - Yup -- Запросы клиента - axios -- Наблюдатель за файловой системой - chokidar - -### Зависимости - -Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm - -### Приступим к коду - -Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки. - -### Создание Pull Request - -- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи -- Пожалуйста, соблюдайте формат создания веток - - feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции - - Пример: feature/dark-mode - - bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки - - Пример bugfix/bug-1 diff --git a/contributing_ua.md b/contributing_ua.md deleted file mode 100644 index 75760f56..00000000 --- a/contributing_ua.md +++ /dev/null @@ -1,37 +0,0 @@ -[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) - -## Давайте зробимо Bruno краще, разом !! - -Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері. - -### Стек технологій - -Bruno побудований на NextJs та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron - -Бібліотеки, які ми використовуємо - -- CSS - Tailwind -- Редактори коду - Codemirror -- Керування станом - Redux -- Іконки - Tabler Icons -- Форми - formik -- Валідація по схемі - Yup -- Клієнт запитів - axios -- Спостерігач за файловою системою - chokidar - -### Залежності - -Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті - -### Починаєм писати код - -Будь ласка, зверніться до [development_ua.md](docs/development_ua.md) за інструкціями щодо запуску локального середовища розробки. - -### Створення Pull Request-ів - -- Будь ласка, робіть PR-и маленькими і сфокусованими на одній речі -- Будь ласка, слідуйте формату назв гілок - - feature/[назва feature]: Така гілка має містити зміни лише щодо конкретної feature - - Приклад: feature/dark-mode - - bugfix/[назва баґу]: Така гілка має містити лише виправлення конкретного багу - - Приклад: bugfix/bug-1 diff --git a/docs/contributing/contributing_bn.md b/docs/contributing/contributing_bn.md new file mode 100644 index 00000000..e30ecc33 --- /dev/null +++ b/docs/contributing/contributing_bn.md @@ -0,0 +1,87 @@ +[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](docs/contributing/contributing_de.md) | [Français](/contributing_fr.md) | **বাংলা** + +## আসুন ব্রুনোকে আরও ভালো করি, একসাথে!! + +আমরা খুশি যে আপনি ব্রুনোর উন্নতি করতে চাইছেন। নীচে আপনার কম্পিউটারে ব্রুনো ইনষ্টল করার নির্দেশিকা রয়েছে৷। + +### Technology Stack (প্রযুক্তি স্ট্যাক) + +ব্রুনো Next.js এবং React ব্যবহার করে নির্মিত। এছাড়াও আমরা একটি ডেস্কটপ সংস্করণ পাঠাতে ইলেক্ট্রন ব্যবহার করি (যা স্থানীয় সংগ্রহ সমর্থন করে) + +নিম্ন লিখিত লাইব্রেরি আমরা ব্যবহার করি - + +- CSS - Tailwind +- Code Editors - Codemirror +- State Management - Redux +- Icons - Tabler Icons +- Forms - formik +- Schema Validation - Yup +- Request Client - axios +- Filesystem Watcher - chokidar + +### Dependencies (নির্ভরতা) + +আপনার প্রয়োজন হবে [নোড v18.x বা সর্বশেষ LTS সংস্করণ](https://nodejs.org/en/) এবং npm 8.x। আমরা প্রকল্পে npm ওয়ার্কস্পেস ব্যবহার করি । + +## Development + +ব্রুনো একটি ডেস্কটপ অ্যাপ হিসেবে তৈরি করা হচ্ছে। আপনাকে একটি টার্মিনালে Next.js অ্যাপটি চালিয়ে অ্যাপটি লোড করতে হবে এবং তারপরে অন্য টার্মিনালে ইলেক্ট্রন অ্যাপটি চালাতে হবে। + +### Dependencies (নির্ভরতা) + +- NodeJS v18 + +### Local Development + +```bash +# nodejs 18 সংস্করণ ব্যবহার করুন +nvm use + +# নির্ভরতা ইনস্টল করুন +npm i --legacy-peer-deps + +# গ্রাফকিউএল ডক্স তৈরি করুন +npm run build:graphql-docs + +# ব্রুনো কোয়েরি তৈরি করুন +npm run build:bruno-query + +# NextJs অ্যাপ চালান (টার্মিনাল 1) +npm run dev:web + +# ইলেক্ট্রন অ্যাপ চালান (টার্মিনাল 2) +npm run dev:electron +``` + +### Troubleshooting (সমস্যা সমাধান) + +আপনি যখন 'npm install' চালান তখন আপনি একটি 'অসমর্থিত প্ল্যাটফর্ম' ত্রুটির সম্মুখীন হতে পারেন৷ এটি ঠিক করতে, আপনাকে `node_modules` এবং `package-lock.json` মুছে ফেলতে হবে এবং `npm install` চালাতে হবে। এটি অ্যাপটি চালানোর জন্য প্রয়োজনীয় সমস্ত প্যাকেজ ইনস্টল করবে যাতে এই ত্রুটি ঠিক হয়ে যেতে পারে । + +```shell +# সাব-ডিরেক্টরিতে নোড_মডিউল মুছুন +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# সাব-ডিরেক্টরিতে প্যাকেজ-লক মুছুন +find . -type f -name "package-lock.json" -delete +``` + +### Testing (পরীক্ষা) + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` + +### Raising Pull Request (পুল অনুরোধ উত্থাপন) + +- অনুগ্রহ করে PR এর আকার ছোট রাখুন এবং একটি বিষয়ে ফোকাস করুন। +- অনুগ্রহ করে শাখা তৈরির বিন্যাস অনুসরণ করুন। + - বৈশিষ্ট্য/[ফিচারের নাম]: এই শাখায় একটি নির্দিষ্ট বৈশিষ্ট্যের জন্য পরিবর্তন থাকতে হবে। + - উদাহরণ: বৈশিষ্ট্য/ডার্ক-মোড। + - বাগফিক্স/[বাগ নাম]: এই শাখায় একটি নির্দিষ্ট বাগ-এর জন্য শুধুমাত্র বাগ ফিক্স থাকা উচিত। + - উদাহরণ বাগফিক্স/বাগ-1। diff --git a/docs/contributing/contributing_de.md b/docs/contributing/contributing_de.md new file mode 100644 index 00000000..be3126c0 --- /dev/null +++ b/docs/contributing/contributing_de.md @@ -0,0 +1,91 @@ +[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | **Deutsch** | [Français](/contributing_fr.md) | [বাংলা](docs/contributing/contributing_bn.md) + +## Lass uns Bruno noch besser machen, gemeinsam !! + +Ich freue mich, dass Du Bruno verbessern möchtest. Hier findest Du eine Anleitung, mit der Du Bruno auf Deinem Computer einrichten kannst. + +### Technologie Stack + +Bruno ist mit Next.js und React erstellt. Außerdem benötigen wir electron für die Desktop Version (die lokale Sammlungen unterstützt). + +Bibliotheken die wir benutzen + +- CSS - Tailwind +- Code Editoren - Codemirror +- State Management - Redux +- Icons - Tabler Icons +- Formulare - formik +- Schema Validierung - Yup +- Request Client - axios +- Dateisystem Watcher - chokidar + +### Abhängigkeiten + +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 + +Eine Anleitung zum Ausführen einer lokalen Entwicklungsumgebung findest Du in [development.md](docs/development_de.md). + +### Pull Request erstellen + +- Bitte halte die PRs klein und begrenzt auf eine Sache +- Bitte halte Dich beim Erstellen eines Branches an das folgende Format + - feature/[feature name]: Dieser Branch soll Änderungen für ein bestimmtes Feature enthalten + - Beispiel: feature/dark-mode + - bugfix/[bug name]: Dieser Branch soll ausschließlich Bugfixes für einen bestimmten Bug enthalten + - Beispiel: bugfix/bug-1 + +## Entwicklung + +Bruno wird als Desktop-Anwendung entwickelt. Um die App zu starten, musst Du zuerst die Next.js App in einem Terminal ausführen und anschließend in einem anderen Terminal die Electron-App. + +### Abhängigkeiten + +- NodeJS v18 + +### Lokales Entwickeln + +```bash +# use nodejs 18 version +nvm use + +# install deps +npm i --legacy-peer-deps + +# build graphql docs +npm run build:graphql-docs + +# build bruno query +npm run build:bruno-query + +# run next app (terminal 1) +npm run dev:web + +# run electron app (terminal 2) +npm run dev:electron +``` + +### Troubleshooting + +Es kann sein, dass Du einen `Unsupported platform`-Fehler bekommst, wenn Du `npm install` ausführst. Um dies zu beheben, musst Du `node_modules` und `package-lock.json` löschen und `npm install` erneut ausführen. Dies sollte alle notwendigen Pakete installieren, die zum Ausführen der Anwendung benötigt werden. + +```shell +# Delete node_modules in sub-directories +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Delete package-lock in sub-directories +find . -type f -name "package-lock.json" -delete +``` + +### Testen + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` diff --git a/docs/contributing/contributing_es.md b/docs/contributing/contributing_es.md new file mode 100644 index 00000000..3e69a2ac --- /dev/null +++ b/docs/contributing/contributing_es.md @@ -0,0 +1,85 @@ +## ¡Juntos, hagamos a Bruno mejor! + +Estamos encantados de que quieras ayudar a mejorar Bruno. A continuación encontrarás las instrucciones para empezar a trabajar con Bruno en tu computadora. + +### Tecnologías utilizadas + +Bruno está construido con NextJs y React. También usamos electron para distribuir una versión de escritorio (que soporta colecciones locales). + +Librerías que utilizamos: + +- CSS - Tailwind +- Editores de código - Codemirror +- Manejo del estado - Redux +- Íconos - Tabler Icons +- Formularios - formik +- Validación de esquemas - Yup +- Cliente de peticiones - axios +- Monitor del sistema de archivos - chokidar + +### Dependencias + +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 + +Bruno está siendo desarrollado como una aplicación de escritorio. Para ejecutarlo, primero debes ejecutar la aplicación de nextjs en una terminal y luego ejecutar la aplicación de electron en otra terminal. + +### Dependencias + +- NodeJS v18 + +### Desarrollo local + +```bash +# Utiliza la versión 18 de nodejs +nvm use + +# Instala las dependencias +npm i --legacy-peer-deps + +# Construye la documentación de graphql +npm run build:graphql-docs + +# Construye bruno-query +npm run build:bruno-query + +# Ejecuta la aplicación de nextjs (terminal 1) +npm run dev:web + +# Ejecuta la aplicación de electron (terminal 2) +npm run dev:electron +``` + +### Solución de problemas + +Es posible que encuentres un error de `Unsupported platform` cuando ejecutes `npm install`. Para solucionarlo, debes eliminar la carpeta `node_modules` y el archivo `package-lock.json`, luego, ejecuta `npm install`. Lo anterior debería instalar todos los paquetes necesarios para ejecutar la aplicación. + +```shell +# Elimina la carpeta node_modules en los subdirectorios +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Elimina el archivo package-lock en los subdirectorios +find . -type f -name "package-lock.json" -delete +``` + +### Pruebas + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` + +### Crea un Pull Request + +- Por favor, mantén los Pull Request pequeños y enfocados en una sola cosa. +- Por favor, sigue el siguiente formato para la creación de ramas: + - feature/[nombre de la funcionalidad]: Esta rama debe contener los cambios para una funcionalidad específica. + - Ejemplo: feature/dark-mode + - bugfix/[nombre del error]: Esta rama debe contener solo correcciones de errores para un error específico. + - Ejemplo: bugfix/bug-1 diff --git a/docs/contributing/contributing_fr.md b/docs/contributing/contributing_fr.md new file mode 100644 index 00000000..006972e9 --- /dev/null +++ b/docs/contributing/contributing_fr.md @@ -0,0 +1,91 @@ +[English](/contributing.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | **Français** | [বাংলা](docs/contributing/contributing_bn.md) + +## Ensemble, améliorons Bruno ! + +Je suis content de voir que vous envisagez améliorer Bruno. Ci-dessous, vous trouverez les règles et guides pour récupérer Bruno sur votre ordinateur. + +### Technologies utilisées + +Bruno est construit en utilisant NextJs et React. Nous utilisons aussi Electron pour embarquer la version ordinateur (qui permet les collections locales). + +Les bibliothèques que nous utilisons : + +- CSS - Tailwind +- Code Editors - Codemirror +- State Management - Redux +- Icons - Tabler Icons +- Forms - formik +- Schema Validation - Yup +- Request Client - axios +- Filesystem Watcher - chokidar + +### Dépendances + +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. + +### Commençons à coder + +Veuillez vous référez à la [documentation de développement](docs/development_fr.md) pour les instructions de démarrage de l'environnement de développement local. + +### Ouvrir une Pull Request + +- Merci de conserver les PR petites et focalisées sur un seul objectif +- Merci de suivre le format de nom des branches + - feature/[feature name]: Cette branche devrait contenir une fonctionnalité spécifique + - Exemple: feature/dark-mode + - bugfix/[bug name]: Cette branche devrait contenir seulement une solution pour pour une bogue spécifique + - Exemple: bugfix/bug-1 + +## Développement + +Bruno est développé comme une application de _lourde_. Vous devez charger l'application en démarrant nextjs dans un terminal, puis démarre l'application Electron dans un autre terminal. + +### Dépendances + +- NodeJS v18 + +### Développement local + +```bash +# use nodejs 18 version +nvm use + +# install deps +npm i --legacy-peer-deps + +# build graphql docs +npm run build:graphql-docs + +# build bruno query +npm run build:bruno-query + +# run next app (terminal 1) +npm run dev:web + +# run electron app (terminal 2) +npm run dev:electron +``` + +### Dépannage + +Vous pourriez rencontrer une error `Unsupported platform` pendant le lancement de `npm install`. Pour résoudre cela, veuillez supprimer le répertoire `node_modules`, le fichier `package-lock.json` et lancer à nouveau `npm install`. Cela devrait isntaller tous les paquets nécessaires pour lancer l'application. + +```shell +# Delete node_modules in sub-directories +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Delete package-lock in sub-directories +find . -type f -name "package-lock.json" -delete +``` + +### Tests + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` diff --git a/docs/contributing/contributing_it.md b/docs/contributing/contributing_it.md new file mode 100644 index 00000000..53bf61e7 --- /dev/null +++ b/docs/contributing/contributing_it.md @@ -0,0 +1,89 @@ +## Insieme, miglioriamo Bruno! + +Sono felice di vedere che hai intenzione di migliorare Bruno. Di seguito, troverai le regole e le guide per ripristinare Bruno sul tuo computer. + +### Tecnologie utilizzate + +Bruno è costruito utilizzando Next.js e React. Utilizziamo anche Electron per incorporare la versione desktop (che consente raccolte locali). + +Le librerie che utilizziamo sono: + +- CSS - Tailwind +- Code Editors - Codemirror +- State Management - Redux +- Icons - Tabler Icons +- Forms - formik +- Schema Validation - Yup +- Request Client - axios +- Filesystem Watcher - chokidar + +### Dependences + +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 + +Si prega di fare riferimento alla [documentazione di sviluppo](docs/development_it.md) per le istruzioni su come avviare l'ambiente di sviluppo locale. + +### Aprire una richiesta di pull (Pull Request) + +- Si prega di mantenere le Pull Request (PR) brevi e concentrate su un singolo obiettivo. +- Si prega di seguire il formato di denominazione dei rami. + - feature/[feature name]: Questo ramo dovrebbe contenere una specifica funzionalità. + - Esempio: feature/dark-mode + - bugfix/[bug name]: Questo ramo dovrebbe contenere solo una soluzione per un bug specifico. + - Esempio: bugfix/bug-1 + +## Sviluppo + +Bruno è sviluppato come un'applicazione "heavy". È necessario caricare l'applicazione avviando Next.js in una finestra del terminale e quindi avviare l'applicazione Electron in un altro terminale. + +### Sviluppo + +- NodeJS v18 + +### Sviluppo locale + +```bash +# use nodejs 18 version +nvm use + +# install deps +npm i --legacy-peer-deps + +# build graphql docs +npm run build:graphql-docs + +# build bruno query +npm run build:bruno-query + +# run next app (terminal 1) +npm run dev:web + +# run electron app (terminal 2) +npm run dev:electron +``` + +### Risoluzione dei problemi + +Potresti trovare un errore `Unsupported platform` durante l'esecuzione di `npm install`. Per risolvere questo problema, ti preghiamo di eliminare la cartella `node_modules`, il file `package-lock.json` e di seguito nuovamente `npm install`. Qeusto dovrebbe installare tutti i pacchetti necessari per avviare l'applicazione. + +```shell +# delete node_modules in sub-directories +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# delete package-lock in sub-directories +find . -type f -name "package-lock.json" -delete +``` + +### Tests + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` diff --git a/docs/contributing/contributing_pt_br.md b/docs/contributing/contributing_pt_br.md new file mode 100644 index 00000000..7f7baabb --- /dev/null +++ b/docs/contributing/contributing_pt_br.md @@ -0,0 +1,85 @@ +## Vamos tornar o Bruno melhor, juntos!! + +Estamos felizes que você queira ajudar a melhorar o Bruno. Abaixo estão as diretrizes e orientações para começar a executar o Bruno no seu computador. + +### Stack de Tecnologias + +O Bruno é construído usando Next.js e React. Também usamos o Electron para disponibilizar uma versão para desktop (que suporta coleções locais). + +Bibliotecas que utilizamos: + +- CSS - Tailwind +- Editor de Código - Codemirror +- Gerenciador de Estado - Redux +- Ícones - Tabler Icons +- Formulários - formik +- Validador de Schema - Yup +- Cliente de Requisições - axios +- Monitor de Arquivos - chokidar + +### Dependências + +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 + +Bruno está sendo desenvolvido como um aplicativo de desktop. Você precisa carregar o programa executando o aplicativo Next.js em um terminal e, em seguida, executar o aplicativo Electron em outro terminal. + +### Dependências + +- NodeJS v18 + +### Desenvolvimento Local + +```bash +# use nodejs 18 version +nvm use + +# install deps +npm i --legacy-peer-deps + +# build graphql docs +npm run build:graphql-docs + +# build bruno query +npm run build:bruno-query + +# run next app (terminal 1) +npm run dev:web + +# run electron app (terminal 2) +npm run dev:electron +``` + +### Troubleshooting + +Você pode se deparar com o erro `Unsupported platform` ao executar o comando `npm install`. Para corrigir isso, você precisará excluir a pasta `node_modules` e o arquivo `package-lock.json` e, em seguida, executar o comando `npm install` novamente. Isso deve instalar todos os pacotes necessários para executar o aplicativo. + +```shell +# delete node_modules in sub-directories +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# delete package-lock in sub-directories +find . -type f -name "package-lock.json" -delete +``` + +### Testando + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` + +### Envio de Pull Request + +- Por favor, mantenha os PRs pequenos e focados em uma única coisa. +- Siga o formato de criação de branches. + - feature/[nome da funcionalidade]: Esta branch deve conter alterações para uma funcionalidade específica. + - Exemplo: feature/dark-mode + - bugfix/[nome do bug]: Esta branch deve conter apenas correções para um bug específico. + - Exemplo: bugfix/bug-1 diff --git a/docs/contributing/contributing_ru.md b/docs/contributing/contributing_ru.md new file mode 100644 index 00000000..5f3c270b --- /dev/null +++ b/docs/contributing/contributing_ru.md @@ -0,0 +1,91 @@ +[English](/contributing.md) | [Українська](/contributing_ua.md) | **Русский** | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [বাংলা](docs/contributing/contributing_bn.md) + +## Давайте вместе сделаем Бруно лучше!!! + +Я рад, что Вы хотите усовершенствовать bruno. Ниже приведены рекомендации по запуску bruno на вашем компьютере. + +### Стек + +Bruno построен с использованием Next.js и React. Мы также используем electron для поставки десктопной версии ( которая поддерживает локальные коллекции ) + +Библиотеки, которые мы используем + +- CSS - Tailwind +- Редакторы кода - Codemirror +- Управление состоянием - Redux +- Иконки - Tabler Icons +- Формы - formik +- Валидация схем - Yup +- Запросы клиента - axios +- Наблюдатель за файловой системой - chokidar + +### Зависимости + +Вам потребуется [Node v18.x или последняя версия LTS](https://nodejs.org/en/) и npm 8.x. В проекте мы используем рабочие пространства npm + +### Приступим к коду + +Пожалуйста, обратитесь к [development_ru.md](docs/development_ru.md) для получения инструкций по запуску локальной среды разработки. + +### Создание Pull Request + +- Пожалуйста, пусть PR будет небольшим и сфокусированным на одной вещи +- Пожалуйста, соблюдайте формат создания веток + - feature/[название функции]: Эта ветка должна содержать изменения для конкретной функции + - Пример: feature/dark-mode + - bugfix/[название ошибки]: Эта ветка должна содержать только исправления для конкретной ошибки + - Пример bugfix/bug-1 + +## Разработка + +Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение Next.js в одном терминале, а затем запустить приложение electron в другом терминале. + +### Зависимости + +- NodeJS v18 + +### Локальная разработка + +```bash +# используйте nodejs 18 версии +nvm use + +# установите зависимости +npm i --legacy-peer-deps + +# билд документации по graphql +npm run build:graphql-docs + +# билд bruno query +npm run build:bruno-query + +# запустить next приложение ( терминал 1 ) +npm run dev:web + +# запустить приложение electron ( терминал 2 ) +npm run dev:electron +``` + +### Устранение неисправностей + +При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения. + +```shell +# Удаление node_modules в подкаталогах +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Удаление package-lock в подкаталогах +find . -type f -name "package-lock.json" -delete +``` + +### Тестирование + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` diff --git a/docs/contributing/contributing_tr.md b/docs/contributing/contributing_tr.md new file mode 100644 index 00000000..4d63bd41 --- /dev/null +++ b/docs/contributing/contributing_tr.md @@ -0,0 +1,37 @@ +[English](/readme.md) | [Українська](/contributing_ua.md) | [Русский](/contributing_ru.md) | **Türkçe** | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [বাংলা](docs/contributing/contributing_bn.md) + +## Bruno'yu birlikte daha iyi hale getirelim !! + +Bruno'yu geliştirmek istemenizden mutluluk duyuyorum. Aşağıda, bruno'yu bilgisayarınıza getirmeye başlamak için yönergeler bulunmaktadır. + +### Kullanılan Teknolojiler + +Bruno, Next.js ve React kullanılarak oluşturulmuştur. Ayrıca bir masaüstü sürümü (yerel koleksiyonları destekleyen) göndermek için electron kullanıyoruz + +Kullandığımız kütüphaneler + +- CSS - Tailwind +- Kod Düzenleyiciler - Codemirror +- Durum Yönetimi - Redux +- Iconlar - Tabler Simgeleri +- Formlar - formik +- Şema Doğrulama - Yup +- İstek İstemcisi - axios +- Dosya Sistemi İzleyicisi - chokidar + +### Bağımlılıklar + +[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 + +### Kodlamaya başlayalım + +Yerel geliştirme ortamının çalıştırılmasına ilişkin talimatlar için lütfen [development.md](docs/development.md) adresine başvurun. + +### Pull Request Oluşturma + +- Lütfen PR'ları küçük tutun ve tek bir şeye odaklanın +- Lütfen şube oluşturma formatını takip edin + - feature/[özellik adı]: Bu dal belirli bir özellik için değişiklikler içermelidir + - Örnek: feature/dark-mode + - bugfix/[hata adı]: Bu dal yalnızca belirli bir hata için hata düzeltmelerini içermelidir + - Örnek bugfix/bug-1 diff --git a/docs/contributing/contributing_ua.md b/docs/contributing/contributing_ua.md new file mode 100644 index 00000000..723aa770 --- /dev/null +++ b/docs/contributing/contributing_ua.md @@ -0,0 +1,91 @@ +[English](/contributing.md) | **Українська** | [Русский](/contributing_ru.md) | [Türkçe](/contributing_tr.md) | [Deutsch](/contributing_de.md) | [Français](/contributing_fr.md) | [বাংলা](docs/contributing/contributing_bn.md) + +## Давайте зробимо Bruno краще, разом !! + +Я дуже радий що Ви бажаєте покращити Bruno. Нижче наведені вказівки як розпочати розробку Bruno на Вашому комп'ютері. + +### Стек технологій + +Bruno побудований на Next.js та React. Також для десктопної версії (яка підтримує локальні колекції) використовується Electron + +Бібліотеки, які ми використовуємо + +- CSS - Tailwind +- Редактори коду - Codemirror +- Керування станом - Redux +- Іконки - Tabler Icons +- Форми - formik +- Валідація по схемі - Yup +- Клієнт запитів - axios +- Спостерігач за файловою системою - chokidar + +### Залежності + +Вам знадобиться [Node v18.x або остання LTS версія](https://nodejs.org/en/) та npm 8.x. Ми використовуєм npm workspaces в цьому проекті + +### Починаєм писати код + +Будь ласка, зверніться до [development_ua.md](docs/development_ua.md) за інструкціями щодо запуску локального середовища розробки. + +### Створення Pull Request-ів + +- Будь ласка, робіть PR-и маленькими і сфокусованими на одній речі +- Будь ласка, слідуйте формату назв гілок + - feature/[назва feature]: Така гілка має містити зміни лише щодо конкретної feature + - Приклад: feature/dark-mode + - bugfix/[назва баґу]: Така гілка має містити лише виправлення конкретного багу + - Приклад: bugfix/bug-1 + +## Розробка + +Bruno розробляється як декстопний застосунок. Вам потрібно запустити Next.js в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу. + +### Залежності + +- NodeJS v18 + +### Локальна розробка + +```bash +# Використовуйте nodejs 18-ї версії +nvm use + +# встановіть залежності +npm i --legacy-peer-deps + +# зберіть документацію graphql +npm run build:graphql-docs + +# зберіть bruno query +npm run build:bruno-query + +# запустіть додаток next (термінал 1) +npm run dev:web + +# запустіть додаток електрон (термінал 2) +npm run dev:electron +``` + +### Усунення несправностей + +Ви можете зтикнутись із помилкою `Unsupported platform` коли запускаєте `npm install`. Щоб усунути цю проблему, вам потрібно видалити `node_modules` та `package-lock.json`, і тоді запустити `npm install`. Це має встановити всі потрібні для запуску додатку пекеджі. + +```shell +# Видаліть node_modules в піддиректоріях +find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do + rm -rf "$dir" +done + +# Видаліть package-lock в піддиректоріях +find . -type f -name "package-lock.json" -delete +``` + +### Тестування + +```bash +# bruno-schema +npm test --workspace=packages/bruno-schema + +# bruno-lang +npm test --workspace=packages/bruno-lang +``` diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index d56d3e6c..00000000 --- a/docs/development.md +++ /dev/null @@ -1,55 +0,0 @@ -**English** | [Українська](/docs/development_ua.md) | [Русский](/docs/development_ru.md) - -## Development - -Bruno is being developed as a desktop app. You need to load the app by running the nextjs app in one terminal and then run the electron app in another terminal. - -### Dependencies - -- NodeJS v18 - -### Local Development - -```bash -# use nodejs 18 version -nvm use - -# install deps -npm i --legacy-peer-deps - -# build graphql docs -npm run build:graphql-docs - -# build bruno query -npm run build:bruno-query - -# run next app (terminal 1) -npm run dev:web - -# run electron app (terminal 2) -npm run dev:electron -``` - -### Troubleshooting - -You might encounter a `Unsupported platform` error when you run `npm install`. To fix this, you will need to delete `node_modules` and `package-lock.json` and run `npm install`. This should install all the necessary packages needed to run the app. - -```shell -# Delete node_modules in sub-directories -find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do - rm -rf "$dir" -done - -# Delete package-lock in sub-directories -find . -type f -name "package-lock.json" -delete -``` - -### Testing - -```bash -# bruno-schema -npm test --workspace=packages/bruno-schema - -# bruno-lang -npm test --workspace=packages/bruno-lang -``` diff --git a/docs/development_ru.md b/docs/development_ru.md deleted file mode 100644 index 3816066e..00000000 --- a/docs/development_ru.md +++ /dev/null @@ -1,55 +0,0 @@ -[English](/docs/development.md) | [Українська](/docs/development_ua.md) | **Русский** - -## Разработка - -Bruno разрабатывается как десктопное приложение. Необходимо загрузить приложение, запустив приложение nextjs в одном терминале, а затем запустить приложение electron в другом терминале. - -### Зависимости - -- NodeJS v18 - -### Локальная разработка - -```bash -# используйте nodejs 18 версии -nvm use - -# установите зависимости -npm i --legacy-peer-deps - -# билд документации по graphql -npm run build:graphql-docs - -# билд bruno query -npm run build:bruno-query - -# запустить next приложение ( терминал 1 ) -npm run dev:web - -# запустить приложение electron ( терминал 2 ) -npm run dev:electron -``` - -### Устранение неисправностей - -При запуске `npm install` может возникнуть ошибка `Unsupported platform`. Чтобы исправить это, необходимо удалить `node_modules` и `package-lock.json` и запустить `npm install`. В результате будут установлены все пакеты, необходимые для работы приложения. - -```shell -# Удаление node_modules в подкаталогах -find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do - rm -rf "$dir" -done - -# Удаление package-lock в подкаталогах -find . -type f -name "package-lock.json" -delete -``` - -### Тестирование - -```bash -# bruno-schema -npm test --workspace=packages/bruno-schema - -# bruno-lang -npm test --workspace=packages/bruno-lang -``` diff --git a/docs/development_ua.md b/docs/development_ua.md deleted file mode 100644 index d6d5bcdf..00000000 --- a/docs/development_ua.md +++ /dev/null @@ -1,55 +0,0 @@ -[English](/docs/development.md) | **Українська** | [Русский](/docs/development_ru.md) - -## Розробка - -Bruno розробляється як декстопний застосунок. Вам потрібно запустити nextjs в одній сесії терміналу, та запустити застосунок Electron в іншій сесії терміналу. - -### Залежності - -- NodeJS v18 - -### Локальна розробка - -```bash -# Використовуйте nodejs 18-ї версії -nvm use - -# встановіть залежності -npm i --legacy-peer-deps - -# зберіть документацію graphql -npm run build:graphql-docs - -# зберіть bruno query -npm run build:bruno-query - -# запустіть додаток next (термінал 1) -npm run dev:web - -# запустіть додаток електрон (термінал 2) -npm run dev:electron -``` - -### Усунення несправностей - -Ви можете зтикнутись із помилкою `Unsupported platform` коли запускаєте `npm install`. Щоб усунути цю проблему, вам потрібно видалити `node_modules` та `package-lock.json`, і тоді запустити `npm install`. Це має встановити всі потрібні для запуску додатку пекеджі. - -```shell -# Видаліть node_modules в піддиректоріях -find ./ -type d -name "node_modules" -print0 | while read -d $'\0' dir; do - rm -rf "$dir" -done - -# Видаліть package-lock в піддиректоріях -find . -type f -name "package-lock.json" -delete -``` - -### Тестування - -```bash -# bruno-schema -npm test --workspace=packages/bruno-schema - -# bruno-lang -npm test --workspace=packages/bruno-lang -``` diff --git a/docs/publishing/publishing_pt_br.md b/docs/publishing/publishing_pt_br.md new file mode 100644 index 00000000..b3d580ee --- /dev/null +++ b/docs/publishing/publishing_pt_br.md @@ -0,0 +1,5 @@ +### Publicando Bruno em um novo gerenciador de pacotes + +Embora nosso código seja de código aberto e esteja disponível para todos usarem, pedimos gentilmente que entre em contato conosco antes de considerar a publicação em novos gerenciadores de pacotes. Como o criador da ferramenta, mantenho a marca registrada `Bruno` para este projeto e gostaria de gerenciar sua distribuição. Se deseja ver o Bruno em um novo gerenciador de pacotes, por favor, solicite através de uma issue no GitHub. + +Embora a maioria de nossas funcionalidades seja gratuita e de código aberto (o que abrange API's REST e GraphQL), buscamos alcançar um equilíbrio harmonioso entre os princípios de código aberto e sustentabilidade. - https://github.com/usebruno/bruno/discussions/269 diff --git a/docs/readme/readme_bn.md b/docs/readme/readme_bn.md new file mode 100644 index 00000000..bcf313d6 --- /dev/null +++ b/docs/readme/readme_bn.md @@ -0,0 +1,123 @@ +
+ + +### ব্রুনো - API অন্বেষণ এবং পরীক্ষা করার জন্য ওপেনসোর্স IDE। + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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) | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | [Français](docs/readme/readme_fr.md) | **বাংলা** + +ব্রুনো হল একটি নতুন এবং উদ্ভাবনী API ক্লায়েন্ট, যার লক্ষ্য পোস্টম্যান এবং অনুরূপ সরঞ্জাম দ্বারা প্রতিনিধিত্ব করা স্থিতাবস্থায় বিপ্লব ঘটানো। + +ব্রুনো আপনার সংগ্রহগুলি সরাসরি আপনার ফাইল সিস্টেমের একটি ফোল্ডারে সঞ্চয় করে। আমরা API অনুরোধ সম্পর্কে তথ্য সংরক্ষণ করতে একটি প্লেইন টেক্সট মার্কআপ ভাষা, ব্রু ব্যবহার করি। + +আপনি আপনার API সংগ্রহে সহযোগিতা করতে গিট বা আপনার পছন্দের যেকোনো সংস্করণ নিয়ন্ত্রণ ব্যবহার করতে পারেন। + +ব্রুনো শুধুমাত্র অফলাইন। ব্রুনোতে ক্লাউড-সিঙ্ক যোগ করার কোন পরিকল্পনা নেই, কখনও। আমরা আপনার ডেটা গোপনীয়তার মূল্য দিই এবং বিশ্বাস করি এটি আপনার ডিভাইসে থাকা উচিত। আমাদের দীর্ঘমেয়াদী দৃষ্টি পড়ুন। [এখানে ](https://github.com/usebruno/bruno/discussions/269) + +📢 ইন্ডিয়া FOSS 3.0 সম্মেলনে আমাদের সাম্প্রতিক আলোচনা দেখুন [এখানে](https://www.youtube.com/watch?v=7bSMFpbcPiY) + +![bruno](/assets/images/landing-2.png)

+ +### স্থাপন + +ব্রুনো বাইনারি ডাউনলোড হিসাবে উপলব্ধ [আমাদের ওয়েবসাইটে](https://www.usebruno.com/downloads) ম্যাক, উইন্ডোজ এবং লিনাক্সের জন্য। + +আপনি Homebrew, Chocolatey, Snap এবং Apt এর মত প্যাকেজ ম্যানেজারদের মাধ্যমে ব্রুনো ইনস্টল করতে পারেন। + +```sh +# Homebrew এর মাধ্যমে Mac-এ +brew install bruno + +# চকোলেটির মাধ্যমে উইন্ডোজে +choco install bruno + +# স্ন্যাপ এর মাধ্যমে লিনাক্সে +snap install bruno + +# Apt এর মাধ্যমে লিনাক্সে +sudo mkdir -p /etc/apt/keyrings +sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266 + +echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list + +sudo apt update +sudo apt install bruno +``` + +### একাধিক প্ল্যাটফর্মে চালান 🖥️ + +![bruno](/assets/images/run-anywhere.png)

+ +### Git এর মাধ্যমে সহযোগিতা করুন 👩‍💻🧑‍💻 + +অথবা আপনার পছন্দের যেকোনো সংস্করণ নিয়ন্ত্রণ ব্যবস্থা + +![bruno](/assets/images/version-control.png)

+ +### গুরুত্বপূর্ণ লিংক 📌 + +- [আমাদের দীর্ঘমেয়াদী দৃষ্টি](https://github.com/usebruno/bruno/discussions/269) +- [রোডম্যাপ](https://github.com/usebruno/bruno/discussions/384) +- [ডকুমেন্টেশন](https://docs.usebruno.com) +- [ওয়েবসাইট](https://www.usebruno.com) +- [মূল্য](https://www.usebruno.com/pricing) +- [ডাউনলোড করুন](https://www.usebruno.com/downloads) + +### শোকেস 🎥 + +- [প্রশংসাপত্র](https://github.com/usebruno/bruno/discussions/343) +- [নলেজ হাব](https://github.com/usebruno/bruno/discussions/386) +- [স্ক্রিপ্টম্যানিয়া](https://github.com/usebruno/bruno/discussions/385) + +### সমর্থন ❤️ + +উফ ! আপনি যদি প্রকল্পটি পছন্দ করেন তবে ⭐ বোতামটি টিপুন !! + +### প্রশংসাপত্র শেয়ার করুন 📣 + +যদি ব্রুনো আপনাকে কর্মক্ষেত্রে এবং আপনার দলগুলিতে সাহায্য করে থাকে, অনুগ্রহ করে আপনার [আমাদের গিটহাব আলোচনায় প্রশংসাপত্রগুলি](https://github.com/usebruno/bruno/discussions/343) শেয়ার করতে ভুলবেন না + +### নতুন প্যাকেজ পরিচালকদের কাছে প্রকাশ করা হচ্ছে + +আরও তথ্যের জন্য অনুগ্রহ করে [এখানে](publishing.md) দেখুন। + +### অবদান 👩‍💻🧑‍💻 + +আমি খুশি যে আপনি ব্রুনোর উন্নতি করতে চাইছেন। অনুগ্রহ করে [অবদানকারী নির্দেশিকা](contributing.md) দেখুন + +আপনি কোডের মাধ্যমে অবদান রাখতে না পারলেও, অনুগ্রহ করে বাগ এবং বৈশিষ্ট্যের অনুরোধ ফাইল করতে দ্বিধা করবেন না যা আপনার ব্যবহারের ক্ষেত্রে সমাধান করার জন্য প্রয়োগ করা প্রয়োজন। + +### লেখক + +
+ + + +
+ +### সাথে থাকুন 🌐 + +[𝕏 (টুইটার)](https://twitter.com/use_bruno)
+[ওয়েবসাইট](https://www.usebruno.com)
+[ডিসকর্ড](https://discord.com/invite/KgcZUncpjq)
+[লিঙ্কডইন](https://www.linkedin.com/company/usebruno) + +### ট্রেডমার্ক + +**নাম** + +`Bruno` হল একটি ট্রেডমার্ক [Anoop M D](https://www.helloanoop.com/) + +**লোগো** + +লোগোটি [OpenMoji](https://openmoji.org/library/emoji-1F436/) থেকে নেওয়া হয়েছে। লাইসেন্স: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### লাইসেন্স 📄 + +[MIT](license.md) diff --git a/docs/readme/readme_de.md b/docs/readme/readme_de.md new file mode 100644 index 00000000..111bb04c --- /dev/null +++ b/docs/readme/readme_de.md @@ -0,0 +1,95 @@ +
+ + +### Bruno - Opensource IDE zum Erkunden und Testen von APIs. + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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** | [Français](/readme_fr.md) | [বাংলা](docs/readme/readme_bn.md) + +Bruno ist ein neuer und innovativer API-Client, der den Status Quo von Postman und ähnlichen Tools revolutionieren soll. + +Bruno speichert Deine Sammlungen direkt in einem Ordner in Deinem Dateisystem. Wir verwenden eine einfache Textauszeichnungssprache - Bru - um Informationen über API-Anfragen zu speichern. + +Du kannst Git oder eine andere Versionskontrolle deiner Wahl verwenden, um an deinen API-Sammlungen gemeinsam mit anderen zu arbeiten. + +Bruno ist ein reines Offline-Tool. Es gibt keine Pläne, Bruno eine Cloud-Synchronisation hinzuzufügen. Wir schätzen den Schutz Deiner Daten und glauben, dass sie auf Deinem Gerät bleiben sollten. Lies unsere Langzeit-Vision [hier](https://github.com/usebruno/bruno/discussions/269). + +![bruno](/assets/images/landing-2.png)

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

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

+ +### Wichtige Links 📌 + +- [Unsere Langzeit-Vision](https://github.com/usebruno/bruno/discussions/269) +- [Roadmap](https://github.com/usebruno/bruno/discussions/384) +- [Dokumentation](https://docs.usebruno.com) +- [Webseite](https://www.usebruno.com) +- [Preise](https://www.usebruno.com/pricing) +- [Download](https://www.usebruno.com/downloads) + +### Showcase 🎥 + +- [Erfahrungsberichte](https://github.com/usebruno/bruno/discussions/343) +- [Wissenswertes](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Unterstützung ❤️ + +Wuff! Wenn Du dieses Projekt magst, klick den ⭐ Button !! + +### Teile Erfahrungsberichte 📣 + +Wenn Bruno Dir bei Deiner Arbeit und in Deinen Teams geholfen hat, vergiss bitte nicht, Deine [Erfahrungsberichte auf unserer GitHub-Diskussion](https://github.com/usebruno/bruno/discussions/343) zu teilen. + +### Veröffentlichung in neuen Paketmanagern + +Bitte [hier](/publishing.md) für mehr Informationen lesen. + +### Mitmachen 👩‍💻🧑‍💻 + +Ich freue mich, dass Du Bruno verbessern willst. Bitte schau Dir den [Leitfaden zum Mitmachen](../contributing/contributing_de.md) an. + +Auch wenn Du nicht in der Lage bist, einen Beitrag in Form von Code zu leisten, zögere bitte nicht, uns Fehler und Funktionswünsche mitzuteilen, die implementiert werden müssen, um Deinen Anwendungsfall zu unterstützen. + +### Autoren + +
+ + + +
+ +### In Verbindung bleiben 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Webseite](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Markenzeichen + +**Name** + +`Bruno` ist ein Markenzeichen von [Anoop M D](https://www.helloanoop.com/) + +**Logo** + +Das Logo stammt von [OpenMoji](https://openmoji.org/library/emoji-1F436/). Lizenz: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### Lizenz 📄 + +[MIT](/license.md) diff --git a/docs/readme/readme_es.md b/docs/readme/readme_es.md new file mode 100644 index 00000000..3e1a4dc6 --- /dev/null +++ b/docs/readme/readme_es.md @@ -0,0 +1,93 @@ +
+ + +### Bruno - IDE de código abierto para explorar y probar APIs. + +[![Versión en Github](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) +[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-tests.yml) +[![Actividad de Commits](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) +[![Sitio Web](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) +[![Descargas](https://img.shields.io/badge/Download-Latest-brightgreen)](https://www.usebruno.com/downloads) + +Bruno 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. + +Puedes usar git o cualquier otro sistema de control de versiones que prefieras para colaborar en tus colecciones. + +Bruno funciona sin conexión a internet. No tenemos intenciones de añadir sincronización en la nube a Bruno, en ningún momento. Valoramos tu privacidad y creemos que tus datos deben permanecer en tu dispositivo. Puedes leer nuestra visión a largo plazo [aquí](https://github.com/usebruno/bruno/discussions/269) + +![bruno](/assets/images/landing-2.png)

+ +### Ejecútalo en múltiples plataformas 🖥️ + +![bruno](/assets/images/run-anywhere.png)

+ +### Colabora vía Git 👩‍💻🧑‍💻 + +O cualquier otro sistema de control de versiones que prefieras + +![bruno](/assets/images/version-control.png)

+ +### Enlaces importantes 📌 + +- [Nuestra Visión a Largo Plazo](https://github.com/usebruno/bruno/discussions/269) +- [Hoja de Ruta](https://github.com/usebruno/bruno/discussions/384) +- [Documentación](https://docs.usebruno.com) +- [Sitio Web](https://www.usebruno.com) +- [Precios](https://www.usebruno.com/pricing) +- [Descargas](https://www.usebruno.com/downloads) + +### Casos de uso 🎥 + +- [Testimonios](https://github.com/usebruno/bruno/discussions/343) +- [Centro de Conocimiento](https://github.com/usebruno/bruno/discussions/386) +- [Scripts de la Comunidad](https://github.com/usebruno/bruno/discussions/385) + +### Apoya el proyecto ❤️ + +¡Guau! Si te gusta el proyecto, ¡dale al botón de ⭐! + +### Comparte tus testimonios 📣 + +Si Bruno te ha ayudado en tu trabajo y con tus equipos, por favor, no olvides compartir tus testimonios en [nuestras discusiones de GitHub](https://github.com/usebruno/bruno/discussions/343) + +### Publicar en nuevos gestores de paquetes + +Por favor, consulta [aquí](publishing.md) para más información. + +### Contribuye 👩‍💻🧑‍💻 + +Estamos encantados de que quieras ayudar a mejorar Bruno. Por favor, consulta la [guía de contribución](contributing_es.md) para más información. + +Incluso si no puedes contribuir con código, no dudes en reportar errores y solicitar nuevas funcionalidades que necesites para resolver tu caso de uso. + +### Colaboradores + +
+ + + +
+ +### Mantente en contacto 🌐 + +[X](https://twitter.com/use_bruno)
+[Sitio Web](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Marca + +**Nombre** + +`Bruno` es una marca propiedad de [Anoop M D](https://www.helloanoop.com/). + +**Logo** + +El logo fue obtenido de [OpenMoji](https://openmoji.org/library/emoji-1F436/). Licencia: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### Licencia 📄 + +[MIT](license.md) diff --git a/docs/readme/readme_fr.md b/docs/readme/readme_fr.md new file mode 100644 index 00000000..f350080a --- /dev/null +++ b/docs/readme/readme_fr.md @@ -0,0 +1,97 @@ +
+ + +### Bruno - IDE Opensource pour explorer et tester des APIs. + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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** | [বাংলা](docs/readme/readme_bn.md) + +Bruno est un nouveau client API, innovant, qui a pour but de révolutionner le _status quo_ que représente Postman et les autres outils. + +Bruno sauvegarde vos collections directement sur votre système de fichiers. Nous utilisons un langage de balise de type texte pour décrire les requêtes API. + +Vous pouvez utiliser git ou tout autre gestionnaire de version pour travailler de manière collaborative sur vos collections d'APIs. + +Bruno ne fonctionne qu'en mode déconnecté. Il n'y a pas de d'abonnement ou de synchronisation avec le cloud Bruno, il n'y en aura jamais. Nous sommes conscients de la confidentialité de vos données et nous sommes convaincus qu'elles doivent rester sur vos appareils. Vous pouvez lire notre vision à long terme [ici (en anglais)](https://github.com/usebruno/bruno/discussions/269). + +![bruno](/assets/images/landing-2.png)

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

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

+ +### Liens importants 📌 + +- [Notre vision à long terme (en anglais)](https://github.com/usebruno/bruno/discussions/269) +- [Roadmap](https://github.com/usebruno/bruno/discussions/384) +- [Documentation](https://docs.usebruno.com) +- [Site web](https://www.usebruno.com) +- [Prix](https://www.usebruno.com/pricing) +- [Téléchargement](https://www.usebruno.com/downloads) + +### Showcase 🎥 + +- [Témoignages](https://github.com/usebruno/bruno/discussions/343) +- [Centre de connaissance](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Soutien ❤️ + +Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !! + +### Partage de témoignages 📣 + +Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de penser à partager votre témoignage sur la [page discussion Github dédiée](https://github.com/usebruno/bruno/discussions/343) + +### Publier Bruno sur un nouveau gestionnaire de paquets + +Veuillez regarder [ici](/publishing.md) pour plus d'information. + +### Contribuer 👩‍💻🧑‍💻 + +Je suis heureux de voir que vous cherchez à améliorer Bruno. Merci de consulter le [guide de contribution](../contributing/contributing_fr.md) + +Même si vous n'êtes pas en mesure de contribuer directement via du code, n'hésitez pas à consigner les bogues et les demandes de nouvelles fonctionnalités pour résoudre vos cas d'usage ! + +### Auteurs + +
+ + + +
+ +### Restons en contact 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Marque + +**Nom** + +`Bruno` est une marque appartenant à [Anoop M D](https://www.helloanoop.com/) + +**Logo** + +Le logo est issu de [OpenMoji](https://openmoji.org/library/emoji-1F436/). +Licence: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### Licence 📄 + +[MIT](/license.md) diff --git a/docs/readme/readme_it.md b/docs/readme/readme_it.md new file mode 100644 index 00000000..671d5199 --- /dev/null +++ b/docs/readme/readme_it.md @@ -0,0 +1,121 @@ +
+ + +### Bruno - Opensource IDE per esplorare e testare gli APIs. + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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) + +Bruno è un nuovo ed innovativo API client, mirato a rivoluzionare lo status quo rappresentato da Postman e strumenti simili disponibili. + +Bruno memorizza le tue raccolte direttamente in una cartella del tuo filesystem. Utilizziamo un linguaggio di markup in testo semplice chiamato Bru per salvare informazioni sulle richeste API. + +Puoi utilizzare Git o qualsiasi sistema di controllo che preferisci per collaborare sulle tue raccolte di API. + +Bruno funziona solo in modalità offline. Non ci sono piani per aggiungere la sincronizzazione su cloud a Bruno in futuro. Valorizziamo la privacy dei tuoi dati e crediamo che dovrebbero rimanere sul tuo dispositivo. Puoi leggere la nostra visione a lungo termine [qui (in inglese)](https://github.com/usebruno/bruno/discussions/269) + +📢 Guarda la nostra presentazione più recente alla conferenza India FOSS 3.0 [qui](https://www.youtube.com/watch?v=7bSMFpbcPiY) + +![bruno](/assets/images/landing-2.png)

+ +### Installazione + +Bruno è disponisible come download binario [sul nostro sito](https://www.usebruno.com/downloads) per Mac, Windows e Linux. + +Puoi installare Bruno anche tramite package manger come Homebrew, Chocolatey, Snap e Apt. + +```sh +# Su Mac come Homebrew +brew install bruno + +# Su Windows come Chocolatey +choco install bruno + +# Su Linux tramite Snap +snap install bruno + +# Su Linux tramite Apt +sudo mkdir -p /etc/apt/keyrings +sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266 + +echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list + +sudo apt update +sudo apt install bruno +``` + +### Funziona su diverse piattaforme 🖥️ + +![bruno](/assets/images/run-anywhere.png)

+ +### Collabora tramite Git 👩‍💻🧑‍💻 + +O con qualsiasi sistema di controllo di versioni a tua scelta + +![bruno](/assets/images/version-control.png)

+ +### Collegamenti importanti 📌 + +- [La nostra visione a lungo termine](https://github.com/usebruno/bruno/discussions/269) +- [Roadmap](https://github.com/usebruno/bruno/discussions/384) +- [Documentazione](https://docs.usebruno.com) +- [Sito internet](https://www.usebruno.com) +- [Prezzo](https://www.usebruno.com/pricing) +- [Download](https://www.usebruno.com/downloads) + +### Showcase 🎥 + +- [Testimonianze](https://github.com/usebruno/bruno/discussions/343) +- [Centro di conoscenza](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Supporto ❤️ + +Woof! se ti piace il progetto, premi quel ⭐ pulsante !! + +### Testimonianze condivise 📣 + +Se Bruno ti ha aiutato con il tuo lavoro ed il tuo team, per favore non dimenticare di condividere le tue [testimonianze nella nostra discussione su GitHub](https://github.com/usebruno/bruno/discussions/343) + +### Pubblica Bruno su un nuovo gestore di pacchetti + +Per favore vedi [qui](publishing.md) per accedere a più informazioni. + +### Contribuire 👩‍💻🧑‍💻 + +Sono felice che vuoi migliorare Bruno. Per favore controlla la [guida per la partecipazione](contributing.md) + +Anche se non sei in grado di contribuire tramite il codice, non esitare a segnalare bug e richieste di funzionalità che devono essere implementati per risolvere il tuo caso d'uso. + +### Autori + +
+ + + +
+ +### Resta in contatto 🌐 + +[𝕏 (Twitter)](https://twitter.com/use_bruno)
+[Sito internet](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Marchio + +**Nome** + +`Bruno` è un marchio registrato appartenente a [Anoop M D](https://www.helloanoop.com/) + +**Logo** + +Il logo è stato creato da [OpenMoji](https://openmoji.org/library/emoji-1F436/). Licenza: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### Licenza 📄 + +[MIT](license.md) diff --git a/docs/readme/readme_kr.md b/docs/readme/readme_kr.md new file mode 100644 index 00000000..070fd086 --- /dev/null +++ b/docs/readme/readme_kr.md @@ -0,0 +1,121 @@ +
+ + +### Bruno - API 탐색 및 테스트를 위한 오픈소스 IDE. + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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) + +Bruno는 새롭고 혁신적인 API 클라이언트로, Postman과 유사한 툴들을 혁신하는 것을 목표로 합니다. + +Bruno는 사용자의 컬렉션을 파일 시스템의 폴더에 직접 저장합니다. 일반 텍스트 마크업 언어인 Bru를 사용해 API 요청에 대한 정보를 저장합니다. + +Git 또는 원하는 버전 관리 도구를 사용하여 API 컬렉션을 연동할 수 있습니다. + +브루는 오프라인 전용입니다. 브루노에 클라우드 동기화 기능을 추가할 계획은 없습니다. 저희는 사용자의 데이터 프라이버시를 소중히 여기며, 데이터는 사용자의 기기에 남아 있어야 한다고 믿습니다. 장기 비전 읽기 [링크](https://github.com/usebruno/bruno/discussions/269) + +📢 Watch our recent talk at India FOSS 3.0 Conference [here](https://www.youtube.com/watch?v=7bSMFpbcPiY) + +![bruno](/assets/images/landing-2.png)

+ +### 설치 + +Bruno 는 여기에서 다운로드 받을 수 있습니다.[링크](https://www.usebruno.com/downloads) (맥, 윈도우, 리눅스) + +Homebrew, Chocolatey, Snap, Apt 같은 패키지 관리자를 통해서도 Bruno를 설치할 수 있습니다. + +```sh +# On Mac via Homebrew +brew install bruno + +# On Windows via Chocolatey +choco install bruno + +# On Linux via Snap +snap install bruno + +# On Linux via Apt +sudo mkdir -p /etc/apt/keyrings +sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266 + +echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list + +sudo apt update +sudo apt install bruno +``` + +### 여러 플랫폼에서 실행하세요. 🖥️ + +![bruno](assets/images/run-anywhere.png)

+ +### Git과 연동하세요. 👩‍💻🧑‍💻 + +또는 원하는 버전 관리 시스템을 선택하세요. + +![bruno](assets/images/version-control.png)

+ +### 중요 링크 📌 + +- [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269) +- [Roadmap](https://github.com/usebruno/bruno/discussions/384) +- [Documentation](https://docs.usebruno.com) +- [Website](https://www.usebruno.com) +- [Pricing](https://www.usebruno.com/pricing) +- [Download](https://www.usebruno.com/downloads) + +### 쇼케이스 🎥 + +- [Testimonials](https://github.com/usebruno/bruno/discussions/343) +- [Knowledge Hub](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### 지원 ❤️ + +프로젝트가 마음에 들면 ⭐ 버튼을 눌러 주세요. + +### 후기 공유 📣 + +Bruno가 여러분과 여러분의 팀에 도움이 되었다면, 잊지 말고 공유해 주세요. [Github discussion 공유 링크](https://github.com/usebruno/bruno/discussions/343) + +### 새 패키지 관리자에게 게시 + +더 많은 정보를 확인하시려명 링크를 클릭해 주세요.[배포 가이드](publishing.md) + +### 컨트리뷰트 👩‍💻🧑‍💻 + +컨트리뷰트에 관심이 있으시면 링크를 참고해 주세요. [컨트리뷰트 가이드](contributing.md) + +코드를 통해 기여할 수 없더라도 사용 사례를 해결하기 위해 구현이 필요한 버그나 기능 요청을 주저하지 마시고 제출해 주세요. + +### Authors + +
+ + + +
+ +### Stay in touch 🌐 + +[𝕏 (Twitter)](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Trademark + +**Name** + +`Bruno` is a trademark held by [Anoop M D](https://www.helloanoop.com/) + +**Logo** + +The logo is sourced from [OpenMoji](https://openmoji.org/library/emoji-1F436/). License: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + +### License 📄 + +[MIT](license.md) diff --git a/docs/readme/readme_pt_br.md b/docs/readme/readme_pt_br.md new file mode 100644 index 00000000..edc47d5e --- /dev/null +++ b/docs/readme/readme_pt_br.md @@ -0,0 +1,121 @@ +
+ + +### Bruno - IDE de código aberto para explorar e testar APIs. + +[![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/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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) + +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. + +Bruno armazena suas coleções diretamente em uma pasta no seu sistema de arquivos. Utilizamos uma linguagem de marcação de texto simples, chamada Bru, para salvar informações sobre requisições de API. + +Você pode usar o Git ou qualquer sistema de controle de versão de sua escolha para colaborar em suas coleções de API. + +Bruno é totalmente offline. Não há planos de adicionar sincronização em nuvem ao Bruno, nunca. Valorizamos a privacidade de seus dados e acreditamos que eles devem permanecer em seu dispositivo. Saiba mais sobre nossa visão a longo prazo [aqui](https://github.com/usebruno/bruno/discussions/269). + +📢 Assista à nossa palestra recente na India FOSS 3.0 Conference [aqui](https://www.youtube.com/watch?v=7bSMFpbcPiY). + +![bruno](../../assets/images/landing-2.png)

+ +### Instalação + +Bruno está disponível para download como binário [em nosso site](https://www.usebruno.com/downloads) para Mac, Windows e Linux. + +Você também pode instalar o Bruno via gerenciadores de pacotes como Homebrew, Chocolatey, Snap e Apt. + +```sh +# Mac via Homebrew +brew install bruno + +# Windows via Chocolatey +choco install bruno + +# Linux via Snap +snap install bruno + +# Linux via Apt +sudo mkdir -p /etc/apt/keyrings +sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266 + +echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list + +sudo apt update +sudo apt install bruno +``` + +### Execute em várias plataformas 🖥️ + +![bruno](../../assets/images/run-anywhere.png)

+ +### Colaboração via Git 👩‍💻🧑‍💻 + +Ou qualquer sistema de controle de versão de sua escolha. + +![bruno](../../assets/images/version-control.png)

+ +### Links Importantes 📌 + +- [Nossa Visão de Longo Prazo](https://github.com/usebruno/bruno/discussions/269) +- [Roadmap](https://github.com/usebruno/bruno/discussions/384) +- [Documentação](https://docs.usebruno.com) +- [Website](https://www.usebruno.com) +- [Preços](https://www.usebruno.com/pricing) +- [Download](https://www.usebruno.com/downloads) + +### Showcase 🎥 + +- [Depoimentos](https://github.com/usebruno/bruno/discussions/343) +- [Hub de Conhecimento](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Apoie ❤️ + +Au-au! Se você gosta do projeto, clique no botão ⭐!! + +### Compartilhe sua experiência 📣 + +Se o Bruno ajudou no seu trabalho e/ou no trabalho de sua equipe, por favor, não se esqueça de compartilhar seu [depoimento em nossas discussões no GitHub](https://github.com/usebruno/bruno/discussions/343). + +### Publicando em Novos Gerenciadores de Pacotes + +Por favor, verifique [aqui](../publishing/publishing_pt_br.md) mais informações. + +### Colabore 👩‍💻🧑‍💻 + +Fico feliz que você queira melhorar o Bruno. Por favor, confira o [guia de colaboração](../contributing/contributing_pt_br.md). + +Mesmo que você não possa contribuir codificando, não deixe de relatar problemas e solicitar recursos que precisam ser implementados para atender ao contexto de seu dia a dia. + +### Authors + +
+ + + +
+ +### Mantenha Contato 🌐 + +[𝕏 (Twitter)](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq)
+[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Trademark + +**Nome** + +`Bruno` é uma marca registrada de [Anoop M D](https://www.helloanoop.com/). + +**Logo** + +A logo é original do [OpenMoji](https://openmoji.org/library/emoji-1F436/). Licença: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). + +### Licença 📄 + +[MIT](license.md) diff --git a/readme_ru.md b/docs/readme/readme_ru.md similarity index 90% rename from readme_ru.md rename to docs/readme/readme_ru.md index d8b8255f..0779bd5a 100644 --- a/readme_ru.md +++ b/docs/readme/readme_ru.md @@ -1,5 +1,5 @@
- + ### Bruno - IDE с открытым исходным кодом для изучения и тестирования API. @@ -10,7 +10,8 @@ [![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) | **Русский** + +[English](/readme.md) | [Українська](/readme_ua.md) | **Русский** | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [বাংলা](docs/readme/readme_bn.md) Bruno - новый и инновационный клиент API, направленный на революцию в установившейся ситуации, представленной Postman и подобными инструментами. @@ -20,17 +21,17 @@ Bruno хранит ваши коллекции непосредственно в Bruno работает только в автономном режиме. Добавление облачной синхронизации в Bruno не планируется. Мы ценим конфиденциальность ваших данных и считаем, что они должны оставаться на вашем устройстве. Ознакомьтесь с нашим долгосрочным видением [здесь](https://github.com/usebruno/bruno/discussions/269) -![bruno](assets/images/landing-2.png)

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

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

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

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

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

### Важные ссылки 📌 @@ -56,7 +57,7 @@ Bruno работает только в автономном режиме. Доб ### Внести вклад 👩‍💻🧑‍💻 -Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](contributing_ru.md) +Я рад, что Вы хотите улучшить Бруно. Пожалуйста, ознакомьтесь с [этим гайдом](../contributing/contributing_ru.md) Даже если вы не можете внести свой вклад с помощью кода, пожалуйста, не стесняйтесь сообщать об ошибках и пожеланиях к функциям, которые необходимо реализовать для решения вашей задачи. @@ -76,4 +77,4 @@ Bruno работает только в автономном режиме. Доб ### Лицензия 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/docs/readme/readme_tr.md b/docs/readme/readme_tr.md new file mode 100644 index 00000000..9e67a980 --- /dev/null +++ b/docs/readme/readme_tr.md @@ -0,0 +1,80 @@ +
+ + +### Bruno - API'leri keşfetmek ve test etmek için açık kaynaklı IDE. + +[![GitHub sürümü](https://badge.fury.io/gh/usebruno%2Fbruno.svg)](https://badge.fury.io/gh/usebruno%bruno) +[![CI](https://github.com/usebruno/bruno/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/usebruno/bruno/workflows/unit-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) +[![Web Sitesi](https://img.shields.io/badge/Website-Visit-blue)](https://www.usebruno.com) +[![İndir](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** | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [বাংলা](docs/readme/readme_bn.md) + +Bruno, Postman ve benzeri araçlar tarafından temsil edilen statükoda devrim yaratmayı amaçlayan yeni ve yenilikçi bir API istemcisidir. + +Bruno koleksiyonlarınızı doğrudan dosya sisteminizdeki bir klasörde saklar. API istekleri hakkındaki bilgileri kaydetmek için düz bir metin biçimlendirme dili olan Bru kullanıyoruz. + +API koleksiyonlarınız üzerinde işbirliği yapmak için git veya seçtiğiniz herhangi bir sürüm kontrolünü kullanabilirsiniz. + +Bruno yalnızca çevrimdışıdır. Bruno'ya bulut senkronizasyonu eklemek gibi bir planımız yok. Veri gizliliğinize değer veriyoruz ve cihazınızda kalması gerektiğine inanıyoruz. Uzun vadeli vizyonumuzu okuyun [burada](https://github.com/usebruno/bruno/discussions/269) + +![bruno](/assets/images/landing-2.png)

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

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

+ +### Önemli Bağlantılar 📌 + +- [Uzun Vadeli Vizyonumuz](https://github.com/usebruno/bruno/discussions/269) +- [Yol Haritası](https://github.com/usebruno/bruno/discussions/384) +- [Dokümantasyon](https://docs.usebruno.com) +- [Web sitesi](https://www.usebruno.com) +- [İndir](https://www.usebruno.com/downloads) + +### Vitrin 🎥 + +- [Görüşler](https://github.com/usebruno/bruno/discussions/343) +- [Bilgi Merkezi](https://github.com/usebruno/bruno/discussions/386) +- [Scriptmania](https://github.com/usebruno/bruno/discussions/385) + +### Destek ❤️ + +Woof! Projeyi beğendiyseniz, şu ⭐ düğmesine basın! + +### Referansları Paylaşın 📣 + +Bruno işinizde ve ekiplerinizde size yardımcı olduysa, lütfen [github tartışmamızdaki referanslarınızı](https://github.com/usebruno/bruno/discussions/343) paylaşmayı unutmayın + +### Katkıda Bulunun 👩‍💻🧑‍💻 + +Bruno'yu geliştirmek istemenize sevindim. Lütfen [katkıda bulunma kılavuzu](../contributing/contributing.md)'na göz atın + +Kod yoluyla katkıda bulunamasanız bile, lütfen kullanım durumunuzu çözmek için uygulanması gereken hataları ve özellik isteklerini bildirmekten çekinmeyin. + +### Katkıda Bulunanlar + +
+ + + +
+ +### İletişimde Kalın 🌐 + +[Twitter](https://twitter.com/use_bruno)
+[Website](https://www.usebruno.com)
+[Discord](https://discord.com/invite/KgcZUncpjq) +[LinkedIn](https://www.linkedin.com/company/usebruno) + +### Lisans 📄 + +[MIT](/license.md) diff --git a/readme_ua.md b/docs/readme/readme_ua.md similarity index 89% rename from readme_ua.md rename to docs/readme/readme_ua.md index 793e11a8..cd58ed56 100644 --- a/readme_ua.md +++ b/docs/readme/readme_ua.md @@ -1,5 +1,5 @@
- + ### Bruno - IDE із відкритим кодом для тестування та дослідження API @@ -10,7 +10,7 @@ [![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_ru.md) +[English](/readme.md) | **Українська** | [Русский](/readme_ru.md) | [Türkçe](/readme_tr.md) | [Deutsch](/readme_de.md) | [Français](/readme_fr.md) | [বাংলা](docs/readme/readme_bn.md) Bruno це новий та іноваційний API клієнт, націлений на революційну зміну статус кво, запровадженого інструментами на кшталт Postman. @@ -20,17 +20,17 @@ Bruno зберігає ваші колекції напряму у теці на Bruno є повністю автономним. Немає жодних планів додавати будь-які синхронізації через хмару, ніколи. Ми цінуємо приватність ваших даних, і вважаєм, що вони мають залишитись лише на вашому комп'ютері. Взнати більше про наше бачення у довготривалій перспективі можна [тут](https://github.com/usebruno/bruno/discussions/269) -![bruno](assets/images/landing-2.png)

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

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

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

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

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

### Важливі посилання 📌 @@ -56,7 +56,7 @@ Bruno є повністю автономним. Немає жодних план ### Зробити свій внесок 👩‍💻🧑‍💻 -Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](contributing_ua.md) +Я радий що ви бажаєте покращити Bruno. Будь ласка переглянте [інструкцію по контрибуції](../contributing/contributing_ua.md) Навіть якщо ви не можете зробити свій внесок пишучи програмний код, будь ласка не соромтесь рапортувати про помилки і писати запити на новий функціонал, який потрібен вам у вашій роботі. @@ -72,9 +72,9 @@ Bruno є повністю автономним. Немає жодних план [Twitter](https://twitter.com/use_bruno)
[Сайт](https://www.usebruno.com)
-[Discord](https://discord.com/invite/KgcZUncpjq) +[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno) ### Ліцензія 📄 -[MIT](license.md) +[MIT](/license.md) diff --git a/package-lock.json b/package-lock.json index a39d0c02..25b94f4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3491,6 +3491,74 @@ "node": ">=10" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/@n1ru4l/push-pull-async-iterable-iterator": { "version": "3.2.0", "license": "MIT", @@ -3568,6 +3636,52 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tough-cookie": { + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@postman/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/@react-dnd/asap": { "version": "5.0.2", "license": "MIT" @@ -5022,6 +5136,12 @@ "dev": true, "license": "MIT" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "node_modules/about-window": { "version": "1.15.2", "license": "MIT" @@ -5065,7 +5185,7 @@ }, "node_modules/agent-base": { "version": "6.0.2", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "debug": "4" @@ -5076,7 +5196,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5306,10 +5425,43 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, "node_modules/arcsecond": { "version": "5.0.0", "license": "MIT" }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/arg": { "version": "5.0.2", "license": "MIT" @@ -5464,7 +5616,6 @@ }, "node_modules/asn1": { "version": "0.2.6", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" @@ -5472,7 +5623,6 @@ }, "node_modules/assert-plus": { "version": "1.0.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8" @@ -5534,78 +5684,8 @@ "node": ">=10.12.0" } }, - "node_modules/autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/autoprefixer/node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/aws-sign2": { "version": "0.7.0", - "dev": true, "license": "Apache-2.0", "engines": { "node": "*" @@ -5615,13 +5695,6 @@ "version": "1.12.0", "license": "MIT" }, - "node_modules/axios": { - "version": "0.26.1", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, "node_modules/babel-jest": { "version": "29.3.1", "dev": true, @@ -5804,7 +5877,6 @@ }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" @@ -5996,8 +6068,17 @@ "node": ">=8" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/browserslist": { "version": "4.21.4", + "dev": true, "funding": [ { "type": "opencollective", @@ -6267,11 +6348,13 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6350,9 +6433,23 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/caseless": { "version": "0.12.0", - "dev": true, "license": "Apache-2.0" }, "node_modules/chai": { @@ -6437,6 +6534,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "dev": true, @@ -6484,6 +6590,18 @@ "node": ">= 10.0" } }, + "node_modules/cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", + "dependencies": { + "exit": "0.1.2", + "glob": "^7.1.1" + }, + "engines": { + "node": ">=0.2.5" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "dev": true, @@ -6583,6 +6701,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "dev": true, @@ -6654,6 +6780,15 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colord": { "version": "2.9.3", "dev": true, @@ -6859,6 +6994,20 @@ "typedarray-to-buffer": "^3.1.5" } }, + "node_modules/console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", + "dependencies": { + "date-now": "^0.1.4" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "license": "MIT", @@ -7228,7 +7377,6 @@ }, "node_modules/dashdash": { "version": "1.14.1", - "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" @@ -7237,6 +7385,11 @@ "node": ">=0.10" } }, + "node_modules/date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==" + }, "node_modules/debounce-fn": { "version": "4.0.0", "license": "MIT", @@ -7348,12 +7501,27 @@ "dev": true, "license": "MIT" }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-properties": { - "version": "1.1.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT", "optional": true, "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -7394,6 +7562,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "license": "MIT", @@ -7409,6 +7583,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "dev": true, @@ -7705,7 +7888,6 @@ }, "node_modules/ecc-jsbn": { "version": "0.1.2", - "dev": true, "license": "MIT", "dependencies": { "jsbn": "~0.1.0", @@ -7919,7 +8101,8 @@ "node_modules/electron-to-chromium": { "version": "1.4.554", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.554.tgz", - "integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==" + "integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==", + "dev": true }, "node_modules/electron-util": { "version": "0.17.2", @@ -8186,7 +8369,6 @@ }, "node_modules/exit": { "version": "0.1.2", - "dev": true, "engines": { "node": ">= 0.8.0" } @@ -8269,7 +8451,6 @@ }, "node_modules/extend": { "version": "3.0.2", - "dev": true, "license": "MIT" }, "node_modules/external-editor": { @@ -8333,7 +8514,6 @@ }, "node_modules/extsprintf": { "version": "1.3.0", - "devOptional": true, "engines": [ "node >=0.6.0" ], @@ -8359,7 +8539,6 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "devOptional": true, "license": "MIT" }, "node_modules/fast-xml-parser": { @@ -8438,6 +8617,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", + "integrity": "sha512-gwabMtChzdnpDJdPEpz8Vr/PX0pU85KailuPV71Zw/un5yJVKvzukhB3qf6O3lnTwIe5CxlMYLh3jOK3w5xrLA==" + }, "node_modules/file-dialog": { "version": "0.0.8", "license": "MIT" @@ -8610,7 +8794,6 @@ }, "node_modules/forever-agent": { "version": "0.6.1", - "dev": true, "license": "Apache-2.0", "engines": { "node": "*" @@ -8664,19 +8847,6 @@ "node": ">= 0.6" } }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "peer": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, "node_modules/fresh": { "version": "0.5.2", "license": "MIT", @@ -8702,6 +8872,36 @@ "node": ">=14.14" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "license": "ISC" @@ -8718,8 +8918,32 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } }, "node_modules/generic-names": { "version": "4.0.0", @@ -8760,12 +8984,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "license": "MIT", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8796,7 +9022,6 @@ }, "node_modules/getpass": { "version": "0.1.7", - "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" @@ -8995,6 +9220,17 @@ "csstype": "^3.0.10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/got": { "version": "9.6.0", "dev": true, @@ -9164,7 +9400,6 @@ }, "node_modules/har-validator": { "version": "5.1.5", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.3", @@ -9203,6 +9438,14 @@ "node": ">=0.10.0" } }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -9212,9 +9455,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.0", - "dev": true, "license": "MIT", - "optional": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -9222,6 +9463,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "license": "MIT", @@ -9232,6 +9484,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, "node_modules/has-yarn": { "version": "2.1.0", "dev": true, @@ -9260,6 +9518,17 @@ "node": ">=0.10.0" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "dev": true, @@ -9468,7 +9737,7 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.1", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -9506,8 +9775,9 @@ }, "node_modules/husky": { "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true, - "license": "MIT", "bin": { "husky": "lib/bin.js" }, @@ -10144,7 +10414,6 @@ }, "node_modules/is-typedarray": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/is-unicode-supported": { @@ -10206,7 +10475,6 @@ }, "node_modules/isstream": { "version": "0.1.2", - "dev": true, "license": "MIT" }, "node_modules/istanbul-lib-coverage": { @@ -10882,7 +11150,6 @@ }, "node_modules/jsbn": { "version": "0.1.1", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { @@ -10895,6 +11162,133 @@ "node": ">=4" } }, + "node_modules/jshint": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", + "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", + "dependencies": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.21", + "minimatch": "~3.0.2", + "strip-json-comments": "1.0.x" + }, + "bin": { + "jshint": "bin/jshint" + } + }, + "node_modules/jshint/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/jshint/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/jshint/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jshint/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/jshint/node_modules/domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/jshint/node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/jshint/node_modules/entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" + }, + "node_modules/jshint/node_modules/htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", + "dependencies": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "node_modules/jshint/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/jshint/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jshint/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/jshint/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/jshint/node_modules/strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==", + "bin": { + "strip-json-comments": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/json-buffer": { "version": "3.0.0", "dev": true, @@ -10912,12 +11306,10 @@ }, "node_modules/json-schema": { "version": "0.4.0", - "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "devOptional": true, "license": "MIT" }, "node_modules/json-schema-typed": { @@ -10926,7 +11318,6 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "dev": true, "license": "ISC" }, "node_modules/json5": { @@ -10949,6 +11340,21 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonlint": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", + "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==", + "dependencies": { + "JSV": "^4.0.x", + "nomnom": "^1.5.x" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/jsprim": { "version": "1.4.2", "dev": true, @@ -10981,6 +11387,14 @@ "extsprintf": "^1.2.0" } }, + "node_modules/JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==", + "engines": { + "node": "*" + } + }, "node_modules/kew": { "version": "0.7.0", "dev": true, @@ -11302,9 +11716,17 @@ "node": ">=12" } }, + "node_modules/make-cancellable-promise": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", + "integrity": "sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==", + "funding": { + "url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1" + } + }, "node_modules/make-dir": { "version": "3.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -11321,6 +11743,14 @@ "dev": true, "license": "ISC" }, + "node_modules/make-event-props": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", + "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, "node_modules/makeerror": { "version": "1.0.12", "dev": true, @@ -11408,6 +11838,22 @@ "version": "1.0.1", "license": "MIT" }, + "node_modules/merge-refs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz", + "integrity": "sha512-RwcT7GsQR3KbuLw1rRuodq4Nt547BKEBkliZ0qqsrpyNne9bGTFtsFIsIpx82huWhcl3kOlOlH4H0xkPk/DqVw==", + "funding": { + "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, @@ -11585,6 +12031,46 @@ "dev": true, "license": "MIT" }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/mkdirp": { "version": "0.5.6", "license": "MIT", @@ -11690,6 +12176,12 @@ "version": "0.0.8", "license": "ISC" }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, "node_modules/nanoclone": { "version": "0.2.1", "license": "MIT" @@ -11856,7 +12348,79 @@ "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/node-vault": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/node-vault/-/node-vault-0.10.2.tgz", + "integrity": "sha512-//uc9/YImE7Dx0QHdwMiAzLaOumiKUnOUP8DymgtkZ8nsq6/V2LKvEu6kw91Lcruw8lWUfj4DO7CIXNPRWBuuA==", + "dependencies": { + "debug": "^4.3.4", + "mustache": "^4.2.0", + "postman-request": "^2.88.1-postman.33", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha512-5s0JxqhDx9/rksG2BTMVN1enjWSvPidpoSgViZU4ZXULyTe+7jxcCRLB6f42Z0l1xYJpleCBtSyY6Lwg3uu5CQ==", + "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.", + "dependencies": { + "chalk": "~0.4.0", + "underscore": "~1.6.0" + } + }, + "node_modules/nomnom/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ==", + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==", + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -11889,15 +12453,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-url": { "version": "6.1.0", "dev": true, @@ -11933,6 +12488,18 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "dev": true, @@ -11958,7 +12525,6 @@ }, "node_modules/oauth-sign": { "version": "0.9.0", - "dev": true, "license": "Apache-2.0", "engines": { "node": "*" @@ -11979,8 +12545,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "license": "MIT", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12364,6 +12931,15 @@ "node": ">=8" } }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "1.1.1", "license": "MIT", @@ -12381,6 +12957,18 @@ "through": "~2.3" } }, + "node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, "node_modules/pend": { "version": "1.2.0", "dev": true, @@ -12388,7 +12976,6 @@ }, "node_modules/performance-now": { "version": "2.1.0", - "dev": true, "license": "MIT" }, "node_modules/phantomjs-prebuilt": { @@ -13199,6 +13786,99 @@ "form-data": "^4.0.0" } }, + "node_modules/postman-request": { + "version": "2.88.1-postman.33", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", + "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "dependencies": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postman-request/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/postman-request/node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/postman-request/node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/postman-request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/postman-request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/postman-request/node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/prepend-http": { "version": "2.0.0", "dev": true, @@ -13417,7 +14097,6 @@ }, "node_modules/psl": { "version": "1.9.0", - "dev": true, "license": "MIT" }, "node_modules/pump": { @@ -13480,6 +14159,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -13670,6 +14354,34 @@ "version": "18.2.0", "license": "MIT" }, + "node_modules/react-pdf": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.5.1.tgz", + "integrity": "sha512-NVno97L3wfX3RLGB3C+QtroOiQrgCKPHLMFKMSQaRqDlH3gkq2CB2NyXJ+IDQNLrT/gSMPPgtZQL8cOUySc/3w==", + "dependencies": { + "clsx": "^2.0.0", + "make-cancellable-promise": "^1.3.1", + "make-event-props": "^1.6.0", + "merge-refs": "^1.2.1", + "pdfjs-dist": "3.11.174", + "prop-types": "^15.6.2", + "tiny-invariant": "^1.0.0", + "tiny-warning": "^1.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-redux": { "version": "7.2.9", "license": "MIT", @@ -14060,6 +14772,11 @@ "dev": true, "license": "ISC" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/reselect": { "version": "4.1.7", "license": "MIT" @@ -14418,7 +15135,7 @@ }, "node_modules/semver": { "version": "6.3.0", - "dev": true, + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14546,9 +15263,23 @@ }, "node_modules/set-blocking": { "version": "2.0.0", - "dev": true, + "devOptional": true, "license": "ISC" }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "4.1.0", "funding": [ @@ -14619,6 +15350,61 @@ "version": "3.0.7", "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "license": "MIT", @@ -14769,7 +15555,6 @@ }, "node_modules/sshpk": { "version": "1.17.0", - "dev": true, "license": "MIT", "dependencies": { "asn1": "~0.2.3", @@ -14830,12 +15615,33 @@ "through": "~2.3.4" } }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dependencies": { + "bluebird": "^2.6.2" + } + }, + "node_modules/stream-length/node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" + }, "node_modules/streamsearch": { "version": "1.1.0", "engines": { "node": ">=10.0.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "license": "MIT", @@ -15228,6 +16034,14 @@ "node": ">= 10" } }, + "node_modules/system": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/system/-/system-2.0.1.tgz", + "integrity": "sha512-BwSUSa8LMHZouGadZ34ck3TsrH5s3oMmTKPK+xHdbBnTCZOZMJ38fHGKLAHkBl0PXru1Z4BsymQU4qqvTxWzdQ==", + "bin": { + "jscat": "bundle.js" + } + }, "node_modules/tailwindcss": { "version": "2.2.19", "license": "MIT", @@ -15308,6 +16122,41 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, "node_modules/temp-file": { "version": "3.4.0", "dev": true, @@ -15493,6 +16342,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "node_modules/tiny-warning": { "version": "1.0.3", "license": "MIT" @@ -15705,9 +16559,16 @@ "node": "*" } }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", - "dev": true, "license": "Unlicense" }, "node_modules/type-detect": { @@ -15778,6 +16639,11 @@ "node": ">=0.8.0" } }, + "node_modules/underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "dev": true, @@ -15843,6 +16709,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -15948,6 +16815,24 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url-parse-lax": { "version": "3.0.0", "dev": true, @@ -15959,6 +16844,25 @@ "node": ">=4" } }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/url/node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "license": "MIT", @@ -16320,6 +17224,15 @@ "dev": true, "license": "ISC" }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/widest-line": { "version": "3.1.0", "dev": true, @@ -16482,7 +17395,8 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { "node": ">=12" } @@ -16534,12 +17448,14 @@ "@tabler/icons": "^1.46.0", "@tippyjs/react": "^4.2.6", "@usebruno/graphql-docs": "0.1.0", - "@usebruno/schema": "0.5.0", - "axios": "^0.26.0", + "@usebruno/schema": "0.6.0", + "axios": "^1.5.1", "classnames": "^2.3.1", "codemirror": "^5.65.2", "codemirror-graphql": "^1.2.5", + "cookie": "^0.6.0", "escape-html": "^1.0.3", + "file": "^0.2.2", "file-dialog": "^0.0.8", "file-saver": "^2.0.5", "formik": "^2.2.9", @@ -16551,6 +17467,9 @@ "httpsnippet": "^3.0.1", "idb": "^7.0.0", "immer": "^9.0.15", + "jsesc": "^3.0.2", + "jshint": "^2.13.6", + "jsonlint": "^1.6.3", "know-your-http-well": "^0.5.0", "lodash": "^4.17.21", "markdown-it": "^13.0.2", @@ -16558,9 +17477,11 @@ "nanoid": "3.3.4", "next": "12.3.3", "path": "^0.12.7", + "pdfjs-dist": "^3.11.174", "platform": "^1.3.6", "posthog-node": "^2.1.0", "qs": "^6.11.0", + "query-string": "^7.0.1", "react": "18.2.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", @@ -16568,12 +17489,16 @@ "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", "react-inspector": "^6.0.2", + "react-pdf": "^7.5.1", "react-redux": "^7.2.6", "react-tooltip": "^5.5.2", "sass": "^1.46.0", "styled-components": "^5.3.3", + "system": "^2.0.1", "tailwindcss": "^2.2.19", + "url": "^0.11.3", "xml-formatter": "^3.5.0", + "yargs-parser": "^21.1.1", "yup": "^0.32.11" }, "devDependencies": { @@ -16599,6 +17524,32 @@ "version": "2.0.1", "license": "Python-2.0" }, + "packages/bruno-app/node_modules/axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "packages/bruno-app/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "packages/bruno-app/node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, "packages/bruno-app/node_modules/entities": { "version": "3.0.1", "license": "BSD-2-Clause", @@ -16609,6 +17560,25 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "packages/bruno-app/node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/bruno-app/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "packages/bruno-app/node_modules/markdown-it": { "version": "13.0.2", "license": "MIT", @@ -16623,13 +17593,38 @@ "markdown-it": "bin/markdown-it.js" } }, + "packages/bruno-app/node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/bruno-app/node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "engines": { + "node": ">=6" + } + }, "packages/bruno-cli": { "name": "@usebruno/cli", - "version": "0.14.0", + "version": "1.1.1", "license": "MIT", "dependencies": { - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", "axios": "^1.5.1", "chai": "^4.3.7", "chalk": "^3.0.0", @@ -16717,18 +17712,18 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v0.25.0", + "version": "v1.1.1", "dependencies": { "@aws-sdk/credential-providers": "^3.425.0", - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", - "@usebruno/schema": "0.5.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", + "@usebruno/schema": "0.6.0", "about-window": "^1.15.2", "aws4-axios": "^3.3.0", "axios": "^1.5.1", "chai": "^4.3.7", - "chai-string": "^1.5.0", "chokidar": "^3.5.3", + "content-disposition": "^0.5.4", "decomment": "^0.9.5", "dotenv": "^16.0.3", "electron-is-dev": "^2.0.0", @@ -16742,7 +17737,9 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "is-valid-path": "^0.1.1", + "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", "node-machine-id": "^1.1.12", @@ -16776,6 +17773,11 @@ "node": ">= 14" } }, + "packages/bruno-electron/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "packages/bruno-electron/node_modules/aws4-axios": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/aws4-axios/-/aws4-axios-3.3.0.tgz", @@ -16892,6 +17894,17 @@ "node": ">= 14" } }, + "packages/bruno-electron/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "packages/bruno-electron/node_modules/uuid": { "version": "9.0.0", "license": "MIT", @@ -16928,15 +17941,16 @@ }, "packages/bruno-js": { "name": "@usebruno/js", - "version": "0.8.0", + "version": "0.9.2", "license": "MIT", "dependencies": { "@usebruno/query": "0.1.0", "ajv": "^8.12.0", "atob": "^2.1.2", - "axios": "^0.26.0", + "axios": "^1.5.1", "btoa": "^1.2.1", "chai": "^4.3.7", + "chai-string": "^1.5.0", "crypto-js": "^4.1.1", "handlebars": "^4.7.8", "json-query": "^2.2.2", @@ -16944,6 +17958,7 @@ "moment": "^2.29.4", "nanoid": "3.3.4", "node-fetch": "2.*", + "node-vault": "^0.10.2", "uuid": "^9.0.0" }, "peerDependencies": { @@ -16964,6 +17979,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "packages/bruno-js/node_modules/axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "packages/bruno-js/node_modules/json-schema-traverse": { "version": "1.0.0", "license": "MIT" @@ -16977,7 +18002,7 @@ }, "packages/bruno-lang": { "name": "@usebruno/lang", - "version": "0.8.0", + "version": "0.9.0", "license": "MIT", "dependencies": { "arcsecond": "^5.0.0", @@ -17014,7 +18039,7 @@ }, "packages/bruno-schema": { "name": "@usebruno/schema", - "version": "0.5.0", + "version": "0.6.0", "license": "MIT", "peerDependencies": { "yup": "^0.32.11" @@ -19491,6 +20516,58 @@ } } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "@n1ru4l/push-pull-async-iterable-iterator": { "version": "3.2.0" }, @@ -19529,6 +20606,42 @@ "@popperjs/core": { "version": "2.11.6" }, + "@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "@postman/tough-cookie": { + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + } + } + }, + "@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "@react-dnd/asap": { "version": "5.0.2" }, @@ -20578,15 +21691,17 @@ "@tabler/icons": "^1.46.0", "@tippyjs/react": "^4.2.6", "@usebruno/graphql-docs": "0.1.0", - "@usebruno/schema": "0.5.0", - "axios": "^0.26.0", + "@usebruno/schema": "0.6.0", + "axios": "^1.5.1", "babel-loader": "^8.2.3", "classnames": "^2.3.1", "codemirror": "^5.65.2", "codemirror-graphql": "^1.2.5", + "cookie": "^0.6.0", "cross-env": "^7.0.3", "css-loader": "^6.5.1", "escape-html": "^1.0.3", + "file": "^0.2.2", "file-dialog": "^0.0.8", "file-loader": "^6.2.0", "file-saver": "^2.0.5", @@ -20601,6 +21716,9 @@ "httpsnippet": "^3.0.1", "idb": "^7.0.0", "immer": "^9.0.15", + "jsesc": "^3.0.2", + "jshint": "^2.13.6", + "jsonlint": "^1.6.3", "know-your-http-well": "^0.5.0", "lodash": "^4.17.21", "markdown-it": "^13.0.2", @@ -20609,10 +21727,12 @@ "nanoid": "3.3.4", "next": "12.3.3", "path": "^0.12.7", + "pdfjs-dist": "^3.11.174", "platform": "^1.3.6", "posthog-node": "^2.1.0", "prettier": "^2.7.1", "qs": "^6.11.0", + "query-string": "^7.0.1", "react": "18.2.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", @@ -20620,24 +21740,58 @@ "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", "react-inspector": "^6.0.2", + "react-pdf": "^7.5.1", "react-redux": "^7.2.6", "react-tooltip": "^5.5.2", "sass": "^1.46.0", "style-loader": "^3.3.1", "styled-components": "^5.3.3", + "system": "^2.0.1", "tailwindcss": "^2.2.19", + "url": "^0.11.3", "webpack": "^5.64.4", "webpack-cli": "^4.9.1", "xml-formatter": "^3.5.0", + "yargs-parser": "^21.1.1", "yup": "^0.32.11" }, "dependencies": { "argparse": { "version": "2.0.1" }, + "axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" + }, "entities": { "version": "3.0.1" }, + "filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + }, + "jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" + }, "markdown-it": { "version": "13.0.2", "requires": { @@ -20647,6 +21801,22 @@ "mdurl": "^1.0.1", "uc.micro": "^1.0.5" } + }, + "query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "requires": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" } } }, @@ -20660,8 +21830,8 @@ "@usebruno/cli": { "version": "file:packages/bruno-cli", "requires": { - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", "axios": "^1.5.1", "chai": "^4.3.7", "chalk": "^3.0.0", @@ -20753,9 +21923,10 @@ "@usebruno/query": "0.1.0", "ajv": "^8.12.0", "atob": "^2.1.2", - "axios": "^0.26.0", + "axios": "^1.5.1", "btoa": "^1.2.1", "chai": "^4.3.7", + "chai-string": "^1.5.0", "crypto-js": "^4.1.1", "handlebars": "^4.7.8", "json-query": "^2.2.2", @@ -20763,6 +21934,7 @@ "moment": "^2.29.4", "nanoid": "3.3.4", "node-fetch": "2.*", + "node-vault": "^0.10.2", "uuid": "^9.0.0" }, "dependencies": { @@ -20775,6 +21947,16 @@ "uri-js": "^4.2.2" } }, + "axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "json-schema-traverse": { "version": "1.0.0" }, @@ -20985,6 +22167,12 @@ "version": "5.1.1", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "about-window": { "version": "1.15.2" }, @@ -21011,14 +22199,13 @@ }, "agent-base": { "version": "6.0.2", - "dev": true, + "devOptional": true, "requires": { "debug": "4" } }, "ajv": { "version": "6.12.6", - "devOptional": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -21172,9 +22359,38 @@ "append-field": { "version": "1.0.0" }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, "arcsecond": { "version": "5.0.0" }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "arg": { "version": "5.0.2" }, @@ -21271,14 +22487,12 @@ }, "asn1": { "version": "0.2.6", - "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { - "version": "1.0.0", - "devOptional": true + "version": "1.0.0" }, "assertion-error": { "version": "1.1.0" @@ -21307,47 +22521,12 @@ "atomically": { "version": "1.7.0" }, - "autoprefixer": { - "version": "10.4.16", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", - "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==", - "peer": true, - "requires": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001538", - "fraction.js": "^4.3.6", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "dependencies": { - "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - } - } - } - }, "aws-sign2": { - "version": "0.7.0", - "dev": true + "version": "0.7.0" }, "aws4": { "version": "1.12.0" }, - "axios": { - "version": "0.26.1", - "requires": { - "follow-redirects": "^1.14.8" - } - }, "babel-jest": { "version": "29.3.1", "dev": true, @@ -21463,7 +22642,6 @@ }, "bcrypt-pbkdf": { "version": "1.0.2", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -21594,8 +22772,17 @@ "fill-range": "^7.0.1" } }, + "brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "requires": { + "base64-js": "^1.1.2" + } + }, "browserslist": { "version": "4.21.4", + "dev": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -21607,15 +22794,15 @@ "version": "file:packages/bruno-electron", "requires": { "@aws-sdk/credential-providers": "^3.425.0", - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", - "@usebruno/schema": "0.5.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", + "@usebruno/schema": "0.6.0", "about-window": "^1.15.2", "aws4-axios": "^3.3.0", "axios": "^1.5.1", "chai": "^4.3.7", - "chai-string": "^1.5.0", "chokidar": "^3.5.3", + "content-disposition": "^0.5.4", "decomment": "^0.9.5", "dmg-license": "^1.0.11", "dotenv": "^16.0.3", @@ -21633,7 +22820,9 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "is-valid-path": "^0.1.1", + "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", "node-machine-id": "^1.1.12", @@ -21654,6 +22843,11 @@ "debug": "^4.3.4" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "aws4-axios": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/aws4-axios/-/aws4-axios-3.3.0.tgz", @@ -21724,6 +22918,14 @@ "debug": "4" } }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, "uuid": { "version": "9.0.0" } @@ -21888,10 +23090,13 @@ } }, "call-bind": { - "version": "1.0.2", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" } }, "callsites": { @@ -21936,9 +23141,19 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==" }, + "canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + } + }, "caseless": { - "version": "0.12.0", - "dev": true + "version": "0.12.0" }, "chai": { "version": "4.3.7", @@ -21988,6 +23203,12 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true + }, "chrome-trace-event": { "version": "1.0.3", "dev": true @@ -22014,6 +23235,15 @@ "source-map": "~0.6.0" } }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==", + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + } + }, "cli-boxes": { "version": "2.2.1", "dev": true @@ -22065,6 +23295,11 @@ "mimic-response": "^1.0.0" } }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + }, "co": { "version": "4.6.0", "dev": true @@ -22109,6 +23344,12 @@ "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true + }, "colord": { "version": "2.9.3", "dev": true @@ -22255,6 +23496,20 @@ } } }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==", + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, "content-disposition": { "version": "0.5.4", "requires": { @@ -22486,11 +23741,15 @@ }, "dashdash": { "version": "1.14.1", - "dev": true, "requires": { "assert-plus": "^1.0.0" } }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==" + }, "debounce-fn": { "version": "4.0.0", "requires": { @@ -22553,11 +23812,24 @@ "version": "1.1.3", "dev": true }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, "define-properties": { - "version": "1.1.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "optional": true, "requires": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } @@ -22580,12 +23852,24 @@ "delayed-stream": { "version": "1.0.0" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, "depd": { "version": "2.0.0" }, "destroy": { "version": "1.2.0" }, + "detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true + }, "detect-newline": { "version": "3.1.0", "dev": true @@ -22790,7 +24074,6 @@ }, "ecc-jsbn": { "version": "0.1.2", - "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -22948,7 +24231,8 @@ "electron-to-chromium": { "version": "1.4.554", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.554.tgz", - "integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==" + "integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==", + "dev": true }, "electron-util": { "version": "0.17.2", @@ -23114,8 +24398,7 @@ "dev": true }, "exit": { - "version": "0.1.2", - "dev": true + "version": "0.1.2" }, "expect": { "version": "29.3.1", @@ -23182,8 +24465,7 @@ } }, "extend": { - "version": "3.0.2", - "dev": true + "version": "3.0.2" }, "external-editor": { "version": "3.1.0", @@ -23228,8 +24510,7 @@ } }, "extsprintf": { - "version": "1.3.0", - "devOptional": true + "version": "1.3.0" }, "fast-deep-equal": { "version": "3.1.3" @@ -23245,8 +24526,7 @@ } }, "fast-json-stable-stringify": { - "version": "2.1.0", - "devOptional": true + "version": "2.1.0" }, "fast-xml-parser": { "version": "4.2.5", @@ -23292,6 +24572,11 @@ } } }, + "file": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", + "integrity": "sha512-gwabMtChzdnpDJdPEpz8Vr/PX0pU85KailuPV71Zw/un5yJVKvzukhB3qf6O3lnTwIe5CxlMYLh3jOK3w5xrLA==" + }, "file-dialog": { "version": "0.0.8" }, @@ -23398,8 +24683,7 @@ "version": "1.15.2" }, "forever-agent": { - "version": "0.6.1", - "dev": true + "version": "0.6.1" }, "form-data": { "version": "4.0.0", @@ -23429,12 +24713,6 @@ "forwarded": { "version": "0.2.0" }, - "fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "peer": true - }, "fresh": { "version": "0.5.2" }, @@ -23452,6 +24730,32 @@ "universalify": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "fs.realpath": { "version": "1.0.0" }, @@ -23460,7 +24764,26 @@ "optional": true }, "function-bind": { - "version": "1.1.1" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } }, "generic-names": { "version": "4.0.0", @@ -23486,11 +24809,14 @@ "version": "2.0.0" }, "get-intrinsic": { - "version": "1.1.3", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-own-enumerable-property-symbols": { @@ -23506,7 +24832,6 @@ }, "getpass": { "version": "0.1.7", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -23640,6 +24965,14 @@ "version": "2.1.11", "requires": {} }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "9.6.0", "dev": true, @@ -23755,7 +25088,6 @@ }, "har-validator": { "version": "5.1.5", - "dev": true, "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" @@ -23780,20 +25112,34 @@ } } }, + "has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==" + }, "has-flag": { "version": "4.0.0" }, "has-property-descriptors": { "version": "1.0.0", - "dev": true, - "optional": true, "requires": { "get-intrinsic": "^1.1.1" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, "has-symbols": { "version": "1.0.3" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, "has-yarn": { "version": "2.1.0", "dev": true @@ -23812,6 +25158,14 @@ } } }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "dev": true @@ -23945,7 +25299,7 @@ }, "https-proxy-agent": { "version": "5.0.1", - "dev": true, + "devOptional": true, "requires": { "agent-base": "6", "debug": "4" @@ -23968,6 +25322,8 @@ }, "husky": { "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true }, "icon-gen": { @@ -24320,8 +25676,7 @@ "dev": true }, "is-typedarray": { - "version": "1.0.0", - "dev": true + "version": "1.0.0" }, "is-unicode-supported": { "version": "1.3.0" @@ -24355,8 +25710,7 @@ "version": "3.0.1" }, "isstream": { - "version": "0.1.2", - "dev": true + "version": "0.1.2" }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -24829,12 +26183,121 @@ } }, "jsbn": { - "version": "0.1.1", - "dev": true + "version": "0.1.1" }, "jsesc": { "version": "2.5.2" }, + "jshint": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz", + "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==", + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.21", + "minimatch": "~3.0.2", + "strip-json-comments": "1.0.x" + }, + "dependencies": { + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==" + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==", + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==" + } + } + }, "json-buffer": { "version": "3.0.0", "dev": true @@ -24846,19 +26309,16 @@ "version": "2.2.2" }, "json-schema": { - "version": "0.4.0", - "dev": true + "version": "0.4.0" }, "json-schema-traverse": { - "version": "0.4.1", - "devOptional": true + "version": "0.4.1" }, "json-schema-typed": { "version": "7.0.3" }, "json-stringify-safe": { - "version": "5.0.1", - "dev": true + "version": "5.0.1" }, "json5": { "version": "2.2.3" @@ -24870,6 +26330,15 @@ "universalify": "^2.0.0" } }, + "jsonlint": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", + "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==", + "requires": { + "JSV": "^4.0.x", + "nomnom": "^1.5.x" + } + }, "jsprim": { "version": "1.4.2", "dev": true, @@ -24895,6 +26364,11 @@ } } }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==" + }, "kew": { "version": "0.7.0", "dev": true @@ -25116,9 +26590,14 @@ "@jridgewell/sourcemap-codec": "^1.4.13" } }, + "make-cancellable-promise": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", + "integrity": "sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==" + }, "make-dir": { "version": "3.1.0", - "dev": true, + "devOptional": true, "requires": { "semver": "^6.0.0" } @@ -25127,6 +26606,11 @@ "version": "1.3.6", "dev": true }, + "make-event-props": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", + "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==" + }, "makeerror": { "version": "1.0.12", "dev": true, @@ -25186,6 +26670,12 @@ "merge-descriptors": { "version": "1.0.1" }, + "merge-refs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz", + "integrity": "sha512-RwcT7GsQR3KbuLw1rRuodq4Nt547BKEBkliZ0qqsrpyNne9bGTFtsFIsIpx82huWhcl3kOlOlH4H0xkPk/DqVw==", + "requires": {} + }, "merge-stream": { "version": "2.0.0", "dev": true @@ -25284,6 +26774,39 @@ "version": "1.2.0", "dev": true }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "optional": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "mkdirp": { "version": "0.5.6", "requires": { @@ -25350,6 +26873,12 @@ "mute-stream": { "version": "0.0.8" }, + "nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, "nanoclone": { "version": "0.2.1" }, @@ -25443,7 +26972,59 @@ "node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node-vault": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/node-vault/-/node-vault-0.10.2.tgz", + "integrity": "sha512-//uc9/YImE7Dx0QHdwMiAzLaOumiKUnOUP8DymgtkZ8nsq6/V2LKvEu6kw91Lcruw8lWUfj4DO7CIXNPRWBuuA==", + "requires": { + "debug": "^4.3.4", + "mustache": "^4.2.0", + "postman-request": "^2.88.1-postman.33", + "tv4": "^1.3.0" + } + }, + "nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha512-5s0JxqhDx9/rksG2BTMVN1enjWSvPidpoSgViZU4ZXULyTe+7jxcCRLB6f42Z0l1xYJpleCBtSyY6Lwg3uu5CQ==", + "requires": { + "chalk": "~0.4.0", + "underscore": "~1.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==" + }, + "chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ==", + "requires": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + } + }, + "strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==" + } + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } }, "normalize-package-data": { "version": "2.5.0", @@ -25468,12 +27049,6 @@ "normalize-path": { "version": "3.0.0" }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "peer": true - }, "normalize-url": { "version": "6.1.0", "dev": true @@ -25494,6 +27069,18 @@ "path-key": "^3.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "nth-check": { "version": "2.1.1", "dev": true, @@ -25509,8 +27096,7 @@ "dev": true }, "oauth-sign": { - "version": "0.9.0", - "dev": true + "version": "0.9.0" }, "object-assign": { "version": "4.1.1" @@ -25519,7 +27105,9 @@ "version": "2.2.0" }, "object-inspect": { - "version": "1.12.3" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-keys": { "version": "1.1.1", @@ -25757,6 +27345,12 @@ "path-type": { "version": "4.0.0" }, + "path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "optional": true + }, "pathval": { "version": "1.1.1" }, @@ -25766,13 +27360,21 @@ "through": "~2.3" } }, + "pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "requires": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, "pend": { "version": "1.2.0", "dev": true }, "performance-now": { - "version": "2.1.0", - "dev": true + "version": "2.1.0" }, "phantomjs-prebuilt": { "version": "2.1.16", @@ -26216,6 +27818,83 @@ } } }, + "postman-request": { + "version": "2.88.1-postman.33", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", + "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", + "requires": { + "@postman/form-data": "~3.1.1", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.12.0", + "brotli": "^1.3.3", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "^2.1.35", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.3", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "uuid": "^8.3.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } + }, "prepend-http": { "version": "2.0.0", "dev": true @@ -26352,8 +28031,7 @@ "version": "1.1.0" }, "psl": { - "version": "1.9.0", - "dev": true + "version": "1.9.0" }, "pump": { "version": "3.0.0", @@ -26393,6 +28071,11 @@ "side-channel": "^1.0.4" } }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "queue-microtask": { "version": "1.2.3" }, @@ -26494,6 +28177,21 @@ "react-is": { "version": "18.2.0" }, + "react-pdf": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.5.1.tgz", + "integrity": "sha512-NVno97L3wfX3RLGB3C+QtroOiQrgCKPHLMFKMSQaRqDlH3gkq2CB2NyXJ+IDQNLrT/gSMPPgtZQL8cOUySc/3w==", + "requires": { + "clsx": "^2.0.0", + "make-cancellable-promise": "^1.3.1", + "make-event-props": "^1.6.0", + "merge-refs": "^1.2.1", + "pdfjs-dist": "3.11.174", + "prop-types": "^15.6.2", + "tiny-invariant": "^1.0.0", + "tiny-warning": "^1.0.0" + } + }, "react-redux": { "version": "7.2.9", "requires": { @@ -26779,6 +28477,11 @@ "version": "1.0.1", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "reselect": { "version": "4.1.7" }, @@ -26999,7 +28702,7 @@ }, "semver": { "version": "6.3.0", - "dev": true + "devOptional": true }, "semver-compare": { "version": "1.0.0", @@ -27092,7 +28795,18 @@ }, "set-blocking": { "version": "2.0.0", - "dev": true + "devOptional": true + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } }, "set-value": { "version": "4.1.0", @@ -27136,6 +28850,40 @@ "signal-exit": { "version": "3.0.7" }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + }, + "dependencies": { + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true + } + } + }, "simple-swizzle": { "version": "0.2.2", "requires": { @@ -27246,7 +28994,6 @@ }, "sshpk": { "version": "1.17.0", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -27284,9 +29031,29 @@ "through": "~2.3.4" } }, + "stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "requires": { + "bluebird": "^2.6.2" + }, + "dependencies": { + "bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" + } + } + }, "streamsearch": { "version": "1.1.0" }, + "strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==" + }, "string_decoder": { "version": "1.1.1", "requires": { @@ -27537,6 +29304,11 @@ } } }, + "system": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/system/-/system-2.0.1.tgz", + "integrity": "sha512-BwSUSa8LMHZouGadZ34ck3TsrH5s3oMmTKPK+xHdbBnTCZOZMJ38fHGKLAHkBl0PXru1Z4BsymQU4qqvTxWzdQ==" + }, "tailwindcss": { "version": "2.2.19", "requires": { @@ -27596,6 +29368,34 @@ "version": "2.2.1", "dev": true }, + "tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "optional": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } + } + }, "temp-file": { "version": "3.4.0", "dev": true, @@ -27717,6 +29517,11 @@ "version": "1.7.1", "dev": true }, + "tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, "tiny-warning": { "version": "1.0.3" }, @@ -27844,9 +29649,13 @@ "safe-buffer": "^5.0.1" } }, + "tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==" + }, "tweetnacl": { - "version": "0.14.5", - "dev": true + "version": "0.14.5" }, "type-detect": { "version": "4.0.8" @@ -27883,6 +29692,11 @@ "version": "3.17.4", "optional": true }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "dev": true @@ -27920,6 +29734,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -27982,6 +29797,39 @@ "punycode": "^2.1.0" } }, + "url": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "requires": { + "punycode": "^1.4.1", + "qs": "^6.11.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "requires": { + "side-channel": "^1.0.4" + } + } + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "url-parse-lax": { "version": "3.0.0", "dev": true, @@ -28220,6 +30068,15 @@ "version": "1.0.0", "dev": true }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "widest-line": { "version": "3.1.0", "dev": true, @@ -28322,7 +30179,9 @@ } }, "yargs-parser": { - "version": "21.1.1" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yauzl": { "version": "2.10.0", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 8ead3b92..ed697c3c 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -3,7 +3,7 @@ "version": "0.3.0", "private": true, "scripts": { - "dev": "cross-env ENV=dev next dev", + "dev": "cross-env ENV=dev next dev -p 3000", "build": "next build && next export", "start": "next start", "lint": "next lint", @@ -19,12 +19,14 @@ "@tabler/icons": "^1.46.0", "@tippyjs/react": "^4.2.6", "@usebruno/graphql-docs": "0.1.0", - "@usebruno/schema": "0.5.0", - "axios": "^0.26.0", + "@usebruno/schema": "0.6.0", + "axios": "^1.5.1", "classnames": "^2.3.1", "codemirror": "^5.65.2", "codemirror-graphql": "^1.2.5", + "cookie": "^0.6.0", "escape-html": "^1.0.3", + "file": "^0.2.2", "file-dialog": "^0.0.8", "file-saver": "^2.0.5", "formik": "^2.2.9", @@ -36,6 +38,9 @@ "httpsnippet": "^3.0.1", "idb": "^7.0.0", "immer": "^9.0.15", + "jsesc": "^3.0.2", + "jshint": "^2.13.6", + "jsonlint": "^1.6.3", "know-your-http-well": "^0.5.0", "lodash": "^4.17.21", "markdown-it": "^13.0.2", @@ -43,9 +48,11 @@ "nanoid": "3.3.4", "next": "12.3.3", "path": "^0.12.7", + "pdfjs-dist": "^3.11.174", "platform": "^1.3.6", "posthog-node": "^2.1.0", "qs": "^6.11.0", + "query-string": "^7.0.1", "react": "18.2.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", @@ -53,12 +60,16 @@ "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", "react-inspector": "^6.0.2", + "react-pdf": "^7.5.1", "react-redux": "^7.2.6", "react-tooltip": "^5.5.2", "sass": "^1.46.0", "styled-components": "^5.3.3", + "system": "^2.0.1", "tailwindcss": "^2.2.19", + "url": "^0.11.3", "xml-formatter": "^3.5.0", + "yargs-parser": "^21.1.1", "yup": "^0.32.11" }, "devDependencies": { diff --git a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js index fe0f4cc1..3623d406 100644 --- a/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/CodeEditor/StyledWrapper.js @@ -5,6 +5,7 @@ const StyledWrapper = styled.div` background: ${(props) => props.theme.codemirror.bg}; border: solid 1px ${(props) => props.theme.codemirror.border}; font-family: ${(props) => (props.font ? props.font : 'default')}; + line-break: anywhere; } .CodeMirror-overlayscroll-horizontal div, diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index 6ad999e6..565e3ec8 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -10,12 +10,85 @@ import isEqual from 'lodash/isEqual'; import { getEnvironmentVariables } from 'utils/collections'; import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror'; import StyledWrapper from './StyledWrapper'; +import jsonlint from 'jsonlint'; +import { JSHINT } from 'jshint'; let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); + window.jsonlint = jsonlint; + window.JSHINT = JSHINT; + //This should be done dynamically if possible + const hintWords = [ + 'res', + 'res.status', + 'res.statusText', + 'res.headers', + 'res.body', + 'res.getStatus()', + 'res.getHeader(name)', + 'res.getHeaders()', + 'res.getBody()', + 'req', + 'req.url', + 'req.method', + 'req.headers', + 'req.body', + 'req.timeout', + 'req.getUrl()', + 'req.setUrl(url)', + 'req.getMethod()', + 'req.setMethod(method)', + 'req.getHeader(name)', + 'req.getHeaders()', + 'req.setHeader(name, value)', + 'req.setHeaders(data)', + 'req.getBody()', + 'req.setBody(data)', + 'req.setMaxRedirects(maxRedirects)', + 'req.getTimeout()', + 'req.setTimeout(timeout)', + 'bru', + 'bru.cwd()', + 'bru.getEnvName(key)', + 'bru.getProcessEnv(key)', + 'bru.getEnvVar(key)', + 'bru.setEnvVar(key,value)', + 'bru.getVar(key)', + 'bru.setVar(key,value)' + ]; + CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => { + const cursor = editor.getCursor(); + const currentLine = editor.getLine(cursor.line); + let startBru = cursor.ch; + let endBru = startBru; + while (endBru < currentLine.length && /[\w.]/.test(currentLine.charAt(endBru))) ++endBru; + while (startBru && /[\w.]/.test(currentLine.charAt(startBru - 1))) --startBru; + let curWordBru = startBru != endBru && currentLine.slice(startBru, endBru); + + let start = cursor.ch; + let end = start; + while (end < currentLine.length && /[\w]/.test(currentLine.charAt(end))) ++end; + while (start && /[\w]/.test(currentLine.charAt(start - 1))) --start; + const jsHinter = CodeMirror.hint.javascript; + let result = jsHinter(editor) || { list: [] }; + result.to = CodeMirror.Pos(cursor.line, end); + result.from = CodeMirror.Pos(cursor.line, start); + if (curWordBru) { + hintWords.forEach((h) => { + if (h.includes('.') == curWordBru.includes('.') && h.startsWith(curWordBru)) { + result.list.push(curWordBru.includes('.') ? h.split('.')[1] : h); + } + }); + result.list?.sort(); + } + return result; + }); + CodeMirror.commands.autocomplete = (cm, hint, options) => { + cm.showHint({ hint, ...options }); + }; } export default class CodeEditor extends React.Component { @@ -41,7 +114,8 @@ export default class CodeEditor extends React.Component { matchBrackets: true, showCursorWhenSelecting: true, foldGutter: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], + lint: { esversion: 11 }, readOnly: this.props.readOnly, scrollbarStyle: 'overlay', theme: this.props.theme === 'dark' ? 'monokai' : 'default', @@ -68,15 +142,72 @@ export default class CodeEditor extends React.Component { }, 'Cmd-F': 'findPersistent', 'Ctrl-F': 'findPersistent', + 'Cmd-H': 'replace', + 'Ctrl-H': 'replace', Tab: function (cm) { - cm.replaceSelection(' ', 'end'); + cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection() + ? cm.execCommand('indentMore') + : cm.replaceSelection(' ', 'end'); + }, + 'Shift-Tab': 'indentLess', + 'Ctrl-Space': 'autocomplete', + 'Cmd-Space': 'autocomplete', + 'Ctrl-Y': 'foldAll', + 'Cmd-Y': 'foldAll', + 'Ctrl-I': 'unfoldAll', + 'Cmd-I': 'unfoldAll' + }, + foldOptions: { + widget: (from, to) => { + var count = undefined; + var internal = this.editor.getRange(from, to); + if (this.props.mode == 'application/ld+json') { + if (this.editor.getLine(from.line).endsWith('[')) { + var toParse = '[' + internal + ']'; + } else var toParse = '{' + internal + '}'; + try { + count = Object.keys(JSON.parse(toParse)).length; + } catch (e) {} + } else if (this.props.mode == 'application/xml') { + var doc = new DOMParser(); + try { + //add header element and remove prefix namespaces for DOMParser + var dcm = doc.parseFromString( + ' ' + internal.replace(/(?<=\<|<\/)\w+:/g, '') + '', + 'application/xml' + ); + count = dcm.documentElement.children.length; + } catch (e) {} + } + return count ? `\u21A4${count}\u21A6` : '\u2194'; } } })); if (editor) { + editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? { esversion: 11 } : false); editor.on('change', this._onEdit); this.addOverlay(); } + if (this.props.mode == 'javascript') { + editor.on('keyup', function (cm, event) { + const cursor = editor.getCursor(); + const currentLine = editor.getLine(cursor.line); + let start = cursor.ch; + let end = start; + while (end < currentLine.length && /[^{}();\s\[\]\,]/.test(currentLine.charAt(end))) ++end; + while (start && /[^{}();\s\[\]\,]/.test(currentLine.charAt(start - 1))) --start; + let curWord = start != end && currentLine.slice(start, end); + //Qualify if autocomplete will be shown + if ( + /^(?!Shift|Tab|Enter|ArrowUp|ArrowDown|ArrowLeft|ArrowRight|\s)\w*/.test(event.key) && + curWord.length > 0 && + !/\/\/|\/\*|.*{{|`[^$]*{|`[^{]*$/.test(currentLine.slice(0, end)) && + /(? { if (!this.ignoreChangeEvent && this.editor) { + this.editor.setOption('lint', this.editor.getValue().trim().length > 0 ? { esversion: 11 } : false); this.cachedValue = this.editor.getValue(); if (this.props.onEdit) { this.props.onEdit(this.cachedValue); diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js index 5cf3a5f7..747ee4d6 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/AuthMode/index.js @@ -61,6 +61,15 @@ const AuthMode = ({ collection }) => { > Bearer Token +
{ + dropdownTippyRef.current.hide(); + onModeChange('digest'); + }} + > + Digest Auth +
{ diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/StyledWrapper.js new file mode 100644 index 00000000..c2bb5d20 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js new file mode 100644 index 00000000..3e084c06 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/DigestAuth/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections'; +import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const DigestAuth = ({ collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const digestAuth = get(collection, 'root.request.auth.digest', {}); + + const handleSave = () => dispatch(saveCollectionRoot(collection.uid)); + + const handleUsernameChange = (username) => { + dispatch( + updateCollectionAuth({ + mode: 'digest', + collectionUid: collection.uid, + content: { + username: username, + password: digestAuth.password + } + }) + ); + }; + + const handlePasswordChange = (password) => { + dispatch( + updateCollectionAuth({ + mode: 'digest', + collectionUid: collection.uid, + content: { + username: digestAuth.username, + password: password + } + }) + ); + }; + + return ( + + +
+ handleUsernameChange(val)} + collection={collection} + /> +
+ + +
+ handlePasswordChange(val)} + collection={collection} + /> +
+
+ ); +}; + +export default DigestAuth; diff --git a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js index d9e80358..7873cfcd 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Auth/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Auth/index.js @@ -5,6 +5,7 @@ import AuthMode from './AuthMode'; import AwsV4Auth from './AwsV4Auth'; import BearerAuth from './BearerAuth'; import BasicAuth from './BasicAuth'; +import DigestAuth from './DigestAuth'; import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions'; import StyledWrapper from './StyledWrapper'; @@ -25,6 +26,9 @@ const Auth = ({ collection }) => { case 'bearer': { return ; } + case 'digest': { + return ; + } } }; diff --git a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js index fd3cc898..c8ccb103 100644 --- a/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/ProxySettings/index.js @@ -7,26 +7,24 @@ import toast from 'react-hot-toast'; const ProxySettings = ({ proxyConfig, onUpdate }) => { const proxySchema = Yup.object({ - use: Yup.string().oneOf(['global', 'true', 'false']), + enabled: Yup.string().oneOf(['global', 'true', 'false']), protocol: Yup.string().oneOf(['http', 'https', 'socks4', 'socks5']), hostname: Yup.string() - .when('use', { - is: true, + .when('enabled', { + is: 'true', then: (hostname) => hostname.required('Specify the hostname for your proxy.'), otherwise: (hostname) => hostname.nullable() }) .max(1024), port: Yup.number() - .when('use', { - is: true, - then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), - otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) - }) .min(1) - .max(65535), + .max(65535) + .typeError('Specify port between 1 and 65535') + .nullable() + .transform((_, val) => (val ? Number(val) : null)), auth: Yup.object() .when('enabled', { - is: true, + is: 'true', then: Yup.object({ enabled: Yup.boolean(), username: Yup.string() @@ -49,7 +47,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { const formik = useFormik({ initialValues: { - use: proxyConfig.use || 'global', + enabled: proxyConfig.enabled || 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -65,11 +63,11 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { proxySchema .validate(values, { abortEarly: true }) .then((validatedProxy) => { - // serialize 'use' to boolean - if (validatedProxy.use === 'true') { - validatedProxy.use = true; - } else if (validatedProxy.use === 'false') { - validatedProxy.use = false; + // serialize 'enabled' to boolean + if (validatedProxy.enabled === 'true') { + validatedProxy.enabled = true; + } else if (validatedProxy.enabled === 'false') { + validatedProxy.enabled = false; } onUpdate(validatedProxy); @@ -83,7 +81,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => { useEffect(() => { formik.setValues({ - use: proxyConfig.use === true ? 'true' : proxyConfig.use === false ? 'false' : 'global', + enabled: proxyConfig.enabled === true ? 'true' : proxyConfig.enabled === false ? 'false' : 'global', protocol: proxyConfig.protocol || 'http', hostname: proxyConfig.hostname || '', port: proxyConfig.port || '', @@ -120,9 +118,9 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
-
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js deleted file mode 100644 index a5aa3e0c..00000000 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/reducer.js +++ /dev/null @@ -1,52 +0,0 @@ -import produce from 'immer'; -import find from 'lodash/find'; -import filter from 'lodash/filter'; -import { uuid } from 'utils/common'; - -const reducer = (state, action) => { - switch (action.type) { - case 'ADD_VAR': { - return produce(state, (draft) => { - draft.variables.push({ - uid: uuid(), - name: '', - value: '', - type: 'text', - secret: false, - enabled: true - }); - draft.hasChanges = true; - }); - } - - case 'UPDATE_VAR': { - return produce(state, (draft) => { - const variable = find(draft.variables, (v) => v.uid === action.variable.uid); - variable.name = action.variable.name; - variable.value = action.variable.value; - variable.enabled = action.variable.enabled; - variable.secret = action.variable.secret; - draft.hasChanges = true; - }); - } - - case 'DELETE_VAR': { - return produce(state, (draft) => { - draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid); - draft.hasChanges = true; - }); - } - - case 'CHANGES_SAVED': { - return produce(state, (draft) => { - draft.hasChanges = false; - }); - } - - default: { - return state; - } - } -}; - -export default reducer; diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js index 687cde46..330ae082 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/StyledWrapper.js @@ -11,6 +11,8 @@ const StyledWrapper = styled.div` border-right: solid 1px ${(props) => props.theme.collection.environment.settings.sidebar.borderRight}; min-height: 400px; height: 100%; + max-height: 85vh; + overflow-y: auto; } .environment-item { @@ -43,10 +45,8 @@ const StyledWrapper = styled.div` border-bottom: none; color: ${(props) => props.theme.textLink}; - &:hover { - span { - text-decoration: underline; - } + span:hover { + text-decoration: underline; } } diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js index dd7ac4f7..9886b54b 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/index.js @@ -1,12 +1,11 @@ import React, { useEffect, useState, forwardRef, useRef } from 'react'; import { findEnvironmentInCollection } from 'utils/collections'; -import toast from 'react-hot-toast'; -import { toastError } from 'utils/common/error'; import usePrevious from 'hooks/usePrevious'; import EnvironmentDetails from './EnvironmentDetails'; import CreateEnvironment from '../CreateEnvironment'; -import { IconDownload } from '@tabler/icons'; +import { IconDownload, IconShieldLock } from '@tabler/icons'; import ImportEnvironment from '../ImportEnvironment'; +import ManageSecrets from '../ManageSecrets'; import StyledWrapper from './StyledWrapper'; const EnvironmentList = ({ collection }) => { @@ -14,6 +13,7 @@ const EnvironmentList = ({ collection }) => { const [selectedEnvironment, setSelectedEnvironment] = useState(null); const [openCreateModal, setOpenCreateModal] = useState(false); const [openImportModal, setOpenImportModal] = useState(false); + const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false); const envUids = environments ? environments.map((env) => env.uid) : []; const prevEnvUids = usePrevious(envUids); @@ -54,6 +54,7 @@ const EnvironmentList = ({ collection }) => { {openCreateModal && setOpenCreateModal(false)} />} {openImportModal && setOpenImportModal(false)} />} + {openManageSecretsModal && setOpenManageSecretsModal(false)} />}
@@ -72,9 +73,15 @@ const EnvironmentList = ({ collection }) => { + Create
-
setOpenImportModal(true)}> - - Import +
+
setOpenImportModal(true)}> + + Import +
+
setOpenManageSecretsModal(true)}> + + Managing Secrets +
diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js new file mode 100644 index 00000000..6c71493f --- /dev/null +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/ManageSecrets/index.js @@ -0,0 +1,31 @@ +import React from 'react'; +import Portal from 'components/Portal'; +import Modal from 'components/Modal'; + +const ManageSecrets = ({ onClose }) => { + return ( + + +
+

In any collection, there are secrets that need to be managed.

+

These secrets can be anything such as API keys, passwords, or tokens.

+

Bruno offers two approaches to manage secrets in collections.

+

+ Read more about it in our{' '} + + docs + + . +

+
+
+
+ ); +}; + +export default ManageSecrets; diff --git a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js index 6b3fb787..788f7927 100644 --- a/packages/bruno-app/src/components/Preferences/ProxySettings/index.js +++ b/packages/bruno-app/src/components/Preferences/ProxySettings/index.js @@ -22,13 +22,11 @@ const ProxySettings = ({ close }) => { }) .max(1024), port: Yup.number() - .when('enabled', { - is: true, - then: (port) => port.required('Specify port between 1 and 65535').typeError('Specify port between 1 and 65535'), - otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null)) - }) .min(1) - .max(65535), + .max(65535) + .typeError('Specify port between 1 and 65535') + .nullable() + .transform((_, val) => (val ? Number(val) : null)), auth: Yup.object() .when('enabled', { is: true, diff --git a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js index 10130c3a..8eb4fee9 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/AuthMode/index.js @@ -62,6 +62,15 @@ const AuthMode = ({ item, collection }) => { > Bearer Token
+
{ + dropdownTippyRef.current.hide(); + onModeChange('digest'); + }} + > + Digest Auth +
{ diff --git a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/StyledWrapper.js new file mode 100644 index 00000000..c2bb5d20 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/StyledWrapper.js @@ -0,0 +1,16 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + label { + font-size: 0.8125rem; + } + + .single-line-editor-wrapper { + padding: 0.15rem 0.4rem; + border-radius: 3px; + border: solid 1px ${(props) => props.theme.input.border}; + background-color: ${(props) => props.theme.input.bg}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js new file mode 100644 index 00000000..e43f18c4 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/Auth/DigestAuth/index.js @@ -0,0 +1,76 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch } from 'react-redux'; +import SingleLineEditor from 'components/SingleLineEditor'; +import { updateAuth } from 'providers/ReduxStore/slices/collections'; +import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; +import StyledWrapper from './StyledWrapper'; + +const DigestAuth = ({ item, collection }) => { + const dispatch = useDispatch(); + const { storedTheme } = useTheme(); + + const digestAuth = item.draft ? get(item, 'draft.request.auth.digest', {}) : get(item, 'request.auth.digest', {}); + + const handleRun = () => dispatch(sendRequest(item, collection.uid)); + const handleSave = () => dispatch(saveRequest(item.uid, collection.uid)); + + const handleUsernameChange = (username) => { + dispatch( + updateAuth({ + mode: 'digest', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + username: username, + password: digestAuth.password + } + }) + ); + }; + + const handlePasswordChange = (password) => { + dispatch( + updateAuth({ + mode: 'digest', + collectionUid: collection.uid, + itemUid: item.uid, + content: { + username: digestAuth.username, + password: password + } + }) + ); + }; + + return ( + + +
+ handleUsernameChange(val)} + onRun={handleRun} + collection={collection} + /> +
+ + +
+ handlePasswordChange(val)} + onRun={handleRun} + collection={collection} + /> +
+
+ ); +}; + +export default DigestAuth; diff --git a/packages/bruno-app/src/components/RequestPane/Auth/index.js b/packages/bruno-app/src/components/RequestPane/Auth/index.js index 669f4a84..bd388737 100644 --- a/packages/bruno-app/src/components/RequestPane/Auth/index.js +++ b/packages/bruno-app/src/components/RequestPane/Auth/index.js @@ -4,6 +4,7 @@ import AuthMode from './AuthMode'; import AwsV4Auth from './AwsV4Auth'; import BearerAuth from './BearerAuth'; import BasicAuth from './BasicAuth'; +import DigestAuth from './DigestAuth'; import StyledWrapper from './StyledWrapper'; const Auth = ({ item, collection }) => { @@ -20,6 +21,9 @@ const Auth = ({ item, collection }) => { case 'bearer': { return ; } + case 'digest': { + return ; + } } }; diff --git a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js index 3cf5842e..6d1d5be2 100644 --- a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js @@ -107,6 +107,7 @@ const FormUrlEncodedParams = ({ item, collection }) => { 'value' ) } + allowNewlines={true} onRun={handleRun} collection={collection} /> diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js index 98845b55..288e66a8 100644 --- a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js +++ b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/index.js @@ -1,8 +1,7 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import find from 'lodash/find'; import get from 'lodash/get'; import classnames from 'classnames'; -import { IconRefresh, IconLoader2, IconBook, IconDownload } from '@tabler/icons'; import { useSelector, useDispatch } from 'react-redux'; import { updateRequestPaneTab } from 'providers/ReduxStore/slices/tabs'; import QueryEditor from 'components/RequestPane/QueryEditor'; @@ -16,10 +15,9 @@ import Tests from 'components/RequestPane/Tests'; import { useTheme } from 'providers/Theme'; import { updateRequestGraphqlQuery } from 'providers/ReduxStore/slices/collections'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; -import { findEnvironmentInCollection } from 'utils/collections'; -import useGraphqlSchema from './useGraphqlSchema'; import StyledWrapper from './StyledWrapper'; import Documentation from 'components/Documentation/index'; +import GraphQLSchemaActions from '../GraphQLSchemaActions/index'; const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, toggleDocs, handleGqlClickReference }) => { const dispatch = useDispatch(); @@ -29,25 +27,11 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog const variables = item.draft ? get(item, 'draft.request.body.graphql.variables') : get(item, 'request.body.graphql.variables'); - const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url'); const { storedTheme } = useTheme(); - - const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); - - const request = item.draft ? item.draft.request : item.request; - - let { schema, loadSchema, isLoading: isSchemaLoading } = useGraphqlSchema(url, environment, request, collection); - - const loadGqlSchema = () => { - if (!isSchemaLoading) { - loadSchema(); - } - }; + const [schema, setSchema] = useState(null); useEffect(() => { - if (onSchemaLoad) { - onSchemaLoad(schema); - } + onSchemaLoad(schema); }, [schema]); const onQueryChange = (value) => { @@ -163,18 +147,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
selectTab('docs')}> Docs
-
-
- {isSchemaLoading ? : null} - {!isSchemaLoading && !schema ? : null} - {!isSchemaLoading && schema ? : null} - Schema -
-
- - Docs -
-
+
{getTabPanel(focusedTab.requestPaneTab)}
diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/useGraphqlSchema.js b/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/useGraphqlSchema.js deleted file mode 100644 index c824c575..00000000 --- a/packages/bruno-app/src/components/RequestPane/GraphQLRequestPane/useGraphqlSchema.js +++ /dev/null @@ -1,60 +0,0 @@ -import { useState } from 'react'; -import toast from 'react-hot-toast'; -import { buildClientSchema } from 'graphql'; -import { fetchGqlSchema } from 'utils/network'; -import { simpleHash } from 'utils/common'; - -const schemaHashPrefix = 'bruno.graphqlSchema'; - -const useGraphqlSchema = (endpoint, environment, request, collection) => { - const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`; - const [error, setError] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [schema, setSchema] = useState(() => { - try { - const saved = localStorage.getItem(localStorageKey); - if (!saved) { - return null; - } - return buildClientSchema(JSON.parse(saved)); - } catch { - localStorage.setItem(localStorageKey, null); - return null; - } - }); - - const loadSchema = () => { - setIsLoading(true); - fetchGqlSchema(endpoint, environment, request, collection) - .then((res) => { - if (!res || res.status !== 200) { - return Promise.reject(new Error(res.statusText)); - } - return res.data; - }) - .then((s) => { - if (s && s.data) { - setSchema(buildClientSchema(s.data)); - setIsLoading(false); - localStorage.setItem(localStorageKey, JSON.stringify(s.data)); - toast.success('GraphQL Schema loaded successfully'); - } else { - return Promise.reject(new Error('An error occurred while introspecting schema')); - } - }) - .catch((err) => { - setIsLoading(false); - setError(err); - toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`); - }); - }; - - return { - isLoading, - schema, - loadSchema, - error - }; -}; - -export default useGraphqlSchema; diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/index.js new file mode 100644 index 00000000..01ce6f32 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/index.js @@ -0,0 +1,70 @@ +import React, { useEffect, useRef, forwardRef } from 'react'; +import useGraphqlSchema from './useGraphqlSchema'; +import { IconBook, IconDownload, IconLoader2, IconRefresh } from '@tabler/icons'; +import get from 'lodash/get'; +import { findEnvironmentInCollection } from 'utils/collections'; +import Dropdown from '../../Dropdown'; + +const GraphQLSchemaActions = ({ item, collection, onSchemaLoad, toggleDocs }) => { + const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url'); + const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); + const request = item.draft ? item.draft.request : item.request; + + let { + schema, + schemaSource, + loadSchema, + isLoading: isSchemaLoading + } = useGraphqlSchema(url, environment, request, collection); + + useEffect(() => { + if (onSchemaLoad) { + onSchemaLoad(schema); + } + }, [schema]); + + const schemaDropdownTippyRef = useRef(); + const onSchemaDropdownCreate = (ref) => (schemaDropdownTippyRef.current = ref); + + const MenuIcon = forwardRef((props, ref) => { + return ( +
+ {isSchemaLoading && } + {!isSchemaLoading && schema && } + {!isSchemaLoading && !schema && } + Schema +
+ ); + }); + + return ( +
+
+ + Docs +
+ } placement="bottom-start"> +
{ + schemaDropdownTippyRef.current.hide(); + loadSchema('introspection'); + }} + > + {schema && schemaSource === 'introspection' ? 'Refresh from Introspection' : 'Load from Introspection'} +
+
{ + schemaDropdownTippyRef.current.hide(); + loadSchema('file'); + }} + > + Load from File +
+
+
+ ); +}; + +export default GraphQLSchemaActions; diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js new file mode 100644 index 00000000..0a5f2bd0 --- /dev/null +++ b/packages/bruno-app/src/components/RequestPane/GraphQLSchemaActions/useGraphqlSchema.js @@ -0,0 +1,89 @@ +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { buildClientSchema } from 'graphql'; +import { fetchGqlSchema } from 'utils/network'; +import { simpleHash } from 'utils/common'; + +const schemaHashPrefix = 'bruno.graphqlSchema'; + +const useGraphqlSchema = (endpoint, environment, request, collection) => { + const { ipcRenderer } = window; + const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`; + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [schemaSource, setSchemaSource] = useState(''); + const [schema, setSchema] = useState(() => { + try { + const saved = localStorage.getItem(localStorageKey); + if (!saved) { + return null; + } + return buildClientSchema(JSON.parse(saved)); + } catch { + localStorage.setItem(localStorageKey, null); + return null; + } + }); + + const loadSchemaFromIntrospection = async () => { + const response = await fetchGqlSchema(endpoint, environment, request, collection); + if (!response) { + throw new Error('Introspection query failed'); + } + if (response.status !== 200) { + throw new Error(response.statusText); + } + const data = response.data?.data; + if (!data) { + throw new Error('No data returned from introspection query'); + } + setSchemaSource('introspection'); + return data; + }; + + const loadSchemaFromFile = async () => { + const schemaContent = await ipcRenderer.invoke('renderer:load-gql-schema-file'); + if (!schemaContent) { + setIsLoading(false); + return; + } + setSchemaSource('file'); + return schemaContent.data; + }; + + const loadSchema = async (schemaSource) => { + if (isLoading) { + return; + } + + setIsLoading(true); + + try { + let data; + if (schemaSource === 'file') { + data = await loadSchemaFromFile(); + } else { + // fallback to introspection if source is unknown + data = await loadSchemaFromIntrospection(); + } + setSchema(buildClientSchema(data)); + localStorage.setItem(localStorageKey, JSON.stringify(data)); + toast.success('GraphQL Schema loaded successfully'); + } catch (err) { + setError(err); + toast.error(`Error occurred while loading GraphQL Schema: ${err.message}`); + } + + setIsLoading(false); + }; + + return { + isLoading, + schema, + schemaSource, + loadSchema, + error + }; +}; + +export default useGraphqlSchema; diff --git a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js index 0a45f1a7..26a7e13d 100644 --- a/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js +++ b/packages/bruno-app/src/components/RequestPane/QueryUrl/index.js @@ -34,7 +34,7 @@ const QueryUrl = ({ item, collection, handleRun }) => { requestUrlChanged({ itemUid: item.uid, collectionUid: collection.uid, - url: value + url: value && typeof value === 'string' ? value.trim() : value }) ); }; diff --git a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js index 6ee76097..4f0177f7 100644 --- a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js @@ -8,6 +8,8 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection import SingleLineEditor from 'components/SingleLineEditor'; import Tooltip from 'components/Tooltip'; import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import { envVariableNameRegex } from 'utils/common/regex'; const VarsTable = ({ item, collection, vars, varType }) => { const dispatch = useDispatch(); @@ -29,7 +31,21 @@ const VarsTable = ({ item, collection, vars, varType }) => { const _var = cloneDeep(v); switch (type) { case 'name': { - _var.name = e.target.value; + const value = e.target.value; + + if (/^(?!\d).*$/.test(value) === false) { + toast.error('Variable names must not start with a number!'); + return; + } + + if (envVariableNameRegex.test(value) === false) { + toast.error( + 'Variable contains invalid character! Variables must only contain alpha-numeric characters, "-" and "_".' + ); + return; + } + + _var.name = value; break; } case 'value': { @@ -88,7 +104,7 @@ const VarsTable = ({ item, collection, vars, varType }) => { {vars && vars.length - ? vars.map((_var, index) => { + ? vars.map((_var) => { return ( diff --git a/packages/bruno-app/src/components/RequestPane/Vars/index.js b/packages/bruno-app/src/components/RequestPane/Vars/index.js index b63c0880..500ebb25 100644 --- a/packages/bruno-app/src/components/RequestPane/Vars/index.js +++ b/packages/bruno-app/src/components/RequestPane/Vars/index.js @@ -1,42 +1,12 @@ import React from 'react'; import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; -import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections'; -import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; -import { useTheme } from 'providers/Theme'; import VarsTable from './VarsTable'; import StyledWrapper from './StyledWrapper'; const Vars = ({ item, collection }) => { - const dispatch = useDispatch(); 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 { storedTheme } = useTheme(); - - const onRequestScriptEdit = (value) => { - dispatch( - updateRequestScript({ - script: value, - itemUid: item.uid, - collectionUid: collection.uid - }) - ); - }; - - const onResponseScriptEdit = (value) => { - dispatch( - updateResponseScript({ - script: value, - itemUid: item.uid, - collectionUid: collection.uid - }) - ); - }; - - const onRun = () => dispatch(sendRequest(item, collection.uid)); - const onSave = () => dispatch(saveRequest(item.uid, collection.uid)); - return (
diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js index 5ed21ee9..af11ebc2 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js @@ -2,6 +2,11 @@ import CodeEditor from 'components/CodeEditor/index'; import { get } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; +import { Document, Page } from 'react-pdf'; +import { useState } from 'react'; +import 'pdfjs-dist/build/pdf.worker'; +import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; +import 'react-pdf/dist/esm/Page/TextLayer.css'; const QueryResultPreview = ({ previewTab, @@ -19,6 +24,10 @@ const QueryResultPreview = ({ const preferences = useSelector((state) => state.app.preferences); const dispatch = useDispatch(); + const [numPages, setNumPages] = useState(null); + function onDocumentLoadSuccess({ numPages }) { + setNumPages(numPages); + } // Fail safe, so we don't render anything with an invalid tab if (!allowedPreviewModes.includes(previewTab)) { return null; @@ -45,6 +54,17 @@ const QueryResultPreview = ({ case 'preview-image': { return ; } + case 'preview-pdf': { + return ( +
+ + {Array.from(new Array(numPages), (el, index) => ( + + ))} + +
+ ); + } default: case 'raw': { return ( diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js index 5429ef44..64ab32f7 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/StyledWrapper.js @@ -18,11 +18,28 @@ const StyledWrapper = styled.div` width: 100%; } + .react-pdf__Page { + margin-top: 10px; + background-color: transparent !important; + } + .react-pdf__Page__textContent { + border: 1px solid darkgrey; + box-shadow: 5px 5px 5px 1px #ccc; + border-radius: 0px; + margin: 0 auto; + } + .react-pdf__Page__canvas { + margin: 0 auto; + } div[role='tablist'] { .active { color: ${(props) => props.theme.colors.text.yellow}; } } + + .muted { + color: ${(props) => props.theme.colors.text.muted}; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js index ba8696a1..a8f13505 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/index.js @@ -35,7 +35,7 @@ const formatResponse = (data, mode) => { return safeStringifyJSON(data); }; -const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => { +const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => { const contentType = getContentType(headers); const mode = getCodeMirrorModeBasedOnContentType(contentType); const formattedData = formatResponse(data, mode); @@ -49,6 +49,8 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h allowedPreviewModes.unshift('preview-web'); } else if (mode.includes('image')) { allowedPreviewModes.unshift('preview-image'); + } else if (contentType.includes('pdf')) { + allowedPreviewModes.unshift('preview-pdf'); } return allowedPreviewModes; @@ -85,12 +87,21 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h {tabs}
{error ? ( - {error} +
+
{error}
+ + {error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? ( +
+ You can disable SSL verification in the Preferences.
+ To open the Preferences, click on the gear icon in the bottom left corner. +
+ ) : null} +
) : ( props.theme.requestTabPanel.responseStatus}; +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js new file mode 100644 index 00000000..9af5e73a --- /dev/null +++ b/packages/bruno-app/src/components/ResponsePane/ResponseSave/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; +import get from 'lodash/get'; +import { IconDownload } from '@tabler/icons'; + +const ResponseSave = ({ item }) => { + const { ipcRenderer } = window; + const response = item.response || {}; + + const saveResponseToFile = () => { + return new Promise((resolve, reject) => { + console.log(item); + ipcRenderer + .invoke('renderer:save-response-to-file', response, item.requestSent.url) + .then(resolve) + .catch((err) => { + toast.error(get(err, 'error.message') || 'Something went wrong!'); + reject(err); + }); + }); + }; + + return ( + + + + ); +}; +export default ResponseSave; diff --git a/packages/bruno-app/src/components/ResponsePane/StatusCode/index.js b/packages/bruno-app/src/components/ResponsePane/StatusCode/index.js index 2917634a..9287899b 100644 --- a/packages/bruno-app/src/components/ResponsePane/StatusCode/index.js +++ b/packages/bruno-app/src/components/ResponsePane/StatusCode/index.js @@ -6,7 +6,7 @@ import StyledWrapper from './StyledWrapper'; // Todo: text-error class is not getting pulled in for 500 errors const StatusCode = ({ status }) => { const getTabClassname = (status) => { - return classnames('', { + return classnames('ml-2', { 'text-ok': status >= 100 && status < 200, 'text-ok': status >= 200 && status < 300, 'text-error': status >= 300 && status < 400, diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index aea70de6..fb5aab04 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -14,6 +14,7 @@ import Timeline from './Timeline'; import TestResults from './TestResults'; import TestResultsLabel from './TestResultsLabel'; import StyledWrapper from './StyledWrapper'; +import ResponseSave from 'src/components/ResponsePane/ResponseSave'; const ResponsePane = ({ rightPaneWidth, item, collection }) => { const dispatch = useDispatch(); @@ -41,6 +42,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { collection={collection} width={rightPaneWidth} data={response.data} + dataBuffer={response.dataBuffer} headers={response.headers} error={response.error} key={item.filename} @@ -111,6 +113,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { {!isLoading ? (
+ diff --git a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js index 6526c745..aeba867f 100644 --- a/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js +++ b/packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js @@ -34,6 +34,7 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => { width={rightPaneWidth} disableRunEventListener={true} data={responseReceived.data} + dataBuffer={responseReceived.dataBuffer} headers={responseReceived.headers} key={item.filename} /> diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js index 64c229ae..18734b28 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js @@ -9,10 +9,11 @@ const CodeView = ({ language, item }) => { const { storedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); const { target, client, language: lang } = language; + const headers = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers'); let snippet = ''; try { - snippet = new HTTPSnippet(buildHarRequest(item.request)).convert(target, client); + snippet = new HTTPSnippet(buildHarRequest({ request: item.request, headers })).convert(target, client); } catch (e) { console.error(e); snippet = 'Error generating code snippet'; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js index ea58fe94..14d7432f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/StyledWrapper.js @@ -33,7 +33,8 @@ const Wrapper = styled.div` overflow: hidden; } - &:hover { + &:hover, + &.item-hovered { background: ${(props) => props.theme.sidebar.collection.item.hoverBg}; .menu-icon { .dropdown { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 31186e38..b12cfde1 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -84,7 +84,8 @@ const CollectionItem = ({ item, collection, searchText }) => { }); const itemRowClassName = classnames('flex collection-item-name items-center', { - 'item-focused-in-tab': item.uid == activeTabUid + 'item-focused-in-tab': item.uid == activeTabUid, + 'item-hovered': isOver }); const scrollToTheActiveTab = () => { diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js index cd8291af..11382b1f 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/RemoveCollection/index.js @@ -18,7 +18,7 @@ const RemoveCollection = ({ onClose, collection }) => { return ( - Are you sure you want to delete collection {collection.name} ? + Are you sure you want to remove collection {collection.name} ? ); }; diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index 185e3c5c..ecfc4183 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -2,6 +2,7 @@ import React from 'react'; import importBrunoCollection from 'utils/importers/bruno-collection'; import importPostmanCollection from 'utils/importers/postman-collection'; import importInsomniaCollection from 'utils/importers/insomnia-collection'; +import importOpenapiCollection from 'utils/importers/openapi-collection'; import { toastError } from 'utils/common/error'; import Modal from 'components/Modal'; @@ -30,6 +31,14 @@ const ImportCollection = ({ onClose, handleSubmit }) => { .catch((err) => toastError(err, 'Insomnia Import collection failed')); }; + const handleImportOpenapiCollection = () => { + importOpenapiCollection() + .then((collection) => { + handleSubmit(collection); + }) + .catch((err) => toastError(err, 'OpenAPI v3 Import collection failed')); + }; + return (
@@ -42,6 +51,9 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
Insomnia Collection
+
+ OpenAPI V3 Spec +
); diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js index 72d1d8a9..9845bd2e 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/StyledWrapper.js @@ -35,6 +35,10 @@ const StyledWrapper = styled.div` } } } + + textarea.curl-command { + min-height: 150px; + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js index 0389e574..f5993893 100644 --- a/packages/bruno-app/src/components/Sidebar/NewRequest/index.js +++ b/packages/bruno-app/src/components/Sidebar/NewRequest/index.js @@ -1,4 +1,4 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useCallback } from 'react'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import toast from 'react-hot-toast'; @@ -11,6 +11,7 @@ import { addTab } from 'providers/ReduxStore/slices/tabs'; import HttpMethodSelector from 'components/RequestPane/QueryUrl/HttpMethodSelector'; import { getDefaultRequestPaneTab } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; +import { getRequestFromCurlCommand } from 'utils/curl'; const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const dispatch = useDispatch(); @@ -24,7 +25,8 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { requestName: '', requestType: collectionPresets.defaultType || 'http-request', requestUrl: collectionPresets.defaultRequestUrl || '', - requestMethod: 'GET' + requestMethod: 'GET', + curlCommand: '' }, validationSchema: Yup.object({ requestName: Yup.string() @@ -38,7 +40,18 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const trimmedValue = value ? value.trim().toLowerCase() : ''; return !['collection', 'folder'].includes(trimmedValue); } - }) + }), + curlCommand: Yup.string().when('requestType', { + is: (requestType) => requestType === 'from-curl', + then: Yup.string() + .min(1, 'must be at least 1 character') + .required('curlCommand is required') + .test({ + name: 'curlCommand', + message: `Invalid cURL Command`, + test: (value) => getRequestFromCurlCommand(value) !== null + }) + }) }), onSubmit: (values) => { if (isEphemeral) { @@ -64,6 +77,22 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { onClose(); }) .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); + } else if (values.requestType === 'from-curl') { + const request = getRequestFromCurlCommand(values.curlCommand); + dispatch( + newHttpRequest({ + requestName: values.requestName, + requestType: 'http-request', + requestUrl: request.url, + requestMethod: request.method, + collectionUid: collection.uid, + itemUid: item ? item.uid : null, + headers: request.headers, + body: request.body + }) + ) + .then(() => onClose()) + .catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request')); } else { dispatch( newHttpRequest({ @@ -89,6 +118,25 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { const onSubmit = () => formik.handleSubmit(); + const handlePaste = useCallback( + (event) => { + const clipboardData = event.clipboardData || window.clipboardData; + const pastedData = clipboardData.getData('Text'); + + // Check if pasted data looks like a cURL command + const curlCommandRegex = /^\s*curl\s/i; + if (curlCommandRegex.test(pastedData)) { + // Switch to the 'from-curl' request type + formik.setFieldValue('requestType', 'from-curl'); + formik.setFieldValue('curlCommand', pastedData); + + // Prevent the default paste behavior to avoid pasting into the textarea + event.preventDefault(); + } + }, + [formik] + ); + return ( @@ -127,15 +175,28 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => { + + + +
-
{
{formik.errors.requestName}
) : null}
+ {formik.values.requestType !== 'from-curl' ? ( + <> +
+ -
- - -
-
- formik.setFieldValue('requestMethod', val)} - /> -
-
- +
+
+ formik.setFieldValue('requestMethod', val)} + /> +
+
+ +
+
+ {formik.touched.requestUrl && formik.errors.requestUrl ? ( +
{formik.errors.requestUrl}
+ ) : null}
+ + ) : ( +
+ + + {formik.touched.curlCommand && formik.errors.curlCommand ? ( +
{formik.errors.curlCommand}
+ ) : null}
- {formik.touched.requestUrl && formik.errors.requestUrl ? ( -
{formik.errors.requestUrl}
- ) : null} -
+ )} diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 08d3655c..670d28d2 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -7,7 +7,7 @@ import Preferences from 'components/Preferences'; import { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { IconSettings } from '@tabler/icons'; -import { updateLeftSidebarWidth, updateIsDragging } from 'providers/ReduxStore/slices/app'; +import { updateLeftSidebarWidth, updateIsDragging, showPreferences } from 'providers/ReduxStore/slices/app'; import { useTheme } from 'providers/Theme'; const MIN_LEFT_SIDEBAR_WIDTH = 222; @@ -15,7 +15,7 @@ const MAX_LEFT_SIDEBAR_WIDTH = 600; const Sidebar = () => { const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth); - const [preferencesOpen, setPreferencesOpen] = useState(false); + const preferencesOpen = useSelector((state) => state.app.showPreferences); const [asideWidth, setAsideWidth] = useState(leftSidebarWidth); @@ -78,7 +78,7 @@ const Sidebar = () => {
diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index bee59b2d..fb3bff21 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -57,6 +57,7 @@ class SingleLineEditor extends Component { } componentDidMount() { // Initialize CodeMirror as a single line editor + /** @type {import("codemirror").Editor} */ this.editor = CodeMirror(this.editorRef.current, { lineWrapping: false, lineNumbers: false, @@ -84,7 +85,10 @@ class SingleLineEditor extends Component { } }, 'Alt-Enter': () => { - if (this.props.onRun) { + if (this.props.allowNewlines) { + this.editor.setValue(this.editor.getValue() + '\n'); + this.editor.setCursor({ line: this.editor.lineCount(), ch: 0 }); + } else if (this.props.onRun) { this.props.onRun(); } }, diff --git a/packages/bruno-app/src/pages/Bruno/index.js b/packages/bruno-app/src/pages/Bruno/index.js index 9f4de243..75b07f0f 100644 --- a/packages/bruno-app/src/pages/Bruno/index.js +++ b/packages/bruno-app/src/pages/Bruno/index.js @@ -21,8 +21,12 @@ if (!SERVER_RENDERED) { require('codemirror/addon/edit/matchbrackets'); require('codemirror/addon/fold/brace-fold'); require('codemirror/addon/fold/foldgutter'); + require('codemirror/addon/fold/xml-fold'); + require('codemirror/addon/hint/javascript-hint'); require('codemirror/addon/hint/show-hint'); require('codemirror/addon/lint/lint'); + require('codemirror/addon/lint/javascript-lint'); + require('codemirror/addon/lint/json-lint'); require('codemirror/addon/mode/overlay'); require('codemirror/addon/scroll/simplescrollbars'); require('codemirror/addon/search/jump-to-line'); diff --git a/packages/bruno-app/src/pages/_app.js b/packages/bruno-app/src/pages/_app.js index 0b2f9c3a..64565bc8 100644 --- a/packages/bruno-app/src/pages/_app.js +++ b/packages/bruno-app/src/pages/_app.js @@ -41,6 +41,19 @@ function MyApp({ Component, pageProps }) { return null; } + if (!window.ipcRenderer) { + return ( + + ); + } + return ( diff --git a/packages/bruno-app/src/providers/App/useIpcEvents.js b/packages/bruno-app/src/providers/App/useIpcEvents.js index 8e87b1cf..e5c1bdb6 100644 --- a/packages/bruno-app/src/providers/App/useIpcEvents.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -14,7 +14,7 @@ import { runFolderEvent, brunoConfigUpdateEvent } from 'providers/ReduxStore/slices/collections'; -import { updatePreferences } from 'providers/ReduxStore/slices/app'; +import { showPreferences, updatePreferences } from 'providers/ReduxStore/slices/app'; import toast from 'react-hot-toast'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; import { isElectron } from 'utils/common/platform'; @@ -127,6 +127,10 @@ const useIpcEvents = () => { dispatch(brunoConfigUpdateEvent(val)) ); + const showPreferencesListener = ipcRenderer.on('main:open-preferences', () => { + dispatch(showPreferences(true)); + }); + const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => { dispatch(updatePreferences(val)); }); @@ -143,6 +147,7 @@ const useIpcEvents = () => { removeProcessEnvUpdatesListener(); removeConsoleLogListener(); removeConfigUpdatesListener(); + showPreferencesListener(); removePreferencesUpdatesListener(); }; }, [isElectron]); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index 6f5c849f..4605f051 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -7,6 +7,7 @@ const initialState = { leftSidebarWidth: 222, screenWidth: 500, showHomePage: false, + showPreferences: false, preferences: { request: { sslVerification: true, @@ -40,6 +41,9 @@ export const appSlice = createSlice({ hideHomePage: (state) => { state.showHomePage = false; }, + showPreferences: (state, action) => { + state.showPreferences = action.payload; + }, updatePreferences: (state, action) => { state.preferences = action.payload; } @@ -53,6 +57,7 @@ export const { updateIsDragging, showHomePage, hideHomePage, + showPreferences, updatePreferences } = appSlice.actions; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 0d6167d8..af22c7f0 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -409,8 +409,8 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => { }); }; -export const sortCollections = () => (dispatch) => { - dispatch(_sortCollections()); +export const sortCollections = (payload) => (dispatch) => { + dispatch(_sortCollections(payload)); }; export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => { const state = getState(); @@ -584,7 +584,7 @@ export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (di }; export const newHttpRequest = (params) => (dispatch, getState) => { - const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid } = params; + const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid, headers, body } = params; return new Promise((resolve, reject) => { const state = getState(); @@ -607,9 +607,9 @@ export const newHttpRequest = (params) => (dispatch, getState) => { request: { method: requestMethod, url: requestUrl, - headers: [], + headers: headers ?? [], params, - body: { + body: body ?? { mode: 'none', json: null, text: null, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index eb3b620a..250e2b13 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -388,6 +388,10 @@ export const collectionsSlice = createSlice({ item.draft.request.auth.mode = 'basic'; item.draft.request.auth.basic = action.payload.content; break; + case 'digest': + item.draft.request.auth.mode = 'digest'; + item.draft.request.auth.digest = action.payload.content; + break; } } } @@ -976,6 +980,9 @@ export const collectionsSlice = createSlice({ case 'basic': set(collection, 'root.request.auth.basic', action.payload.content); break; + case 'digest': + set(collection, 'root.request.auth.digest', action.payload.content); + break; } } }, diff --git a/packages/bruno-app/src/utils/codegenerator/har.js b/packages/bruno-app/src/utils/codegenerator/har.js index b48fbc3c..e0e2b2a6 100644 --- a/packages/bruno-app/src/utils/codegenerator/har.js +++ b/packages/bruno-app/src/utils/codegenerator/har.js @@ -9,25 +9,17 @@ const createContentType = (mode) => { case 'multipartForm': return 'multipart/form-data'; default: - return 'application/json'; + return ''; } }; -const createHeaders = (headers, mode) => { - const contentType = createContentType(mode); - const headersArray = headers +const createHeaders = (headers) => { + return headers .filter((header) => header.enabled) - .map((header) => { - return { - name: header.name, - value: header.value - }; - }); - const headerNames = headersArray.map((header) => header.name); - if (!headerNames.includes('Content-Type')) { - return [...headersArray, { name: 'Content-Type', value: contentType }]; - } - return headersArray; + .map((header) => ({ + name: header.name, + value: header.value + })); }; const createQuery = (queryParams = []) => { @@ -56,13 +48,13 @@ const createPostData = (body) => { } }; -export const buildHarRequest = (request) => { +export const buildHarRequest = ({ request, headers }) => { return { method: request.method, url: request.url, httpVersion: 'HTTP/1.1', cookies: [], - headers: createHeaders(request.headers, request.body.mode), + headers: createHeaders(headers), queryString: createQuery(request.params), postData: createPostData(request.body), headersSize: 0, diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 0b98fbc2..05dd0fb4 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -494,6 +494,10 @@ export const humanizeRequestAuthMode = (mode) => { label = 'Bearer Token'; break; } + case 'digest': { + label = 'Digest Auth'; + break; + } } return label; diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index 78016de6..4be4dc47 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -1,5 +1,4 @@ import get from 'lodash/get'; -import isString from 'lodash/isString'; let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; @@ -10,7 +9,7 @@ if (!SERVER_RENDERED) { const pathFoundInVariables = (path, obj) => { const value = get(obj, path); - return isString(value); + return value !== undefined; }; export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { diff --git a/packages/bruno-app/src/utils/common/index.js b/packages/bruno-app/src/utils/common/index.js index 992ec233..be698243 100644 --- a/packages/bruno-app/src/utils/common/index.js +++ b/packages/bruno-app/src/utils/common/index.js @@ -94,3 +94,15 @@ export const getContentType = (headers) => { return ''; }; + +export const startsWith = (str, search) => { + if (!str || !str.length || typeof str !== 'string') { + return false; + } + + if (!search || !search.length || typeof search !== 'string') { + return false; + } + + return str.substr(0, search.length) === search; +}; diff --git a/packages/bruno-app/src/utils/common/index.spec.js b/packages/bruno-app/src/utils/common/index.spec.js index 6d52ed1a..3484fac9 100644 --- a/packages/bruno-app/src/utils/common/index.spec.js +++ b/packages/bruno-app/src/utils/common/index.spec.js @@ -1,6 +1,6 @@ const { describe, it, expect } = require('@jest/globals'); -import { normalizeFileName } from './index'; +import { normalizeFileName, startsWith } from './index'; describe('common utils', () => { describe('normalizeFileName', () => { @@ -16,4 +16,37 @@ describe('common utils', () => { expect(normalizeFileName('foo\\bar\\')).toBe('foo-bar-'); }); }); + + describe('startsWith', () => { + it('should return false if str is not a string', () => { + expect(startsWith(null, 'foo')).toBe(false); + expect(startsWith(undefined, 'foo')).toBe(false); + expect(startsWith(123, 'foo')).toBe(false); + expect(startsWith({}, 'foo')).toBe(false); + expect(startsWith([], 'foo')).toBe(false); + }); + + it('should return false if search is not a string', () => { + expect(startsWith('foo', null)).toBe(false); + expect(startsWith('foo', undefined)).toBe(false); + expect(startsWith('foo', 123)).toBe(false); + expect(startsWith('foo', {})).toBe(false); + expect(startsWith('foo', [])).toBe(false); + }); + + it('should return false if str does not start with search', () => { + expect(startsWith('foo', 'bar')).toBe(false); + expect(startsWith('foo', 'baz')).toBe(false); + expect(startsWith('foo', 'bar')).toBe(false); + expect(startsWith('foo', 'baz')).toBe(false); + expect(startsWith('foo', 'bar')).toBe(false); + expect(startsWith('foo', 'baz')).toBe(false); + }); + + it('should return true if str starts with search', () => { + expect(startsWith('foo', 'f')).toBe(true); + expect(startsWith('foo', 'fo')).toBe(true); + expect(startsWith('foo', 'foo')).toBe(true); + }); + }); }); diff --git a/packages/bruno-app/src/utils/common/regex.js b/packages/bruno-app/src/utils/common/regex.js new file mode 100644 index 00000000..d55bb55b --- /dev/null +++ b/packages/bruno-app/src/utils/common/regex.js @@ -0,0 +1 @@ +export const envVariableNameRegex = /^(?!\d)[\w-]*$/; diff --git a/packages/bruno-app/src/utils/curl/curl-to-json.js b/packages/bruno-app/src/utils/curl/curl-to-json.js new file mode 100644 index 00000000..8313e0a7 --- /dev/null +++ b/packages/bruno-app/src/utils/curl/curl-to-json.js @@ -0,0 +1,169 @@ +/** + * Copyright (c) 2014-2016 Nick Carneiro + * https://github.com/curlconverter/curlconverter + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import parseCurlCommand from './parse-curl'; +import * as querystring from 'query-string'; +import * as jsesc from 'jsesc'; + +function getContentType(headers = {}) { + const contentType = Object.keys(headers).find((key) => key.toLowerCase() === 'content-type'); + + return contentType ? headers[contentType] : null; +} + +function repr(value, isKey) { + return isKey ? "'" + jsesc(value, { quotes: 'single' }) + "'" : value; +} + +function getQueries(request) { + const queries = {}; + for (const paramName in request.query) { + const rawValue = request.query[paramName]; + let paramValue; + if (Array.isArray(rawValue)) { + paramValue = rawValue.map(repr); + } else { + paramValue = repr(rawValue); + } + queries[repr(paramName)] = paramValue; + } + + return queries; +} + +function getDataString(request) { + if (typeof request.data === 'number') { + request.data = request.data.toString(); + } + + const contentType = getContentType(request.headers); + + if (contentType && contentType.includes('application/json')) { + return { data: request.data.toString() }; + } + + const parsedQueryString = querystring.parse(request.data, { sort: false }); + const keyCount = Object.keys(parsedQueryString).length; + const singleKeyOnly = keyCount === 1 && !parsedQueryString[Object.keys(parsedQueryString)[0]]; + const singularData = request.isDataBinary || singleKeyOnly; + if (singularData) { + const data = {}; + data[repr(request.data)] = ''; + return { data: data }; + } else { + return getMultipleDataString(request, parsedQueryString); + } +} + +function getMultipleDataString(request, parsedQueryString) { + const data = {}; + + for (const key in parsedQueryString) { + const value = parsedQueryString[key]; + if (Array.isArray(value)) { + data[repr(key)] = value; + } else { + data[repr(key)] = repr(value); + } + } + + return { data: data }; +} + +function getFilesString(request) { + const data = {}; + + data.files = {}; + data.data = {}; + + for (const multipartKey in request.multipartUploads) { + const multipartValue = request.multipartUploads[multipartKey]; + if (multipartValue.startsWith('@')) { + const fileName = multipartValue.slice(1); + data.files[repr(multipartKey)] = repr(fileName); + } else { + data.data[repr(multipartKey)] = repr(multipartValue); + } + } + + if (Object.keys(data.files).length === 0) { + delete data.files; + } + + if (Object.keys(data.data).length === 0) { + delete data.data; + } + + return data; +} + +const curlToJson = (curlCommand) => { + const request = parseCurlCommand(curlCommand); + + const requestJson = {}; + + // curl automatically prepends 'http' if the scheme is missing, but python fails and returns an error + // we tack it on here to mimic curl + if (!request.url.match(/https?:/)) { + request.url = 'http://' + request.url; + } + if (!request.urlWithoutQuery.match(/https?:/)) { + request.urlWithoutQuery = 'http://' + request.urlWithoutQuery; + } + + requestJson.url = request.urlWithoutQuery.replace(/\/$/, ''); + requestJson.raw_url = request.url; + requestJson.method = request.method; + + if (request.cookies) { + const cookies = {}; + for (const cookieName in request.cookies) { + cookies[repr(cookieName)] = repr(request.cookies[cookieName]); + } + + requestJson.cookies = cookies; + } + + if (request.headers) { + const headers = {}; + for (const headerName in request.headers) { + headers[repr(headerName)] = repr(request.headers[headerName]); + } + + requestJson.headers = headers; + } + + if (request.query) { + requestJson.queries = getQueries(request); + } + + if (typeof request.data === 'string' || typeof request.data === 'number') { + Object.assign(requestJson, getDataString(request)); + } else if (request.multipartUploads) { + Object.assign(requestJson, getFilesString(request)); + } + + if (request.insecure) { + requestJson.insecure = false; + } + + if (request.auth) { + 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 : {}; +}; + +export default curlToJson; diff --git a/packages/bruno-app/src/utils/curl/curl-to-json.spec.js b/packages/bruno-app/src/utils/curl/curl-to-json.spec.js new file mode 100644 index 00000000..c8f7d1c0 --- /dev/null +++ b/packages/bruno-app/src/utils/curl/curl-to-json.spec.js @@ -0,0 +1,62 @@ +const { describe, it, expect } = require('@jest/globals'); + +import curlToJson from './curl-to-json'; + +describe('curlToJson', () => { + it('should return a parse a simple curl command', () => { + 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' + }); + }); + + it('should return a parse a curl command with headers', () => { + const curlCommand = `curl https://www.usebruno.com + -H 'Accept: application/json, text/plain, */*' + -H 'Accept-Language: en-US,en;q=0.9,hi;q=0.8' + `; + + const result = curlToJson(curlCommand); + + expect(result).toEqual({ + url: 'https://www.usebruno.com', + raw_url: 'https://www.usebruno.com', + method: 'get', + headers: { + Accept: 'application/json, text/plain, */*', + 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8' + } + }); + }); + + it('should return a parse a curl with a post body', () => { + const curlCommand = `curl 'https://www.usebruno.com' + -H 'Accept: application/json, text/plain, */*' + -H 'Accept-Language: en-US,en;q=0.9,hi;q=0.8' + -H 'Content-Type: application/json;charset=utf-8' + -H 'Origin: https://www.usebruno.com' + -H 'Referer: https://www.usebruno.com/' + --data-raw '{"email":"test@usebruno.com","password":"test"}' + `; + + const result = curlToJson(curlCommand); + + expect(result).toEqual({ + url: 'https://www.usebruno.com', + raw_url: 'https://www.usebruno.com', + method: 'post', + headers: { + Accept: 'application/json, text/plain, */*', + 'Accept-Language': 'en-US,en;q=0.9,hi;q=0.8', + 'Content-Type': 'application/json;charset=utf-8', + Origin: 'https://www.usebruno.com', + Referer: 'https://www.usebruno.com/' + }, + data: '{"email":"test@usebruno.com","password":"test"}' + }); + }); +}); diff --git a/packages/bruno-app/src/utils/curl/index.js b/packages/bruno-app/src/utils/curl/index.js new file mode 100644 index 00000000..ecaf582c --- /dev/null +++ b/packages/bruno-app/src/utils/curl/index.js @@ -0,0 +1,67 @@ +import { forOwn } from 'lodash'; +import { safeStringifyJSON } from 'utils/common'; +import curlToJson from './curl-to-json'; + +export const getRequestFromCurlCommand = (curlCommand) => { + const parseFormData = (parsedBody) => { + const formData = []; + forOwn(parsedBody, (value, key) => { + formData.push({ name: key, value, enabled: true }); + }); + + return formData; + }; + + try { + if (!curlCommand || typeof curlCommand !== 'string' || curlCommand.length === 0) { + return null; + } + + const request = curlToJson(curlCommand); + const parsedHeaders = request?.headers; + const headers = + parsedHeaders && + Object.keys(parsedHeaders).map((key) => ({ name: key, value: parsedHeaders[key], enabled: true })); + + const contentType = headers?.find((h) => h.name.toLowerCase() === 'content-type')?.value; + const body = { + mode: 'none', + json: null, + text: null, + xml: null, + sparql: null, + multipartForm: null, + formUrlEncoded: null + }; + const parsedBody = request.data; + if (parsedBody && contentType && typeof contentType === 'string') { + if (contentType.includes('application/json')) { + body.mode = 'json'; + body.json = safeStringifyJSON(parsedBody); + } else if (contentType.includes('text/xml')) { + body.mode = 'xml'; + body.xml = parsedBody; + } else if (contentType.includes('application/x-www-form-urlencoded')) { + body.mode = 'formUrlEncoded'; + console.log(parsedBody); + console.log(parseFormData(parsedBody)); + body.formUrlEncoded = parseFormData(parsedBody); + } else if (contentType.includes('multipart/form-data')) { + body.mode = 'multipartForm'; + body.multipartForm = parsedBody; + } else if (contentType.includes('text/plain')) { + body.mode = 'text'; + body.text = parsedBody; + } + } + return { + url: request.url, + method: request.method, + body, + headers: headers + }; + } catch (error) { + console.error(error); + return null; + } +}; diff --git a/packages/bruno-app/src/utils/curl/parse-curl.js b/packages/bruno-app/src/utils/curl/parse-curl.js new file mode 100644 index 00000000..b7f6572f --- /dev/null +++ b/packages/bruno-app/src/utils/curl/parse-curl.js @@ -0,0 +1,238 @@ +/** + * Copyright (c) 2014-2016 Nick Carneiro + * https://github.com/curlconverter/curlconverter + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as cookie from 'cookie'; +import * as URL from 'url'; +import * as querystring from 'query-string'; +import yargs from 'yargs-parser'; + +const parseCurlCommand = (curlCommand) => { + // Remove newlines (and from continuations) + curlCommand = curlCommand.replace(/\\\r|\\\n/g, ''); + + // Remove extra whitespace + curlCommand = curlCommand.replace(/\s+/g, ' '); + + // yargs parses -XPOST as separate arguments. just prescreen for it. + curlCommand = curlCommand.replace(/ -XPOST/, ' -X POST'); + curlCommand = curlCommand.replace(/ -XGET/, ' -X GET'); + curlCommand = curlCommand.replace(/ -XPUT/, ' -X PUT'); + curlCommand = curlCommand.replace(/ -XPATCH/, ' -X PATCH'); + curlCommand = curlCommand.replace(/ -XDELETE/, ' -X DELETE'); + curlCommand = curlCommand.replace(/ -XOPTIONS/, ' -X OPTIONS'); + // Safari adds `-Xnull` if is unable to determine the request type, it can be ignored + curlCommand = curlCommand.replace(/ -Xnull/, ' '); + curlCommand = curlCommand.trim(); + + const parsedArguments = yargs(curlCommand, { + boolean: ['I', 'head', 'compressed', 'L', 'k', 'silent', 's'], + alias: { + H: 'header', + A: 'user-agent' + } + }); + + let cookieString; + let cookies; + let url = parsedArguments._[1] || ''; + + // remove surrounding quotes if present + if (url && url.length) { + url = url.replace(/^['"]|['"]$/g, ''); + } + + // if url argument wasn't where we expected it, try to find it in the other arguments + if (!url) { + for (const argName in parsedArguments) { + if (typeof parsedArguments[argName] === 'string') { + if (parsedArguments[argName].indexOf('http') === 0 || parsedArguments[argName].indexOf('www.') === 0) { + url = parsedArguments[argName]; + } + } + } + } + + let headers; + + if (parsedArguments.header) { + if (!headers) { + headers = {}; + } + if (!Array.isArray(parsedArguments.header)) { + parsedArguments.header = [parsedArguments.header]; + } + parsedArguments.header.forEach((header) => { + if (header.indexOf('Cookie') !== -1) { + cookieString = header; + } else { + const components = header.split(/:(.*)/); + if (components[1]) { + headers[components[0]] = components[1].trim(); + } + } + }); + } + + if (parsedArguments['user-agent']) { + if (!headers) { + headers = {}; + } + headers['User-Agent'] = parsedArguments['user-agent']; + } + + if (parsedArguments.b) { + cookieString = parsedArguments.b; + } + if (parsedArguments.cookie) { + cookieString = parsedArguments.cookie; + } + let multipartUploads; + if (parsedArguments.F) { + multipartUploads = {}; + if (!Array.isArray(parsedArguments.F)) { + parsedArguments.F = [parsedArguments.F]; + } + parsedArguments.F.forEach((multipartArgument) => { + // input looks like key=value. value could be json or a file path prepended with an @ + const splitArguments = multipartArgument.split('=', 2); + const key = splitArguments[0]; + const value = splitArguments[1]; + multipartUploads[key] = value; + }); + } + if (cookieString) { + const cookieParseOptions = { + decode: function (s) { + return s; + } + }; + // separate out cookie headers into separate data structure + // note: cookie is case insensitive + cookies = cookie.parse(cookieString.replace(/^Cookie: /gi, ''), cookieParseOptions); + } + let method; + if (parsedArguments.X === 'POST') { + method = 'post'; + } else if (parsedArguments.X === 'PUT' || parsedArguments.T) { + method = 'put'; + } else if (parsedArguments.X === 'PATCH') { + method = 'patch'; + } else if (parsedArguments.X === 'DELETE') { + method = 'delete'; + } else if (parsedArguments.X === 'OPTIONS') { + method = 'options'; + } else if ( + (parsedArguments.d || + parsedArguments.data || + parsedArguments['data-ascii'] || + parsedArguments['data-binary'] || + parsedArguments['data-raw'] || + parsedArguments.F || + parsedArguments.form) && + !(parsedArguments.G || parsedArguments.get) + ) { + method = 'post'; + } else if (parsedArguments.I || parsedArguments.head) { + method = 'head'; + } else { + method = 'get'; + } + + const compressed = !!parsedArguments.compressed; + const urlObject = URL.parse(url || ''); + + // if GET request with data, convert data to query string + // NB: the -G flag does not change the http verb. It just moves the data into the url. + if (parsedArguments.G || parsedArguments.get) { + urlObject.query = urlObject.query ? urlObject.query : ''; + const option = 'd' in parsedArguments ? 'd' : 'data' in parsedArguments ? 'data' : null; + if (option) { + let urlQueryString = ''; + + if (url.indexOf('?') < 0) { + url += '?'; + } else { + urlQueryString += '&'; + } + + if (typeof parsedArguments[option] === 'object') { + urlQueryString += parsedArguments[option].join('&'); + } else { + urlQueryString += parsedArguments[option]; + } + urlObject.query += urlQueryString; + url += urlQueryString; + delete parsedArguments[option]; + } + } + if (urlObject.query && urlObject.query.endsWith('&')) { + urlObject.query = urlObject.query.slice(0, -1); + } + const query = querystring.parse(urlObject.query, { sort: false }); + for (const param in query) { + if (query[param] === null) { + query[param] = ''; + } + } + + urlObject.search = null; // Clean out the search/query portion. + const request = { + url: url, + urlWithoutQuery: URL.format(urlObject) + }; + if (compressed) { + request.compressed = true; + } + + if (Object.keys(query).length > 0) { + request.query = query; + } + if (headers) { + request.headers = headers; + } + request.method = method; + + if (cookies) { + request.cookies = cookies; + request.cookieString = cookieString.replace('Cookie: ', ''); + } + if (multipartUploads) { + request.multipartUploads = multipartUploads; + } + if (parsedArguments.data) { + request.data = parsedArguments.data; + } else if (parsedArguments['data-binary']) { + request.data = parsedArguments['data-binary']; + request.isDataBinary = true; + } else if (parsedArguments.d) { + request.data = parsedArguments.d; + } else if (parsedArguments['data-ascii']) { + request.data = parsedArguments['data-ascii']; + } else if (parsedArguments['data-raw']) { + request.data = parsedArguments['data-raw']; + request.isDataRaw = true; + } + + 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('&'); + } + + if (parsedArguments.k || parsedArguments.insecure) { + request.insecure = true; + } + return request; +}; + +export default parseCurlCommand; diff --git a/packages/bruno-app/src/utils/importers/insomnia-collection.js b/packages/bruno-app/src/utils/importers/insomnia-collection.js index 8b7b7fb6..80e9bb0a 100644 --- a/packages/bruno-app/src/utils/importers/insomnia-collection.js +++ b/packages/bruno-app/src/utils/importers/insomnia-collection.js @@ -1,3 +1,4 @@ +import jsyaml from 'js-yaml'; import each from 'lodash/each'; import get from 'lodash/get'; import fileDialog from 'file-dialog'; @@ -8,7 +9,22 @@ import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } fr const readFile = (files) => { return new Promise((resolve, reject) => { const fileReader = new FileReader(); - fileReader.onload = (e) => resolve(e.target.result); + fileReader.onload = (e) => { + try { + // try to load JSON + const parsedData = JSON.parse(e.target.result); + resolve(parsedData); + } catch (jsonError) { + // not a valid JSOn, try yaml + try { + const parsedData = jsyaml.load(e.target.result); + resolve(parsedData); + } catch (yamlError) { + console.error('Error parsing the file :', jsonError, yamlError); + reject(new BrunoError('Import collection failed')); + } + } + }; fileReader.onerror = (err) => reject(err); fileReader.readAsText(files[0]); }); @@ -64,7 +80,8 @@ const transformInsomniaRequestItem = (request, index, allRequests) => { auth: { mode: 'none', basic: null, - bearer: null + bearer: null, + digest: null }, headers: [], params: [], @@ -167,7 +184,7 @@ const parseInsomniaCollection = (data) => { return new Promise((resolve, reject) => { try { - const insomniaExport = JSON.parse(data); + const insomniaExport = data; const insomniaResources = get(insomniaExport, 'resources', []); const insomniaCollection = insomniaResources.find((resource) => resource._type === 'workspace'); @@ -213,7 +230,7 @@ const parseInsomniaCollection = (data) => { const importCollection = () => { return new Promise((resolve, reject) => { - fileDialog({ accept: 'application/json' }) + fileDialog({ accept: '.json, .yaml, .yml, application/json, application/yaml, application/x-yaml' }) .then(readFile) .then(parseInsomniaCollection) .then(transformItemsInCollection) @@ -221,8 +238,8 @@ const importCollection = () => { .then(validateSchema) .then((collection) => resolve(collection)) .catch((err) => { - console.log(err); - reject(new BrunoError('Import collection failed')); + console.error(err); + reject(new BrunoError('Import collection failed: ' + err.message)); }); }); }; diff --git a/packages/bruno-app/src/utils/importers/openapi-collection.js b/packages/bruno-app/src/utils/importers/openapi-collection.js new file mode 100644 index 00000000..febe7d61 --- /dev/null +++ b/packages/bruno-app/src/utils/importers/openapi-collection.js @@ -0,0 +1,388 @@ +import jsyaml from 'js-yaml'; +import each from 'lodash/each'; +import get from 'lodash/get'; +import fileDialog from 'file-dialog'; +import { uuid } from 'utils/common'; +import { BrunoError } from 'utils/common/error'; +import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common'; + +const readFile = (files) => { + return new Promise((resolve, reject) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => { + try { + // try to load JSON + const parsedData = JSON.parse(e.target.result); + resolve(parsedData); + } catch (jsonError) { + // not a valid JSOn, try yaml + try { + const parsedData = jsyaml.load(e.target.result); + resolve(parsedData); + } catch (yamlError) { + console.error('Error parsing the file :', jsonError, yamlError); + reject(new BrunoError('Import collection failed')); + } + } + }; + fileReader.onerror = (err) => reject(err); + fileReader.readAsText(files[0]); + }); +}; + +const ensureUrl = (url) => { + let protUrl = url.startsWith('http') ? url : `http://${url}`; + // replace any double or triple slashes + return protUrl.replace(/([^:]\/)\/+/g, '$1'); +}; + +const buildEmptyJsonBody = (bodySchema) => { + let _jsonBody = {}; + each(bodySchema.properties || {}, (prop, name) => { + if (prop.type === 'object') { + _jsonBody[name] = buildEmptyJsonBody(prop); + // handle arrays + } else if (prop.type === 'array') { + _jsonBody[name] = []; + } else { + _jsonBody[name] = ''; + } + }); + return _jsonBody; +}; + +const transformOpenapiRequestItem = (request) => { + let _operationObject = request.operationObject; + + let operationName = _operationObject.operationId || _operationObject.summary || _operationObject.description; + if (!operationName) { + operationName = `${request.method} ${request.path}`; + } + + const brunoRequestItem = { + uid: uuid(), + name: operationName, + type: 'http-request', + request: { + url: ensureUrl(request.global.server + '/' + request.path), + method: request.method.toUpperCase(), + auth: { + mode: 'none', + basic: null, + bearer: null, + digest: null + }, + headers: [], + params: [], + body: { + mode: 'none', + json: null, + text: null, + xml: null, + formUrlEncoded: [], + multipartForm: [] + } + } + }; + + each(_operationObject.parameters || [], (param) => { + if (param.in === 'query') { + brunoRequestItem.request.params.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } else if (param.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: param.name, + value: '', + description: param.description || '', + enabled: param.required + }); + } + }); + + let auth; + // allow operation override + if (_operationObject.security && _operationObject.security.length > 0) { + let schemeName = Object.keys(_operationObject.security[0])[0]; + auth = request.global.security.getScheme(schemeName); + } else if (request.global.security.supported.length > 0) { + auth = request.global.security.supported[0]; + } + + if (auth) { + if (auth.type === 'http' && auth.scheme === 'basic') { + brunoRequestItem.request.auth.mode = 'basic'; + brunoRequestItem.request.auth.basic = { + username: '{{username}}', + password: '{{password}}' + }; + } else if (auth.type === 'http' && auth.scheme === 'bearer') { + brunoRequestItem.request.auth.mode = 'bearer'; + brunoRequestItem.request.auth.bearer = { + token: '{{token}}' + }; + } else if (auth.type === 'apiKey' && auth.in === 'header') { + brunoRequestItem.request.headers.push({ + uid: uuid(), + name: auth.name, + value: '{{apiKey}}', + description: 'Authentication header', + enabled: true + }); + } + } + + // TODO: handle allOf/anyOf/oneOf + if (_operationObject.requestBody) { + let content = get(_operationObject, 'requestBody.content', {}); + let mimeType = Object.keys(content)[0]; + let body = content[mimeType] || {}; + let bodySchema = body.schema; + if (mimeType === 'application/json') { + brunoRequestItem.request.body.mode = 'json'; + if (bodySchema && bodySchema.type === 'object') { + let _jsonBody = buildEmptyJsonBody(bodySchema); + brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); + } + } else if (mimeType === 'application/x-www-form-urlencoded') { + brunoRequestItem.request.body.mode = 'formUrlEncoded'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.formUrlEncoded.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'multipart/form-data') { + brunoRequestItem.request.body.mode = 'multipartForm'; + if (bodySchema && bodySchema.type === 'object') { + each(bodySchema.properties || {}, (prop, name) => { + brunoRequestItem.request.body.multipartForm.push({ + uid: uuid(), + name: name, + value: '', + description: prop.description || '', + enabled: true + }); + }); + } + } else if (mimeType === 'text/plain') { + brunoRequestItem.request.body.mode = 'text'; + brunoRequestItem.request.body.text = ''; + } else if (mimeType === 'text/xml') { + brunoRequestItem.request.body.mode = 'xml'; + brunoRequestItem.request.body.xml = ''; + } + } + + return brunoRequestItem; +}; + +const resolveRefs = (spec, components = spec.components, visitedItems = new Set()) => { + if (!spec || typeof spec !== 'object') { + return spec; + } + + if (Array.isArray(spec)) { + return spec.map((item) => resolveRefs(item, components, visitedItems)); + } + + if ('$ref' in spec) { + const refPath = spec.$ref; + + if (visitedItems.has(refPath)) { + return spec; + } else { + visitedItems.add(refPath); + } + + if (refPath.startsWith('#/components/')) { + // Local reference within components + const refKeys = refPath.replace('#/components/', '').split('/'); + let ref = components; + + for (const key of refKeys) { + if (ref[key]) { + ref = ref[key]; + } else { + // Handle invalid references gracefully? + return spec; + } + } + + return resolveRefs(ref, components, visitedItems); + } else { + // Handle external references (not implemented here) + // You would need to fetch the external reference and resolve it. + // Example: Fetch and resolve an external reference from a URL. + } + } + + // Recursively resolve references in nested objects + for (const prop in spec) { + spec[prop] = resolveRefs(spec[prop], components, visitedItems); + } + + return spec; +}; + +const groupRequestsByTags = (requests) => { + let _groups = {}; + let ungrouped = []; + each(requests, (request) => { + let tags = request.operationObject.tags || []; + if (tags.length > 0) { + let tag = tags[0]; // take first tag + if (!_groups[tag]) { + _groups[tag] = []; + } + _groups[tag].push(request); + } else { + ungrouped.push(request); + } + }); + + let groups = Object.keys(_groups).map((groupName) => { + return { + name: groupName, + requests: _groups[groupName] + }; + }); + + return [groups, ungrouped]; +}; + +const getDefaultUrl = (serverObject) => { + let url = serverObject.url; + if (serverObject.variables) { + each(serverObject.variables, (variable, variableName) => { + let sub = variable.default || (variable.enum ? variable.enum[0] : `{{${variableName}}}`); + url = url.replace(`{${variableName}}`, sub); + }); + } + return url; +}; + +const getSecurity = (apiSpec) => { + let defaultSchemes = apiSpec.security || []; + + let securitySchemes = get(apiSpec, 'components.securitySchemes', {}); + if (Object.keys(securitySchemes) === 0) { + return { + supported: [] + }; + } + + return { + supported: defaultSchemes.map((scheme) => { + var schemeName = Object.keys(scheme)[0]; + return securitySchemes[schemeName]; + }), + schemes: securitySchemes, + getScheme: (schemeName) => { + return securitySchemes[schemeName]; + } + }; +}; + +const parseOpenApiCollection = (data) => { + const brunoCollection = { + name: '', + uid: uuid(), + version: '1', + items: [], + environments: [] + }; + + return new Promise((resolve, reject) => { + try { + const collectionData = resolveRefs(data); + if (!collectionData) { + reject(new BrunoError('Invalid OpenAPI collection. Failed to resolve refs.')); + return; + } + + // Currently parsing of openapi spec is "do your best", that is + // allows "invalid" openapi spec + + // assumes v3 if not defined. v2 no supported yet + if (collectionData.openapi && !collectionData.openapi.startsWith('3')) { + reject(new BrunoError('Only OpenAPI v3 is supported currently.')); + return; + } + + // TODO what if info.title not defined? + brunoCollection.name = collectionData.info.title; + let servers = collectionData.servers || []; + let baseUrl = servers[0] ? getDefaultUrl(servers[0]) : ''; + let securityConfig = getSecurity(collectionData); + + let allRequests = Object.entries(collectionData.paths) + .map(([path, methods]) => { + return Object.entries(methods) + .filter(([method, op]) => { + return ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes( + method.toLowerCase() + ); + }) + .map(([method, operationObject]) => { + return { + method: method, + path: path, + operationObject: operationObject, + global: { + server: baseUrl, + security: securityConfig + } + }; + }); + }) + .reduce((acc, val) => acc.concat(val), []); // flatten + + let [groups, ungroupedRequests] = groupRequestsByTags(allRequests); + let brunoFolders = groups.map((group) => { + return { + uid: uuid(), + name: group.name, + type: 'folder', + items: group.requests.map(transformOpenapiRequestItem) + }; + }); + + let ungroupedItems = ungroupedRequests.map(transformOpenapiRequestItem); + let brunoCollectionItems = brunoFolders.concat(ungroupedItems); + brunoCollection.items = brunoCollectionItems; + resolve(brunoCollection); + } catch (err) { + console.error(err); + reject(new BrunoError('An error occurred while parsing the OpenAPI collection')); + } + }); +}; + +const importCollection = () => { + return new Promise((resolve, reject) => { + fileDialog({ accept: '.json, .yaml, .yml, application/json, application/yaml, application/x-yaml' }) + .then(readFile) + .then(parseOpenApiCollection) + .then(transformItemsInCollection) + .then(hydrateSeqInCollection) + .then(validateSchema) + .then((collection) => resolve(collection)) + .catch((err) => { + console.error(err); + reject(new BrunoError('Import collection failed: ' + err.message)); + }); + }); +}; + +export default importCollection; diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index 7fd36dad..136bef94 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -14,11 +14,46 @@ const readFile = (files) => { }); }; +const parseGraphQLRequest = (graphqlSource) => { + try { + let queryResultObject = { + query: '', + variables: '' + }; + + if (typeof graphqlSource === 'string') { + graphqlSource = JSON.parse(text); + } + + if (graphqlSource.hasOwnProperty('variables') && graphqlSource.variables !== '') { + queryResultObject.variables = graphqlSource.variables; + } + + if (graphqlSource.hasOwnProperty('query') && graphqlSource.query !== '') { + queryResultObject.query = graphqlSource.query; + } + + return queryResultObject; + } catch (e) { + return { + query: '', + variables: '' + }; + } +}; + const isItemAFolder = (item) => { return !item.request; }; -const importPostmanV2CollectionItem = (brunoParent, item) => { +const convertV21Auth = (array) => { + return array.reduce((accumulator, currentValue) => { + accumulator[currentValue.key] = currentValue.value; + return accumulator; + }, {}); +}; + +const importPostmanV2CollectionItem = (brunoParent, item, parentAuth) => { brunoParent.items = brunoParent.items || []; each(item, (i) => { @@ -31,7 +66,7 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { }; brunoParent.items.push(brunoFolderItem); if (i.item && i.item.length) { - importPostmanV2CollectionItem(brunoFolderItem, i.item); + importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth); } } else { if (i.request) { @@ -49,6 +84,12 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { request: { url: url, method: i.request.method, + auth: { + mode: 'none', + basic: null, + bearer: null, + awsv4: null + }, headers: [], params: [], body: { @@ -62,6 +103,31 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { } }; + if (i.event) { + i.event.forEach((event) => { + if (event.listen === 'prerequest' && event.script && event.script.exec) { + if (!brunoRequestItem.request.script) { + brunoRequestItem.request.script = {}; + } + if (Array.isArray(event.script.exec)) { + brunoRequestItem.request.script.req = event.script.exec.map((line) => `// ${line}`).join('\n'); + } else { + brunoRequestItem.request.script.req = `// ${event.script.exec[0]} `; + } + } + if (event.listen === 'test' && event.script && event.script.exec) { + if (!brunoRequestItem.request.tests) { + brunoRequestItem.request.tests = {}; + } + if (Array.isArray(event.script.exec)) { + brunoRequestItem.request.tests = event.script.exec.map((line) => `// ${line}`).join('\n'); + } else { + brunoRequestItem.request.tests = `// ${event.script.exec[0]} `; + } + } + }); + } + const bodyMode = get(i, 'request.body.mode'); if (bodyMode) { if (bodyMode === 'formdata') { @@ -108,6 +174,12 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { } } + if (bodyMode === 'graphql') { + brunoRequestItem.type = 'graphql-request'; + brunoRequestItem.request.body.mode = 'graphql'; + brunoRequestItem.request.body.graphql = parseGraphQLRequest(i.request.body.graphql); + } + each(i.request.header, (header) => { brunoRequestItem.request.headers.push({ uid: uuid(), @@ -118,6 +190,36 @@ const importPostmanV2CollectionItem = (brunoParent, item) => { }); }); + const auth = i.request.auth ?? parentAuth; + if (auth?.[auth.type] && auth.type !== 'noauth') { + let authValues = auth[auth.type]; + if (Array.isArray(authValues)) { + authValues = convertV21Auth(authValues); + } + if (auth.type === 'basic') { + brunoRequestItem.request.auth.mode = 'basic'; + brunoRequestItem.request.auth.basic = { + username: authValues.username, + password: authValues.password + }; + } else if (auth.type === 'bearer') { + brunoRequestItem.request.auth.mode = 'bearer'; + brunoRequestItem.request.auth.bearer = { + token: authValues.token + }; + } else if (auth.type === 'awsv4') { + brunoRequestItem.request.auth.mode = 'awsv4'; + brunoRequestItem.request.auth.awsv4 = { + accessKeyId: authValues.accessKey, + secretAccessKey: authValues.secretKey, + sessionToken: authValues.sessionToken, + service: authValues.service, + region: authValues.region, + profileName: '' + }; + } + } + each(get(i, 'request.url.query'), (param) => { brunoRequestItem.request.params.push({ uid: uuid(), @@ -158,7 +260,7 @@ const importPostmanV2Collection = (collection) => { environments: [] }; - importPostmanV2CollectionItem(brunoCollection, collection.item); + importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth); return brunoCollection; }; diff --git a/packages/bruno-cli/changelog.md b/packages/bruno-cli/changelog.md index 44130498..1d672930 100644 --- a/packages/bruno-cli/changelog.md +++ b/packages/bruno-cli/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 1.1.0 + +- Upgraded axios to 1.5.1 + +## 1.0.0 + +- Announcing Stable Release + ## 0.13.0 - feat(#306) Module whitelisting and filesystem access support diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json index f36459c7..37ea480a 100644 --- a/packages/bruno-cli/package.json +++ b/packages/bruno-cli/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/cli", - "version": "0.14.0", + "version": "1.1.1", "license": "MIT", "main": "src/index.js", "bin": { @@ -24,8 +24,8 @@ "package.json" ], "dependencies": { - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", "axios": "^1.5.1", "chai": "^4.3.7", "chalk": "^3.0.0", diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index bd440d62..32122f74 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -3,7 +3,7 @@ const qs = require('qs'); const chalk = require('chalk'); const decomment = require('decomment'); const fs = require('fs'); -const { forOwn, each, extend, get, compact } = require('lodash'); +const { forOwn, isUndefined, isNull, each, extend, get, compact } = require('lodash'); const FormData = require('form-data'); const prepareRequest = require('./prepare-request'); const interpolateVars = require('./interpolate-vars'); @@ -12,11 +12,10 @@ const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@use const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); const https = require('https'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('../utils/axios-instance'); -const { shouldUseProxy } = require('../utils/proxy-util'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'); const runSingleRequest = async function ( filename, @@ -137,14 +136,15 @@ const runSingleRequest = async function ( const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false); const socksEnabled = proxyProtocol.includes('socks'); + let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`; let proxyUri; if (proxyAuthEnabled) { const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions); const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions); - proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`; } else { - proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`; } if (socksEnabled) { @@ -152,7 +152,7 @@ const runSingleRequest = async function ( request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); diff --git a/packages/bruno-cli/src/utils/proxy-util.js b/packages/bruno-cli/src/utils/proxy-util.js index dd526d0d..729e0335 100644 --- a/packages/bruno-cli/src/utils/proxy-util.js +++ b/packages/bruno-cli/src/utils/proxy-util.js @@ -1,4 +1,6 @@ const parseUrl = require('url').parse; +const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -9,7 +11,7 @@ const DEFAULT_PORTS = { wss: 443 }; /** - * check for proxy bypass, Copied form 'proxy-from-env' + * check for proxy bypass, copied form 'proxy-from-env' */ const shouldUseProxy = (url, proxyBypass) => { if (proxyBypass === '*') { @@ -39,7 +41,6 @@ const shouldUseProxy = (url, proxyBypass) => { if (!dontProxyFor) { return true; // Skip zero-length hosts. } - const parsedProxy = dontProxyFor.match(/^(.+):(\d+)$/); let parsedProxyHostname = parsedProxy ? parsedProxy[1] : dontProxyFor; const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0; @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores + * options like ca and rejectUnauthorized when upgrading the socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 3b456d31..7fa75c1b 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v0.26.0", + "version": "v1.1.1", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", @@ -20,15 +20,15 @@ }, "dependencies": { "@aws-sdk/credential-providers": "^3.425.0", - "@usebruno/js": "0.8.0", - "@usebruno/lang": "0.8.0", - "@usebruno/schema": "0.5.0", + "@usebruno/js": "0.9.2", + "@usebruno/lang": "0.9.0", + "@usebruno/schema": "0.6.0", "about-window": "^1.15.2", "aws4-axios": "^3.3.0", "axios": "^1.5.1", "chai": "^4.3.7", - "chai-string": "^1.5.0", "chokidar": "^3.5.3", + "content-disposition": "^0.5.4", "decomment": "^0.9.5", "dotenv": "^16.0.3", "electron-is-dev": "^2.0.0", @@ -42,7 +42,9 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "is-valid-path": "^0.1.1", + "js-yaml": "^4.1.0", "lodash": "^4.17.21", + "mime-types": "^2.1.35", "mustache": "^4.2.0", "nanoid": "3.3.4", "node-machine-id": "^1.1.12", diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index cc0f1267..c91740af 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -12,6 +12,14 @@ const template = [ ipcMain.emit('main:open-collection'); } }, + { + label: 'Preferences', + accelerator: 'CommandOrControl+,', + click() { + ipcMain.emit('main:open-preferences'); + } + }, + { type: 'separator' }, { role: 'quit' } ] }, diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index f7cb3acc..a65dc28c 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -14,16 +14,18 @@ const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window') const lastOpenedCollections = new LastOpenedCollections(); +// Reference: https://content-security-policy.com/ const contentSecurityPolicy = [ - isDev ? "default-src 'self' 'unsafe-inline' 'unsafe-eval'" : "default-src 'self'", - "connect-src 'self' https://api.github.com/repos/usebruno/bruno", - "font-src 'self' https://fonts.gstatic.com", + "default-src 'self'", + "script-src * 'unsafe-inline' 'unsafe-eval'", + "connect-src 'self' api.github.com", + "font-src 'self' https:", "form-action 'none'", - "img-src 'self' blob: data:", - "style-src 'self' https://fonts.googleapis.com" + "img-src 'self' blob: data: https:", + "style-src 'self' 'unsafe-inline' https:" ]; -setContentSecurityPolicy(contentSecurityPolicy.join(';')); +setContentSecurityPolicy(contentSecurityPolicy.join(';') + ';'); const menu = Menu.buildFromTemplate(menuTemplate); Menu.setApplicationMenu(menu); @@ -67,7 +69,21 @@ app.on('ready', async () => { slashes: true }); - mainWindow.loadURL(url); + mainWindow.loadURL(url).catch((reason) => { + console.error(`Error: Failed to load URL: "${url}" (Electron shows a blank screen because of this).`); + console.error('Original message:', reason); + if (isDev) { + console.error( + 'Could not connect to Next.Js dev server, is it running?' + + ' Start the dev server using "npm run dev:web" and restart electron' + ); + } else { + console.error( + 'If you are using an official production build: the above error is most likely a bug! ' + + ' Please report this under: https://github.com/usebruno/bruno/issues' + ); + } + }); watcher = new Watcher(); const handleBoundsChange = () => { diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ab92d50b..be6afbea 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -1,7 +1,7 @@ const _ = require('lodash'); const fs = require('fs'); const path = require('path'); -const { ipcMain, shell } = require('electron'); +const { ipcMain, shell, dialog } = require('electron'); const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru'); const { @@ -461,6 +461,22 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection ipcMain.handle('renderer:open-devtools', async () => { mainWindow.webContents.openDevTools(); }); + + ipcMain.handle('renderer:load-gql-schema-file', async () => { + try { + const { filePaths } = await dialog.showOpenDialog(mainWindow, { + properties: ['openFile'] + }); + if (filePaths.length === 0) { + return; + } + + const jsonData = fs.readFileSync(filePaths[0], 'utf8'); + return JSON.parse(jsonData); + } catch (err) { + return Promise.reject(new Error('Failed to load GraphQL schema file')); + } + }); }; const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => { diff --git a/packages/bruno-electron/src/ipc/network/digestauth-helper.js b/packages/bruno-electron/src/ipc/network/digestauth-helper.js new file mode 100644 index 00000000..fdcf77cc --- /dev/null +++ b/packages/bruno-electron/src/ipc/network/digestauth-helper.js @@ -0,0 +1,79 @@ +const crypto = require('crypto'); + +function isStrPresent(str) { + return str && str !== '' && str !== 'undefined'; +} + +function stripQuotes(str) { + return str.replace(/"/g, ''); +} + +function containsDigestHeader(response) { + const authHeader = response?.headers?.['www-authenticate']; + return authHeader ? authHeader.trim().toLowerCase().startsWith('digest') : false; +} + +function containsAuthorizationHeader(originalRequest) { + return Boolean(originalRequest.headers['Authorization']); +} + +function md5(input) { + return crypto.createHash('md5').update(input).digest('hex'); +} + +function addDigestInterceptor(axiosInstance, request) { + const { username, password } = request.digestConfig; + + console.debug(request); + + if (!isStrPresent(username) || !isStrPresent(password)) { + console.warn('Required Digest Auth fields are not present'); + return; + } + + axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + const originalRequest = error.config; + + if ( + error.response?.status === 401 && + containsDigestHeader(error.response) && + !containsAuthorizationHeader(originalRequest) + ) { + console.debug(error.response.headers['www-authenticate']); + + const authDetails = error.response.headers['www-authenticate'] + .split(', ') + .map((v) => v.split('=').map(stripQuotes)) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + console.debug(authDetails); + + const nonceCount = '00000001'; + const cnonce = crypto.randomBytes(24).toString('hex'); + + if (authDetails.algorithm.toUpperCase() !== 'MD5') { + console.warn(`Unsupported Digest algorithm: ${algo}`); + return Promise.reject(error); + } + const HA1 = md5(`${username}:${authDetails['Digest realm']}:${password}`); + const HA2 = md5(`${request.method}:${request.url}`); + const response = md5(`${HA1}:${authDetails.nonce}:${nonceCount}:${cnonce}:auth:${HA2}`); + + const authorizationHeader = + `Digest username="${username}",realm="${authDetails['Digest realm']}",` + + `nonce="${authDetails.nonce}",uri="${request.url}",qop="auth",algorithm="${authDetails.algorithm}",` + + `response="${response}",nc="${nonceCount}",cnonce="${cnonce}"`; + originalRequest.headers['Authorization'] = authorizationHeader; + console.debug(`Authorization: ${originalRequest.headers['Authorization']}`); + + delete originalRequest.digestConfig; + return axiosInstance(originalRequest); + } + + return Promise.reject(error); + } + ); +} + +module.exports = { addDigestInterceptor }; diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index f092e9ee..d6afbe6f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -3,11 +3,13 @@ const fs = require('fs'); const qs = require('qs'); const https = require('https'); const axios = require('axios'); +const path = require('path'); const decomment = require('decomment'); const Mustache = require('mustache'); -const FormData = require('form-data'); +const contentDispositionParser = require('content-disposition'); +const mime = require('mime-types'); const { ipcMain } = require('electron'); -const { forOwn, extend, each, get, compact } = require('lodash'); +const { isUndefined, isNull, each, get, compact } = require('lodash'); const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js'); const prepareRequest = require('./prepare-request'); const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request'); @@ -19,12 +21,13 @@ const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); const { preferencesUtil } = require('../../store/preferences'); const { getProcessEnvVars } = require('../../store/process-env'); const { getBrunoConfig } = require('../../store/bruno-config'); -const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { makeAxiosInstance } = require('./axios-instance'); const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper'); -const { shouldUseProxy } = require('../../utils/proxy-util'); +const { addDigestInterceptor } = require('./digestauth-helper'); +const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../../utils/proxy-util'); +const { chooseFileToSave, writeBinaryFile } = require('../../utils/filesystem'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -68,23 +71,14 @@ const getEnvVars = (environment = {}) => { }; }; -const getSize = (data) => { - if (!data) { - return 0; - } - - if (typeof data === 'string') { - return Buffer.byteLength(data, 'utf8'); - } - - if (typeof data === 'object') { - return Buffer.byteLength(safeStringifyJSON(data), 'utf8'); - } - - return 0; -}; - -const configureRequest = async (collectionUid, request, envVars, collectionVariables, processEnvVars) => { +const configureRequest = async ( + collectionUid, + request, + envVars, + collectionVariables, + processEnvVars, + collectionPath +) => { const httpsAgentRequestFields = {}; if (!preferencesUtil.shouldVerifyTls()) { httpsAgentRequestFields['rejectUnauthorized'] = false; @@ -101,18 +95,29 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria const clientCertConfig = get(brunoConfig, 'clientCertificates.certs', []); for (let clientCert of clientCertConfig) { const domain = interpolateString(clientCert.domain, interpolationOptions); - const certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); - const keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + + let certFilePath = interpolateString(clientCert.certFilePath, interpolationOptions); + certFilePath = path.isAbsolute(certFilePath) ? certFilePath : path.join(collectionPath, certFilePath); + + let keyFilePath = interpolateString(clientCert.keyFilePath, interpolationOptions); + keyFilePath = path.isAbsolute(keyFilePath) ? keyFilePath : path.join(collectionPath, keyFilePath); + if (domain && certFilePath && keyFilePath) { const hostRegex = '^https:\\/\\/' + domain.replaceAll('.', '\\.').replaceAll('*', '.*'); if (request.url.match(hostRegex)) { try { httpsAgentRequestFields['cert'] = fs.readFileSync(certFilePath); + } catch (err) { + console.log('Error reading cert file', err); + } + + try { httpsAgentRequestFields['key'] = fs.readFileSync(keyFilePath); } catch (err) { - console.log('Error reading cert/key file', err); + console.log('Error reading key file', err); } + httpsAgentRequestFields['passphrase'] = interpolateString(clientCert.passphrase, interpolationOptions); break; } @@ -121,7 +126,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria // proxy configuration let proxyConfig = get(brunoConfig, 'proxy', {}); - let proxyEnabled = get(proxyConfig, 'use', false); + let proxyEnabled = get(proxyConfig, 'enabled', 'global'); if (proxyEnabled === 'global') { proxyConfig = preferencesUtil.getGlobalProxyConfig(); proxyEnabled = get(proxyConfig, 'enabled', false); @@ -134,14 +139,15 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria const proxyAuthEnabled = get(proxyConfig, 'auth.enabled', false); const socksEnabled = proxyProtocol.includes('socks'); + let uriPort = isUndefined(proxyPort) || isNull(proxyPort) ? '' : `:${proxyPort}`; let proxyUri; if (proxyAuthEnabled) { const proxyAuthUsername = interpolateString(get(proxyConfig, 'auth.username'), interpolationOptions); const proxyAuthPassword = interpolateString(get(proxyConfig, 'auth.password'), interpolationOptions); - proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`; + proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}${uriPort}`; } else { - proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`; + proxyUri = `${proxyProtocol}://${proxyHostname}${uriPort}`; } if (socksEnabled) { @@ -149,7 +155,7 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria request.httpsAgent = socksProxyAgent; request.httpAgent = socksProxyAgent; } else { - request.httpsAgent = new HttpsProxyAgent( + request.httpsAgent = new PatchedHttpsProxyAgent( proxyUri, Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined ); @@ -169,6 +175,10 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria delete request.awsv4config; } + if (request.digestConfig) { + addDigestInterceptor(axiosInstance, request); + } + request.timeout = preferencesUtil.getRequestTimeout(); return axiosInstance; @@ -177,10 +187,10 @@ const configureRequest = async (collectionUid, request, envVars, collectionVaria const parseDataFromResponse = (response) => { const dataBuffer = Buffer.from(response.data); // Parse the charset from content type: https://stackoverflow.com/a/33192813 - const charset = /charset=([^()<>@,;:\"/[\]?.=\s]*)/i.exec(response.headers['Content-Type'] || ''); + const charset = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['Content-Type'] || ''); // Overwrite the original data for backwards compatability let data = dataBuffer.toString(charset || 'utf-8'); - // Try to parse response to JSON, this can quitly fail + // Try to parse response to JSON, this can quietly fail try { data = JSON.parse(response.data); } catch {} @@ -189,6 +199,144 @@ const parseDataFromResponse = (response) => { }; const registerNetworkIpc = (mainWindow) => { + const onConsoleLog = (type, args) => { + console[type](...args); + + mainWindow.webContents.send('main:console-log', { + type, + args + }); + }; + + const runPreRequest = async ( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run pre-request vars + const preRequestVars = get(request, 'vars.req', []); + if (preRequestVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPreRequestVars( + preRequestVars, + request, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run pre-request script + const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(os.EOL); + if (requestScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runRequestScript( + decomment(requestScript), + request, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + + // interpolate variables inside request + interpolateVars(request, envVars, collectionVariables, processEnvVars); + + // stringify the request url encoded params + if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { + request.data = qs.stringify(request.data); + } + }; + + const runPostResponse = async ( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ) => { + // run post-response vars + const postResponseVars = get(request, 'vars.res', []); + if (postResponseVars?.length) { + const varsRuntime = new VarsRuntime(); + const result = varsRuntime.runPostResponseVars( + postResponseVars, + request, + response, + envVars, + collectionVariables, + collectionPath, + processEnvVars + ); + + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + } + + // run post-response script + const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( + os.EOL + ); + if (responseScript?.length) { + const scriptRuntime = new ScriptRuntime(); + const result = await scriptRuntime.runResponseScript( + decomment(responseScript), + request, + response, + envVars, + collectionVariables, + collectionPath, + onConsoleLog, + processEnvVars, + scriptingConfig + ); + + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } + }; + // handler for sending http request ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => { const collectionUid = collection.uid; @@ -196,15 +344,6 @@ const registerNetworkIpc = (mainWindow) => { const cancelTokenUid = uuid(); const requestUid = uuid(); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - mainWindow.webContents.send('main:run-request-event', { type: 'request-queued', requestUid, @@ -222,75 +361,21 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - const cancelToken = axios.CancelToken.source(); request.cancelToken = cancelToken.token; saveCancelToken(cancelTokenUid, cancelToken); - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run pre-request script - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - - interpolateVars(request, envVars, collectionVariables, processEnvVars); - - // stringify the request url encoded params - if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { - request.data = qs.stringify(request.data); - } // todo: // i have no clue why electron can't send the request object @@ -315,7 +400,8 @@ const registerNetworkIpc = (mainWindow) => { request, envVars, collectionVariables, - processEnvVars + processEnvVars, + collectionPath ); let response, responseTime; @@ -353,55 +439,18 @@ const registerNetworkIpc = (mainWindow) => { const { data, dataBuffer } = parseDataFromResponse(response); response.data = data; - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } - } - - // run post-response script - const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join( - os.EOL + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (responseScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); - } // run assertions const assertions = get(request, 'assertions'); @@ -502,18 +551,50 @@ const registerNetworkIpc = (mainWindow) => { }); } - const processEnvVars = getProcessEnvVars(collection.uid); - interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars); + const requestUid = uuid(); + const collectionPath = collection.pathname; + const collectionUid = collection.uid; + const collectionVariables = collection.collectionVariables; + const processEnvVars = getProcessEnvVars(collectionUid); + const brunoConfig = getBrunoConfig(collection.uid); + const scriptingConfig = get(brunoConfig, 'scripts', {}); + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + + interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars); const axiosInstance = await configureRequest( collection.uid, preparedRequest, envVars, collection.collectionVariables, - processEnvVars + processEnvVars, + collectionPath ); const response = await axiosInstance(preparedRequest); + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); + return { status: response.status, statusText: response.statusText, @@ -544,15 +625,6 @@ const registerNetworkIpc = (mainWindow) => { const scriptingConfig = get(brunoConfig, 'scripts', {}); const collectionRoot = get(collection, 'root', {}); - const onConsoleLog = (type, args) => { - console[type](...args); - - mainWindow.webContents.send('main:console-log', { - type, - args - }); - }; - if (!folder) { folder = collection; } @@ -602,67 +674,21 @@ const registerNetworkIpc = (mainWindow) => { const _request = item.draft ? item.draft.request : item.request; const request = prepareRequest(_request, collectionRoot); + const requestUid = uuid(); const processEnvVars = getProcessEnvVars(collectionUid); try { - // make axios work in node using form data - // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 - if (request.headers && request.headers['content-type'] === 'multipart/form-data') { - const form = new FormData(); - forOwn(request.data, (value, key) => { - form.append(key, value); - }); - extend(request.headers, form.getHeaders()); - request.data = form; - } - - // run pre-request vars - const preRequestVars = get(request, 'vars.req', []); - if (preRequestVars && preRequestVars.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPreRequestVars( - preRequestVars, - request, - envVars, - collectionVariables, - collectionPath - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - } - - // run pre-request script - const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join( - os.EOL + await runPreRequest( + request, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig ); - if (requestScript?.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runRequestScript( - decomment(requestScript), - request, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - - // interpolate variables inside request - interpolateVars(request, envVars, collectionVariables, processEnvVars); // todo: // i have no clue why electron can't send the request object @@ -683,7 +709,8 @@ const registerNetworkIpc = (mainWindow) => { request, envVars, collectionVariables, - processEnvVars + processEnvVars, + collectionPath ); timeStart = Date.now(); @@ -738,54 +765,18 @@ const registerNetworkIpc = (mainWindow) => { } } - // run post-response vars - const postResponseVars = get(request, 'vars.res', []); - if (postResponseVars?.length) { - const varsRuntime = new VarsRuntime(); - const result = varsRuntime.runPostResponseVars( - postResponseVars, - request, - response, - envVars, - collectionVariables, - collectionPath, - processEnvVars - ); - - if (result) { - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } - } - - // run response script - const responseScript = compact([ - get(collectionRoot, 'request.script.res'), - get(request, 'script.res') - ]).join(os.EOL); - if (responseScript && responseScript.length) { - const scriptRuntime = new ScriptRuntime(); - const result = await scriptRuntime.runResponseScript( - decomment(responseScript), - request, - response, - envVars, - collectionVariables, - collectionPath, - onConsoleLog, - processEnvVars, - scriptingConfig - ); - - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); - } + await runPostResponse( + request, + response, + requestUid, + envVars, + collectionPath, + collectionRoot, + collectionUid, + collectionVariables, + processEnvVars, + scriptingConfig + ); // run assertions const assertions = get(item, 'request.assertions'); @@ -862,6 +853,51 @@ const registerNetworkIpc = (mainWindow) => { } } ); + + // save response to file + ipcMain.handle('renderer:save-response-to-file', async (event, response, url) => { + try { + const getHeaderValue = (headerName) => { + if (response.headers) { + const header = response.headers.find((header) => header[0] === headerName); + if (header && header.length > 1) { + return header[1]; + } + } + }; + + const getFileNameFromContentDispositionHeader = () => { + const contentDisposition = getHeaderValue('content-disposition'); + try { + const disposition = contentDispositionParser.parse(contentDisposition); + return disposition && disposition.parameters['filename']; + } catch (error) {} + }; + + const getFileNameFromUrlPath = () => { + const lastPathLevel = new URL(url).pathname.split('/').pop(); + if (lastPathLevel && /\..+/.exec(lastPathLevel)) { + return lastPathLevel; + } + }; + + const getFileNameBasedOnContentTypeHeader = () => { + const contentType = getHeaderValue('content-type'); + const extension = (contentType && mime.extension(contentType)) || 'txt'; + return `response.${extension}`; + }; + + const fileName = + getFileNameFromContentDispositionHeader() || getFileNameFromUrlPath() || getFileNameBasedOnContentTypeHeader(); + + const filePath = await chooseFileToSave(mainWindow, fileName); + if (filePath) { + await writeBinaryFile(filePath, Buffer.from(response.dataBuffer, 'base64')); + } + } catch (error) { + return Promise.reject(error); + } + }); }; module.exports = registerNetworkIpc; diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 31d5cab5..6df6a7c1 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -131,6 +131,12 @@ const interpolateVars = (request, envVars = {}, collectionVariables = {}, proces request.awsv4config.profileName = interpolate(request.awsv4config.profileName) || ''; } + // interpolate vars for digest auth + if (request.digestConfig) { + request.digestConfig.username = interpolate(request.digestConfig.username) || ''; + request.digestConfig.password = interpolate(request.digestConfig.password) || ''; + } + return request; }; diff --git a/packages/bruno-electron/src/ipc/network/prepare-request.js b/packages/bruno-electron/src/ipc/network/prepare-request.js index 6c2d7d4b..bd5f7a8d 100644 --- a/packages/bruno-electron/src/ipc/network/prepare-request.js +++ b/packages/bruno-electron/src/ipc/network/prepare-request.js @@ -1,5 +1,6 @@ -const { get, each, filter } = require('lodash'); +const { get, each, filter, forOwn, extend } = require('lodash'); const decomment = require('decomment'); +const FormData = require('form-data'); // Authentication // A request can override the collection auth with another auth @@ -28,6 +29,12 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { case 'bearer': axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`; break; + case 'digest': + axiosRequest.digestConfig = { + username: get(collectionAuth, 'digest.username'), + password: get(collectionAuth, 'digest.password') + }; + break; } } @@ -52,6 +59,11 @@ const setAuthHeaders = (axiosRequest, request, collectionRoot) => { case 'bearer': axiosRequest.headers['authorization'] = `Bearer ${get(request, 'auth.bearer.token')}`; break; + case 'digest': + axiosRequest.digestConfig = { + username: get(request, 'auth.digest.username'), + password: get(request, 'auth.digest.password') + }; } } @@ -137,6 +149,15 @@ const prepareRequest = (request, collectionRoot) => { each(enabledParams, (p) => (params[p.name] = p.value)); axiosRequest.headers['content-type'] = 'multipart/form-data'; axiosRequest.data = params; + + // make axios work in node using form data + // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 + const form = new FormData(); + forOwn(axiosRequest.data, (value, key) => { + form.append(key, value); + }); + extend(axiosRequest.headers, form.getHeaders()); + axiosRequest.data = form; } if (request.body.mode === 'graphql') { diff --git a/packages/bruno-electron/src/ipc/preferences.js b/packages/bruno-electron/src/ipc/preferences.js index 679c74e3..f07c79c0 100644 --- a/packages/bruno-electron/src/ipc/preferences.js +++ b/packages/bruno-electron/src/ipc/preferences.js @@ -23,6 +23,10 @@ const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => { } }); + ipcMain.on('main:open-preferences', () => { + mainWindow.webContents.send('main:open-preferences'); + }); + ipcMain.handle('renderer:save-preferences', async (event, preferences) => { try { await savePreferences(preferences); diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js index fcf514f5..56db5d9a 100644 --- a/packages/bruno-electron/src/store/preferences.js +++ b/packages/bruno-electron/src/store/preferences.js @@ -20,7 +20,7 @@ const defaultPreferences = { enabled: false, protocol: 'http', hostname: '', - port: '', + port: null, auth: { enabled: false, username: '', diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index b55dfd72..4f3ea980 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -60,6 +60,14 @@ const writeFile = async (pathname, content) => { } }; +const writeBinaryFile = async (pathname, content) => { + try { + fs.writeFileSync(pathname, content); + } catch (err) { + return Promise.reject(err); + } +}; + const hasJsonExtension = (filename) => { if (!filename || typeof filename !== 'string') return false; return ['json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`)); @@ -95,6 +103,14 @@ const browseDirectory = async (win) => { return isDirectory(resolvedPath) ? resolvedPath : false; }; +const chooseFileToSave = async (win, preferredFileName = '') => { + const { filePath } = await dialog.showSaveDialog(win, { + defaultPath: preferredFileName + }); + + return filePath; +}; + const searchForFiles = (dir, extension) => { let results = []; const files = fs.readdirSync(dir); @@ -126,10 +142,12 @@ module.exports = { isDirectory, normalizeAndResolvePath, writeFile, + writeBinaryFile, hasJsonExtension, hasBruExtension, createDirectory, browseDirectory, + chooseFileToSave, searchForFiles, searchForBruFiles, sanitizeDirectoryName diff --git a/packages/bruno-electron/src/utils/proxy-util.js b/packages/bruno-electron/src/utils/proxy-util.js index a9fdc1b9..ef64d37a 100644 --- a/packages/bruno-electron/src/utils/proxy-util.js +++ b/packages/bruno-electron/src/utils/proxy-util.js @@ -1,5 +1,6 @@ const parseUrl = require('url').parse; const { isEmpty } = require('lodash'); +const { HttpsProxyAgent } = require('https-proxy-agent'); const DEFAULT_PORTS = { ftp: 21, @@ -61,6 +62,24 @@ const shouldUseProxy = (url, proxyBypass) => { }); }; +/** + * Patched version of HttpsProxyAgent to get around a bug that ignores options + * such as ca and rejectUnauthorized when upgrading the proxied socket to TLS: + * https://github.com/TooTallNate/proxy-agents/issues/194 + */ +class PatchedHttpsProxyAgent extends HttpsProxyAgent { + constructor(proxy, opts) { + super(proxy, opts); + this.constructorOpts = opts; + } + + async connect(req, opts) { + const combinedOpts = { ...this.constructorOpts, ...opts }; + return super.connect(req, combinedOpts); + } +} + module.exports = { - shouldUseProxy + shouldUseProxy, + PatchedHttpsProxyAgent }; diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index 00ccdcb1..d751c032 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/js", - "version": "0.8.0", + "version": "0.9.2", "license": "MIT", "main": "src/index.js", "files": [ @@ -17,9 +17,10 @@ "@usebruno/query": "0.1.0", "ajv": "^8.12.0", "atob": "^2.1.2", - "axios": "^0.26.0", + "axios": "^1.5.1", "btoa": "^1.2.1", "chai": "^4.3.7", + "chai-string": "^1.5.0", "crypto-js": "^4.1.1", "handlebars": "^4.7.8", "json-query": "^2.2.2", @@ -27,6 +28,7 @@ "moment": "^2.29.4", "nanoid": "3.3.4", "node-fetch": "2.*", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "node-vault": "^0.10.2" } } diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index 3cd9e8f5..4813a73d 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -1,6 +1,8 @@ const Handlebars = require('handlebars'); const { cloneDeep } = require('lodash'); +const envVariableNameRegex = /^(?!\d)[\w-]*$/; + class Bru { constructor(envVariables, collectionVariables, processEnvVars, collectionPath) { this.envVariables = envVariables; @@ -43,7 +45,7 @@ class Bru { setEnvVar(key, value) { if (!key) { - throw new Error('Key is required'); + throw new Error('Creating a env variable without specifying a name is not allowed.'); } // gracefully ignore if key is not present in environment @@ -56,13 +58,27 @@ class Bru { setVar(key, value) { if (!key) { - throw new Error('Key is required'); + throw new Error('Creating a variable without specifying a name is not allowed.'); + } + + if (envVariableNameRegex.test(key) === false) { + throw new Error( + `Variable name: "${key}" contains invalid characters!` + + ' Names must only contain alpha-numeric characters, "-", "_" and cannot start with a digit.' + ); } this.collectionVariables[key] = value; } getVar(key) { + if (envVariableNameRegex.test(key) === false) { + throw new Error( + `Variable name: "${key}" contains invalid characters!` + + ' Names must only contain alpha-numeric characters and cannot start with a digit.' + ); + } + return this.collectionVariables[key]; } } diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index 391d047d..f8776851 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -26,6 +26,7 @@ const axios = require('axios'); const fetch = require('node-fetch'); const chai = require('chai'); const CryptoJS = require('crypto-js'); +const NodeVault = require('node-vault'); class ScriptRuntime { constructor() {} @@ -46,6 +47,11 @@ class ScriptRuntime { const req = new BrunoRequest(request); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); + const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); + const additionalContextRootsAbsolute = lodash + .chain(additionalContextRoots) + .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr))) + .value(); const whitelistedModules = {}; @@ -83,7 +89,7 @@ class ScriptRuntime { require: { context: 'sandbox', external: true, - root: [collectionPath], + root: [collectionPath, ...additionalContextRootsAbsolute], mock: { // node libs path, @@ -107,7 +113,8 @@ class ScriptRuntime { 'node-fetch': fetch, 'crypto-js': CryptoJS, ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault } } }); @@ -196,7 +203,8 @@ class ScriptRuntime { 'node-fetch': fetch, 'crypto-js': CryptoJS, ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault } } }); diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 9da2cb28..cc46fd14 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -28,6 +28,7 @@ const nanoid = require('nanoid'); const axios = require('axios'); const fetch = require('node-fetch'); const CryptoJS = require('crypto-js'); +const NodeVault = require('node-vault'); class TestRuntime { constructor() {} @@ -48,6 +49,11 @@ class TestRuntime { const res = new BrunoResponse(response); const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false); const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []); + const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []); + const additionalContextRootsAbsolute = lodash + .chain(additionalContextRoots) + .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr))) + .value(); const whitelistedModules = {}; @@ -101,7 +107,7 @@ class TestRuntime { require: { context: 'sandbox', external: true, - root: [collectionPath], + root: [collectionPath, ...additionalContextRootsAbsolute], mock: { // node libs path, @@ -125,7 +131,8 @@ class TestRuntime { 'node-fetch': fetch, 'crypto-js': CryptoJS, ...whitelistedModules, - fs: allowScriptFilesystemAccess ? fs : undefined + fs: allowScriptFilesystemAccess ? fs : undefined, + 'node-vault': NodeVault } } }); diff --git a/packages/bruno-lang/package.json b/packages/bruno-lang/package.json index 2e4b3df0..0096efad 100644 --- a/packages/bruno-lang/package.json +++ b/packages/bruno-lang/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/lang", - "version": "0.8.0", + "version": "0.9.0", "license": "MIT", "main": "src/index.js", "files": [ diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 5ce65eba..fbe28997 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -23,7 +23,7 @@ const { outdentString } = require('../../v1/src/utils'); */ const grammar = ohm.grammar(`Bru { BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)* - auths = authawsv4 | authbasic | authbearer + auths = authawsv4 | authbasic | authbearer | authdigest bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body bodyforms = bodyformurlencoded | bodymultipart @@ -79,6 +79,7 @@ const grammar = ohm.grammar(`Bru { authawsv4 = "auth:awsv4" dictionary authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary + authdigest = "auth:digest" dictionary body = "body" st* "{" nl* textblock tagend bodyjson = "body:json" st* "{" nl* textblock tagend @@ -104,7 +105,7 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => { } return _.map(pairList[0], (pair) => { let name = _.keys(pair)[0]; - let value = pair[name]; + let value = decodeURIComponent(pair[name]); if (!parseEnabled) { return { @@ -350,6 +351,21 @@ const sem = grammar.createSemantics().addAttribute('ast', { } }; }, + authdigest(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const usernameKey = _.find(auth, { name: 'username' }); + const passwordKey = _.find(auth, { name: 'password' }); + const username = usernameKey ? usernameKey.value : ''; + const password = passwordKey ? passwordKey.value : ''; + return { + auth: { + digest: { + username, + password + } + } + }; + }, bodyformurlencoded(_1, dictionary) { return { body: { diff --git a/packages/bruno-lang/v2/src/collectionBruToJson.js b/packages/bruno-lang/v2/src/collectionBruToJson.js index 4569736f..c24f6e6a 100644 --- a/packages/bruno-lang/v2/src/collectionBruToJson.js +++ b/packages/bruno-lang/v2/src/collectionBruToJson.js @@ -4,7 +4,7 @@ const { outdentString } = require('../../v1/src/utils'); const grammar = ohm.grammar(`Bru { BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)* - auths = authawsv4 | authbasic | authbearer + auths = authawsv4 | authbasic | authbearer | authdigest nl = "\\r"? "\\n" st = " " | "\\t" @@ -41,6 +41,7 @@ const grammar = ohm.grammar(`Bru { authawsv4 = "auth:awsv4" dictionary authbasic = "auth:basic" dictionary authbearer = "auth:bearer" dictionary + authdigest = "auth:digest" dictionary script = scriptreq | scriptres scriptreq = "script:pre-request" st* "{" nl* textblock tagend @@ -226,6 +227,21 @@ const sem = grammar.createSemantics().addAttribute('ast', { } }; }, + authdigest(_1, dictionary) { + const auth = mapPairListToKeyValPairs(dictionary.ast, false); + const usernameKey = _.find(auth, { name: 'username' }); + const passwordKey = _.find(auth, { name: 'password' }); + const username = usernameKey ? usernameKey.value : ''; + const password = passwordKey ? passwordKey.value : ''; + return { + auth: { + digest: { + username, + password + } + } + }; + }, varsreq(_1, dictionary) { const vars = mapPairListToKeyValPairs(dictionary.ast); _.each(vars, (v) => { diff --git a/packages/bruno-lang/v2/src/jsonToBru.js b/packages/bruno-lang/v2/src/jsonToBru.js index 757406b7..f4959500 100644 --- a/packages/bruno-lang/v2/src/jsonToBru.js +++ b/packages/bruno-lang/v2/src/jsonToBru.js @@ -114,6 +114,15 @@ ${indentString(`password: ${auth.basic.password}`)} ${indentString(`token: ${auth.bearer.token}`)} } +`; + } + + if (auth && auth.digest) { + bru += `auth:digest { +${indentString(`username: ${auth.digest.username}`)} +${indentString(`password: ${auth.digest.password}`)} +} + `; } @@ -154,7 +163,7 @@ ${indentString(body.sparql)} if (enabled(body.formUrlEncoded).length) { bru += `\n${indentString( enabled(body.formUrlEncoded) - .map((item) => `${item.name}: ${item.value}`) + .map((item) => `${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } @@ -162,7 +171,7 @@ ${indentString(body.sparql)} if (disabled(body.formUrlEncoded).length) { bru += `\n${indentString( disabled(body.formUrlEncoded) - .map((item) => `~${item.name}: ${item.value}`) + .map((item) => `~${item.name}: ${encodeURIComponent(item.value)}`) .join('\n') )}`; } diff --git a/packages/bruno-lang/v2/src/jsonToCollectionBru.js b/packages/bruno-lang/v2/src/jsonToCollectionBru.js index ea928a68..08a3abad 100644 --- a/packages/bruno-lang/v2/src/jsonToCollectionBru.js +++ b/packages/bruno-lang/v2/src/jsonToCollectionBru.js @@ -102,6 +102,15 @@ ${indentString(`password: ${auth.basic.password}`)} ${indentString(`token: ${auth.bearer.token}`)} } +`; + } + + if (auth && auth.digest) { + bru += `auth:digest { +${indentString(`username: ${auth.digest.username}`)} +${indentString(`password: ${auth.digest.password}`)} +} + `; } diff --git a/packages/bruno-lang/v2/tests/fixtures/collection.bru b/packages/bruno-lang/v2/tests/fixtures/collection.bru index a02be30c..44a66c8d 100644 --- a/packages/bruno-lang/v2/tests/fixtures/collection.bru +++ b/packages/bruno-lang/v2/tests/fixtures/collection.bru @@ -21,6 +21,11 @@ auth:bearer { token: 123 } +auth:digest { + username: john + password: secret +} + vars:pre-request { departingDate: 2020-01-01 ~returningDate: 2020-01-02 diff --git a/packages/bruno-lang/v2/tests/fixtures/collection.json b/packages/bruno-lang/v2/tests/fixtures/collection.json index de827d11..7bda2534 100644 --- a/packages/bruno-lang/v2/tests/fixtures/collection.json +++ b/packages/bruno-lang/v2/tests/fixtures/collection.json @@ -27,6 +27,10 @@ }, "bearer": { "token": "123" + }, + "digest": { + "username": "john", + "password": "secret" } }, "vars": { diff --git a/packages/bruno-lang/v2/tests/fixtures/request.bru b/packages/bruno-lang/v2/tests/fixtures/request.bru index 22168b19..4855506a 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.bru +++ b/packages/bruno-lang/v2/tests/fixtures/request.bru @@ -40,6 +40,11 @@ auth:bearer { token: 123 } +auth:digest { + username: john + password: secret +} + body:json { { "hello": "world" @@ -66,7 +71,7 @@ body:sparql { body:form-urlencoded { apikey: secret - numbers: +91998877665 + numbers: %2B91998877665 ~message: hello } diff --git a/packages/bruno-lang/v2/tests/fixtures/request.json b/packages/bruno-lang/v2/tests/fixtures/request.json index def7b5f0..c23c4647 100644 --- a/packages/bruno-lang/v2/tests/fixtures/request.json +++ b/packages/bruno-lang/v2/tests/fixtures/request.json @@ -59,6 +59,10 @@ }, "bearer": { "token": "123" + }, + "digest": { + "username": "john", + "password": "secret" } }, "body": { diff --git a/packages/bruno-schema/package.json b/packages/bruno-schema/package.json index f927a326..b1ff8db8 100644 --- a/packages/bruno-schema/package.json +++ b/packages/bruno-schema/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/schema", - "version": "0.5.0", + "version": "0.6.0", "license": "MIT", "main": "src/index.js", "files": [ diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index e63d8c3d..37e6629a 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -94,11 +94,19 @@ const authBearerSchema = Yup.object({ .noUnknown(true) .strict(); +const authDigestSchema = Yup.object({ + username: Yup.string().nullable(), + password: Yup.string().nullable() +}) + .noUnknown(true) + .strict(); + const authSchema = Yup.object({ - mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer']).required('mode is required'), + mode: Yup.string().oneOf(['none', 'awsv4', 'basic', 'bearer', 'digest']).required('mode is required'), awsv4: authAwsV4Schema.nullable(), basic: authBasicSchema.nullable(), - bearer: authBearerSchema.nullable() + bearer: authBearerSchema.nullable(), + digest: authDigestSchema.nullable() }) .noUnknown(true) .strict(); diff --git a/packages/bruno-tauri/src/next-server.js b/packages/bruno-tauri/src/next-server.js index 317dd91b..410f3080 100644 --- a/packages/bruno-tauri/src/next-server.js +++ b/packages/bruno-tauri/src/next-server.js @@ -7,7 +7,7 @@ const devServer = async (dir, port) => { // Build the renderer code and watch the files await next.prepare(); - // NextJS Server + // Next.js Server const server = createServer(requestHandler); server.listen(port || 8000, () => { diff --git a/publishing.md b/publishing.md new file mode 100644 index 00000000..0d23e1f3 --- /dev/null +++ b/publishing.md @@ -0,0 +1,8 @@ +**English** | [Português (BR)](docs/publishing/publishing_pt_br.md) + +### Publishing Bruno to a new package manager + +While our code is open source and available for everyone to use, we kindly request that you reach out to us before considering publication on new package managers. As the creator of Bruno, I hold the trademark `Bruno` for this project and would like to manage its distribution. If you'd like to see Bruno on a new package manager, please raise a GitHub issue. + +While the majority of our features are free and open source (which covers REST and GraphQL Apis), +we strive to strike a harmonious balance between open-source principles and sustainability - https://github.com/usebruno/bruno/discussions/269 diff --git a/readme.md b/readme.md index cf3d2dc6..5cbb789d 100644 --- a/readme.md +++ b/readme.md @@ -10,18 +10,46 @@ [![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_ua.md) | [Русский](/readme_ru.md) +**English** | [Українська](docs/readme/readme_ua.md) | [Русский](docs/readme/readme_ru.md) | [Türkçe](docs/readme/readme_tr.md) | [Deutsch](docs/readme/readme_de.md) | [Français](docs/readme/readme_fr.md) | [Português (BR)](docs/readme/readme_pt_br.md)) | [한국어](docs/readme/readme_kr.md) ) | [বাংলা](docs/readme/readme_bn.md) | [Español](docs/readme/readme_es.md) | [Italiano](docs/readme/readme_it.md) Bruno is a new and innovative API client, aimed at revolutionizing the status quo represented by Postman and similar tools out there. Bruno stores your collections directly in a folder on your filesystem. We use a plain text markup language, Bru, to save information about API requests. -You can use git or any version control of your choice to collaborate over your API collections. +You can use Git or any version control of your choice to collaborate over your API collections. Bruno is offline-only. There are no plans to add cloud-sync to Bruno, ever. We value your data privacy and believe it should stay on your device. Read our long-term vision [here](https://github.com/usebruno/bruno/discussions/269) +📢 Watch our recent talk at India FOSS 3.0 Conference [here](https://www.youtube.com/watch?v=7bSMFpbcPiY) + ![bruno](assets/images/landing-2.png)

+### Installation + +Bruno is available as binary download [on our website](https://www.usebruno.com/downloads) for Mac, Windows and Linux. + +You can also install Bruno via package managers like Homebrew, Chocolatey, Snap and Apt. + +```sh +# On Mac via Homebrew +brew install bruno + +# On Windows via Chocolatey +choco install bruno + +# On Linux via Snap +snap install bruno + +# On Linux via Apt +sudo mkdir -p /etc/apt/keyrings +sudo gpg --no-default-keyring --keyring /etc/apt/keyrings/bruno.gpg --keyserver keyserver.ubuntu.com --recv-keys 9FA6017ECABE0266 + +echo "deb [signed-by=/etc/apt/keyrings/bruno.gpg] http://debian.usebruno.com/ bruno stable" | sudo tee /etc/apt/sources.list.d/bruno.list + +sudo apt update +sudo apt install bruno +``` + ### Run across multiple platforms 🖥️ ![bruno](assets/images/run-anywhere.png)

@@ -37,7 +65,9 @@ Or any version control system of your choice - [Our Long Term Vision](https://github.com/usebruno/bruno/discussions/269) - [Roadmap](https://github.com/usebruno/bruno/discussions/384) - [Documentation](https://docs.usebruno.com) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/bruno) - [Website](https://www.usebruno.com) +- [Pricing](https://www.usebruno.com/pricing) - [Download](https://www.usebruno.com/downloads) ### Showcase 🎥 @@ -52,11 +82,15 @@ Woof! If you like project, hit that ⭐ button !! ### Share Testimonials 📣 -If Bruno has helped you at work and your teams, please don't forget to share your [testimonials on our github discussion](https://github.com/usebruno/bruno/discussions/343) +If Bruno has helped you at work and your teams, please don't forget to share your [testimonials on our GitHub discussion](https://github.com/usebruno/bruno/discussions/343) + +### Publishing to New Package Managers + +Please see [here](publishing.md) for more information. ### Contribute 👩‍💻🧑‍💻 -I am happy that you are looking to improve bruno. Please checkout the [contributing guide](contributing.md) +I am happy that you are looking to improve bruno. Please check out the [contributing guide](contributing.md) Even if you are not able to make contributions via code, please don't hesitate to file bugs and feature requests that needs to be implemented to solve your use case. @@ -70,11 +104,21 @@ Even if you are not able to make contributions via code, please don't hesitate t ### Stay in touch 🌐 -[Twitter](https://twitter.com/use_bruno)
+[𝕏 (Twitter)](https://twitter.com/use_bruno)
[Website](https://www.usebruno.com)
-[Discord](https://discord.com/invite/KgcZUncpjq) +[Discord](https://discord.com/invite/KgcZUncpjq)
[LinkedIn](https://www.linkedin.com/company/usebruno) +### Trademark + +**Name** + +`Bruno` is a trademark held by [Anoop M D](https://www.helloanoop.com/) + +**Logo** + +The logo is sourced from [OpenMoji](https://openmoji.org/library/emoji-1F436/). License: CC [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) + ### License 📄 [MIT](license.md)