Merge branch 'build' into feature/binary-response-in-tests

This commit is contained in:
Raino Pikkarainen 2024-10-30 21:36:45 +02:00
commit 236b548c76
55 changed files with 8653 additions and 6542 deletions

View File

@ -48,7 +48,7 @@ Bruno is being developed as a desktop app. You need to load the app by running t
### Local Development ### Local Development
```bash ```bash
# use nodejs 18 version # use nodejs 20 version
nvm use nvm use
# install deps # install deps

14235
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -51,10 +51,6 @@
"prepare": "husky install" "prepare": "husky install"
}, },
"overrides": { "overrides": {
"rollup":"3.29.4" "rollup":"3.29.5"
},
"dependencies": {
"json-bigint": "^1.0.0",
"lossless-json": "^4.0.1"
} }
} }

View File

@ -1,4 +1,5 @@
module.exports = { module.exports = {
output: 'export',
reactStrictMode: false, reactStrictMode: false,
publicRuntimeConfig: { publicRuntimeConfig: {
CI: process.env.CI, CI: process.env.CI,
@ -10,6 +11,12 @@ module.exports = {
if (!isServer) { if (!isServer) {
config.resolve.fallback.fs = false; config.resolve.fallback.fs = false;
} }
Object.defineProperty(config, 'devtool', {
get() {
return 'source-map';
},
set() {},
});
return config; return config;
}, },
}; };

View File

@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "cross-env ENV=dev next dev -p 3000", "dev": "cross-env ENV=dev next dev -p 3000",
"build": "next build && next export", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"test": "jest", "test": "jest",
@ -13,27 +13,25 @@
}, },
"dependencies": { "dependencies": {
"@fontsource/inter": "^5.0.15", "@fontsource/inter": "^5.0.15",
"@fortawesome/fontawesome-svg-core": "^1.2.36", "@prantlf/jsonlint": "^16.0.0",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.16",
"@reduxjs/toolkit": "^1.8.0", "@reduxjs/toolkit": "^1.8.0",
"@tabler/icons": "^1.46.0", "@tabler/icons": "^1.46.0",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@usebruno/common": "0.1.0", "@usebruno/common": "0.1.0",
"@usebruno/graphql-docs": "0.1.0", "@usebruno/graphql-docs": "0.1.0",
"@usebruno/schema": "0.7.0", "@usebruno/schema": "0.7.0",
"axios": "^1.5.1", "axios": "1.7.5",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"codemirror": "5.65.2", "codemirror": "5.65.2",
"codemirror-graphql": "1.2.5", "codemirror-graphql": "2.1.1",
"cookie": "^0.6.0", "cookie": "0.7.1",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"file": "^0.2.2", "file": "^0.2.2",
"file-dialog": "^0.0.8", "file-dialog": "^0.0.8",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"formik": "^2.2.9", "formik": "^2.2.9",
"github-markdown-css": "^5.2.0", "github-markdown-css": "^5.2.0",
"graphiql": "^1.5.9", "graphiql": "3.7.1",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-request": "^3.7.0", "graphql-request": "^3.7.0",
"httpsnippet": "^3.0.6", "httpsnippet": "^3.0.6",
@ -44,19 +42,18 @@
"jshint": "^2.13.6", "jshint": "^2.13.6",
"json5": "^2.2.3", "json5": "^2.2.3",
"jsonc-parser": "^3.2.1", "jsonc-parser": "^3.2.1",
"jsonlint": "^1.6.3", "jsonpath-plus": "10.1.0",
"jsonpath-plus": "^7.2.0",
"know-your-http-well": "^0.5.0", "know-your-http-well": "^0.5.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^13.0.2", "markdown-it": "^13.0.2",
"markdown-it-replace-link": "^1.2.0", "markdown-it-replace-link": "^1.2.0",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"nanoid": "3.3.4", "nanoid": "3.3.4",
"next": "12.3.3", "next": "14.2.16",
"path": "^0.12.7", "path": "^0.12.7",
"pdfjs-dist": "^3.11.174", "pdfjs-dist": "4.4.168",
"platform": "^1.3.6", "platform": "^1.3.6",
"posthog-node": "^2.1.0", "posthog-node": "4.2.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"qs": "^6.11.0", "qs": "^6.11.0",
"query-string": "^7.0.1", "query-string": "^7.0.1",
@ -65,11 +62,10 @@
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-github-btn": "^1.4.0",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-inspector": "^6.0.2", "react-inspector": "^6.0.2",
"react-pdf": "^7.5.1", "react-pdf": "9.1.1",
"react-redux": "^7.2.6", "react-redux": "^7.2.6",
"react-tooltip": "^5.5.2", "react-tooltip": "^5.5.2",
"sass": "^1.46.0", "sass": "^1.46.0",
@ -87,15 +83,15 @@
"@babel/preset-env": "^7.16.4", "@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0", "@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.16.3", "@babel/runtime": "^7.16.3",
"autoprefixer": "^10.4.17", "autoprefixer": "10.4.20",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^6.5.1", "css-loader": "7.1.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-loader": "^3.0.1", "html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.5", "mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.35", "postcss": "8.4.47",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"webpack": "^5.64.4", "webpack": "^5.64.4",

View File

@ -14,6 +14,9 @@ const StyledWrapper = styled.div`
.CodeMirror-foldmarker { .CodeMirror-foldmarker {
text-shadow: none; text-shadow: none;
color: ${(props) => props.theme.textLink}; color: ${(props) => props.theme.textLink};
background: none;
padding: 0;
margin: 0;
} }
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-horizontal div,
@ -75,6 +78,10 @@ const StyledWrapper = styled.div`
.cm-variable-invalid { .cm-variable-invalid {
color: red; color: red;
} }
.CodeMirror-search-hint {
display: inline;
}
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -10,7 +10,7 @@ import { isEqual, escapeRegExp } from 'lodash';
import { getEnvironmentVariables } from 'utils/collections'; import { getEnvironmentVariables } from 'utils/collections';
import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror'; import { defineCodeMirrorBrunoVariablesMode } from 'utils/common/codemirror';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import jsonlint from 'jsonlint'; import * as jsonlint from '@prantlf/jsonlint';
import { JSHINT } from 'jshint'; import { JSHINT } from 'jshint';
import stripJsonComments from 'strip-json-comments'; import stripJsonComments from 'strip-json-comments';
@ -251,17 +251,20 @@ export default class CodeEditor extends React.Component {
return found; return found;
} }
let jsonlint = window.jsonlint.parser || window.jsonlint; let jsonlint = window.jsonlint.parser || window.jsonlint;
jsonlint.parseError = function (str, hash) {
let loc = hash.loc;
found.push({
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
message: str
});
};
try { try {
jsonlint.parse(stripJsonComments(text.replace(/(?<!"[^":{]*){{[^}]*}}(?![^"},]*")/g, '1'))); jsonlint.parse(stripJsonComments(text.replace(/(?<!"[^":{]*){{[^}]*}}(?![^"},]*")/g, '1')));
} catch (e) {} } catch (error) {
const { message, location } = error;
const line = location?.start?.line;
const column = location?.start?.column;
if (line && column) {
found.push({
from: CodeMirror.Pos(line - 1, column),
to: CodeMirror.Pos(line - 1, column),
message
});
}
}
return found; return found;
}); });
if (editor) { if (editor) {
@ -336,7 +339,7 @@ export default class CodeEditor extends React.Component {
} }
return ( return (
<StyledWrapper <StyledWrapper
className="h-full w-full flex flex-col relative" className="h-full w-full flex flex-col relative graphiql-container"
aria-label="Code Editor" aria-label="Code Editor"
font={this.props.font} font={this.props.font}
fontSize={this.props.fontSize} fontSize={this.props.fontSize}

View File

@ -17,6 +17,15 @@ import Presets from './Presets';
import Info from './Info'; import Info from './Info';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import Vars from './Vars/index'; import Vars from './Vars/index';
import DotIcon from 'components/Icons/Dot';
const ContentIndicator = () => {
return (
<sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
);
};
const CollectionSettings = ({ collection }) => { const CollectionSettings = ({ collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -30,10 +39,23 @@ const CollectionSettings = ({ collection }) => {
); );
}; };
const proxyConfig = get(collection, 'brunoConfig.proxy', {}); const root = collection?.root;
const hasScripts = root?.request?.script.res || root?.request?.script.req;
const hasTests = root?.request?.tests;
const hasDocs = root?.docs;
const headers = get(collection, 'root.request.headers', []);
const activeHeadersCount = headers.filter((header) => header.enabled).length;
const requestVars = get(collection, 'root.request.vars.req', []);
const responseVars = get(collection, 'root.request.vars.res', []);
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
const auth = get(collection, 'root.request.auth', {}).mode;
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []); const clientCertConfig = get(collection, 'brunoConfig.clientCertificates.certs', []);
const onProxySettingsUpdate = (config) => { const onProxySettingsUpdate = (config) => {
const brunoConfig = cloneDeep(collection.brunoConfig); const brunoConfig = cloneDeep(collection.brunoConfig);
brunoConfig.proxy = config; brunoConfig.proxy = config;
@ -126,30 +148,38 @@ const CollectionSettings = ({ collection }) => {
<div className="flex flex-wrap items-center tabs" role="tablist"> <div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}> <div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers Headers
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
</div> </div>
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}> <div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
Vars Vars
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
</div> </div>
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}> <div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
Auth Auth
{auth !== 'none' && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}> <div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script Script
{hasScripts && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}> <div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
Tests Tests
{hasTests && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}> <div className={getTabClassname('presets')} role="tab" onClick={() => setTab('presets')}>
Presets Presets
</div> </div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}> <div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy Proxy
{Object.keys(proxyConfig).length > 0 && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}> <div className={getTabClassname('clientCert')} role="tab" onClick={() => setTab('clientCert')}>
Client Certificates Client Certificates
{clientCertConfig.length > 0 && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}> <div className={getTabClassname('docs')} role="tab" onClick={() => setTab('docs')}>
Docs Docs
{hasDocs && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}> <div className={getTabClassname('info')} role="tab" onClick={() => setTab('info')}>
Info Info

View File

@ -7,6 +7,15 @@ import Script from './Script';
import Tests from './Tests'; import Tests from './Tests';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import Vars from './Vars'; import Vars from './Vars';
import DotIcon from 'components/Icons/Dot';
const ContentIndicator = () => {
return (
<sup className="ml-[.125rem] opacity-80 font-medium">
<DotIcon width="10"></DotIcon>
</sup>
);
};
const FolderSettings = ({ collection, folder }) => { const FolderSettings = ({ collection, folder }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -16,6 +25,17 @@ const FolderSettings = ({ collection, folder }) => {
tab = folderLevelSettingsSelectedTab[folder?.uid]; tab = folderLevelSettingsSelectedTab[folder?.uid];
} }
const folderRoot = collection?.items.find((item) => item.uid === folder?.uid)?.root;
const hasScripts = folderRoot?.request?.script.res || folderRoot?.request?.script.req;
const hasTests = folderRoot?.request?.tests;
const headers = folderRoot?.request?.headers || [];
const activeHeadersCount = headers.filter((header) => header.enabled).length;
const requestVars = folderRoot?.request?.vars?.req || [];
const responseVars = folderRoot?.request?.vars?.res || [];
const activeVarsCount = requestVars.filter((v) => v.enabled).length + responseVars.filter((v) => v.enabled).length;
const setTab = (tab) => { const setTab = (tab) => {
dispatch( dispatch(
updatedFolderSettingsSelectedTab({ updatedFolderSettingsSelectedTab({
@ -55,15 +75,19 @@ const FolderSettings = ({ collection, folder }) => {
<div className="flex flex-wrap items-center tabs" role="tablist"> <div className="flex flex-wrap items-center tabs" role="tablist">
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}> <div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
Headers Headers
{activeHeadersCount > 0 && <sup className="ml-1 font-medium">{activeHeadersCount}</sup>}
</div> </div>
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}> <div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
Script Script
{hasScripts && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('test')} role="tab" onClick={() => setTab('test')}> <div className={getTabClassname('test')} role="tab" onClick={() => setTab('test')}>
Test Test
{hasTests && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}> <div className={getTabClassname('vars')} role="tab" onClick={() => setTab('vars')}>
Vars Vars
{activeVarsCount > 0 && <sup className="ml-1 font-medium">{activeVarsCount}</sup>}
</div> </div>
</div> </div>
<section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section> <section className={`flex mt-4 h-full`}>{getTabPanel(tab)}</section>

View File

@ -7,6 +7,7 @@ import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments'; import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import ToolHint from 'components/ToolHint/index';
const EnvironmentSelector = () => { const EnvironmentSelector = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -18,12 +19,14 @@ const EnvironmentSelector = () => {
const Icon = forwardRef((props, ref) => { const Icon = forwardRef((props, ref) => {
return ( return (
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}> <div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
<IconWorld className="globe" size={16} strokeWidth={1.5} /> <ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId" className='flex flex-row'>
{ <IconWorld className="globe" size={16} strokeWidth={1.5} />
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null {
} activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
</div> }
</ToolHint>
</div>
); );
}); });

View File

@ -39,6 +39,11 @@ const Wrapper = styled.div`
font-size: 0.8125rem; font-size: 0.8125rem;
} }
.tooltip-mod {
font-size: 11px !important;
width: 150px !important;
}
input[type='text'] { input[type='text'] {
width: 100%; width: 100%;
border: solid 1px transparent; border: solid 1px transparent;

View File

@ -1,6 +1,6 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons'; import { IconTrash, IconAlertCircle } from '@tabler/icons';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import SingleLineEditor from 'components/SingleLineEditor'; import SingleLineEditor from 'components/SingleLineEditor';
@ -11,6 +11,7 @@ import * as Yup from 'yup';
import { variableNameRegex } from 'utils/common/regex'; import { variableNameRegex } from 'utils/common/regex';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments'; import { saveGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { Tooltip } from 'react-tooltip';
const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables }) => { const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentVariables }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -62,14 +63,15 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
const ErrorMessage = ({ name }) => { const ErrorMessage = ({ name }) => {
const meta = formik.getFieldMeta(name); const meta = formik.getFieldMeta(name);
const id = uuid();
if (!meta.error || !meta.touched) { if (!meta.error || !meta.touched) {
return null; return null;
} }
return ( return (
<label htmlFor={name} className="text-red-500"> <span>
{meta.error} <IconAlertCircle id={id} className="text-red-600 cursor-pointer " size={20} />
</label> <Tooltip className="tooltip-mod" anchorId={id} html={meta.error || ''} />
</span>
); );
}; };
@ -127,19 +129,21 @@ const EnvironmentVariables = ({ environment, setIsModified, originalEnvironmentV
/> />
</td> </td>
<td> <td>
<input <div className="flex items-center">
type="text" <input
autoComplete="off" type="text"
autoCorrect="off" autoComplete="off"
autoCapitalize="off" autoCorrect="off"
spellCheck="false" autoCapitalize="off"
className="mousetrap" spellCheck="false"
id={`${index}.name`} className="mousetrap"
name={`${index}.name`} id={`${index}.name`}
value={variable.name} name={`${index}.name`}
onChange={formik.handleChange} value={variable.name}
/> onChange={formik.handleChange}
<ErrorMessage name={`${index}.name`} /> />
<ErrorMessage name={`${index}.name`} />
</div>
</td> </td>
<td className="flex flex-row flex-nowrap"> <td className="flex flex-row flex-nowrap">
<div className="overflow-hidden grow w-full relative"> <div className="overflow-hidden grow w-full relative">

View File

@ -7,6 +7,7 @@ import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from './ConfirmSwitchEnv'; import ConfirmSwitchEnv from './ConfirmSwitchEnv';
import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index'; import ManageSecrets from 'components/Environments/EnvironmentSettings/ManageSecrets/index';
import ImportEnvironment from '../ImportEnvironment'; import ImportEnvironment from '../ImportEnvironment';
import { isEqual } from 'lodash';
const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => { const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironment, setSelectedEnvironment, isModified, setIsModified }) => {
const [openCreateModal, setOpenCreateModal] = useState(false); const [openCreateModal, setOpenCreateModal] = useState(false);
@ -20,18 +21,28 @@ const EnvironmentList = ({ environments, activeEnvironmentUid, selectedEnvironme
const prevEnvUids = usePrevious(envUids); const prevEnvUids = usePrevious(envUids);
useEffect(() => { useEffect(() => {
if (!environments?.length) {
setSelectedEnvironment(null);
setOriginalEnvironmentVariables([]);
return;
}
if (selectedEnvironment) { if (selectedEnvironment) {
const _selectedEnvironment = environments?.find(env => env?.uid === selectedEnvironment?.uid);
const hasSelectedEnvironmentChanged = !isEqual(selectedEnvironment, _selectedEnvironment);
if (hasSelectedEnvironmentChanged) {
setSelectedEnvironment(_selectedEnvironment);
}
setOriginalEnvironmentVariables(selectedEnvironment.variables); setOriginalEnvironmentVariables(selectedEnvironment.variables);
return; return;
} }
const environment = environments?.find(env => env?.uid === activeEnvironmentUid); const environment = environments?.find(env => env.uid === activeEnvironmentUid) || environments?.[0];
if (environment) {
setSelectedEnvironment(environment); setSelectedEnvironment(environment);
} else { setOriginalEnvironmentVariables(environment?.variables || []);
setSelectedEnvironment(environments && environments.length ? environments[0] : null); }, [environments, activeEnvironmentUid]);
}
}, [environments, selectedEnvironment]);
useEffect(() => { useEffect(() => {
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) { if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {

View File

@ -62,7 +62,7 @@ const EnvironmentSettings = ({ globalEnvironments, activeGlobalEnvironmentUid, o
} }
return ( return (
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}> <Modal size="lg" title="Global Environments" handleCancel={onClose} hideFooter={true}>
<EnvironmentList <EnvironmentList
environments={globalEnvironments} environments={globalEnvironments}
activeEnvironmentUid={activeGlobalEnvironmentUid} activeEnvironmentUid={activeGlobalEnvironmentUid}

View File

@ -1,19 +0,0 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
.collection-dropdown {
color: rgb(110 110 110);
&:hover {
color: inherit;
}
.tippy-box {
top: -0.5rem;
position: relative;
user-select: none;
}
}
`;
export default StyledWrapper;

View File

@ -1,60 +0,0 @@
import React, { useState, forwardRef, useRef } from 'react';
import Dropdown from '../Dropdown';
import { faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconBox, IconSearch, IconDots } from '@tabler/icons';
import StyledWrapper from './StyledWrapper';
const Navbar = () => {
const [modalOpen, setModalOpen] = useState(false);
const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => {
return (
<div ref={ref} className="dropdown-icon cursor-pointer">
<IconDots size={22} />
</div>
);
});
return (
<StyledWrapper className="px-2 py-2 flex items-center">
<div>
<span className="ml-2">Collections</span>
{/* <FontAwesomeIcon className="ml-2" icon={faCaretDown} style={{fontSize: 13}}/> */}
</div>
<div className="collection-dropdown flex flex-grow items-center justify-end">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
onClick={(e) => {
menuDropdownTippyRef.current.hide();
setModalOpen(true);
}}
>
Create Collection
</div>
<div
className="dropdown-item"
onClick={(e) => {
menuDropdownTippyRef.current.hide();
}}
>
Import Collection
</div>
<div
className="dropdown-item"
onClick={(e) => {
menuDropdownTippyRef.current.hide();
}}
>
Settings
</div>
</Dropdown>
</div>
</StyledWrapper>
);
};
export default Navbar;

View File

@ -17,9 +17,11 @@ import { find, get } from 'lodash';
import Documentation from 'components/Documentation/index'; import Documentation from 'components/Documentation/index';
const ContentIndicator = () => { const ContentIndicator = () => {
return <sup className="ml-[.125rem] opacity-80 font-medium"> return (
<DotIcon width="10"></DotIcon> <sup className="ml-[.125rem] opacity-80 font-medium">
</sup> <DotIcon width="10"></DotIcon>
</sup>
);
}; };
const HttpRequestPane = ({ item, collection, leftPaneWidth }) => { const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
@ -100,6 +102,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
const docs = getPropertyFromDraftOrRequest('request.docs'); const docs = getPropertyFromDraftOrRequest('request.docs');
const requestVars = getPropertyFromDraftOrRequest('request.vars.req'); const requestVars = getPropertyFromDraftOrRequest('request.vars.req');
const responseVars = getPropertyFromDraftOrRequest('request.vars.res'); const responseVars = getPropertyFromDraftOrRequest('request.vars.res');
const auth = getPropertyFromDraftOrRequest('request.auth');
const activeParamsLength = params.filter((param) => param.enabled).length; const activeParamsLength = params.filter((param) => param.enabled).length;
const activeHeadersLength = headers.filter((header) => header.enabled).length; const activeHeadersLength = headers.filter((header) => header.enabled).length;
@ -125,6 +128,7 @@ const HttpRequestPane = ({ item, collection, leftPaneWidth }) => {
</div> </div>
<div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}> <div className={getTabClassname('auth')} role="tab" onClick={() => selectTab('auth')}>
Auth Auth
{auth.mode !== 'none' && <ContentIndicator />}
</div> </div>
<div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}> <div className={getTabClassname('vars')} role="tab" onClick={() => selectTab('vars')}>
Vars Vars

View File

@ -50,6 +50,10 @@ const StyledWrapper = styled.div`
.cm-variable-invalid { .cm-variable-invalid {
color: red; color: red;
} }
.CodeMirror-search-hint {
display: inline;
}
`; `;
export default StyledWrapper; export default StyledWrapper;

View File

@ -209,7 +209,7 @@ export default class QueryEditor extends React.Component {
return ( return (
<> <>
<StyledWrapper <StyledWrapper
className="h-full w-full flex flex-col relative" className="h-full w-full flex flex-col relative graphiql-container"
aria-label="Query Editor" aria-label="Query Editor"
font={this.props.font} font={this.props.font}
fontSize={this.props.fontSize} fontSize={this.props.fontSize}

View File

@ -9,7 +9,6 @@ import StyledWrapper from './StyledWrapper';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index'; import { updateRequestBody } from 'providers/ReduxStore/slices/collections/index';
import { toastError } from 'utils/common/error'; import { toastError } from 'utils/common/error';
import { format, applyEdits } from 'jsonc-parser'; import { format, applyEdits } from 'jsonc-parser';
import { parse, stringify } from 'lossless-json';
import xmlFormat from 'xml-formatter'; import xmlFormat from 'xml-formatter';
const RequestBodyMode = ({ item, collection }) => { const RequestBodyMode = ({ item, collection }) => {

View File

@ -1,18 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
.folder-list {
border: 1px solid #ccc;
border-radius: 5px;
.folder-name {
padding-block: 8px;
padding-inline: 12px;
cursor: pointer;
&:hover {
background-color: #e8e8e8;
}
}
`;
export default Wrapper;

View File

@ -1,55 +0,0 @@
import React, { useState, useEffect } from 'react';
import { faFolder } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import StyledWrapper from './StyledWrapper';
import Modal from 'components//Modal';
const SaveRequest = ({ items, onClose }) => {
const [showFolders, setShowFolders] = useState([]);
useEffect(() => {
setShowFolders(items || []);
}, [items]);
const handleFolderClick = (folder) => {
let subFolders = [];
if (folder.items && folder.items.length) {
for (let item of folder.items) {
if (item.items) {
subFolders.push(item);
}
}
if (subFolders.length) {
setShowFolders(subFolders);
}
}
};
return (
<StyledWrapper>
<Modal
size="md"
title="Save Request"
confirmText="Save"
cancelText="Cancel"
handleCancel={onClose}
handleConfirm={onClose}
>
<p className="mb-2">Select a folder to save request:</p>
<div className="folder-list">
{showFolders && showFolders.length
? showFolders.map((folder) => (
<div key={folder.uid} className="folder-name" onClick={() => handleFolderClick(folder)}>
<FontAwesomeIcon className="mr-3 text-gray-500" icon={faFolder} style={{ fontSize: 20 }} />
{folder.name}
</div>
))
: null}
</div>
</Modal>
</StyledWrapper>
);
};
export default SaveRequest;

View File

@ -21,7 +21,7 @@ import StyledWrapper from './StyledWrapper';
import SecuritySettings from 'components/SecuritySettings'; import SecuritySettings from 'components/SecuritySettings';
import FolderSettings from 'components/FolderSettings'; import FolderSettings from 'components/FolderSettings';
import { getGlobalEnvironmentVariables } from 'utils/collections/index'; import { getGlobalEnvironmentVariables } from 'utils/collections/index';
import { cloneDeep } from 'lodash'; import { produce } from 'immer';
const MIN_LEFT_PANE_WIDTH = 300; const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350; const MIN_RIGHT_PANE_WIDTH = 350;
@ -34,12 +34,25 @@ const RequestTabPanel = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const tabs = useSelector((state) => state.tabs.tabs); const tabs = useSelector((state) => state.tabs.tabs);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const collections = useSelector((state) => state.collections.collections);
const screenWidth = useSelector((state) => state.app.screenWidth);
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const focusedTab = find(tabs, (t) => t.uid === activeTabUid); const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
const { globalEnvironments, activeGlobalEnvironmentUid } = useSelector((state) => state.globalEnvironments);
const _collections = useSelector((state) => state.collections.collections);
// merge `globalEnvironmentVariables` into the active collection and rebuild `collections` immer proxy object
let collections = produce(_collections, draft => {
let collection = find(draft, (c) => c.uid === focusedTab?.collectionUid);
if (collection) {
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collection.globalEnvironmentVariables = globalEnvironmentVariables;
}
});
let collection = find(collections, (c) => c.uid === focusedTab?.collectionUid);
const screenWidth = useSelector((state) => state.app.screenWidth);
let asideWidth = useSelector((state) => state.app.leftSidebarWidth);
const [leftPaneWidth, setLeftPaneWidth] = useState( const [leftPaneWidth, setLeftPaneWidth] = useState(
focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2 focusedTab && focusedTab.requestPaneWidth ? focusedTab.requestPaneWidth : (screenWidth - asideWidth) / 2.2
); // 2.2 so that request pane is relatively smaller ); // 2.2 so that request pane is relatively smaller
@ -120,14 +133,6 @@ const RequestTabPanel = () => {
return <div className="pb-4 px-4">An error occurred!</div>; return <div className="pb-4 px-4">An error occurred!</div>;
} }
let _collection = find(collections, (c) => c.uid === focusedTab.collectionUid);
let collection = cloneDeep(_collection);
// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
collection.globalEnvironmentVariables = globalEnvironmentVariables;
if (!collection || !collection.uid) { if (!collection || !collection.uid) {
return <div className="pb-4 px-4">Collection not found!</div>; return <div className="pb-4 px-4">Collection not found!</div>;
} }

View File

@ -69,11 +69,8 @@ const CollectionToolBar = ({ collection }) => {
</ToolHint> </ToolHint>
</span> </span>
<span> <span>
<ToolHint text="Global Environments" toolhintId="GlobalEnvironmentsToolhintId"> <GlobalEnvironmentSelector />
<GlobalEnvironmentSelector />
</ToolHint>
</span> </span>
<EnvironmentSelector collection={collection} /> <EnvironmentSelector collection={collection} />
</div> </div>
</div> </div>

View File

@ -7,6 +7,8 @@ import { useState } from 'react';
import 'pdfjs-dist/build/pdf.worker'; import 'pdfjs-dist/build/pdf.worker';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import 'react-pdf/dist/esm/Page/TextLayer.css'; import 'react-pdf/dist/esm/Page/TextLayer.css';
import { GlobalWorkerOptions } from 'pdfjs-dist/build/pdf';
GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.min.mjs';
const QueryResultPreview = ({ const QueryResultPreview = ({
previewTab, previewTab,

View File

@ -1,7 +1,6 @@
import TitleBar from './TitleBar'; import TitleBar from './TitleBar';
import Collections from './Collections'; import Collections from './Collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import GitHubButton from 'react-github-btn';
import Preferences from 'components/Preferences'; import Preferences from 'components/Preferences';
import Cookies from 'components/Cookies'; import Cookies from 'components/Cookies';
import ToolHint from 'components/ToolHint'; import ToolHint from 'components/ToolHint';
@ -185,7 +184,7 @@ const Sidebar = () => {
Star Star
</GitHubButton> */} </GitHubButton> */}
</div> </div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.33.1</div> <div className="flex flex-grow items-center justify-end text-xs mr-2">v1.34.1</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,74 +0,0 @@
import styled from 'styled-components';
const Wrapper = styled.div`
&.bruno-toast {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
}
.bruno-toast-card {
-webkit-animation-duration: 0.85s;
animation-duration: 0.85s;
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
border-radius: var(--border-radius);
position: relative;
max-width: calc(100% - var(--spacing-base-unit));
margin: 3vh 10vw;
animation: fade-and-slide-in-from-top 0.5s forwards cubic-bezier(0.19, 1, 0.22, 1);
}
.notification-toast-content {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5px;
border-radius: 4px;
}
.alert {
position: relative;
padding: 0.25rem 0.75rem;
border: 1px solid transparent;
border-radius: 0.25rem;
display: flex;
justify-content: space-between;
}
.alert-error {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.alert-info {
color: #004085;
background-color: #cce5ff;
border-color: #b8daff;
}
.alert-warning {
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
}
.alert-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
.closeToast {
cursor: pointer;
padding-left: 10px;
}
`;
export default Wrapper;

View File

@ -1,33 +0,0 @@
import React, { useEffect } from 'react';
import StyledWrapper from './StyledWrapper';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
const ToastContent = ({ type, text, handleClose }) => (
<div className={`alert alert-${type}`} role="alert">
<div> {text} </div>
<div onClick={handleClose} className="closeToast">
<FontAwesomeIcon size="xs" icon={faTimes} />
</div>
</div>
);
const Toast = ({ text, type, duration, handleClose }) => {
let lifetime = duration ? duration : 3000;
useEffect(() => {
if (text) {
setTimeout(handleClose, lifetime);
}
}, [text]);
return (
<StyledWrapper className="bruno-toast">
<div className="bruno-toast-card">
<ToastContent type={type} text={text} handleClose={handleClose}></ToastContent>
</div>
</StyledWrapper>
);
};
export default Toast;

View File

@ -10,7 +10,8 @@ const ToolHint = ({
tooltipStyle = {}, tooltipStyle = {},
place = 'top', place = 'top',
offset, offset,
theme = null theme = null,
className = ''
}) => { }) => {
const { theme: contextTheme } = useTheme(); const { theme: contextTheme } = useTheme();
const appliedTheme = theme || contextTheme; const appliedTheme = theme || contextTheme;
@ -29,7 +30,7 @@ const ToolHint = ({
return ( return (
<> <>
<span id={toolhintId}>{children}</span> <span id={toolhintId} className={className}>{children}</span>
<StyledWrapper theme={appliedTheme}> <StyledWrapper theme={appliedTheme}>
<ReactToolHint <ReactToolHint
anchorId={toolhintId} anchorId={toolhintId}

View File

@ -231,6 +231,11 @@ const GlobalStyle = createGlobalStyle`
.CodeMirror-brunoVarInfo p { .CodeMirror-brunoVarInfo p {
margin: 1em 0; margin: 1em 0;
} }
.CodeMirror-hint-active {
background: #89f !important;
color: #fff !important;
}
`; `;
export default GlobalStyle; export default GlobalStyle;

View File

@ -23,6 +23,8 @@ import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css'; import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css'; import '@fontsource/inter/800.css';
import '@fontsource/inter/900.css'; import '@fontsource/inter/900.css';
import { setupPolyfills } from 'utils/common/setupPolyfills';
setupPolyfills();
function SafeHydrate({ children }) { function SafeHydrate({ children }) {
return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>; return <div suppressHydrationWarning>{typeof window === 'undefined' ? null : children}</div>;

View File

@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start', event: 'start',
properties: { properties: {
os: platformLib.os.family, os: platformLib.os.family,
version: '1.33.1' version: '1.34.1'
} }
}); });
}; };

View File

@ -3,7 +3,6 @@ import toast from 'react-hot-toast';
import find from 'lodash/find'; import find from 'lodash/find';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import SaveRequest from 'components/RequestPane/SaveRequest';
import EnvironmentSettings from 'components/Environments/EnvironmentSettings'; import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
import NetworkError from 'components/ResponsePane/NetworkError'; import NetworkError from 'components/ResponsePane/NetworkError';
import NewRequest from 'components/Sidebar/NewRequest'; import NewRequest from 'components/Sidebar/NewRequest';
@ -20,19 +19,9 @@ export const HotkeysProvider = (props) => {
const collections = useSelector((state) => state.collections.collections); const collections = useSelector((state) => state.collections.collections);
const activeTabUid = useSelector((state) => state.tabs.activeTabUid); const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen); const isEnvironmentSettingsModalOpen = useSelector((state) => state.app.isEnvironmentSettingsModalOpen);
const [showSaveRequestModal, setShowSaveRequestModal] = useState(false);
const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false); const [showEnvSettingsModal, setShowEnvSettingsModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const getCurrentCollectionItems = () => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if (activeTab) {
const collection = findCollectionByUid(collections, activeTab.collectionUid);
return collection ? collection.items : [];
}
};
const getCurrentCollection = () => { const getCurrentCollection = () => {
const activeTab = find(tabs, (t) => t.uid === activeTabUid); const activeTab = find(tabs, (t) => t.uid === activeTabUid);
if (activeTab) { if (activeTab) {
@ -57,9 +46,6 @@ export const HotkeysProvider = (props) => {
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid)); dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
} else if (activeTab.type === 'collection-settings') { } else if (activeTab.type === 'collection-settings') {
dispatch(saveCollectionRoot(collection.uid)); dispatch(saveCollectionRoot(collection.uid));
} else {
// todo: when ephermal requests go live
// setShowSaveRequestModal(true);
} }
} }
} }
@ -218,9 +204,6 @@ export const HotkeysProvider = (props) => {
return ( return (
<HotkeysContext.Provider {...props} value="hotkey"> <HotkeysContext.Provider {...props} value="hotkey">
{showSaveRequestModal && (
<SaveRequest items={getCurrentCollectionItems()} onClose={() => setShowSaveRequestModal(false)} />
)}
{showEnvSettingsModal && ( {showEnvSettingsModal && (
<EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} /> <EnvironmentSettings collection={getCurrentCollection()} onClose={() => setShowEnvSettingsModal(false)} />
)} )}

View File

@ -201,7 +201,7 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid); const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
_sendCollectionOauth2Request(collection, environment, collectionCopy.runtimeVariables) _sendCollectionOauth2Request(collectionCopy, environment, collectionCopy.runtimeVariables)
.then((response) => { .then((response) => {
if (response?.data?.error) { if (response?.data?.error) {
toast.error(response?.data?.error); toast.error(response?.data?.error);

View File

@ -9,7 +9,7 @@ const initialState = {
}; };
export const globalEnvironmentsSlice = createSlice({ export const globalEnvironmentsSlice = createSlice({
name: 'app', name: 'global-environments',
initialState, initialState,
reducers: { reducers: {
updateGlobalEnvironments: (state, action) => { updateGlobalEnvironments: (state, action) => {

View File

@ -23,6 +23,54 @@
--color-method-options: rgb(52 52 52); --color-method-options: rgb(52 52 52);
--color-method-head: rgb(52 52 52); --color-method-head: rgb(52 52 52);
} }
:root,.graphiql-container,.CodeMirror-info,.CodeMirror-lint-tooltip,reach-portal {
/* Required CSS variables after upgrading GraphiQL from v1.5.9 to v2.4.7 */
/* Colors */
--color-primary: 320, 95%, 43% !important;
--color-secondary: 242, 51%, 61% !important;
--color-tertiary: 188, 100%, 36% !important;
--color-info: 208, 100%, 46% !important;
--color-success: 158, 60%, 42% !important;
--color-warning: 36, 100%, 41% !important;
--color-error: 13, 93%, 58% !important;
--color-neutral: 219, 28%, 32% !important;
--color-base: 219, 28%, 100% !important;
/* Color alpha values */
--alpha-secondary: 0.76 !important;
--alpha-tertiary: 0.5 !important;
--alpha-background-heavy: 0.15 !important;
--alpha-background-medium: 0.1 !important;
--alpha-background-light: 0.07 !important;
--font-size-hint: .75rem;
--font-size-inline-code: .8125rem;
--font-size-body: .9375rem;
--font-size-h4: 1.125rem;
--font-size-h3: 1.375rem;
--font-size-h2: 1.8125rem;
--font-weight-regular: 400;
--font-weight-medium: 500;
--line-height: 1.5;
--px-2: 2px;
--px-4: 4px;
--px-6: 6px;
--px-8: 8px;
--px-10: 10px;
--px-12: 12px;
--px-16: 16px;
--px-20: 20px;
--px-24: 24px;
--border-radius-2: 2px !important;
--border-radius-4: 2px !important;
--border-radius-8: 2px !important;
--border-radius-12: 2px !important;
--popover-box-shadow: 0px 0px 1px #000 !important;
--popover-border: none;
--sidebar-width: 60px;
--toolbar-width: 40px;
--session-header-height: 51px
}
html, html,
body { body {

View File

@ -0,0 +1,24 @@
export const setupPolyfills = () => {
// polyfill required to make react-pdf
if (typeof Promise.withResolvers === "undefined") {
if (typeof window !== 'undefined') {
window.Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
} else {
global.Promise.withResolvers = function () {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
}
}

View File

@ -97,6 +97,75 @@ const constructUrl = (url) => {
let translationLog = {}; let translationLog = {};
/* struct of translation log
{
[collectionName]: {
script: [index1, index2],
test: [index1, index2]
}
}
*/
const pushTranslationLog = (type, index) => {
if (!translationLog[i.name]) {
translationLog[i.name] = {};
}
if (!translationLog[i.name][type]) {
translationLog[i.name][type] = [];
}
translationLog[i.name][type].push(index + 1);
};
const importScriptsFromEvents = (events, requestObject, options, pushTranslationLog) => {
events.forEach((event) => {
if (event.script && event.script.exec) {
if (event.listen === 'prerequest') {
if (!requestObject.script) {
requestObject.script = {};
}
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
requestObject.script.req = event.script.exec
.map((line, index) =>
options.enablePostmanTranslations.enabled
? postmanTranslation(line, () => pushTranslationLog('script', index))
: `// ${line}`
)
.join('\n');
} else if (typeof event.script.exec === 'string') {
requestObject.script.req = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec, () => pushTranslationLog('script', 0))
: `// ${event.script.exec}`;
} else {
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
if (event.listen === 'test') {
if (!requestObject.tests) {
requestObject.tests = {};
}
if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
requestObject.tests = event.script.exec
.map((line, index) =>
options.enablePostmanTranslations.enabled
? postmanTranslation(line, () => pushTranslationLog('test', index))
: `// ${line}`
)
.join('\n');
} else if (typeof event.script.exec === 'string') {
requestObject.tests = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec, () => pushTranslationLog('test', 0))
: `// ${event.script.exec}`;
} else {
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
}
}
}
});
};
const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => { const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) => {
brunoParent.items = brunoParent.items || []; brunoParent.items = brunoParent.items || [];
const folderMap = {}; const folderMap = {};
@ -117,13 +186,35 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
uid: uuid(), uid: uuid(),
name: folderName, name: folderName,
type: 'folder', type: 'folder',
items: [] items: [],
root: {
meta: {
name: folderName
},
request: {
auth: {
mode: 'none',
basic: null,
bearer: null,
awsv4: null
},
headers: [],
script: {},
tests: '',
vars: {}
}
}
}; };
brunoParent.items.push(brunoFolderItem);
folderMap[folderName] = brunoFolderItem;
if (i.item && i.item.length) { if (i.item && i.item.length) {
importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options); importPostmanV2CollectionItem(brunoFolderItem, i.item, i.auth ?? parentAuth, options);
} }
if (i.event) {
importScriptsFromEvents(i.event, brunoFolderItem.root.request, options, pushTranslationLog);
}
brunoParent.items.push(brunoFolderItem);
folderMap[folderName] = brunoFolderItem;
} else { } else {
if (i.request) { if (i.request) {
const baseRequestName = i.name; const baseRequestName = i.name;
@ -163,32 +254,14 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
docs: i.request.description docs: i.request.description
} }
}; };
/* struct of translation log
{
[collectionName]: {
script: [index1, index2],
test: [index1, index2]
}
}
*/
// type could be script or test
const pushTranslationLog = (type, index) => {
if (!translationLog[i.name]) {
translationLog[i.name] = {};
}
if (!translationLog[i.name][type]) {
translationLog[i.name][type] = [];
}
translationLog[i.name][type].push(index + 1);
};
if (i.event) { if (i.event) {
i.event.forEach((event) => { i.event.forEach((event) => {
if (event.listen === 'prerequest' && event.script && event.script.exec) { if (event.listen === 'prerequest' && event.script && event.script.exec) {
if (!brunoRequestItem.request.script) { if (!brunoRequestItem.request.script) {
brunoRequestItem.request.script = {}; brunoRequestItem.request.script = {};
} }
if (Array.isArray(event.script.exec)) { if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
brunoRequestItem.request.script.req = event.script.exec brunoRequestItem.request.script.req = event.script.exec
.map((line, index) => .map((line, index) =>
options.enablePostmanTranslations.enabled options.enablePostmanTranslations.enabled
@ -196,17 +269,19 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
: `// ${line}` : `// ${line}`
) )
.join('\n'); .join('\n');
} else { } else if (typeof event.script.exec === 'string') {
brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled brunoRequestItem.request.script.req = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('script', 0)) ? postmanTranslation(event.script.exec, () => pushTranslationLog('script', 0))
: `// ${event.script.exec[0]} `; : `// ${event.script.exec}`;
} else {
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
} }
} }
if (event.listen === 'test' && event.script && event.script.exec) { if (event.listen === 'test' && event.script && event.script.exec) {
if (!brunoRequestItem.request.tests) { if (!brunoRequestItem.request.tests) {
brunoRequestItem.request.tests = {}; brunoRequestItem.request.tests = {};
} }
if (Array.isArray(event.script.exec)) { if (Array.isArray(event.script.exec) && event.script.exec.length > 0) {
brunoRequestItem.request.tests = event.script.exec brunoRequestItem.request.tests = event.script.exec
.map((line, index) => .map((line, index) =>
options.enablePostmanTranslations.enabled options.enablePostmanTranslations.enabled
@ -214,10 +289,12 @@ const importPostmanV2CollectionItem = (brunoParent, item, parentAuth, options) =
: `// ${line}` : `// ${line}`
) )
.join('\n'); .join('\n');
} else { } else if (typeof event.script.exec === 'string') {
brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled brunoRequestItem.request.tests = options.enablePostmanTranslations.enabled
? postmanTranslation(event.script.exec[0], () => pushTranslationLog('test', 0)) ? postmanTranslation(event.script.exec, () => pushTranslationLog('test', 0))
: `// ${event.script.exec[0]} `; : `// ${event.script.exec}`;
} else {
console.warn('Unexpected event.script.exec type', typeof event.script.exec);
} }
} }
}); });
@ -393,9 +470,30 @@ const importPostmanV2Collection = (collection, options) => {
uid: uuid(), uid: uuid(),
version: '1', version: '1',
items: [], items: [],
environments: [] environments: [],
root: {
meta: {
name: collection.info.name
},
request: {
auth: {
mode: 'none',
basic: null,
bearer: null,
awsv4: null
},
headers: [],
script: {},
tests: '',
vars: {}
}
}
}; };
if (collection.event) {
importScriptsFromEvents(collection.event, brunoCollection.root.request, options, pushTranslationLog);
}
importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options); importPostmanV2CollectionItem(brunoCollection, collection.item, collection.auth, options);
return brunoCollection; return brunoCollection;

View File

@ -5,6 +5,8 @@ const replacements = {
'pm\\.variables\\.set\\(': 'bru.setVar(', 'pm\\.variables\\.set\\(': 'bru.setVar(',
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(', 'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(', 'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
'pm\\.collectionVariables\\.has\\(': 'bru.hasVar(',
'pm\\.collectionVariables\\.unset\\(': 'bru.deleteVar(',
'pm\\.setNextRequest\\(': 'bru.setNextRequest(', 'pm\\.setNextRequest\\(': 'bru.setNextRequest(',
'pm\\.test\\(': 'test(', 'pm\\.test\\(': 'test(',
'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(', 'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(',

View File

@ -24,12 +24,12 @@
"package.json" "package.json"
], ],
"dependencies": { "dependencies": {
"@aws-sdk/credential-providers": "3.525.0", "@aws-sdk/credential-providers": "3.658.1",
"@usebruno/common": "0.1.0", "@usebruno/common": "0.1.0",
"@usebruno/js": "0.12.0", "@usebruno/js": "0.12.0",
"@usebruno/lang": "0.12.0", "@usebruno/lang": "0.12.0",
"aws4-axios": "^3.3.0", "aws4-axios": "^3.3.0",
"axios": "^1.5.1", "axios": "1.7.5",
"chai": "^4.3.7", "chai": "^4.3.7",
"chalk": "^3.0.0", "chalk": "^3.0.0",
"decomment": "^0.9.5", "decomment": "^0.9.5",
@ -37,12 +37,11 @@
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"http-proxy-agent": "^7.0.0", "http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.2",
"inquirer": "^9.1.4",
"json-bigint": "^1.0.0", "json-bigint": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"qs": "^6.11.0", "qs": "^6.11.0",
"socks-proxy-agent": "^8.0.2", "socks-proxy-agent": "^8.0.2",
"vm2": "^3.9.13", "@usebruno/vm2": "^3.9.13",
"xmlbuilder": "^15.1.1", "xmlbuilder": "^15.1.1",
"yargs": "^17.6.2" "yargs": "^17.6.2"
} }

View File

@ -22,13 +22,13 @@
"@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^9.0.2", "@rollup/plugin-typescript": "^9.0.2",
"rollup":"3.29.4", "rollup":"3.29.5",
"rollup-plugin-dts": "^5.0.0", "rollup-plugin-dts": "^5.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"overrides": { "overrides": {
"rollup":"3.29.4" "rollup":"3.29.5"
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "v1.33.1", "version": "v1.34.1",
"name": "bruno", "name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs", "description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com", "homepage": "https://www.usebruno.com",
@ -22,7 +22,7 @@
"modulePaths": ["node_modules"] "modulePaths": ["node_modules"]
}, },
"dependencies": { "dependencies": {
"@aws-sdk/credential-providers": "3.525.0", "@aws-sdk/credential-providers": "3.658.1",
"@usebruno/common": "0.1.0", "@usebruno/common": "0.1.0",
"@usebruno/js": "0.12.0", "@usebruno/js": "0.12.0",
"@usebruno/lang": "0.12.0", "@usebruno/lang": "0.12.0",
@ -30,7 +30,7 @@
"@usebruno/schema": "0.7.0", "@usebruno/schema": "0.7.0",
"about-window": "^1.15.2", "about-window": "^1.15.2",
"aws4-axios": "^3.3.0", "aws4-axios": "^3.3.0",
"axios": "^1.5.1", "axios": "1.7.5",
"chai": "^4.3.7", "chai": "^4.3.7",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"content-disposition": "^0.5.4", "content-disposition": "^0.5.4",
@ -48,7 +48,6 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"is-valid-path": "^0.1.1", "is-valid-path": "^0.1.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"nanoid": "3.3.4", "nanoid": "3.3.4",
@ -56,7 +55,7 @@
"socks-proxy-agent": "^8.0.2", "socks-proxy-agent": "^8.0.2",
"tough-cookie": "^4.1.3", "tough-cookie": "^4.1.3",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vm2": "^3.9.13", "@usebruno/vm2": "^3.9.13",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"optionalDependencies": { "optionalDependencies": {
@ -64,6 +63,6 @@
}, },
"devDependencies": { "devDependencies": {
"electron": "31.2.1", "electron": "31.2.1",
"electron-builder": "23.0.2" "electron-builder": "25.1.8"
} }
} }

View File

@ -65,14 +65,20 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
try { try {
const dirPath = path.join(collectionLocation, collectionFolderName); const dirPath = path.join(collectionLocation, collectionFolderName);
if (fs.existsSync(dirPath)) { if (fs.existsSync(dirPath)) {
throw new Error(`collection: ${dirPath} already exists`); const files = fs.readdirSync(dirPath);
if (files.length > 0) {
throw new Error(`collection: ${dirPath} already exists and is not empty`);
}
} }
if (!isValidPathname(dirPath)) { if (!isValidPathname(dirPath)) {
throw new Error(`collection: invalid pathname - ${dir}`); throw new Error(`collection: invalid pathname - ${dir}`);
} }
await createDirectory(dirPath); if (!fs.existsSync(dirPath)) {
await createDirectory(dirPath);
}
const uid = generateUidBasedOnHash(dirPath); const uid = generateUidBasedOnHash(dirPath);
const brunoConfig = { const brunoConfig = {

View File

@ -233,6 +233,10 @@ const configureRequest = async (
); );
request.httpAgent = new HttpProxyAgent(proxyUri); request.httpAgent = new HttpProxyAgent(proxyUri);
} }
} else {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
});
} }
} else if (proxyMode === 'system') { } else if (proxyMode === 'system') {
const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables(); const { http_proxy, https_proxy, no_proxy } = preferencesUtil.getSystemProxyEnvVariables();
@ -257,6 +261,10 @@ const configureRequest = async (
} catch (error) { } catch (error) {
throw new Error('Invalid system https_proxy'); throw new Error('Invalid system https_proxy');
} }
} else {
request.httpsAgent = new https.Agent({
...httpsAgentRequestFields
});
} }
} else if (Object.keys(httpsAgentRequestFields).length > 0) { } else if (Object.keys(httpsAgentRequestFields).length > 0) {
request.httpsAgent = new https.Agent({ request.httpsAgent = new https.Agent({
@ -734,7 +742,7 @@ const registerNetworkIpc = (mainWindow) => {
const collectionRoot = get(collection, 'root', {}); const collectionRoot = get(collection, 'root', {});
const _request = collectionRoot?.request; const _request = collectionRoot?.request;
const request = prepareCollectionRequest(_request, collectionRoot, collectionPath); const request = prepareCollectionRequest(_request, collection, collectionPath);
request.__bruno__executionMode = 'standalone'; request.__bruno__executionMode = 'standalone';
const envVars = getEnvVars(environment); const envVars = getEnvVars(environment);
const processEnvVars = getProcessEnvVars(collectionUid); const processEnvVars = getProcessEnvVars(collectionUid);

View File

@ -1,7 +1,8 @@
const { get, each } = require('lodash'); const { get, each } = require('lodash');
const { setAuthHeaders } = require('./prepare-request'); const { setAuthHeaders } = require('./prepare-request');
const prepareCollectionRequest = (request, collectionRoot) => { const prepareCollectionRequest = (request, collection) => {
const collectionRoot = get(collection, 'root', {});
const headers = {}; const headers = {};
let contentTypeDefined = false; let contentTypeDefined = false;
let url = request.url; let url = request.url;
@ -34,6 +35,8 @@ const prepareCollectionRequest = (request, collectionRoot) => {
}; };
axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot); axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot);
axiosRequest.globalEnvironmentVariables = collection?.globalEnvironmentVariables;
if (request.script) { if (request.script) {
axiosRequest.script = request.script; axiosRequest.script = request.script;

View File

@ -19,10 +19,10 @@
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"postcss": "^8.4.18", "postcss": "8.4.47",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"rollup":"3.29.4", "rollup":"3.29.5",
"rollup-plugin-dts": "^5.0.0", "rollup-plugin-dts": "^5.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
@ -34,6 +34,6 @@
"markdown-it": "^13.0.1" "markdown-it": "^13.0.1"
}, },
"overrides": { "overrides": {
"rollup":"3.29.4" "rollup":"3.29.5"
} }
} }

View File

@ -8,7 +8,7 @@
"package.json" "package.json"
], ],
"peerDependencies": { "peerDependencies": {
"@n8n/vm2": "^3.9.23" "@usebruno/vm2": "^3.9.13"
}, },
"scripts": { "scripts": {
"test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js", "test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
@ -20,7 +20,7 @@
"ajv": "^8.12.0", "ajv": "^8.12.0",
"ajv-formats": "^2.1.1", "ajv-formats": "^2.1.1",
"atob": "^2.1.2", "atob": "^2.1.2",
"axios": "^1.5.1", "axios": "1.7.5",
"btoa": "^1.2.1", "btoa": "^1.2.1",
"chai": "^4.3.7", "chai": "^4.3.7",
"chai-string": "^1.5.0", "chai-string": "^1.5.0",
@ -38,16 +38,10 @@
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "3.2.5", "rollup": "3.29.5",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-polyfill-node": "^0.13.0",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"stream": "^0.0.2", "stream": "^0.0.2",
"terser": "^5.31.1",
"uglify-js": "^3.18.0",
"util": "^0.12.5" "util": "^0.12.5"
} }
} }

View File

@ -1,4 +1,4 @@
const { NodeVM } = require('vm2'); const { NodeVM } = require('@usebruno/vm2');
const path = require('path'); const path = require('path');
const http = require('http'); const http = require('http');
const https = require('https'); const https = require('https');

View File

@ -1,4 +1,4 @@
const { NodeVM } = require('vm2'); const { NodeVM } = require('@usebruno/vm2');
const chai = require('chai'); const chai = require('chai');
const path = require('path'); const path = require('path');
const http = require('http'); const http = require('http');

View File

@ -1,17 +1,19 @@
const { marshallToVm } = require('../utils'); const { marshallToVm } = require('../utils');
const addBrunoResponseShimToContext = (vm, res) => { const addBrunoResponseShimToContext = (vm, res) => {
const resObject = vm.newObject(); let resFn = vm.newFunction('res', function (exprStr) {
return marshallToVm(res(vm.dump(exprStr)), vm);
});
const status = marshallToVm(res?.status, vm); const status = marshallToVm(res?.status, vm);
const headers = marshallToVm(res?.headers, vm); const headers = marshallToVm(res?.headers, vm);
const body = marshallToVm(res?.body, vm); const body = marshallToVm(res?.body, vm);
const responseTime = marshallToVm(res?.responseTime, vm); const responseTime = marshallToVm(res?.responseTime, vm);
vm.setProp(resObject, 'status', status); vm.setProp(resFn, 'status', status);
vm.setProp(resObject, 'headers', headers); vm.setProp(resFn, 'headers', headers);
vm.setProp(resObject, 'body', body); vm.setProp(resFn, 'body', body);
vm.setProp(resObject, 'responseTime', responseTime); vm.setProp(resFn, 'responseTime', responseTime);
status.dispose(); status.dispose();
headers.dispose(); headers.dispose();
@ -21,41 +23,41 @@ const addBrunoResponseShimToContext = (vm, res) => {
let getStatus = vm.newFunction('getStatus', function () { let getStatus = vm.newFunction('getStatus', function () {
return marshallToVm(res.getStatus(), vm); return marshallToVm(res.getStatus(), vm);
}); });
vm.setProp(resObject, 'getStatus', getStatus); vm.setProp(resFn, 'getStatus', getStatus);
getStatus.dispose(); getStatus.dispose();
let getHeader = vm.newFunction('getHeader', function (name) { let getHeader = vm.newFunction('getHeader', function (name) {
return marshallToVm(res.getHeader(vm.dump(name)), vm); return marshallToVm(res.getHeader(vm.dump(name)), vm);
}); });
vm.setProp(resObject, 'getHeader', getHeader); vm.setProp(resFn, 'getHeader', getHeader);
getHeader.dispose(); getHeader.dispose();
let getHeaders = vm.newFunction('getHeaders', function () { let getHeaders = vm.newFunction('getHeaders', function () {
return marshallToVm(res.getHeaders(), vm); return marshallToVm(res.getHeaders(), vm);
}); });
vm.setProp(resObject, 'getHeaders', getHeaders); vm.setProp(resFn, 'getHeaders', getHeaders);
getHeaders.dispose(); getHeaders.dispose();
let getBody = vm.newFunction('getBody', function () { let getBody = vm.newFunction('getBody', function () {
return marshallToVm(res.getBody(), vm); return marshallToVm(res.getBody(), vm);
}); });
vm.setProp(resObject, 'getBody', getBody); vm.setProp(resFn, 'getBody', getBody);
getBody.dispose(); getBody.dispose();
let getResponseTime = vm.newFunction('getResponseTime', function () { let getResponseTime = vm.newFunction('getResponseTime', function () {
return marshallToVm(res.getResponseTime(), vm); return marshallToVm(res.getResponseTime(), vm);
}); });
vm.setProp(resObject, 'getResponseTime', getResponseTime); vm.setProp(resFn, 'getResponseTime', getResponseTime);
getResponseTime.dispose(); getResponseTime.dispose();
let setBody = vm.newFunction('setBody', function (data) { let setBody = vm.newFunction('setBody', function (data) {
res.setBody(vm.dump(data)); res.setBody(vm.dump(data));
}); });
vm.setProp(resObject, 'setBody', setBody); vm.setProp(resFn, 'setBody', setBody);
setBody.dispose(); setBody.dispose();
vm.setProp(vm.global, 'res', resObject); vm.setProp(vm.global, 'res', resFn);
resObject.dispose(); resFn.dispose();
}; };
module.exports = addBrunoResponseShimToContext; module.exports = addBrunoResponseShimToContext;

View File

@ -21,13 +21,13 @@
"@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^9.0.2", "@rollup/plugin-typescript": "^9.0.2",
"rollup":"3.29.4", "rollup":"3.29.5",
"rollup-plugin-dts": "^5.0.0", "rollup-plugin-dts": "^5.0.0",
"rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"typescript": "^4.8.4" "typescript": "^4.8.4"
}, },
"overrides": { "overrides": {
"rollup":"3.29.4" "rollup":"3.29.5"
} }
} }

View File

@ -25,9 +25,7 @@ tests {
const data = res.getBody(); const data = res.getBody();
expect(res.getBody()).to.eql({ expect(res.getBody()).to.eql({
"hello": { "hello": {
"world": [ "world": ["bruno"]
"bruno"
]
} }
}); });
}); });

View File

@ -18,13 +18,13 @@
}, },
"homepage": "https://github.com/usebruno/bruno-testbench#readme", "homepage": "https://github.com/usebruno/bruno-testbench#readme",
"dependencies": { "dependencies": {
"axios": "^1.5.1", "axios": "1.7.5",
"body-parser": "^1.20.0", "body-parser": "1.20.3",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.1", "express": "4.21.1",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-xml-bodyparser": "^0.3.0", "fast-xml-parser": "^4.5.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",

View File

@ -1,22 +1,21 @@
const express = require('express'); const express = require('express');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const xmlparser = require('express-xml-bodyparser');
const cors = require('cors'); const cors = require('cors');
const multer = require('multer'); const multer = require('multer');
const authRouter = require('./auth');
const echoRouter = require('./echo');
const xmlParser = require('./utils/xmlParser');
const app = new express(); const app = new express();
const port = process.env.PORT || 8080; const port = process.env.PORT || 8080;
const upload = multer(); const upload = multer();
app.use(cors()); app.use(cors());
app.use(xmlparser()); app.use(xmlParser());
app.use(bodyParser.text()); app.use(bodyParser.text());
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
const authRouter = require('./auth');
const echoRouter = require('./echo');
app.use('/api/auth', authRouter); app.use('/api/auth', authRouter);
app.use('/api/echo', echoRouter); app.use('/api/echo', echoRouter);

View File

@ -0,0 +1,30 @@
const { XMLParser } = require('fast-xml-parser');
const xmlParser = () => {
const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
});
return (req, res, next) => {
if (req.is('application/xml') || req.is('text/xml')) {
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
try {
req.body = parser.parse(data);
next();
} catch (err) {
res.status(400).send('Invalid XML');
}
});
} else {
next();
}
};
};
module.exports = xmlParser;