From e3ce420216aeda12ae9d7f1d481f5d23183a5242 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Sat, 23 Sep 2023 02:55:54 +0530 Subject: [PATCH] feat(#122): supporting process.env vars in UI and electron layer --- .../EnvironmentVariables/StyledWrapper.js | 12 ++- .../EnvironmentVariables/index.js | 15 ++-- .../src/components/Modal/StyledWrapper.js | 4 +- .../SingleLineEditor/StyledWrapper.js | 1 + .../src/components/SingleLineEditor/index.js | 1 + .../VariablesView/VariablesTable/index.js | 17 +++- .../providers/App/useCollectionTreeSync.js | 9 +- .../ReduxStore/slices/collections/index.js | 9 ++ packages/bruno-app/src/styles/_buttons.scss | 1 - packages/bruno-app/src/styles/app.scss | 2 +- packages/bruno-app/src/styles/globals.css | 17 ++-- .../src/utils/codemirror/brunoVarInfo.js | 3 +- .../bruno-app/src/utils/collections/index.js | 7 +- .../bruno-app/src/utils/common/codemirror.js | 11 ++- packages/bruno-electron/package.json | 1 + packages/bruno-electron/src/app/watcher.js | 47 ++++++++++ .../bruno-electron/src/ipc/network/index.js | 65 +++++++++----- .../src/ipc/network/interpolate-vars.js | 43 ++++++++-- .../bruno-electron/src/store/process-env.js | 37 ++++++++ packages/bruno-electron/src/utils/common.js | 31 ++++++- .../bruno-electron/tests/utils/common.spec.js | 85 +++++++++++++++++++ packages/bruno-lang/src/index.js | 5 +- 22 files changed, 367 insertions(+), 56 deletions(-) create mode 100644 packages/bruno-electron/src/store/process-env.js create mode 100644 packages/bruno-electron/tests/utils/common.spec.js 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 e3ad6e080..b50c8de6a 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; @@ -86,15 +89,11 @@ const EnvironmentVariables = ({ environment, collection }) => { /> - handleVarChange(e, variable, 'value')} + theme={storedTheme} + onChange={(newValue) => 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/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/VariablesView/VariablesTable/index.js b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js index b56cea098..3707359f2 100644 --- a/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js +++ b/packages/bruno-app/src/components/VariablesView/VariablesTable/index.js @@ -1,5 +1,6 @@ import React from 'react'; import forOwn from 'lodash/forOwn'; +import isObject from 'lodash/isObject'; import cloneDeep from 'lodash/cloneDeep'; import { uuid } from 'utils/common'; import StyledWrapper from './StyledWrapper'; @@ -15,6 +16,14 @@ const VariablesTable = ({ variables, collectionVariables }) => { }); }); + const getValueToDisplay = (value) => { + if (value === undefined) { + return ''; + } + + return isObject(value) ? JSON.stringify(value) : value; + }; + return (
@@ -24,7 +33,9 @@ const VariablesTable = ({ variables, collectionVariables }) => { return (
{variable.name}
-
{variable.value}
+
+ {getValueToDisplay(variable.value)} +
); }) @@ -38,7 +49,9 @@ const VariablesTable = ({ variables, collectionVariables }) => { return (
{variable.name}
-
{variable.value}
+
+ {getValueToDisplay(variable.value)} +
); }) 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/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 d9f89baa3..5ec2fcfc4 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -27,6 +27,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/watcher.js b/packages/bruno-electron/src/app/watcher.js index 6edd0b617..5e6b27206 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -4,11 +4,13 @@ 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 { setDotEnvVars } = require('../store/process-env'); const isJsonEnvironmentConfig = (pathname, collectionPath) => { const dirname = path.dirname(pathname); @@ -17,6 +19,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'); @@ -125,6 +134,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); @@ -220,6 +248,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); } diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 7c5f95e19..bc8deb73e 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -13,6 +13,7 @@ const { uuid } = require('../../utils/common'); const interpolateVars = require('./interpolate-vars'); const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper'); 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/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-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 };