diff --git a/package.json b/package.json index 3aef7fa12..107b0d744 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "packages/bruno-testbench", "packages/bruno-graphql-docs" ], + "homepage": "https://usebruno.com", "devDependencies": { "@faker-js/faker": "^7.6.0", "@jest/globals": "^29.2.0", "@playwright/test": "^1.27.1", + "about-window": "^1.15.2", "husky": "^8.0.3", "jest": "^29.2.0", "pretty-quick": "^3.1.3", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 810f0e6ff..f8ad53c01 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -47,6 +47,7 @@ "react-dom": "18.2.0", "react-github-btn": "^1.4.0", "react-hot-toast": "^2.4.0", + "react-inspector": "^6.0.2", "react-redux": "^7.2.6", "react-tooltip": "^5.5.2", "sass": "^1.46.0", diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js index 22872cd46..c0762a441 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/StyledWrapper.js @@ -5,10 +5,20 @@ const Wrapper = styled.div` width: 100%; border-collapse: collapse; font-weight: 600; + table-layout: fixed; thead, td { border: 1px solid ${(props) => props.theme.collection.environment.settings.gridBorder}; + padding: 4px 10px; + + &:nth-child(1) { + width: 30%; + } + + &:nth-child(3) { + width: 70px; + } } thead { @@ -16,7 +26,7 @@ const Wrapper = styled.div` font-size: 0.8125rem; user-select: none; } - td { + thead td { padding: 6px 10px; } } diff --git a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js index 4f5aa4dfa..2f1d05f63 100644 --- a/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js +++ b/packages/bruno-app/src/components/Environments/EnvironmentSettings/EnvironmentList/EnvironmentDetails/EnvironmentVariables/index.js @@ -2,13 +2,16 @@ import React, { useReducer } from 'react'; import toast from 'react-hot-toast'; import cloneDeep from 'lodash/cloneDeep'; import { IconTrash } from '@tabler/icons'; +import { useTheme } from 'providers/Theme'; import { useDispatch } from 'react-redux'; import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import reducer from './reducer'; +import SingleLineEditor from 'components/SingleLineEditor'; import StyledWrapper from './StyledWrapper'; const EnvironmentVariables = ({ environment, collection }) => { const dispatch = useDispatch(); + const { storedTheme } = useTheme(); const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] }); const { variables, hasChanges } = state; @@ -100,15 +103,11 @@ const EnvironmentVariables = ({ environment, collection }) => { /> - handleVarChange(e, variable, 'value')} + handleVarChange({ target: { value: newValue } }, variable, 'value')} + collection={collection} /> diff --git a/packages/bruno-app/src/components/Modal/StyledWrapper.js b/packages/bruno-app/src/components/Modal/StyledWrapper.js index 583bfe2ec..f0cb79353 100644 --- a/packages/bruno-app/src/components/Modal/StyledWrapper.js +++ b/packages/bruno-app/src/components/Modal/StyledWrapper.js @@ -19,7 +19,7 @@ const Wrapper = styled.div` align-items: flex-start; justify-content: center; overflow-y: auto; - z-index: 1003; + z-index: 10; } .bruno-modal-card { @@ -28,7 +28,7 @@ const Wrapper = styled.div` background: var(--color-background-top); border-radius: var(--border-radius); position: relative; - z-index: 1003; + z-index: 10; max-width: calc(100% - var(--spacing-base-unit)); box-shadow: var(--box-shadow-base); display: flex; diff --git a/packages/bruno-app/src/components/RequestTabPanel/index.js b/packages/bruno-app/src/components/RequestTabPanel/index.js index 371778fbf..511fb5cd4 100644 --- a/packages/bruno-app/src/components/RequestTabPanel/index.js +++ b/packages/bruno-app/src/components/RequestTabPanel/index.js @@ -13,6 +13,7 @@ import RequestNotFound from './RequestNotFound'; import QueryUrl from 'components/RequestPane/QueryUrl'; import NetworkError from 'components/ResponsePane/NetworkError'; import RunnerResults from 'components/RunnerResults'; +import VariablesEditor from 'components/VariablesEditor'; import { DocExplorer } from '@usebruno/graphql-docs'; import StyledWrapper from './StyledWrapper'; @@ -123,6 +124,10 @@ const RequestTabPanel = () => { return ; } + if (focusedTab.type === 'variables') { + return ; + } + const item = findItemInCollection(collection, activeTabUid); if (!item || !item.uid) { return ; diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js index 1d96df94b..c461e3bb4 100644 --- a/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js +++ b/packages/bruno-app/src/components/RequestTabs/CollectionToolBar/index.js @@ -1,7 +1,8 @@ import React from 'react'; -import { IconFiles, IconRun } from '@tabler/icons'; +import { uuid } from 'utils/common'; +import { IconFiles, IconRun, IconEye } from '@tabler/icons'; import EnvironmentSelector from 'components/Environments/EnvironmentSelector'; -import VariablesView from 'components/VariablesView'; +import { addTab } from 'providers/ReduxStore/slices/tabs'; import { useDispatch } from 'react-redux'; import { toggleRunnerView } from 'providers/ReduxStore/slices/collections'; import StyledWrapper from './StyledWrapper'; @@ -17,6 +18,16 @@ const CollectionToolBar = ({ collection }) => { ); }; + const viewVariables = () => { + dispatch( + addTab({ + uid: uuid(), + collectionUid: collection.uid, + type: 'variables' + }) + ); + }; + return (
@@ -28,7 +39,9 @@ const CollectionToolBar = ({ collection }) => { - + + +
diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js new file mode 100644 index 000000000..d086d5b90 --- /dev/null +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/SpecialTab.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { IconVariable } from '@tabler/icons'; + +const SpecialTab = ({ handleCloseClick, text }) => { + return ( + <> +
+ + {text} +
+
handleCloseClick(e)}> + + + +
+ + ); +}; + +export default SpecialTab; diff --git a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js index a10fd8994..744f85c27 100644 --- a/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js +++ b/packages/bruno-app/src/components/RequestTabs/RequestTab/index.js @@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux'; import { findItemInCollection } from 'utils/collections'; import StyledWrapper from './StyledWrapper'; import RequestTabNotFound from './RequestTabNotFound'; +import SpecialTab from './SpecialTab'; const RequestTab = ({ tab, collection }) => { const dispatch = useDispatch(); @@ -56,6 +57,14 @@ const RequestTab = ({ tab, collection }) => { return color; }; + if (tab.type === 'variables') { + return ( + + + + ); + } + const item = findItemInCollection(collection, tab.uid); if (!item) { diff --git a/packages/bruno-app/src/components/RequestTabs/index.js b/packages/bruno-app/src/components/RequestTabs/index.js index a92c55271..c51da327f 100644 --- a/packages/bruno-app/src/components/RequestTabs/index.js +++ b/packages/bruno-app/src/components/RequestTabs/index.js @@ -114,7 +114,7 @@ const RequestTabs = () => { role="tab" onClick={() => handleClick(tab)} > - + ); }) diff --git a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js index fbd05bb53..c63a0f2a1 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js +++ b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js @@ -19,6 +19,7 @@ const StyledWrapper = styled.div` .CodeMirror-scroll { overflow: hidden !important; + padding-bottom: 50px !important; } .CodeMirror-hscrollbar { diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js index 5925b598e..eef157ff9 100644 --- a/packages/bruno-app/src/components/SingleLineEditor/index.js +++ b/packages/bruno-app/src/components/SingleLineEditor/index.js @@ -31,6 +31,7 @@ class SingleLineEditor extends Component { brunoVarInfo: { variables: getAllVariables(this.props.collection) }, + scrollbarStyle: null, extraKeys: { Enter: () => { if (this.props.onRun) { diff --git a/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js b/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js new file mode 100644 index 000000000..e4d976b0d --- /dev/null +++ b/packages/bruno-app/src/components/VariablesEditor/StyledWrapper.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + table { + thead, + td { + border: 1px solid ${(props) => props.theme.table.border}; + + li { + background-color: ${(props) => props.theme.bg} !important; + } + } + } + + .muted { + color: ${(props) => props.theme.colors.text.muted}; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesEditor/index.js b/packages/bruno-app/src/components/VariablesEditor/index.js new file mode 100644 index 000000000..80176065e --- /dev/null +++ b/packages/bruno-app/src/components/VariablesEditor/index.js @@ -0,0 +1,92 @@ +import React from 'react'; +import get from 'lodash/get'; +import filter from 'lodash/filter'; +import { Inspector } from 'react-inspector'; +import { useTheme } from 'providers/Theme'; +import { findEnvironmentInCollection } from 'utils/collections'; +import StyledWrapper from './StyledWrapper'; + +const KeyValueExplorer = ({ data, theme }) => { + data = data || {}; + + return ( +
+ + + {Object.entries(data).map(([key, value]) => ( + + + + + ))} + +
{key} + +
+
+ ); +}; + +const EnvVariables = ({ collection, theme }) => { + const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); + + if (!environment) { + return ( + <> +

Environment Variables

+
No environment selected
+ + ); + } + + const envVars = get(environment, 'variables', []); + const enabledEnvVars = filter(envVars, (variable) => variable.enabled); + const envVarsObj = enabledEnvVars.reduce((acc, curr) => { + acc[curr.name] = curr.value; + return acc; + }, {}); + + return ( + <> +
+

Environment Variables

+ ({environment.name}) +
+ {enabledEnvVars.length > 0 ? ( + + ) : ( +
No environment variables found
+ )} + + ); +}; + +const CollectionVariables = ({ collection, theme }) => { + const collectionVariablesFound = Object.keys(collection.collectionVariables).length > 0; + + return ( + <> +

Collection Variables

+ {collectionVariablesFound ? ( + + ) : ( +
No collection variables found
+ )} + + ); +}; + +const VariablesEditor = ({ collection }) => { + const { storedTheme } = useTheme(); + + const reactInspectorTheme = storedTheme === 'light' ? 'chromeLight' : 'chromeDark'; + + return ( + + + + + ); +}; + +export default VariablesEditor; diff --git a/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js deleted file mode 100644 index 45fa9b60b..000000000 --- a/packages/bruno-app/src/components/VariablesView/Popover/StyledWrapper.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components'; - -const Wrapper = styled.div` - position: absolute; - min-width: fit-content; - font-size: 14px; - top: 36px; - right: 0; - white-space: nowrap; - z-index: 1000; - background-color: ${(props) => props.theme.variables.bg}; - - .popover { - border-radius: 2px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); - } -`; - -export default Wrapper; diff --git a/packages/bruno-app/src/components/VariablesView/Popover/index.js b/packages/bruno-app/src/components/VariablesView/Popover/index.js deleted file mode 100644 index ab1700f88..000000000 --- a/packages/bruno-app/src/components/VariablesView/Popover/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useRef } from 'react'; -import StyledWrapper from './StyledWrapper'; -import useOnClickOutside from 'hooks/useOnClickOutside'; - -const PopOver = ({ children, iconRef, handleClose }) => { - const popOverRef = useRef(null); - - useOnClickOutside(popOverRef, (e) => { - if (iconRef && iconRef.current) { - if (e.target == iconRef.current || iconRef.current.contains(e.target)) { - return; - } - } - handleClose(); - }); - - return ( - -
-
{children}
-
-
- ); -}; - -export default PopOver; diff --git a/packages/bruno-app/src/components/VariablesView/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/StyledWrapper.js deleted file mode 100644 index f280aad41..000000000 --- a/packages/bruno-app/src/components/VariablesView/StyledWrapper.js +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - position: relative; - align-self: stretch; - display: flex; - align-items: center; - - .view-environment { - width: 1rem; - font-size: 10px; - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js deleted file mode 100644 index a3970cdb1..000000000 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/StyledWrapper.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - .variable-name { - color: ${(props) => props.theme.variables.name.color}; - } - - .variable-name { - min-width: 180px; - } - - .variable-value { - max-width: 600px; - inline-size: 600px; - overflow-wrap: break-word; - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js deleted file mode 100644 index b56cea098..000000000 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import forOwn from 'lodash/forOwn'; -import cloneDeep from 'lodash/cloneDeep'; -import { uuid } from 'utils/common'; -import StyledWrapper from './StyledWrapper'; - -const VariablesTable = ({ variables, collectionVariables }) => { - const collectionVars = []; - - forOwn(cloneDeep(collectionVariables), (value, key) => { - collectionVars.push({ - uid: uuid(), - name: key, - value: value - }); - }); - - return ( - -
-
Environment Variables
- {variables && variables.length ? ( - variables.map((variable) => { - return ( -
-
{variable.name}
-
{variable.value}
-
- ); - }) - ) : ( - No env variables found - )} - -
Collection Variables
- {collectionVars && collectionVars.length ? ( - collectionVars.map((variable) => { - return ( -
-
{variable.name}
-
{variable.value}
-
- ); - }) - ) : ( - No collection variables found - )} -
-
- ); -}; - -export default VariablesTable; diff --git a/packages/bruno-app/src/components/VariablesView/index.js b/packages/bruno-app/src/components/VariablesView/index.js deleted file mode 100644 index 8f04fed70..000000000 --- a/packages/bruno-app/src/components/VariablesView/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState, useRef } from 'react'; -import get from 'lodash/get'; -import filter from 'lodash/filter'; -import { findEnvironmentInCollection } from 'utils/collections'; -import VariablesTable from './VariablesTable'; -import StyledWrapper from './StyledWrapper'; -import PopOver from './Popover'; -import { IconEye } from '@tabler/icons'; - -const VariablesView = ({ collection }) => { - const iconRef = useRef(null); - const [popOverOpen, setPopOverOpen] = useState(false); - - const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid); - const variables = get(environment, 'variables', []); - const enabledVariables = filter(variables, (variable) => variable.enabled); - const showVariablesTable = - enabledVariables.length > 0 || - (collection.collectionVariables && Object.keys(collection.collectionVariables).length > 0); - - return ( - -
setPopOverOpen(true)} - onMouseEnter={() => setPopOverOpen(true)} - onMouseLeave={() => setPopOverOpen(false)} - > -
- -
- {popOverOpen && ( - setPopOverOpen(false)}> -
- {showVariablesTable ? ( - - ) : ( - 'No variables found' - )} -
-
- )} -
-
- ); -}; - -export default VariablesView; diff --git a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js index 1f3c02c6f..858682e82 100644 --- a/packages/bruno-app/src/providers/App/useCollectionTreeSync.js +++ b/packages/bruno-app/src/providers/App/useCollectionTreeSync.js @@ -8,6 +8,7 @@ import { collectionUnlinkDirectoryEvent, collectionUnlinkEnvFileEvent, scriptEnvironmentUpdateEvent, + processEnvUpdateEvent, collectionRenamedEvent, runRequestEvent, runFolderEvent @@ -97,6 +98,10 @@ const useCollectionTreeSync = () => { dispatch(scriptEnvironmentUpdateEvent(val)); }; + const _processEnvUpdate = (val) => { + dispatch(processEnvUpdateEvent(val)); + }; + const _collectionRenamed = (val) => { dispatch(collectionRenamedEvent(val)); }; @@ -119,7 +124,8 @@ const useCollectionTreeSync = () => { const removeListener6 = ipcRenderer.on('main:collection-renamed', _collectionRenamed); const removeListener7 = ipcRenderer.on('main:run-folder-event', _runFolderEvent); const removeListener8 = ipcRenderer.on('main:run-request-event', _runRequestEvent); - const removeListener9 = ipcRenderer.on('main:console-log', (val) => { + const removeListener9 = ipcRenderer.on('main:process-env-update', _processEnvUpdate); + const removeListener10 = ipcRenderer.on('main:console-log', (val) => { console[val.type](...val.args); }); @@ -133,6 +139,7 @@ const useCollectionTreeSync = () => { removeListener7(); removeListener8(); removeListener9(); + removeListener10(); }; }, [isElectron]); }; 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 c12b81eed..e1137aae7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -177,6 +177,14 @@ export const collectionsSlice = createSlice({ collection.collectionVariables = collectionVariables; } }, + processEnvUpdateEvent: (state, action) => { + const { collectionUid, processEnvVariables } = action.payload; + const collection = findCollectionByUid(state.collections, collectionUid); + + if (collection) { + collection.processEnvVariables = processEnvVariables; + } + }, requestCancelled: (state, action) => { const { itemUid, collectionUid } = action.payload; const collection = findCollectionByUid(state.collections, collectionUid); @@ -1158,6 +1166,7 @@ export const { renameItem, cloneItem, scriptEnvironmentUpdateEvent, + processEnvUpdateEvent, requestCancelled, responseReceived, saveRequest, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js index e1181aac7..4f8778a5e 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js @@ -19,12 +19,22 @@ export const tabsSlice = createSlice({ if (alreadyExists) { return; } + + if (action.payload.type === 'variables') { + const tab = find(state.tabs, (t) => t.collectionUid === action.payload.collectionUid && t.type === 'variables'); + if (tab) { + state.activeTabUid = tab.uid; + return; + } + } + state.tabs.push({ uid: action.payload.uid, collectionUid: action.payload.collectionUid, requestPaneWidth: null, requestPaneTab: action.payload.requestPaneTab || 'params', - responsePaneTab: 'response' + responsePaneTab: 'response', + type: action.payload.type || 'request' }); state.activeTabUid = action.payload.uid; }, @@ -55,16 +65,22 @@ export const tabsSlice = createSlice({ closeTabs: (state, action) => { const activeTab = find(state.tabs, (t) => t.uid === state.activeTabUid); const tabUids = action.payload.tabUids || []; + + // remove the tabs from the state state.tabs = filter(state.tabs, (t) => !tabUids.includes(t.uid)); if (activeTab && state.tabs.length) { const { collectionUid } = activeTab; const activeTabStillExists = find(state.tabs, (t) => t.uid === state.activeTabUid); + // if the active tab no longer exists, set the active tab to the last tab in the list + // this implies that the active tab was closed if (!activeTabStillExists) { - // attempt to load sibling tabs (based on collections) of the dead tab + // load sibling tabs of the current collection const siblingTabs = filter(state.tabs, (t) => t.collectionUid === collectionUid); + // if there are sibling tabs, set the active tab to the last sibling tab + // otherwise, set the active tab to the last tab in the list if (siblingTabs && siblingTabs.length) { state.activeTabUid = last(siblingTabs).uid; } else { diff --git a/packages/bruno-app/src/styles/_buttons.scss b/packages/bruno-app/src/styles/_buttons.scss index 8b1378917..e69de29bb 100644 --- a/packages/bruno-app/src/styles/_buttons.scss +++ b/packages/bruno-app/src/styles/_buttons.scss @@ -1 +0,0 @@ - diff --git a/packages/bruno-app/src/styles/app.scss b/packages/bruno-app/src/styles/app.scss index 64423a4bb..46e81fc09 100644 --- a/packages/bruno-app/src/styles/app.scss +++ b/packages/bruno-app/src/styles/app.scss @@ -1 +1 @@ -@import "buttons"; +@import 'buttons'; diff --git a/packages/bruno-app/src/styles/globals.css b/packages/bruno-app/src/styles/globals.css index 69f95ddcb..fb8eb5b5f 100644 --- a/packages/bruno-app/src/styles/globals.css +++ b/packages/bruno-app/src/styles/globals.css @@ -1,4 +1,3 @@ - :root { --color-brand: #546de5; --color-text: rgb(52 52 52); @@ -21,7 +20,8 @@ --color-method-head: rgb(52 52 52); } -html, body { +html, +body { margin: 0; padding: 0; font-size: 1rem; @@ -38,15 +38,18 @@ body { font-size: 0.875rem; } -body::-webkit-scrollbar, .CodeMirror-vscrollbar::-webkit-scrollbar { +body::-webkit-scrollbar, +.CodeMirror-vscrollbar::-webkit-scrollbar { width: 0.6rem; } - -body::-webkit-scrollbar-track, .CodeMirror-vscrollbar::-webkit-scrollbar-track { + +body::-webkit-scrollbar-track, +.CodeMirror-vscrollbar::-webkit-scrollbar-track { background-color: #f1f1f1; } - -body::-webkit-scrollbar-thumb, .CodeMirror-vscrollbar::-webkit-scrollbar-thumb { + +body::-webkit-scrollbar-thumb, +.CodeMirror-vscrollbar::-webkit-scrollbar-thumb { background-color: #cdcdcd; border-radius: 5rem; } diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js index 7a1a928b3..50f314dac 100644 --- a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js +++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js @@ -8,6 +8,7 @@ let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; +const { get } = require('lodash'); if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); @@ -20,7 +21,7 @@ if (!SERVER_RENDERED) { // str is of format {{variableName}}, extract variableName // we are seeing that from the gql query editor, the token string is of format variableName const variableName = str.replace('{{', '').replace('}}', '').trim(); - const variableValue = options.variables[variableName]; + const variableValue = get(options.variables, variableName); const into = document.createElement('div'); const descriptionDiv = document.createElement('div'); diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 3a0591324..80fe41dd3 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -542,6 +542,11 @@ export const getAllVariables = (collection) => { return { ...environmentVariables, - ...collection.collectionVariables + ...collection.collectionVariables, + process: { + env: { + ...collection.processEnvVariables + } + } }; }; diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js index a565062e9..59daee837 100644 --- a/packages/bruno-app/src/utils/common/codemirror.js +++ b/packages/bruno-app/src/utils/common/codemirror.js @@ -1,3 +1,6 @@ +import get from 'lodash/get'; +import isString from 'lodash/isString'; + let CodeMirror; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; @@ -5,6 +8,11 @@ if (!SERVER_RENDERED) { CodeMirror = require('codemirror'); } +const pathFoundInVariables = (path, obj) => { + const value = get(obj, path); + return isString(value); +}; + export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { CodeMirror.defineMode('brunovariables', function (config, parserConfig) { let variablesOverlay = { @@ -15,7 +23,8 @@ export const defineCodeMirrorBrunoVariablesMode = (variables, mode) => { while ((ch = stream.next()) != null) { if (ch == '}' && stream.next() == '}') { stream.eat('}'); - if (word in variables) { + let found = pathFoundInVariables(word, variables); + if (found) { return 'variable-valid'; } else { return 'variable-invalid'; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 2cacfda50..1334f3f5d 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,7 +1,7 @@ { "version": "v0.14.1", "name": "bruno", - "description": "Opensource API Client", + "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", "private": true, "main": "src/index.js", @@ -28,6 +28,7 @@ "form-data": "^4.0.0", "fs-extra": "^10.1.0", "graphql": "^16.6.0", + "handlebars": "^4.7.8", "is-valid-path": "^0.1.1", "lodash": "^4.17.21", "mustache": "^4.2.0", diff --git a/packages/bruno-electron/src/app/menu-template.js b/packages/bruno-electron/src/app/menu-template.js index 2e3beb82b..45125bb97 100644 --- a/packages/bruno-electron/src/app/menu-template.js +++ b/packages/bruno-electron/src/app/menu-template.js @@ -1,11 +1,13 @@ const { ipcMain } = require('electron'); +const openAboutWindow = require('about-window').default; +const { join } = require('path'); const template = [ { label: 'Collection', submenu: [ { - label: 'Open Local Collection', + label: 'Open Collection', click() { ipcMain.emit('main:open-collection'); } @@ -21,7 +23,8 @@ const template = [ { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, - { role: 'paste' } + { role: 'paste' }, + { role: 'selectAll' } ] }, { @@ -42,7 +45,19 @@ const template = [ }, { role: 'help', - submenu: [{ label: 'Learn More' }] + submenu: [ + { + label: 'About Bruno', + click: () => + openAboutWindow({ + product_name: 'Bruno', + icon_path: join(__dirname, '../../resources/icons/png/256x256.png'), + homepage: 'https://www.usebruno.com/', + package_json_dir: join(__dirname, '../..') + }) + }, + { label: 'Documentation', click: () => ipcMain.emit('main:open-docs') } + ] } ]; diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index 84c959cba..3fd2b08a2 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -4,12 +4,14 @@ const path = require('path'); const chokidar = require('chokidar'); const { hasJsonExtension, hasBruExtension, writeFile } = require('../utils/filesystem'); const { bruToEnvJson, envJsonToBru, bruToJson, jsonToBru } = require('../bru'); +const { dotenvToJson } = require('@usebruno/lang'); const { isLegacyEnvFile, migrateLegacyEnvFile, isLegacyBruFile, migrateLegacyBruFile } = require('../bru/migrate'); const { itemSchema } = require('@usebruno/schema'); const { uuid } = require('../utils/common'); const { getRequestUid } = require('../cache/requestUids'); const { decryptString } = require('../utils/encryption'); +const { setDotEnvVars } = require('../store/process-env'); const EnvironmentSecretsStore = require('../store/env-secrets'); const environmentSecretsStore = new EnvironmentSecretsStore(); @@ -21,6 +23,13 @@ const isJsonEnvironmentConfig = (pathname, collectionPath) => { return dirname === collectionPath && basename === 'environments.json'; }; +const isDotEnvFile = (pathname, collectionPath) => { + const dirname = path.dirname(pathname); + const basename = path.basename(pathname); + + return dirname === collectionPath && basename === '.env'; +}; + const isBruEnvironmentConfig = (pathname, collectionPath) => { const dirname = path.dirname(pathname); const envDirectory = path.join(collectionPath, 'environments'); @@ -158,6 +167,25 @@ const unlinkEnvironmentFile = async (win, pathname, collectionUid) => { const add = async (win, pathname, collectionUid, collectionPath) => { console.log(`watcher add: ${pathname}`); + if (isDotEnvFile(pathname, collectionPath)) { + try { + const content = fs.readFileSync(pathname, 'utf8'); + const jsonData = dotenvToJson(content); + + setDotEnvVars(collectionUid, jsonData); + const payload = { + collectionUid, + processEnvVariables: { + ...process.env, + ...jsonData + } + }; + win.webContents.send('main:process-env-update', payload); + } catch (err) { + console.error(err); + } + } + if (isJsonEnvironmentConfig(pathname, collectionPath)) { try { const dirname = path.dirname(pathname); @@ -253,6 +281,25 @@ const addDirectory = (win, pathname, collectionUid, collectionPath) => { }; const change = async (win, pathname, collectionUid, collectionPath) => { + if (isDotEnvFile(pathname, collectionPath)) { + try { + const content = fs.readFileSync(pathname, 'utf8'); + const jsonData = dotenvToJson(content); + + setDotEnvVars(collectionUid, jsonData); + const payload = { + collectionUid, + processEnvVariables: { + ...process.env, + ...jsonData + } + }; + win.webContents.send('main:process-env-update', payload); + } catch (err) { + console.error(err); + } + } + if (isBruEnvironmentConfig(pathname, collectionPath)) { return changeEnvironmentFile(win, pathname, collectionUid, collectionPath); } diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 30a98da39..2a62dd969 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -5,7 +5,7 @@ const { BrowserWindow, app, Menu } = require('electron'); const { setContentSecurityPolicy } = require('electron-util'); const menuTemplate = require('./app/menu-template'); -const LastOpenedCollections = require('./app/last-opened-collections'); +const LastOpenedCollections = require('./store/last-opened-collections'); const registerNetworkIpc = require('./ipc/network'); const registerCollectionsIpc = require('./ipc/collection'); const Watcher = require('./app/watcher'); diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index cf87ab1fd..7431d4fed 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 } = require('electron'); +const { ipcMain, shell } = require('electron'); const { envJsonToBru, bruToJson, jsonToBru } = require('../bru'); const { @@ -17,7 +17,7 @@ const { stringifyJson } = require('../utils/common'); const { openCollectionDialog, openCollection } = require('../app/collections'); const { generateUidBasedOnHash } = require('../utils/common'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); -const { setPreferences } = require('../app/preferences'); +const { setPreferences } = require('../store/preferences'); const EnvironmentSecretsStore = require('../store/env-secrets'); const environmentSecretsStore = new EnvironmentSecretsStore(); @@ -460,6 +460,11 @@ const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) = } }); + ipcMain.on('main:open-docs', () => { + const docsURL = 'https://docs.usebruno.com'; + shell.openExternal(docsURL); + }); + ipcMain.on('main:collection-opened', (win, pathname, uid) => { watcher.addWatcher(win, pathname, uid); lastOpenedCollections.add(pathname); diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 4858d61d6..bc8deb73e 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -12,7 +12,8 @@ const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../util const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); -const { getPreferences } = require('../../app/preferences'); +const { getPreferences } = require('../../store/preferences'); +const { getProcessEnvVars } = require('../../store/process-env'); // override the default escape function to prevent escaping Mustache.escape = function (value) { @@ -129,12 +130,14 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } } // run pre-request script @@ -158,7 +161,9 @@ const registerNetworkIpc = (mainWindow) => { }); } - interpolateVars(request, envVars, collectionVariables); + const processEnvVars = getProcessEnvVars(collectionUid); + + interpolateVars(request, envVars, collectionVariables, processEnvVars); // stringify the request url encoded params if (request.headers['content-type'] === 'application/x-www-form-urlencoded') { @@ -222,12 +227,14 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - requestUid, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + requestUid, + collectionUid + }); + } } // run post-response script @@ -520,7 +527,21 @@ const registerNetworkIpc = (mainWindow) => { const preRequestVars = get(request, 'vars.req', []); if (preRequestVars && preRequestVars.length) { const varsRuntime = new VarsRuntime(); - varsRuntime.runPreRequestVars(preRequestVars, request, envVars, collectionVariables, collectionPath); + 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 @@ -543,8 +564,10 @@ const registerNetworkIpc = (mainWindow) => { }); } + const processEnvVars = getProcessEnvVars(collectionUid); + // interpolate variables inside request - interpolateVars(request, envVars, collectionVariables); + interpolateVars(request, envVars, collectionVariables, processEnvVars); // todo: // i have no clue why electron can't send the request object @@ -587,11 +610,13 @@ const registerNetworkIpc = (mainWindow) => { collectionPath ); - mainWindow.webContents.send('main:script-environment-update', { - envVariables: result.envVariables, - collectionVariables: result.collectionVariables, - collectionUid - }); + if (result) { + mainWindow.webContents.send('main:script-environment-update', { + envVariables: result.envVariables, + collectionVariables: result.collectionVariables, + collectionUid + }); + } } // run response script diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index 8c90e00a5..bfb0601da 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -1,24 +1,51 @@ -const Mustache = require('mustache'); -const { each, get, forOwn } = require('lodash'); +const Handlebars = require('handlebars'); +const { each, forOwn, cloneDeep } = require('lodash'); -// override the default escape function to prevent escaping -Mustache.escape = function (value) { - return value; +const interpolateEnvVars = (str, processEnvVars) => { + if (!str || !str.length || typeof str !== 'string') { + return str; + } + + const template = Handlebars.compile(str, { noEscape: true }); + + return template({ + process: { + env: { + ...processEnvVars + } + } + }); }; -const interpolateVars = (request, envVars = {}, collectionVariables = {}) => { +const interpolateVars = (request, envVars = {}, collectionVariables = {}, processEnvVars = {}) => { + // we clone envVars because we don't want to modify the original object + envVars = cloneDeep(envVars); + + // envVars can inturn have values as {{process.env.VAR_NAME}} + // so we need to interpolate envVars first with processEnvVars + forOwn(envVars, (value, key) => { + envVars[key] = interpolateEnvVars(value, processEnvVars); + }); + const interpolate = (str) => { if (!str || !str.length || typeof str !== 'string') { return str; } + const template = Handlebars.compile(str, { noEscape: true }); + // collectionVariables take precedence over envVars const combinedVars = { ...envVars, - ...collectionVariables + ...collectionVariables, + process: { + env: { + ...processEnvVars + } + } }; - return Mustache.render(str, combinedVars); + return template(combinedVars); }; request.url = interpolate(request.url); diff --git a/packages/bruno-electron/src/app/last-opened-collections.js b/packages/bruno-electron/src/store/last-opened-collections.js similarity index 100% rename from packages/bruno-electron/src/app/last-opened-collections.js rename to packages/bruno-electron/src/store/last-opened-collections.js diff --git a/packages/bruno-electron/src/app/preferences.js b/packages/bruno-electron/src/store/preferences.js similarity index 100% rename from packages/bruno-electron/src/app/preferences.js rename to packages/bruno-electron/src/store/preferences.js diff --git a/packages/bruno-electron/src/store/process-env.js b/packages/bruno-electron/src/store/process-env.js new file mode 100644 index 000000000..578d8df71 --- /dev/null +++ b/packages/bruno-electron/src/store/process-env.js @@ -0,0 +1,37 @@ +/** + * This file stores all the process.env variables under collection scope + * + * process.env variables are sourced from 2 places: + * 1. .env file in the root of the project + * 2. process.env variables set in the OS + * + * Multiple collections can be opened in the same electron app. + * Each collection's .env file can have different values for the same process.env variable. + */ + +const dotEnvVars = {}; + +// collectionUid is a hash based on the collection path) +const getProcessEnvVars = (collectionUid) => { + // if there are no .env vars for this collection, return the process.env + if (!dotEnvVars[collectionUid]) { + return { + ...process.env + }; + } + + // if there are .env vars for this collection, return the process.env merged with the .env vars + return { + ...process.env, + ...dotEnvVars[collectionUid] + }; +}; + +const setDotEnvVars = (collectionUid, envVars) => { + dotEnvVars[collectionUid] = envVars; +}; + +module.exports = { + getProcessEnvVars, + setDotEnvVars +}; diff --git a/packages/bruno-electron/src/utils/common.js b/packages/bruno-electron/src/utils/common.js index d85d137dd..83d22c7ce 100644 --- a/packages/bruno-electron/src/utils/common.js +++ b/packages/bruno-electron/src/utils/common.js @@ -41,10 +41,39 @@ const generateUidBasedOnHash = (str) => { return `${hash}`.padEnd(21, '0'); }; +const flattenDataForDotNotation = (data) => { + var result = {}; + function recurse(current, prop) { + if (Object(current) !== current) { + result[prop] = current; + } else if (Array.isArray(current)) { + for (var i = 0, l = current.length; i < l; i++) { + recurse(current[i], prop + '[' + i + ']'); + } + if (l == 0) { + result[prop] = []; + } + } else { + var isEmpty = true; + for (var p in current) { + isEmpty = false; + recurse(current[p], prop ? prop + '.' + p : p); + } + if (isEmpty && prop) { + result[prop] = {}; + } + } + } + + recurse(data, ''); + return result; +}; + module.exports = { uuid, stringifyJson, parseJson, simpleHash, - generateUidBasedOnHash + generateUidBasedOnHash, + flattenDataForDotNotation }; diff --git a/packages/bruno-electron/tests/utils/common.spec.js b/packages/bruno-electron/tests/utils/common.spec.js new file mode 100644 index 000000000..077aac16d --- /dev/null +++ b/packages/bruno-electron/tests/utils/common.spec.js @@ -0,0 +1,85 @@ +const { flattenDataForDotNotation } = require('../../src/utils/common'); + +describe('utils: flattenDataForDotNotation', () => { + test('Flatten a simple object with dot notation', () => { + const input = { + person: { + name: 'John', + age: 30, + }, + }; + + const expectedOutput = { + 'person.name': 'John', + 'person.age': 30, + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with nested arrays', () => { + const input = { + users: [ + { name: 'Alice', age: 25 }, + { name: 'Bob', age: 28 }, + ], + }; + + const expectedOutput = { + 'users[0].name': 'Alice', + 'users[0].age': 25, + 'users[1].name': 'Bob', + 'users[1].age': 28, + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an empty object', () => { + const input = {}; + + const expectedOutput = {}; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with nested objects', () => { + const input = { + person: { + name: 'Alice', + address: { + city: 'New York', + zipcode: '10001', + }, + }, + }; + + const expectedOutput = { + 'person.name': 'Alice', + 'person.address.city': 'New York', + 'person.address.zipcode': '10001', + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); + + test('Flatten an object with arrays of objects', () => { + const input = { + teams: [ + { name: 'Team A', members: ['Alice', 'Bob'] }, + { name: 'Team B', members: ['Charlie', 'David'] }, + ], + }; + + const expectedOutput = { + 'teams[0].name': 'Team A', + 'teams[0].members[0]': 'Alice', + 'teams[0].members[1]': 'Bob', + 'teams[1].name': 'Team B', + 'teams[1].members[0]': 'Charlie', + 'teams[1].members[1]': 'David', + }; + + expect(flattenDataForDotNotation(input)).toEqual(expectedOutput); + }); +}); \ No newline at end of file diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js index ac9d954a0..9f4871ae6 100644 --- a/packages/bruno-js/src/runtime/script-runtime.js +++ b/packages/bruno-js/src/runtime/script-runtime.js @@ -10,6 +10,7 @@ const punycode = require('punycode'); const Bru = require('../bru'); const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); +const { cleanJson } = require('../utils'); // Inbuilt Library Support const atob = require('atob'); @@ -37,7 +38,7 @@ class ScriptRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -81,8 +82,8 @@ class ScriptRuntime { await asyncVM(); return { request, - envVariables, - collectionVariables + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables) }; } @@ -100,7 +101,7 @@ class ScriptRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -136,8 +137,8 @@ class ScriptRuntime { return { response, - envVariables, - collectionVariables + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables) }; } } diff --git a/packages/bruno-js/src/runtime/test-runtime.js b/packages/bruno-js/src/runtime/test-runtime.js index 323a001ab..427506524 100644 --- a/packages/bruno-js/src/runtime/test-runtime.js +++ b/packages/bruno-js/src/runtime/test-runtime.js @@ -6,6 +6,7 @@ const BrunoRequest = require('../bruno-request'); const BrunoResponse = require('../bruno-response'); const Test = require('../test'); const TestResults = require('../test-results'); +const { cleanJson } = require('../utils'); // Inbuilt Library Support const atob = require('atob'); @@ -49,7 +50,7 @@ class TestRuntime { if (onConsoleLog && typeof onConsoleLog === 'function') { const customLogger = (type) => { return (...args) => { - onConsoleLog(type, args); + onConsoleLog(type, cleanJson(args)); }; }; context.console = { @@ -82,9 +83,9 @@ class TestRuntime { return { request, - envVariables, - collectionVariables, - results: __brunoTestResults.getResults() + envVariables: cleanJson(envVariables), + collectionVariables: cleanJson(collectionVariables), + results: cleanJson(__brunoTestResults.getResults()) }; } } diff --git a/packages/bruno-js/src/utils.js b/packages/bruno-js/src/utils.js index a1ba5a7ed..aca101962 100644 --- a/packages/bruno-js/src/utils.js +++ b/packages/bruno-js/src/utils.js @@ -118,9 +118,28 @@ const createResponseParser = (response = {}) => { return res; }; +/** + * Objects that are created inside vm2 execution context result in an serilaization error when sent to the renderer process + * Error sending from webFrameMain: Error: Failed to serialize arguments + * at s.send (node:electron/js2c/browser_init:169:631) + * at g.send (node:electron/js2c/browser_init:165:2156) + * How to reproduce + * Remove the cleanJson fix and execute the below post response script + * bru.setVar("a", {b:3}); + * Todo: Find a better fix + */ +const cleanJson = (data) => { + try { + return JSON.parse(JSON.stringify(data)); + } catch (e) { + return data; + } +}; + module.exports = { evaluateJsExpression, evaluateJsTemplateLiteral, createResponseParser, - internalExpressionCache + internalExpressionCache, + cleanJson }; diff --git a/packages/bruno-lang/src/index.js b/packages/bruno-lang/src/index.js index bb7f35a80..f27179c45 100644 --- a/packages/bruno-lang/src/index.js +++ b/packages/bruno-lang/src/index.js @@ -4,6 +4,7 @@ const bruToJsonV2 = require('../v2/src/bruToJson'); const jsonToBruV2 = require('../v2/src/jsonToBru'); const bruToEnvJsonV2 = require('../v2/src/envToJson'); const envJsonToBruV2 = require('../v2/src/jsonToEnv'); +const dotenvToJson = require('../v2/src/dotenvToJson'); module.exports = { bruToJson, @@ -14,5 +15,7 @@ module.exports = { bruToJsonV2, jsonToBruV2, bruToEnvJsonV2, - envJsonToBruV2 + envJsonToBruV2, + + dotenvToJson };