mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 09:58:35 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6265db353 |
19
.github/workflows/unit-tests.yml
vendored
19
.github/workflows/unit-tests.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm i --legacy-peer-deps
|
||||
- name: Test Package bruno-lang
|
||||
run: npm run test --workspace=packages/bruno-lang
|
||||
16
catalog-info.yaml
Normal file
16
catalog-info.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: "bruno"
|
||||
links:
|
||||
- url: https://example.com/user
|
||||
title: Examples Users
|
||||
icon: user
|
||||
- url: https://example.com/group
|
||||
title: Example Group
|
||||
icon: group
|
||||
spec:
|
||||
type: component
|
||||
lifecycle: production
|
||||
owner: anoop
|
||||
system: tech-docs
|
||||
@@ -6,7 +6,6 @@
|
||||
"packages/bruno-electron",
|
||||
"packages/bruno-tauri",
|
||||
"packages/bruno-schema",
|
||||
"packages/bruno-lang",
|
||||
"packages/bruno-testbench",
|
||||
"packages/bruno-graphql-docs"
|
||||
],
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"type": "http-request",
|
||||
"name": "Send Bulk SMS",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=998877665&message=hello&sender=600010",
|
||||
"params": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "secret",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "numbers",
|
||||
"value": "998877665",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "hello",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "sender",
|
||||
"value": "600010",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"headers": [],
|
||||
"body": {
|
||||
"mode": "json",
|
||||
"json": "{\n apikey: \"secret\",\n numbers: \"+919988776655\",\n data: {\n sender: \"TXTLCL\",\n messages: [{\n numbers: \"+919988776655\",\n message: \"Hello World\"\n }]\n }\n}",
|
||||
"text": null,
|
||||
"xml": null,
|
||||
"multipartForm": null,
|
||||
"formUrlEncoded": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
ver 1.0
|
||||
type http-request
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
|
||||
params
|
||||
1 apiKey secret
|
||||
1 numbers 998877665
|
||||
1 message hello
|
||||
/params
|
||||
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body-mode json
|
||||
|
||||
body(type=json)
|
||||
{
|
||||
apikey: "secret",
|
||||
numbers: "+91998877665",
|
||||
data: {
|
||||
sender: "TXTLCL",
|
||||
messages: [{
|
||||
numbers: "+91998877665",
|
||||
message: "Hello World"
|
||||
}]
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=graphql)
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
script
|
||||
let user = 'John Doe';
|
||||
|
||||
function onRequest(request) {
|
||||
request.body.user = user;
|
||||
}
|
||||
|
||||
function onResponse(request, response) {
|
||||
expect(response.status).to.equal(200);
|
||||
}
|
||||
/script
|
||||
|
||||
assert
|
||||
{
|
||||
"$res.data.order.items.length": 1,
|
||||
"$res.data.orderNumber.isDefined": true
|
||||
}
|
||||
/assert
|
||||
|
||||
vars
|
||||
1 petId $res.data.id
|
||||
/vars
|
||||
|
||||
readme
|
||||
Documentation about the request
|
||||
/readme
|
||||
|
||||
response-example
|
||||
name Created
|
||||
status 201
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body
|
||||
{
|
||||
"data": {
|
||||
"launchesPast": [
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "CCAFS SLC 40"
|
||||
},
|
||||
"launch_success": true
|
||||
},
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "VAFB SLC 4E"
|
||||
},
|
||||
"launch_success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
/body
|
||||
/response-example
|
||||
@@ -1,101 +0,0 @@
|
||||
ver 1.0
|
||||
type http-request
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
body-mode json
|
||||
|
||||
params
|
||||
1 apiKey secret
|
||||
1 numbers 998877665
|
||||
1 message hello
|
||||
/params
|
||||
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body(type=json)
|
||||
{
|
||||
apikey: "secret",
|
||||
numbers: "+91998877665",
|
||||
data: {
|
||||
sender: "TXTLCL",
|
||||
messages: [{
|
||||
numbers: "+91998877665",
|
||||
message: "Hello World"
|
||||
}]
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=graphql)
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
script
|
||||
let user = 'John Doe';
|
||||
|
||||
function onRequest(request) {
|
||||
request.body.user = user;
|
||||
}
|
||||
|
||||
function onResponse(request, response) {
|
||||
expect(response.status).to.equal(200);
|
||||
}
|
||||
/script
|
||||
|
||||
assert
|
||||
{
|
||||
"$res.data.order.items.length": 1,
|
||||
"$res.data.orderNumber.isDefined": true
|
||||
}
|
||||
/assert
|
||||
|
||||
vars
|
||||
1 petId $res.data.id
|
||||
/vars
|
||||
|
||||
readme
|
||||
Documentation about the request
|
||||
/readme
|
||||
|
||||
response-example
|
||||
name Created
|
||||
status 201
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body
|
||||
{
|
||||
"data": {
|
||||
"launchesPast": [
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "CCAFS SLC 40"
|
||||
},
|
||||
"launch_success": true
|
||||
},
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "VAFB SLC 4E"
|
||||
},
|
||||
"launch_success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
/body
|
||||
/response-example
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
reactStrictMode: false,
|
||||
reactStrictMode: true,
|
||||
publicRuntimeConfig: {
|
||||
CI: process.env.CI,
|
||||
PLAYWRIGHT: process.env.PLAYWRIGHT
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"name": "@usebruno/app",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -16,8 +15,8 @@
|
||||
"@reduxjs/toolkit": "^1.8.0",
|
||||
"@tabler/icons": "^1.46.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"@usebruno/schema": "0.2.0",
|
||||
"@usebruno/graphql-docs": "0.1.0",
|
||||
"axios": "^0.26.0",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^5.65.2",
|
||||
@@ -35,17 +34,16 @@
|
||||
"markdown-it": "^13.0.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"nanoid": "3.3.4",
|
||||
"next": "12.3.3",
|
||||
"next": "12.3.1",
|
||||
"path": "^0.12.7",
|
||||
"platform": "^1.3.6",
|
||||
"posthog-node": "^2.1.0",
|
||||
"qs": "^6.11.0",
|
||||
"react": "18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-tabs": "^3.2.3",
|
||||
"reckonjs": "^0.1.2",
|
||||
"sass": "^1.46.0",
|
||||
"split-on-first": "^3.0.0",
|
||||
|
||||
@@ -70,7 +70,7 @@ const Wrapper = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
&.is-sidebar-dragging .collection-item-name {
|
||||
&.is-dragging .collection-item-name {
|
||||
cursor: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,12 +2,10 @@ import React, { useState, useRef, forwardRef, useEffect } from 'react';
|
||||
import range from 'lodash/range';
|
||||
import filter from 'lodash/filter';
|
||||
import classnames from 'classnames';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { addTab, focusTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { collectionFolderClicked } from 'providers/ReduxStore/slices/collections';
|
||||
import { moveItem } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import NewFolder from 'components/Sidebar/NewFolder';
|
||||
@@ -25,7 +23,7 @@ import StyledWrapper from './StyledWrapper';
|
||||
const CollectionItem = ({ item, collection, searchText }) => {
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const isSidebarDragging = useSelector((state) => state.app.isDragging);
|
||||
const isDragging = useSelector((state) => state.app.isDragging);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [renameItemModalOpen, setRenameItemModalOpen] = useState(false);
|
||||
@@ -35,29 +33,6 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
const [newFolderModalOpen, setNewFolderModalOpen] = useState(false);
|
||||
const [itemIsCollapsed, setItemisCollapsed] = useState(item.collapsed);
|
||||
|
||||
// const [{ isDragging }, drag] = useDrag({
|
||||
// type: 'COLLECTION_ITEM',
|
||||
// item: item,
|
||||
// collect: (monitor) => ({
|
||||
// isDragging: monitor.isDragging()
|
||||
// })
|
||||
// });
|
||||
|
||||
// const [{ isOver }, drop] = useDrop({
|
||||
// accept: 'COLLECTION_ITEM',
|
||||
// drop: (draggedItem) => {
|
||||
// if (draggedItem.uid !== item.uid) {
|
||||
// dispatch(moveItem(collection.uid, draggedItem.uid, item.uid));
|
||||
// }
|
||||
// },
|
||||
// canDrop: (draggedItem) => {
|
||||
// return draggedItem.uid !== item.uid;
|
||||
// },
|
||||
// collect: (monitor) => ({
|
||||
// isOver: monitor.isOver()
|
||||
// })
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
if (searchText && searchText.length) {
|
||||
setItemisCollapsed(false);
|
||||
@@ -116,7 +91,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
||||
const isFolder = isItemAFolder(item);
|
||||
|
||||
const className = classnames('flex flex-col w-full', {
|
||||
'is-sidebar-dragging': isSidebarDragging
|
||||
'is-dragging': isDragging
|
||||
});
|
||||
|
||||
if (searchText && searchText.length) {
|
||||
|
||||
@@ -2,11 +2,9 @@ import React, { useState, forwardRef, useRef, useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import filter from 'lodash/filter';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { useDrop } from 'react-dnd';
|
||||
import { IconChevronRight, IconDots } from '@tabler/icons';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
import { collectionClicked } from 'providers/ReduxStore/slices/collections';
|
||||
import { moveItemToRootOfCollection } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import NewRequest from 'components/Sidebar/NewRequest';
|
||||
import NewFolder from 'components/Sidebar/NewFolder';
|
||||
@@ -73,21 +71,6 @@ const Collection = ({ collection, searchText }) => {
|
||||
|
||||
const isLocal = isLocalCollection(collection);
|
||||
|
||||
// const [{ isOver }, drop] = useDrop({
|
||||
// accept: 'COLLECTION_ITEM',
|
||||
// drop: (draggedItem) => {
|
||||
// console.log('drop', draggedItem);
|
||||
// dispatch(moveItemToRootOfCollection(collection.uid, draggedItem.uid));
|
||||
// },
|
||||
// canDrop: (draggedItem) => {
|
||||
// // todo need to make sure that draggedItem belongs to the collection
|
||||
// return true;
|
||||
// },
|
||||
// collect: (monitor) => ({
|
||||
// isOver: monitor.isOver()
|
||||
// })
|
||||
// });
|
||||
|
||||
return (
|
||||
<StyledWrapper className="flex flex-col">
|
||||
{showNewRequestModal && <NewRequest collection={collection} onClose={() => setShowNewRequestModal(false)} />}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import find from 'lodash/find';
|
||||
import filter from 'lodash/filter';
|
||||
import Collection from './Collection';
|
||||
@@ -28,11 +26,7 @@ const Collections = ({ searchText }) => {
|
||||
<div className="mt-4 flex flex-col">
|
||||
{collectionToDisplay && collectionToDisplay.length
|
||||
? collectionToDisplay.map((c) => {
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend} key={c.uid}>
|
||||
<Collection searchText={searchText} collection={c} />
|
||||
</DndProvider>
|
||||
);
|
||||
return <Collection searchText={searchText} collection={c} key={c.uid} />;
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ const Welcome = () => {
|
||||
<Bruno width={50} />
|
||||
</div>
|
||||
<div className="text-xl font-semibold select-none">bruno</div>
|
||||
<div className="mt-4">Opensource IDE for exploring and testing api's</div>
|
||||
<div className="mt-4">Local-first, Opensource API Client.</div>
|
||||
|
||||
<div className="uppercase font-semibold heading mt-10">Collections</div>
|
||||
<div className="mt-4 flex items-center collection-options select-none">
|
||||
|
||||
@@ -21,6 +21,17 @@ const Wrapper = styled.div`
|
||||
.fw-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.react-tabs {
|
||||
.react-tabs__tab-list {
|
||||
padding-left: 1rem;
|
||||
border-bottom: 1px solid #cfcfcf;
|
||||
|
||||
.react-tabs__tab--selected {
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
@@ -166,7 +166,7 @@ const Login = () => {
|
||||
<div className="font-semibold" style={{ fontSize: '2rem' }}>
|
||||
bruno
|
||||
</div>
|
||||
<div className="mt-1">Opensource IDE for exploring and testing api's.</div>
|
||||
<div className="mt-1">Opensource API Collection Collaboration Platform</div>
|
||||
</div>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<div className="flex justify-center flex-col form-container mx-auto mt-10 p-5">
|
||||
|
||||
@@ -171,7 +171,7 @@ const SignUp = () => {
|
||||
<div className="font-semibold" style={{ fontSize: '2rem' }}>
|
||||
bruno
|
||||
</div>
|
||||
<div className="mt-1">Opensource IDE for exploring and testing api's.</div>
|
||||
<div className="mt-1">Opensource API Collection Collaboration Platform</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { AppProvider } from 'providers/App';
|
||||
import { ToastProvider } from 'providers/Toaster';
|
||||
@@ -10,6 +9,7 @@ import ThemeProvider from 'providers/Theme/index';
|
||||
import '../styles/app.scss';
|
||||
import '../styles/globals.css';
|
||||
import 'tailwindcss/dist/tailwind.min.css';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'graphiql/graphiql.min.css';
|
||||
|
||||
@@ -28,16 +28,6 @@ function NoSsr({ children }) {
|
||||
}
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
const [domLoaded, setDomLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDomLoaded(true);
|
||||
}, []);
|
||||
|
||||
if(!domLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeHydrate>
|
||||
<NoSsr>
|
||||
|
||||
@@ -7,8 +7,6 @@ import { uuid } from 'utils/common';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import {
|
||||
findItemInCollection,
|
||||
moveCollectionItem,
|
||||
moveCollectionItemToRootOfCollection,
|
||||
findCollectionByUid,
|
||||
recursivelyGetAllItemUids,
|
||||
transformCollectionToSaveToIdb,
|
||||
@@ -35,8 +33,6 @@ import {
|
||||
renameItem as _renameItem,
|
||||
cloneItem as _cloneItem,
|
||||
deleteItem as _deleteItem,
|
||||
moveItem as _moveItem,
|
||||
moveItemToRootOfCollection as _moveItemToRootOfCollection,
|
||||
saveRequest as _saveRequest,
|
||||
addEnvironment as _addEnvironment,
|
||||
renameEnvironment as _renameEnvironment,
|
||||
@@ -554,121 +550,6 @@ export const deleteItem = (itemUid, collectionUid) => (dispatch, getState) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const moveItem = (collectionUid, draggedItemUid, targetItemUid) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!collection) {
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
if (isLocalCollection(collection)) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collection, targetItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
if (!targetItem) {
|
||||
return reject(new Error('Target item not found'));
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
ipcRenderer
|
||||
.invoke('renderer:move-item', draggedItem.pathname, targetItem.pathname)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collectionCopy, targetItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
if (!targetItem) {
|
||||
return reject(new Error('Target item not found'));
|
||||
}
|
||||
|
||||
moveCollectionItem(collectionCopy, draggedItem, targetItem);
|
||||
|
||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||
|
||||
collectionSchema
|
||||
.validate(collectionToSave)
|
||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||
.then(() => {
|
||||
dispatch(
|
||||
_moveItem({
|
||||
collectionUid: collectionUid,
|
||||
draggedItemUid: draggedItemUid,
|
||||
targetItemUid: targetItemUid
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const moveItemToRootOfCollection = (collectionUid, draggedItemUid) => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!collection) {
|
||||
return reject(new Error('Collection not found'));
|
||||
}
|
||||
|
||||
if (isLocalCollection(collection)) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window;
|
||||
|
||||
ipcRenderer
|
||||
.invoke('renderer:move-item-to-root-of-collection', draggedItem.pathname)
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
const draggedItem = findItemInCollection(collectionCopy, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return reject(new Error('Dragged item not found'));
|
||||
}
|
||||
|
||||
moveCollectionItemToRootOfCollection(collectionCopy, draggedItem);
|
||||
|
||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||
|
||||
collectionSchema
|
||||
.validate(collectionToSave)
|
||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||
.then(() => {
|
||||
dispatch(
|
||||
_moveItemToRootOfCollection({
|
||||
collectionUid: collectionUid,
|
||||
draggedItemUid: draggedItemUid
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => resolve())
|
||||
.catch((error) => reject(error));
|
||||
});
|
||||
};
|
||||
|
||||
export const newHttpRequest = (params) => (dispatch, getState) => {
|
||||
const { requestName, requestType, requestUrl, requestMethod, collectionUid, itemUid } = params;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
findEnvironmentInCollection,
|
||||
findItemInCollectionByPathname,
|
||||
addDepth,
|
||||
moveCollectionItem,
|
||||
collapseCollection,
|
||||
deleteItemInCollection,
|
||||
isItemARequest
|
||||
@@ -182,38 +181,6 @@ export const collectionsSlice = createSlice({
|
||||
}
|
||||
}
|
||||
},
|
||||
moveItem: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const draggedItemUid = action.payload.draggedItemUid;
|
||||
const targetItemUid = action.payload.targetItemUid;
|
||||
|
||||
if (collection) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
const targetItem = findItemInCollection(collection, targetItemUid);
|
||||
|
||||
if (!draggedItem || !targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveCollectionItem(collection, draggedItem, targetItem);
|
||||
addDepth(collection.items);
|
||||
}
|
||||
},
|
||||
moveItemToRootOfCollection: (state, action) => {
|
||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||
const draggedItemUid = action.payload.draggedItemUid;
|
||||
|
||||
if (collection) {
|
||||
const draggedItem = findItemInCollection(collection, draggedItemUid);
|
||||
|
||||
if (!draggedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveCollectionItemToRootOfCollection(collection, draggedItem);
|
||||
addDepth(collection.items);
|
||||
}
|
||||
},
|
||||
requestSent: (state, action) => {
|
||||
const { itemUid, collectionUid, cancelTokenUid } = action.payload;
|
||||
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||
@@ -818,8 +785,6 @@ export const {
|
||||
deleteItem,
|
||||
renameItem,
|
||||
cloneItem,
|
||||
moveItem,
|
||||
moveItemToRootOfCollection,
|
||||
requestSent,
|
||||
requestCancelled,
|
||||
responseReceived,
|
||||
|
||||
@@ -2,7 +2,6 @@ import reckon from 'reckonjs';
|
||||
import get from 'lodash/get';
|
||||
import each from 'lodash/each';
|
||||
import find from 'lodash/find';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import isString from 'lodash/isString';
|
||||
import map from 'lodash/map';
|
||||
import filter from 'lodash/filter';
|
||||
@@ -123,43 +122,6 @@ export const findEnvironmentInCollection = (collection, envUid) => {
|
||||
return find(collection.environments, (e) => e.uid === envUid);
|
||||
};
|
||||
|
||||
export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
||||
|
||||
if (draggedItemParent) {
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
} else {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
||||
}
|
||||
|
||||
if (targetItem.type === 'folder') {
|
||||
targetItem.items = targetItem.items || [];
|
||||
targetItem.items.push(draggedItem);
|
||||
} else {
|
||||
let targetItemParent = findParentItemInCollection(collection, targetItem.uid);
|
||||
|
||||
if (targetItemParent) {
|
||||
let targetItemIndex = findIndex(targetItemParent.items, (i) => i.uid === targetItem.uid);
|
||||
targetItemParent.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
} else {
|
||||
let targetItemIndex = findIndex(collection.items, (i) => i.uid === targetItem.uid);
|
||||
collection.items.splice(targetItemIndex + 1, 0, draggedItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const moveCollectionItemToRootOfCollection = (collection, draggedItem) => {
|
||||
let draggedItemParent = findParentItemInCollection(collection, draggedItem.uid);
|
||||
|
||||
if (draggedItemParent) {
|
||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||
} else {
|
||||
collection.items = filter(collection.items, (i) => i.uid !== draggedItem.uid);
|
||||
}
|
||||
|
||||
collection.items.push(draggedItem);
|
||||
};
|
||||
|
||||
export const transformCollectionToSaveToIdb = (collection, options = {}) => {
|
||||
const copyHeaders = (headers) => {
|
||||
return map(headers, (header) => {
|
||||
@@ -414,6 +376,7 @@ export const interpolateEnvironmentVars = (item, variables) => {
|
||||
};
|
||||
|
||||
request.url = interpolate(request.url);
|
||||
console.log(request.url);
|
||||
|
||||
each(request.headers, (header) => {
|
||||
header.value = interpolate(header.value);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const isLocalCollection = (collection) => {
|
||||
};
|
||||
|
||||
export const resolveRequestFilename = (name) => {
|
||||
return `${trim(name)}.bru`;
|
||||
return `${trim(name)}.json`;
|
||||
};
|
||||
|
||||
export const getSubdirectoriesFromRoot = (rootPath, pathname) => {
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
"pack-app": "electron-builder --dir"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/bruno-lang": "0.1.0",
|
||||
"@usebruno/schema": "0.1.0",
|
||||
"axios": "^0.26.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
@@ -32,7 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^21.1.1",
|
||||
"electron-builder": "23.0.2",
|
||||
"electron-builder": "23.3.3",
|
||||
"electron-icon-maker": "^0.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ const path = require('path');
|
||||
const { dialog, ipcMain } = require('electron');
|
||||
const Yup = require('yup');
|
||||
const { isDirectory, normalizeAndResolvePath } = require('../utils/filesystem');
|
||||
const { generateUidBasedOnHash } = require('../utils/common');
|
||||
|
||||
const uidSchema = Yup.string()
|
||||
.length(21, 'uid must be 21 characters in length')
|
||||
@@ -12,6 +11,7 @@ const uidSchema = Yup.string()
|
||||
.strict();
|
||||
|
||||
const configSchema = Yup.object({
|
||||
uid: uidSchema,
|
||||
name: Yup.string().nullable().max(256, 'name must be 256 characters or less'),
|
||||
type: Yup.string().oneOf(['collection']).required('type is required'),
|
||||
version: Yup.string().oneOf(['1']).required('type is required')
|
||||
@@ -65,9 +65,12 @@ const openCollection = async (win, watcher, collectionPath, options = {}) => {
|
||||
if(!watcher.hasWatcher(collectionPath)) {
|
||||
try {
|
||||
const {
|
||||
uid,
|
||||
name
|
||||
} = await getCollectionConfigFile(collectionPath);
|
||||
const uid = generateUidBasedOnHash(collectionPath);
|
||||
|
||||
console.log(uid);
|
||||
console.log(name);
|
||||
|
||||
win.webContents.send('main:collection-opened', collectionPath, uid, name);
|
||||
ipcMain.emit('main:collection-opened', win, collectionPath, uid);
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
const { hasJsonExtension, hasBruExtension, writeFile } = require('../utils/filesystem');
|
||||
const {
|
||||
bruToJson,
|
||||
jsonToBru
|
||||
} = require('@usebruno/bruno-lang');
|
||||
const { itemSchema } = require('@usebruno/schema');
|
||||
const { generateUidBasedOnHash, uuid } = require('../utils/common');
|
||||
const { hasJsonExtension } = require('../utils/filesystem');
|
||||
|
||||
const isEnvironmentConfig = (pathname, collectionPath) => {
|
||||
const dirname = path.dirname(pathname);
|
||||
@@ -17,22 +10,6 @@ const isEnvironmentConfig = (pathname, collectionPath) => {
|
||||
return dirname === collectionPath && basename === 'environments.json';
|
||||
}
|
||||
|
||||
const hydrateRequestWithUuid = (request, pathname) => {
|
||||
request.uid = generateUidBasedOnHash(pathname);
|
||||
|
||||
const params = _.get(request, 'request.params', []);
|
||||
const headers = _.get(request, 'request.headers', []);
|
||||
const bodyFormUrlEncoded = _.get(request, 'request.body.formUrlEncoded', []);
|
||||
const bodyMultipartForm = _.get(request, 'request.body.multipartForm', []);
|
||||
|
||||
params.forEach((param) => param.uid = uuid());
|
||||
headers.forEach((header) => header.uid = uuid());
|
||||
bodyFormUrlEncoded.forEach((param) => param.uid = uuid());
|
||||
bodyMultipartForm.forEach((param) => param.uid = uuid());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
const addEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||
try {
|
||||
const file = {
|
||||
@@ -94,30 +71,7 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
||||
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||
return addEnvironmentFile(win, pathname, collectionUid);
|
||||
}
|
||||
}
|
||||
|
||||
// migrate old json files to bru
|
||||
if(hasJsonExtension(pathname)) {
|
||||
try {
|
||||
const json = fs.readFileSync(pathname, 'utf8');
|
||||
const jsonData = JSON.parse(json);
|
||||
|
||||
await itemSchema.validate(jsonData);
|
||||
|
||||
const content = jsonToBru(jsonData);
|
||||
|
||||
const re = /(.*)\.json$/;
|
||||
const subst = `$1.bru`;
|
||||
const bruFilename = pathname.replace(re, subst);
|
||||
|
||||
await writeFile(bruFilename, content);
|
||||
await fs.unlinkSync(pathname);
|
||||
} catch (err) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if(hasBruExtension(pathname)) {
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
@@ -127,9 +81,8 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const bru = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = bruToJson(bru);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
const jsonData = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = JSON.parse(jsonData);
|
||||
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@@ -151,30 +104,25 @@ const addDirectory = (win, pathname, collectionUid) => {
|
||||
|
||||
const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||
console.log(`watcher change: ${pathname}`);
|
||||
|
||||
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||
return changeEnvironmentFile(win, pathname, collectionUid);
|
||||
}
|
||||
|
||||
if(hasBruExtension(pathname)) {
|
||||
try {
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
pathname,
|
||||
name: path.basename(pathname),
|
||||
}
|
||||
};
|
||||
|
||||
const bru = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = bruToJson(bru);
|
||||
hydrateRequestWithUuid(file.data, pathname);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
try {
|
||||
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||
return changeEnvironmentFile(win, pathname, collectionUid);
|
||||
}
|
||||
}
|
||||
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
pathname,
|
||||
name: path.basename(pathname),
|
||||
}
|
||||
};
|
||||
|
||||
const jsonData = fs.readFileSync(pathname, 'utf8');
|
||||
file.data = await JSON.parse(jsonData);
|
||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
};
|
||||
|
||||
const unlink = (win, pathname, collectionUid, collectionPath) => {
|
||||
@@ -182,19 +130,19 @@ const unlink = (win, pathname, collectionUid, collectionPath) => {
|
||||
return unlinkEnvironmentFile(win, pathname, collectionUid);
|
||||
}
|
||||
|
||||
if(hasBruExtension(pathname)) {
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
pathname,
|
||||
name: path.basename(pathname)
|
||||
}
|
||||
};
|
||||
win.webContents.send('main:collection-tree-updated', 'unlink', file);
|
||||
}
|
||||
console.log(`watcher unlink: ${pathname}`);
|
||||
const file = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
pathname,
|
||||
name: path.basename(pathname)
|
||||
}
|
||||
};
|
||||
win.webContents.send('main:collection-tree-updated', 'unlink', file);
|
||||
}
|
||||
|
||||
const unlinkDir = (win, pathname, collectionUid) => {
|
||||
console.log(`watcher unlinkDir: ${pathname}`);
|
||||
const directory = {
|
||||
meta: {
|
||||
collectionUid,
|
||||
@@ -215,11 +163,6 @@ class Watcher {
|
||||
this.watchers[watchPath].close();
|
||||
}
|
||||
|
||||
// todo
|
||||
// enable this in a future release
|
||||
// once we can confirm all older json based files have been auto migrated to .bru format
|
||||
// watchPath = path.join(watchPath, '**/*.bru');
|
||||
|
||||
const self = this;
|
||||
setTimeout(() => {
|
||||
const watcher = chokidar.watch(watchPath, {
|
||||
|
||||
@@ -2,19 +2,15 @@ const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { ipcMain } = require('electron');
|
||||
const {
|
||||
jsonToBru,
|
||||
bruToJson,
|
||||
} = require('@usebruno/bruno-lang');
|
||||
const {
|
||||
isValidPathname,
|
||||
writeFile,
|
||||
hasBruExtension,
|
||||
hasJsonExtension,
|
||||
isDirectory,
|
||||
browseDirectory,
|
||||
createDirectory
|
||||
} = require('../utils/filesystem');
|
||||
const { uuid, stringifyJson } = require('../utils/common');
|
||||
const { uuid, stringifyJson, parseJson } = require('../utils/common');
|
||||
const { openCollectionDialog, openCollection } = require('../app/collections');
|
||||
|
||||
const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||
@@ -68,7 +64,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
throw new Error(`path: ${pathname} already exists`);
|
||||
}
|
||||
|
||||
const content = jsonToBru(request);
|
||||
const content = await stringifyJson(request);
|
||||
await writeFile(pathname, content);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
@@ -82,7 +78,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
throw new Error(`path: ${pathname} does not exist`);
|
||||
}
|
||||
|
||||
const content = jsonToBru(request);
|
||||
const content = await stringifyJson(request);
|
||||
await writeFile(pathname, content);
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
@@ -116,18 +112,18 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
||||
return fs.renameSync(oldPath, newPath);
|
||||
}
|
||||
|
||||
const isBru = hasBruExtension(oldPath);
|
||||
if(!isBru) {
|
||||
throw new Error(`path: ${oldPath} is not a bru file`);
|
||||
const isJson = hasJsonExtension(oldPath);
|
||||
if(!isJson) {
|
||||
throw new Error(`path: ${oldPath} is not a json file`);
|
||||
}
|
||||
|
||||
// update name in file and save new copy, then delete old copy
|
||||
const data = fs.readFileSync(oldPath, 'utf8');
|
||||
const jsonData = bruToJson(data);
|
||||
const jsonData = await parseJson(data);
|
||||
|
||||
jsonData.name = newName;
|
||||
|
||||
const content = jsonToBru(jsonData);
|
||||
const content = await stringifyJson(jsonData);
|
||||
await writeFile(newPath, content);
|
||||
await fs.unlinkSync(oldPath);
|
||||
} catch (error) {
|
||||
|
||||
@@ -25,26 +25,8 @@ const parseJson = async (obj) => {
|
||||
}
|
||||
}
|
||||
|
||||
const simpleHash = (str) => {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash &= hash; // Convert to 32bit integer
|
||||
}
|
||||
return new Uint32Array([hash])[0].toString(36);
|
||||
};
|
||||
|
||||
const generateUidBasedOnHash = (str) => {
|
||||
const hash = simpleHash(str);
|
||||
|
||||
return `${hash}`.padEnd(21, '0');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
uuid,
|
||||
stringifyJson,
|
||||
parseJson,
|
||||
simpleHash,
|
||||
generateUidBasedOnHash
|
||||
parseJson
|
||||
};
|
||||
|
||||
@@ -65,11 +65,6 @@ const hasJsonExtension = filename => {
|
||||
return ['json'].some(ext => filename.toLowerCase().endsWith(`.${ext}`))
|
||||
}
|
||||
|
||||
const hasBruExtension = filename => {
|
||||
if (!filename || typeof filename !== 'string') return false
|
||||
return ['bru'].some(ext => filename.toLowerCase().endsWith(`.${ext}`))
|
||||
}
|
||||
|
||||
const createDirectory = async (dir) => {
|
||||
if(!dir) {
|
||||
throw new Error(`directory: path is null`);
|
||||
@@ -104,7 +99,6 @@ module.exports = {
|
||||
normalizeAndResolvePath,
|
||||
writeFile,
|
||||
hasJsonExtension,
|
||||
hasBruExtension,
|
||||
createDirectory,
|
||||
browseDirectory
|
||||
};
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"graphql": "^16.6.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"postcss": "^8.4.18",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"rollup": "3.2.5",
|
||||
"rollup-plugin-dts": "^5.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
@@ -30,7 +30,8 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16.6.0",
|
||||
"markdown-it": "^13.0.1"
|
||||
"markdown-it": "^13.0.1",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"overrides": {
|
||||
"rollup": "3.2.5"
|
||||
|
||||
7
packages/bruno-lang/.gitignore
vendored
7
packages/bruno-lang/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
node_modules
|
||||
web
|
||||
out
|
||||
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -1,55 +0,0 @@
|
||||
ver 1.0
|
||||
type http-request
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
body-mode json
|
||||
|
||||
params
|
||||
1 apiKey secret
|
||||
1 numbers 998877665
|
||||
1 message hello
|
||||
/params
|
||||
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body(type=json)
|
||||
{
|
||||
"apikey": "secret",
|
||||
"numbers": "+91998877665",
|
||||
"data": {
|
||||
"sender": "TXTLCL",
|
||||
"messages": [{
|
||||
"numbers": "+91998877665",
|
||||
"message": "Hello World"
|
||||
}]
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=graphql)
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
script
|
||||
let user = 'John Doe';
|
||||
|
||||
function onRequest(request) {
|
||||
request.body.user = user;
|
||||
}
|
||||
|
||||
function onResponse(request, response) {
|
||||
expect(response.status).to.equal(200);
|
||||
}
|
||||
/script
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"type": "http-request",
|
||||
"name": "Send Bulk SMS",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=998877665&message=hello&sender=600010",
|
||||
"params": [
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "secret",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "numbers",
|
||||
"value": "998877665",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "hello",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "sender",
|
||||
"value": "600010",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"headers": [],
|
||||
"body": {
|
||||
"mode": "json",
|
||||
"json": "{\n apikey: \"secret\",\n numbers: \"+919988776655\",\n data: {\n sender: \"TXTLCL\",\n messages: [{\n numbers: \"+919988776655\",\n message: \"Hello World\"\n }]\n }\n}",
|
||||
"text": null,
|
||||
"xml": null,
|
||||
"multipartForm": null,
|
||||
"formUrlEncoded": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
ver 1.0
|
||||
type http-request
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
body-mode json
|
||||
|
||||
params
|
||||
1 apiKey secret
|
||||
1 numbers 998877665
|
||||
1 message hello
|
||||
/params
|
||||
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body(type=json)
|
||||
{
|
||||
apikey: "secret",
|
||||
numbers: "+91998877665",
|
||||
data: {
|
||||
sender: "TXTLCL",
|
||||
messages: [{
|
||||
numbers: "+91998877665",
|
||||
message: "Hello World"
|
||||
}]
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=graphql)
|
||||
{
|
||||
launchesPast {
|
||||
launch_site {
|
||||
site_name
|
||||
}
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
script
|
||||
let user = 'John Doe';
|
||||
|
||||
function onRequest(request) {
|
||||
request.body.user = user;
|
||||
}
|
||||
|
||||
function onResponse(request, response) {
|
||||
expect(response.status).to.equal(200);
|
||||
}
|
||||
/script
|
||||
|
||||
assert
|
||||
{
|
||||
"$res.data.order.items.length": 1,
|
||||
"$res.data.orderNumber.isDefined": true
|
||||
}
|
||||
/assert
|
||||
|
||||
docs
|
||||
Documentation about the request
|
||||
/docs
|
||||
|
||||
response-example(name="Created", status=201)
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body
|
||||
{
|
||||
"data": {
|
||||
"launchesPast": [
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "CCAFS SLC 40"
|
||||
},
|
||||
"launch_success": true
|
||||
},
|
||||
{
|
||||
"launch_site": {
|
||||
"site_name": "VAFB SLC 4E"
|
||||
},
|
||||
"launch_success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
/body
|
||||
/response-example
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "@usebruno/bruno-lang",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"arcsecond": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# bruno-lang
|
||||
|
||||
The language utils for working with `.bru` files
|
||||
@@ -1,121 +0,0 @@
|
||||
const {
|
||||
between,
|
||||
regex,
|
||||
everyCharUntil,
|
||||
digit,
|
||||
whitespace,
|
||||
optionalWhitespace,
|
||||
endOfInput,
|
||||
choice,
|
||||
many,
|
||||
sepBy,
|
||||
sequenceOf
|
||||
} = require("arcsecond");
|
||||
|
||||
// body(type=json)
|
||||
const bodyJsonBegin = regex(/^body\s*\(\s*type\s*=\s*json\s*\)\s*\r?\n/);
|
||||
|
||||
// body(type=graphql)
|
||||
const bodyGraphqlBegin = regex(/^body\s*\(\s*type\s*=\s*graphql\s*\)\s*\r?\n/);
|
||||
|
||||
// body(type=text)
|
||||
const bodyTextBegin = regex(/^body\s*\(\s*type\s*=\s*text\s*\)\s*\r?\n/);
|
||||
|
||||
// body(type=xml)
|
||||
const bodyXmlBegin = regex(/^body\s*\(\s*type\s*=\s*xml\s*\)\s*\r?\n/);
|
||||
|
||||
const bodyEnd = regex(/^[\r?\n]+\/body\s*[\r?\n]*/);
|
||||
|
||||
const bodyJsonTag = between(bodyJsonBegin)(bodyEnd)(everyCharUntil(bodyEnd)).map((bodyJson) => {
|
||||
return {
|
||||
body: {
|
||||
json: bodyJson
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const bodyGraphqlTag = between(bodyGraphqlBegin)(bodyEnd)(everyCharUntil(bodyEnd)).map((bodyGraphql) => {
|
||||
return {
|
||||
body: {
|
||||
graphql: {
|
||||
query: bodyGraphql
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bodyTextTag = between(bodyTextBegin)(bodyEnd)(everyCharUntil(bodyEnd)).map((bodyText) => {
|
||||
return {
|
||||
body: {
|
||||
text: bodyText
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bodyXmlTag = between(bodyXmlBegin)(bodyEnd)(everyCharUntil(bodyEnd)).map((bodyXml) => {
|
||||
return {
|
||||
body: {
|
||||
xml: bodyXml
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// generic key value parser
|
||||
const newline = regex(/^\r?\n/);
|
||||
const newLineOrEndOfInput = choice([newline, endOfInput]);
|
||||
const word = regex(/^[^\s\t\n]+/g);
|
||||
|
||||
const line = sequenceOf([
|
||||
optionalWhitespace,
|
||||
digit,
|
||||
whitespace,
|
||||
word,
|
||||
whitespace,
|
||||
word,
|
||||
newLineOrEndOfInput
|
||||
]).map(([_, enabled, __, key, ___, value]) => {
|
||||
return {
|
||||
"enabled": Number(enabled) ? true : false,
|
||||
"name": key,
|
||||
"value": value
|
||||
};
|
||||
});
|
||||
|
||||
const lines = many(line);
|
||||
const keyvalLines = sepBy(newline)(lines);
|
||||
|
||||
// body(type=form-url-encoded)
|
||||
const bodyFormUrlEncoded = regex(/^body\s*\(\s*type\s*=\s*form-url-encoded\s*\)\s*\r?\n/);
|
||||
|
||||
// body(type=multipart-form)
|
||||
const bodyMultipartForm = regex(/^body\s*\(\s*type\s*=\s*multipart-form\s*\)\s*\r?\n/);
|
||||
|
||||
// this regex allows the body end tag to start without a newline
|
||||
// currently the line parser consumes the last newline
|
||||
// todo: fix this
|
||||
const bodyEndRelaxed = regex(/^[\r?\n]*\/body\s*[\r?\n]*/);
|
||||
|
||||
const bodyFormUrlEncodedTag = between(bodyFormUrlEncoded)(bodyEndRelaxed)(keyvalLines).map(([result]) => {
|
||||
return {
|
||||
body: {
|
||||
formUrlEncoded: result
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bodyMultipartFormTag = between(bodyMultipartForm)(bodyEndRelaxed)(keyvalLines).map(([result]) => {
|
||||
return {
|
||||
body: {
|
||||
multipartForm: result
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
bodyJsonTag,
|
||||
bodyGraphqlTag,
|
||||
bodyTextTag,
|
||||
bodyXmlTag,
|
||||
bodyFormUrlEncodedTag,
|
||||
bodyMultipartFormTag
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
const {
|
||||
sequenceOf,
|
||||
whitespace,
|
||||
optionalWhitespace,
|
||||
choice,
|
||||
endOfInput,
|
||||
between,
|
||||
digit,
|
||||
many,
|
||||
regex,
|
||||
sepBy
|
||||
} = require("arcsecond");
|
||||
|
||||
const newline = regex(/^\r?\n/);
|
||||
const newLineOrEndOfInput = choice([newline, endOfInput]);
|
||||
|
||||
const begin = regex(/^headers\s*\r?\n/);
|
||||
const end = regex(/^[\r?\n]*\/headers\s*[\r?\n]*/);
|
||||
const word = regex(/^[^\s\t\n]+/g);
|
||||
|
||||
const line = sequenceOf([
|
||||
optionalWhitespace,
|
||||
digit,
|
||||
whitespace,
|
||||
word,
|
||||
whitespace,
|
||||
word,
|
||||
newLineOrEndOfInput
|
||||
]).map(([_, enabled, __, key, ___, value]) => {
|
||||
return {
|
||||
"enabled": Number(enabled) ? true : false,
|
||||
"name": key,
|
||||
"value": value
|
||||
};
|
||||
});
|
||||
|
||||
const lines = many(line);
|
||||
const headersLines = sepBy(newline)(lines);
|
||||
const headersTag = between(begin)(end)(headersLines).map(([headers]) => {
|
||||
return {
|
||||
headers
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = headersTag;
|
||||
@@ -1,167 +0,0 @@
|
||||
const {
|
||||
many,
|
||||
choice,
|
||||
anyChar
|
||||
} = require("arcsecond");
|
||||
const _ = require('lodash');
|
||||
const {
|
||||
indentString,
|
||||
outdentString,
|
||||
get
|
||||
} = require('./utils');
|
||||
|
||||
const inlineTag = require('./inline-tag');
|
||||
const paramsTag = require('./params-tag');
|
||||
const headersTag = require('./headers-tag');
|
||||
const {
|
||||
bodyJsonTag,
|
||||
bodyGraphqlTag,
|
||||
bodyTextTag,
|
||||
bodyXmlTag,
|
||||
bodyFormUrlEncodedTag,
|
||||
bodyMultipartFormTag
|
||||
} = require('./body-tag');
|
||||
|
||||
const bruToJson = (fileContents) => {
|
||||
const parser = many(choice([
|
||||
inlineTag,
|
||||
paramsTag,
|
||||
headersTag,
|
||||
bodyJsonTag,
|
||||
bodyGraphqlTag,
|
||||
bodyTextTag,
|
||||
bodyXmlTag,
|
||||
bodyFormUrlEncodedTag,
|
||||
bodyMultipartFormTag,
|
||||
anyChar
|
||||
]));
|
||||
|
||||
const parsed = parser
|
||||
.run(fileContents)
|
||||
.result
|
||||
.reduce((acc, item) => _.merge(acc, item), {});
|
||||
|
||||
const json = {
|
||||
type: parsed.type || '',
|
||||
name: parsed.name || '',
|
||||
request: {
|
||||
method: parsed.method || '',
|
||||
url: parsed.url || '',
|
||||
params: parsed.params || [],
|
||||
headers: parsed.headers || [],
|
||||
body: parsed.body || {mode: 'none'}
|
||||
}
|
||||
}
|
||||
|
||||
const body = get(json, 'request.body');
|
||||
|
||||
if(body && body.text) {
|
||||
body.text = outdentString(body.text);
|
||||
}
|
||||
|
||||
if(body && body.json) {
|
||||
body.json = outdentString(body.json);
|
||||
}
|
||||
|
||||
if(body && body.xml) {
|
||||
body.xml = outdentString(body.xml);
|
||||
}
|
||||
|
||||
if(body && body.graphql && body.graphql.query) {
|
||||
body.graphql.query = outdentString(body.graphql.query);
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
const jsonToBru = (json) => {
|
||||
const {
|
||||
type,
|
||||
name,
|
||||
request: {
|
||||
method,
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
body
|
||||
}
|
||||
} = json;
|
||||
|
||||
let bru = `name ${name}
|
||||
method ${method}
|
||||
url ${url}
|
||||
type ${type}
|
||||
body-mode ${body ? body.mode : 'none'}
|
||||
`;
|
||||
|
||||
if(params && params.length) {
|
||||
bru += `
|
||||
params
|
||||
${params.map(param => ` ${param.enabled ? 1 : 0} ${param.name} ${param.value}`).join('\n')}
|
||||
/params
|
||||
`;
|
||||
}
|
||||
|
||||
if(headers && headers.length) {
|
||||
bru += `
|
||||
headers
|
||||
${headers.map(header => ` ${header.enabled ? 1 : 0} ${header.name} ${header.value}`).join('\n')}
|
||||
/headers
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.json && body.json.length) {
|
||||
bru += `
|
||||
body(type=json)
|
||||
${indentString(body.json)}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.graphql && body.graphql.query) {
|
||||
bru += `
|
||||
body(type=graphql)
|
||||
${indentString(body.graphql.query)}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.text && body.text.length) {
|
||||
bru += `
|
||||
body(type=text)
|
||||
${indentString(body.text)}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.xml && body.xml.length) {
|
||||
bru += `
|
||||
body(type=xml)
|
||||
${indentString(body.xml)}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.formUrlEncoded && body.formUrlEncoded.length) {
|
||||
bru += `
|
||||
body(type=form-url-encoded)
|
||||
${body.formUrlEncoded.map(item => ` ${item.enabled ? 1 : 0} ${item.name} ${item.value}`).join('\n')}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
if(body && body.multipartForm && body.multipartForm.length) {
|
||||
bru += `
|
||||
body(type=multipart-form)
|
||||
${body.multipartForm.map(item => ` ${item.enabled ? 1 : 0} ${item.name} ${item.value}`).join('\n')}
|
||||
/body
|
||||
`;
|
||||
}
|
||||
|
||||
return bru;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
bruToJson,
|
||||
jsonToBru
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
const {
|
||||
sequenceOf,
|
||||
whitespace,
|
||||
str,
|
||||
lookAhead,
|
||||
choice,
|
||||
endOfInput,
|
||||
everyCharUntil
|
||||
} = require("arcsecond");
|
||||
|
||||
const newline = lookAhead(str("\n"));
|
||||
const newLineOrEndOfInput = choice([endOfInput, newline]);
|
||||
|
||||
const inlineTag = sequenceOf([
|
||||
choice([
|
||||
str('type'),
|
||||
str('name'),
|
||||
str('method'),
|
||||
str('url'),
|
||||
str('body-mode')
|
||||
]),
|
||||
whitespace,
|
||||
everyCharUntil(newLineOrEndOfInput)
|
||||
]).map(([key, _, val]) => {
|
||||
if(key === 'body-mode') {
|
||||
return {
|
||||
body: {
|
||||
mode: val
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { [key]: val };
|
||||
});
|
||||
|
||||
module.exports = inlineTag;
|
||||
@@ -1,46 +0,0 @@
|
||||
const {
|
||||
sequenceOf,
|
||||
whitespace,
|
||||
optionalWhitespace,
|
||||
choice,
|
||||
endOfInput,
|
||||
everyCharUntil,
|
||||
between,
|
||||
digit,
|
||||
many,
|
||||
regex,
|
||||
sepBy
|
||||
} = require("arcsecond");
|
||||
|
||||
const newline = regex(/^\r?\n/);
|
||||
const newLineOrEndOfInput = choice([newline, endOfInput]);
|
||||
|
||||
const begin = regex(/^params\s*\r?\n/);
|
||||
const end = regex(/^[\r?\n]*\/params\s*[\r?\n]*/);
|
||||
const word = regex(/^[^\s\t\n]+/g);
|
||||
|
||||
const line = sequenceOf([
|
||||
optionalWhitespace,
|
||||
digit,
|
||||
whitespace,
|
||||
word,
|
||||
whitespace,
|
||||
word,
|
||||
newLineOrEndOfInput
|
||||
]).map(([_, enabled, __, key, ___, value]) => {
|
||||
return {
|
||||
"enabled": Number(enabled) ? true : false,
|
||||
"name": key,
|
||||
"value": value
|
||||
};
|
||||
});
|
||||
|
||||
const lines = many(line);
|
||||
const paramsLines = sepBy(newline)(lines);
|
||||
const paramsTag = between(begin)(end)(paramsLines).map(([params]) => {
|
||||
return {
|
||||
params
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = paramsTag;
|
||||
@@ -1,44 +0,0 @@
|
||||
// safely parse json
|
||||
const safeParseJson = (json) => {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const indentString = (str) => {
|
||||
if(!str || !str.length) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return str.split("\n").map(line => " " + line).join("\n");
|
||||
};
|
||||
|
||||
const outdentString = (str) => {
|
||||
if(!str || !str.length) {
|
||||
return str;
|
||||
}
|
||||
|
||||
return str.split("\n").map(line => line.replace(/^ /, '')).join("\n");
|
||||
};
|
||||
|
||||
// implement lodash _.get functionality
|
||||
const get = (obj, path, defaultValue) => {
|
||||
const pathParts = path.split('.');
|
||||
let current = obj;
|
||||
for(let i = 0; i < pathParts.length; i++) {
|
||||
if(current[pathParts[i]] === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
current = current[pathParts[i]];
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
get,
|
||||
safeParseJson,
|
||||
indentString,
|
||||
outdentString
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
const { bodyJsonTag } = require('../src/body-tag');
|
||||
|
||||
|
||||
describe('bodyJsonTag', () => {
|
||||
const testbodyJson = (input, expected) => {
|
||||
const result = bodyJsonTag.run(input);
|
||||
expect(result.isError).toBe(false);
|
||||
expect(result.result.body.json).toEqual('{ "foo": "bar" }');
|
||||
};
|
||||
|
||||
// simple case
|
||||
it('should parse json body tag - 1', () => {
|
||||
const input = 'body(type=json)\n{ "foo": "bar" }\n/body';
|
||||
testbodyJson(input, '{ "foo": "bar" }\n');
|
||||
});
|
||||
|
||||
// space between body and args
|
||||
it('should parse json body tag - 2', () => {
|
||||
const input = 'body (type = json)\n{ "foo": "bar" }\n/body';
|
||||
testbodyJson(input, '{ "foo": "bar" }\n');
|
||||
});
|
||||
|
||||
// space after body tag
|
||||
it('should parse json body tag - 3', () => {
|
||||
const input = 'body (type = json) \n{ "foo": "bar" }\n/body';
|
||||
testbodyJson(input, '{ "foo": "bar" }\n');
|
||||
});
|
||||
|
||||
// space after body tag
|
||||
it('should parse json body tag - 4', () => {
|
||||
const input = 'body (type = json) \n{ "foo": "bar" }\n/body ';
|
||||
testbodyJson(input, '{ "foo": "bar" }\n');
|
||||
});
|
||||
|
||||
it('should fail to parse when body tag is missing', () => {
|
||||
const input = '{ "foo": "bar" }\n/body';
|
||||
const result = bodyJsonTag.run(input);
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
|
||||
it('should fail to parse when body end tag is missing', () => {
|
||||
const input = 'body (type = json)\n{ "foo": "bar" }';
|
||||
const result = bodyJsonTag.run(input);
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,91 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
bruToJson
|
||||
} = require('../src');
|
||||
|
||||
describe('bruToJson', () => {
|
||||
it('should parse .bru file contents', () => {
|
||||
const requestFile = fs.readFileSync(path.join(__dirname, 'fixtures', 'request.bru'), 'utf8');
|
||||
const result = bruToJson(requestFile);
|
||||
|
||||
expect(result).toEqual({
|
||||
"type": "http-request",
|
||||
"name": "Send Bulk SMS",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010",
|
||||
"params": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "apiKey",
|
||||
"value": "secret"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "numbers",
|
||||
"value": "998877665"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "message",
|
||||
"value": "hello"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "content-type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "accept-language",
|
||||
"value": "en-US,en;q=0.9,hi;q=0.8"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "transaction-id",
|
||||
"value": "{{transactionId}}"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "json",
|
||||
"json": '{\n "apikey": "secret",\n "numbers": "+91998877665"\n}',
|
||||
"graphql": {
|
||||
"query": "{\n launchesPast {\n launch_success\n }\n}"
|
||||
},
|
||||
"text": "Hello, there. You must be from the past",
|
||||
"xml": "<body>back to the ice age</body>",
|
||||
"formUrlEncoded": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "username",
|
||||
"value": "john"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "password",
|
||||
"value": "{{password}}"
|
||||
}
|
||||
],
|
||||
"multipartForm": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "username",
|
||||
"value": "nash"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "password",
|
||||
"value": "governingdynamics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
50
packages/bruno-lang/tests/fixtures/request.bru
vendored
50
packages/bruno-lang/tests/fixtures/request.bru
vendored
@@ -1,50 +0,0 @@
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
type http-request
|
||||
body-mode json
|
||||
|
||||
params
|
||||
1 apiKey secret
|
||||
1 numbers 998877665
|
||||
1 message hello
|
||||
/params
|
||||
|
||||
headers
|
||||
1 content-type application/json
|
||||
1 accept-language en-US,en;q=0.9,hi;q=0.8
|
||||
0 transaction-id {{transactionId}}
|
||||
/headers
|
||||
|
||||
body(type=json)
|
||||
{
|
||||
"apikey": "secret",
|
||||
"numbers": "+91998877665"
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=graphql)
|
||||
{
|
||||
launchesPast {
|
||||
launch_success
|
||||
}
|
||||
}
|
||||
/body
|
||||
|
||||
body(type=text)
|
||||
Hello, there. You must be from the past
|
||||
/body
|
||||
|
||||
body(type=xml)
|
||||
<body>back to the ice age</body>
|
||||
/body
|
||||
|
||||
body(type=form-url-encoded)
|
||||
1 username john
|
||||
0 password {{password}}
|
||||
/body
|
||||
|
||||
body(type=multipart-form)
|
||||
1 username nash
|
||||
0 password governingdynamics
|
||||
/body
|
||||
@@ -1,58 +0,0 @@
|
||||
const inlineTag = require('../src/inline-tag');
|
||||
const {
|
||||
sepBy,
|
||||
char,
|
||||
many
|
||||
} = require('arcsecond');
|
||||
|
||||
describe('type', () => {
|
||||
it('should parse the type', () => {
|
||||
const input = 'type http-request';
|
||||
const result = inlineTag.run(input);
|
||||
expect(result.isError).toBe(false);
|
||||
expect(result.result).toEqual({ type: 'http-request' });
|
||||
});
|
||||
|
||||
it('should allow whitespaces while parsing the type', () => {
|
||||
const input = 'type http-request';
|
||||
const result = inlineTag.run(input);
|
||||
expect(result.isError).toBe(false);
|
||||
expect(result.result).toEqual({ type: 'http-request' });
|
||||
});
|
||||
|
||||
it('should fail to parse when type is missing', () => {
|
||||
const input = 'type';
|
||||
const result = inlineTag.run(input);
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple inline tags', () => {
|
||||
it('should parse the multiple inline tags', () => {
|
||||
const input = `
|
||||
type http-request
|
||||
name Send Bulk SMS
|
||||
method GET
|
||||
url https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010
|
||||
body-mode json
|
||||
`;
|
||||
|
||||
const newline = char('\n');
|
||||
const line = inlineTag;
|
||||
const lines = many(line);
|
||||
const parser = sepBy(newline)(lines);
|
||||
|
||||
const result = parser.run(input);
|
||||
|
||||
expect(result.isError).toBe(false);
|
||||
expect(result.result).toEqual([
|
||||
[],
|
||||
[{ type: 'http-request' }],
|
||||
[{ name: 'Send Bulk SMS' }],
|
||||
[{ method: 'GET' }],
|
||||
[{ url: 'https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010' }],
|
||||
[{ body: { mode: 'json' } }],
|
||||
[]
|
||||
]);
|
||||
})
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
jsonToBru
|
||||
} = require('../src');
|
||||
|
||||
describe('bruToJson', () => {
|
||||
it('should convert json file into .bru file', () => {
|
||||
const request = {
|
||||
"type": "http-request",
|
||||
"name": "Send Bulk SMS",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://api.textlocal.in/bulk_json?apiKey=secret=&numbers=919988776655&message=hello&sender=600010",
|
||||
"params": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "apiKey",
|
||||
"value": "secret"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "numbers",
|
||||
"value": "998877665"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "message",
|
||||
"value": "hello"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "content-type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "accept-language",
|
||||
"value": "en-US,en;q=0.9,hi;q=0.8"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "transaction-id",
|
||||
"value": "{{transactionId}}"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "json",
|
||||
"json": '{\n "apikey": "secret",\n "numbers": "+91998877665"\n}',
|
||||
"graphql": {
|
||||
"query": "{\n launchesPast {\n launch_success\n }\n}"
|
||||
},
|
||||
"text": "Hello, there. You must be from the past",
|
||||
"xml": "<body>back to the ice age</body>",
|
||||
"formUrlEncoded": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "username",
|
||||
"value": "john"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "password",
|
||||
"value": "{{password}}"
|
||||
}
|
||||
],
|
||||
"multipartForm": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "username",
|
||||
"value": "nash"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "password",
|
||||
"value": "governingdynamics"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const expectedBruFile = fs.readFileSync(path.join(__dirname, 'fixtures', 'request.bru'), 'utf8');
|
||||
const actualBruFile = jsonToBru(request);
|
||||
|
||||
expect(expectedBruFile).toEqual(actualBruFile);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
const {
|
||||
safeParseJson,
|
||||
indentString,
|
||||
outdentString,
|
||||
get
|
||||
} = require('../src/utils');
|
||||
|
||||
describe('utils', () => {
|
||||
describe('safeParseJson', () => {
|
||||
it('should parse valid json', () => {
|
||||
const input = '{"a": 1}';
|
||||
const result = safeParseJson(input);
|
||||
expect(result).toEqual({ a: 1 });
|
||||
});
|
||||
|
||||
it('should return null for invalid json', () => {
|
||||
const input = '{"a": 1';
|
||||
const result = safeParseJson(input);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('indentString', () => {
|
||||
it('correctly indents a multiline string', () => {
|
||||
const input = "line1\nline2\nline3";
|
||||
const expectedOutput = " line1\n line2\n line3";
|
||||
expect(indentString(input)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('outdentString', () => {
|
||||
it('correctly outdents a multiline string', () => {
|
||||
const input = " line1\n line2\n line3";
|
||||
const expectedOutput = "line1\nline2\nline3";
|
||||
expect(outdentString(input)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('returns the value at the given path', () => {
|
||||
const input = { a: { b: { c: 1 } } };
|
||||
expect(get(input, 'a.b.c')).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the defaultValue if the path does not exist', () => {
|
||||
const input = { a: { b: { c: 1 } } };
|
||||
expect(get(input, 'a.b.d', 2)).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// A launch configuration that launches the extension inside a new window
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
.gitignore
|
||||
vsc-extension-quickstart.md
|
||||
@@ -1,9 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to the "bruno" extension will be documented in this file.
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Initial release
|
||||
@@ -1,65 +0,0 @@
|
||||
# bruno README
|
||||
|
||||
This is the README for your extension "bruno". After writing up a brief description, we recommend including the following sections.
|
||||
|
||||
## Features
|
||||
|
||||
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
|
||||
|
||||
For example if there is an image subfolder under your extension project workspace:
|
||||
|
||||
\!\[feature X\]\(images/feature-x.png\)
|
||||
|
||||
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
|
||||
|
||||
## Requirements
|
||||
|
||||
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
|
||||
|
||||
## Extension Settings
|
||||
|
||||
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
|
||||
|
||||
For example:
|
||||
|
||||
This extension contributes the following settings:
|
||||
|
||||
* `myExtension.enable`: Enable/disable this extension.
|
||||
* `myExtension.thing`: Set to `blah` to do something.
|
||||
|
||||
## Known Issues
|
||||
|
||||
Calling out known issues can help limit users opening duplicate issues against your extension.
|
||||
|
||||
## Release Notes
|
||||
|
||||
Users appreciate release notes as you update your extension.
|
||||
|
||||
### 1.0.0
|
||||
|
||||
Initial release of ...
|
||||
|
||||
### 1.0.1
|
||||
|
||||
Fixed issue #.
|
||||
|
||||
### 1.1.0
|
||||
|
||||
Added features X, Y, and Z.
|
||||
|
||||
---
|
||||
|
||||
## Working with Markdown
|
||||
|
||||
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
|
||||
|
||||
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
|
||||
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
|
||||
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
|
||||
|
||||
## For more information
|
||||
|
||||
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
|
||||
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
|
||||
|
||||
**Enjoy!**
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"comments": {
|
||||
// symbol used for single line comment. Remove this entry if your language does not support line comments
|
||||
"lineComment": "//",
|
||||
// symbols used for start and end a block comment. Remove this entry if your language does not support block comments
|
||||
"blockComment": [ "/*", "*/" ]
|
||||
},
|
||||
// symbols used as brackets
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
// symbols that are auto closed when typing
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
// symbols that can be used to surround a selection
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "bruno",
|
||||
"displayName": "Bruno",
|
||||
"description": "Bruno support for Visual Studio Code.",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.74.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
],
|
||||
"contributes": {
|
||||
"languages": [{
|
||||
"id": "bruno",
|
||||
"aliases": ["bruno", "bruno"],
|
||||
"extensions": [".bru"],
|
||||
"configuration": "./language-configuration.json"
|
||||
}],
|
||||
"grammars": [{
|
||||
"language": "bruno",
|
||||
"scopeName": "source.bru",
|
||||
"path": "./syntaxes/bruno.tmLanguage.json"
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||
"name": "bruno",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"include": "#strings"
|
||||
},
|
||||
{
|
||||
"include": "#script-block"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"keywords": {
|
||||
"patterns": [{
|
||||
"name": "keyword.control.bruno",
|
||||
"match": "\\b(ver|type|name|method|url|params|body-mode|body|script|assert|vars|response-example|readme)\\b"
|
||||
}]
|
||||
},
|
||||
"strings": {
|
||||
"name": "string.quoted.double.bruno",
|
||||
"begin": "\"",
|
||||
"end": "\"",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.bruno",
|
||||
"match": "\\\\."
|
||||
}
|
||||
]
|
||||
},
|
||||
"script-block": {
|
||||
"name": "meta.script-block.bruno",
|
||||
"begin": "script",
|
||||
"end": "/script",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scopeName": "source.bru"
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
# Welcome to your VS Code Extension
|
||||
|
||||
## What's in the folder
|
||||
|
||||
* This folder contains all of the files necessary for your extension.
|
||||
* `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension.
|
||||
* `syntaxes/bruno.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization.
|
||||
* `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets.
|
||||
|
||||
## Get up and running straight away
|
||||
|
||||
* Make sure the language configuration settings in `language-configuration.json` are accurate.
|
||||
* Press `F5` to open a new window with your extension loaded.
|
||||
* Create a new file with a file name suffix matching your language.
|
||||
* Verify that syntax highlighting works and that the language configuration settings are working.
|
||||
|
||||
## Make changes
|
||||
|
||||
* You can relaunch the extension from the debug toolbar after making changes to the files listed above.
|
||||
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
||||
|
||||
## Add more language features
|
||||
|
||||
* To add features such as IntelliSense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/docs
|
||||
|
||||
## Install your extension
|
||||
|
||||
* To start using your extension with Visual Studio Code copy it into the `<user home>/.vscode/extensions` folder and restart Code.
|
||||
* To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension.
|
||||
@@ -1,8 +1,15 @@
|
||||
# bruno
|
||||
Opensource IDE for exploring and testing api's.
|
||||
Local-first, Opensource API Client.
|
||||
|
||||
### Live Demo 🏂
|
||||
Woof! Lets play with some api's [here](https://play.usebruno.com).
|
||||
Your api must allow CORS for it to work in the browser, else checkout the chrome extension ot the desktop app
|
||||
|
||||
You can visit the [Website](https://www.usebruno.com) or watch a [4 min demo](https://www.youtube.com/watch?v=wwXJW7_qyLA)
|
||||
|
||||

|
||||
|
||||
|
||||
### Bring Your Own Version Control ✨
|
||||
Bruno is built from the ground up with the Local-first paradigm in mind. This allows developers to directly store there collections on top of their local filesystem. The collections are mirrored as folders and files on the filesystem.
|
||||

|
||||
|
||||
Reference in New Issue
Block a user