Merge branch 'main' of github.com:usebruno/bruno

This commit is contained in:
Anoop M D 2024-02-23 23:42:45 +05:30
commit 064281d438
27 changed files with 874 additions and 458 deletions

1051
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,6 @@
"strip-json-comments": "^5.0.1",
"styled-components": "^5.3.3",
"system": "^2.0.1",
"tailwindcss": "^2.2.19",
"url": "^0.11.3",
"xml-formatter": "^3.5.0",
"yargs-parser": "^21.1.1",
@ -82,6 +81,7 @@
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/runtime": "^7.16.3",
"autoprefixer": "^10.4.17",
"babel-loader": "^8.2.3",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
@ -89,8 +89,10 @@
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.5",
"postcss": "^8.4.35",
"prettier": "^2.7.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.4.1",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

@ -11,7 +11,7 @@ import StyledWrapper from './StyledWrapper';
const Docs = ({ collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const docs = get(collection, 'root.docs', '');
const preferences = useSelector((state) => state.app.preferences);
@ -40,7 +40,7 @@ const Docs = ({ collection }) => {
{isEditing ? (
<CodeEditor
collection={collection}
theme={storedTheme}
theme={displayedTheme}
value={docs || ''}
onEdit={onEdit}
onSave={onSave}

View File

@ -12,7 +12,7 @@ const Script = ({ collection }) => {
const requestScript = get(collection, 'root.request.script.req', '');
const responseScript = get(collection, 'root.request.script.res', '');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onRequestScriptEdit = (value) => {
@ -44,7 +44,7 @@ const Script = ({ collection }) => {
<CodeEditor
collection={collection}
value={requestScript || ''}
theme={storedTheme}
theme={displayedTheme}
onEdit={onRequestScriptEdit}
mode="javascript"
onSave={handleSave}
@ -56,7 +56,7 @@ const Script = ({ collection }) => {
<CodeEditor
collection={collection}
value={responseScript || ''}
theme={storedTheme}
theme={displayedTheme}
onEdit={onResponseScriptEdit}
mode="javascript"
onSave={handleSave}

View File

@ -11,7 +11,7 @@ const Tests = ({ collection }) => {
const dispatch = useDispatch();
const tests = get(collection, 'root.request.tests', '');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
@ -30,7 +30,7 @@ const Tests = ({ collection }) => {
<CodeEditor
collection={collection}
value={tests || ''}
theme={storedTheme}
theme={displayedTheme}
onEdit={onEdit}
mode="javascript"
onSave={handleSave}

View File

@ -11,7 +11,7 @@ import StyledWrapper from './StyledWrapper';
const Documentation = ({ item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const [isEditing, setIsEditing] = useState(false);
const docs = item.draft ? get(item, 'draft.request.docs') : get(item, 'request.docs');
const preferences = useSelector((state) => state.app.preferences);
@ -45,7 +45,7 @@ const Documentation = ({ item, collection }) => {
{isEditing ? (
<CodeEditor
collection={collection}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
value={docs || ''}
onEdit={onEdit}

View File

@ -10,7 +10,7 @@ import StyledWrapper from './StyledWrapper';
const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch();
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
@ -31,7 +31,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
<CodeEditor
collection={collection}
value={variables || ''}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit}
mode="javascript"

View File

@ -13,7 +13,7 @@ const RequestBody = ({ item, collection }) => {
const dispatch = useDispatch();
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
@ -48,7 +48,7 @@ const RequestBody = ({ item, collection }) => {
<StyledWrapper className="w-full">
<CodeEditor
collection={collection}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
value={bodyContent[bodyMode] || ''}
onEdit={onEdit}

View File

@ -12,7 +12,7 @@ const Script = ({ item, collection }) => {
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onRequestScriptEdit = (value) => {
@ -45,7 +45,7 @@ const Script = ({ item, collection }) => {
<CodeEditor
collection={collection}
value={requestScript || ''}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onRequestScriptEdit}
mode="javascript"
@ -58,7 +58,7 @@ const Script = ({ item, collection }) => {
<CodeEditor
collection={collection}
value={responseScript || ''}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onResponseScriptEdit}
mode="javascript"

View File

@ -11,7 +11,7 @@ const Tests = ({ item, collection }) => {
const dispatch = useDispatch();
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => {
@ -32,7 +32,7 @@ const Tests = ({ item, collection }) => {
<CodeEditor
collection={collection}
value={tests || ''}
theme={storedTheme}
theme={displayedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit}
mode="javascript"

View File

@ -19,7 +19,7 @@ const QueryResultPreview = ({
collection,
mode,
disableRunEventListener,
storedTheme
displayedTheme
}) => {
const preferences = useSelector((state) => state.app.preferences);
const dispatch = useDispatch();
@ -71,7 +71,7 @@ const QueryResultPreview = ({
<CodeEditor
collection={collection}
font={get(preferences, 'font.codeFont', 'default')}
theme={storedTheme}
theme={displayedTheme}
onRun={onRun}
value={formattedData}
mode={mode}

View File

@ -51,7 +51,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
const mode = getCodeMirrorModeBasedOnContentType(contentType, data);
const [filter, setFilter] = useState(null);
const formattedData = formatResponse(data, mode, filter);
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const debouncedResultFilterOnChange = debounce((e) => {
setFilter(e.target.value);
@ -132,7 +132,7 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
collection={collection}
allowedPreviewModes={allowedPreviewModes}
disableRunEventListener={disableRunEventListener}
storedTheme={storedTheme}
displayedTheme={displayedTheme}
/>
{queryFilterEnabled && <QueryResultFilter onChange={debouncedResultFilterOnChange} mode={mode} />}
</>

View File

@ -22,13 +22,23 @@ const getRelativePath = (fullPath, pathname) => {
export default function RunnerResults({ collection }) {
const dispatch = useDispatch();
const listWrapperRef = useRef();
const [selectedItem, setSelectedItem] = useState(null);
// ref for the runner output body
const runnerBodyRef = useRef();
const autoScrollRunnerBody = () => {
if (runnerBodyRef?.current) {
// mimicks the native terminal scroll style
runnerBodyRef.current.scrollTo(0, 100000);
}
};
useEffect(() => {
if (!collection.runnerResult) {
setSelectedItem(null);
}
autoScrollRunnerBody();
}, [collection, setSelectedItem]);
const collectionCopy = cloneDeep(collection);
@ -67,11 +77,6 @@ export default function RunnerResults({ collection }) {
})
.filter(Boolean);
useEffect(() => {
if (listWrapperRef.current) {
listWrapperRef.current.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
}
}, [items]);
const runCollection = () => {
dispatch(runCollectionFolder(collection.uid, null, true));
};
@ -102,12 +107,11 @@ export default function RunnerResults({ collection }) {
if (!items || !items.length) {
return (
<StyledWrapper className="px-4">
<StyledWrapper className="px-4 pb-4">
<div className="font-medium mt-6 title flex items-center">
Runner
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
</div>
<div className="mt-6">
You have <span className="font-medium">{totalRequestsInCollection}</span> requests in this collection.
</div>
@ -124,21 +128,25 @@ export default function RunnerResults({ collection }) {
}
return (
<StyledWrapper ref={listWrapperRef} className="px-4 pb-4 flex flex-grow flex-col relative">
<div className="flex flex-row mt-6 mb-4">
<div className="font-medium title flex items-center">
<StyledWrapper className="px-4 pb-4 flex flex-grow flex-col relative">
<div className="flex flex-row">
<div className="font-medium my-6 title flex items-center">
Runner
<IconRun size={20} strokeWidth={1.5} className="ml-2" />
</div>
{runnerInfo.status !== 'ended' && runnerInfo.cancelTokenUid && (
<button className="btn ml-6 btn-sm btn-danger" onClick={cancelExecution}>
<button className="btn ml-6 my-4 btn-sm btn-danger" onClick={cancelExecution}>
Cancel Execution
</button>
)}
</div>
<div className="flex flex-1">
<div
className="flex flex-col overflow-y-auto"
ref={runnerBodyRef}
style={{ height: 'calc(100vh - 12rem)', maxHeight: 'calc(100vh - 12rem)' }}
>
<div className="flex flex-col flex-1">
<div className="py-2 font-medium test-summary">
<div className="pb-2 font-medium test-summary">
Total Requests: {items.length}, Passed: {passedRequests.length}, Failed: {failedRequests.length}
</div>
{items.map((item) => {

View File

@ -11,7 +11,7 @@ import { IconCopy } from '@tabler/icons';
import { findCollectionByItemUid } from '../../../../../../../utils/collections/index';
const CodeView = ({ language, item }) => {
const { storedTheme } = useTheme();
const { displayedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const { target, client, language: lang } = language;
const requestHeaders = item.draft ? get(item, 'draft.request.headers') : get(item, 'request.headers');
@ -45,7 +45,7 @@ const CodeView = ({ language, item }) => {
readOnly
value={snippet}
font={get(preferences, 'font.codeFont', 'default')}
theme={storedTheme}
theme={displayedTheme}
mode={lang}
/>
</StyledWrapper>

View File

@ -159,6 +159,33 @@ const GlobalStyle = createGlobalStyle`
}
}
// scrollbar styling
// the below media query target non-macos devices
// (macos scrollbar styling is the ideal style reference)
@media not all and (pointer: coarse) {
* {
scrollbar-width: thin;
scrollbar-color: ${(props) => props.theme.scrollbar.color};
}
*::-webkit-scrollbar {
width: 5px;
}
*::-webkit-scrollbar-track {
background: transparent;
border-radius: 5px;
}
*::-webkit-scrollbar-thumb {
background-color: ${(props) => props.theme.scrollbar.color};
border-radius: 14px;
border: 3px solid ${(props) => props.theme.scrollbar.color};
}
}
// codemirror
.CodeMirror {
.cm-variable-valid {

View File

@ -10,7 +10,6 @@ import ErrorBoundary from './ErrorBoundary';
import '../styles/app.scss';
import '../styles/globals.css';
import 'tailwindcss/dist/tailwind.min.css';
import 'codemirror/lib/codemirror.css';
import 'graphiql/graphiql.min.css';
import 'react-tooltip/dist/react-tooltip.css';

View File

@ -1,3 +1,7 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
:root {
--color-brand: #546de5;
--color-text: rgb(52 52 52);

View File

@ -238,6 +238,10 @@ const darkTheme = {
plainGrid: {
hoverBg: '#3D3D3D'
},
scrollbar: {
color: 'rgb(52 51 49)'
}
};

View File

@ -242,6 +242,10 @@ const lightTheme = {
plainGrid: {
hoverBg: '#f4f4f4'
},
scrollbar: {
color: 'rgb(152 151 149)'
}
};

View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
theme: {
extend: {}
},
plugins: []
};

View File

@ -28,7 +28,7 @@ class EnvironmentSecretsStore {
}
isValidValue(val) {
return val && typeof val === 'string' && val.length > 0;
return typeof val === 'string' && val.length >= 0;
}
storeEnvSecrets(collectionPathname, environment) {

View File

@ -48,7 +48,7 @@ function safeStorageDecrypt(str) {
}
function encryptString(str) {
if (!str || typeof str !== 'string' || str.length === 0) {
if (typeof str !== 'string') {
throw new Error('Encrypt failed: invalid string');
}

View File

@ -16,6 +16,7 @@
"dependencies": {
"@usebruno/query": "0.1.0",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"atob": "^2.1.2",
"axios": "^1.5.1",
"btoa": "^1.2.1",
@ -28,7 +29,7 @@
"moment": "^2.29.4",
"nanoid": "3.3.4",
"node-fetch": "2.*",
"uuid": "^9.0.0",
"node-vault": "^0.10.2"
"node-vault": "^0.10.2",
"uuid": "^9.0.0"
}
}

View File

@ -16,6 +16,7 @@ const { cleanJson } = require('../utils');
// Inbuilt Library Support
const ajv = require('ajv');
const addFormats = require('ajv-formats');
const atob = require('atob');
const btoa = require('btoa');
const lodash = require('lodash');
@ -102,6 +103,7 @@ class ScriptRuntime {
zlib,
// 3rd party libs
ajv,
'ajv-formats': addFormats,
atob,
btoa,
lodash,
@ -194,6 +196,7 @@ class ScriptRuntime {
zlib,
// 3rd party libs
ajv,
'ajv-formats': addFormats,
atob,
btoa,
lodash,

View File

@ -19,6 +19,7 @@ const { cleanJson } = require('../utils');
// Inbuilt Library Support
const ajv = require('ajv');
const addFormats = require('ajv-formats');
const atob = require('atob');
const btoa = require('btoa');
const lodash = require('lodash');
@ -120,6 +121,7 @@ class TestRuntime {
zlib,
// 3rd party libs
ajv,
'ajv-formats': addFormats,
btoa,
atob,
lodash,

View File

@ -1,5 +1,6 @@
const { describe, it, expect } = require('@jest/globals');
const TestRuntime = require('../src/runtime/test-runtime');
const ScriptRuntime = require('../src/runtime/script-runtime');
describe('runtime', () => {
describe('test-runtime', () => {
@ -49,5 +50,129 @@ describe('runtime', () => {
{ description: 'async test', status: 'pass' }
]);
});
it('should have ajv and ajv-formats dependencies available', async () => {
const testFile = `
const Ajv = require('ajv');
const addFormats = require("ajv-formats");
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'string',
format: 'date-time'
};
const validate = ajv.compile(schema)
test('format valid', () => {
const valid = validate(new Date().toISOString())
expect(valid).to.be.true;
})
`;
const runtime = new TestRuntime();
const result = await runtime.runTests(
testFile,
{ ...baseRequest },
{ ...baseResponse },
{},
{},
'.',
null,
process.env
);
expect(result.results.map((el) => ({ description: el.description, status: el.status }))).toEqual([
{ description: 'format valid', status: 'pass' }
]);
});
});
describe('script-runtime', () => {
describe('run-request-script', () => {
const baseRequest = {
method: 'GET',
url: 'http://localhost:3000/',
headers: {},
data: undefined
};
it('should have ajv and ajv-formats dependencies available', async () => {
const script = `
const Ajv = require('ajv');
const addFormats = require("ajv-formats");
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'string',
format: 'date-time'
};
const validate = ajv.compile(schema)
bru.setVar('validation', validate(new Date().toISOString()))
`;
const runtime = new ScriptRuntime();
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
expect(result.collectionVariables.validation).toBeTruthy();
});
});
describe('run-response-script', () => {
const baseRequest = {
method: 'GET',
url: 'http://localhost:3000/',
headers: {},
data: undefined
};
const baseResponse = {
status: 200,
statusText: 'OK',
data: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
};
it('should have ajv and ajv-formats dependencies available', async () => {
const script = `
const Ajv = require('ajv');
const addFormats = require("ajv-formats");
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'string',
format: 'date-time'
};
const validate = ajv.compile(schema)
bru.setVar('validation', validate(new Date().toISOString()))
`;
const runtime = new ScriptRuntime();
const result = await runtime.runResponseScript(
script,
{ ...baseRequest },
{ ...baseResponse },
{},
{},
'.',
null,
process.env
);
expect(result.collectionVariables.validation).toBeTruthy();
});
});
});
});