mirror of
https://github.com/usebruno/bruno.git
synced 2025-08-14 03:19:16 +02:00
Feat/safe mode quickjs (#2848)
Safe Mode Sandbox using QuickJS Co-authored-by: Anoop M D <anoop.md1421@gmail.com> Co-authored-by: lohit <lohit.jiddimani@gmail.com>
This commit is contained in:
1
packages/bruno-js/.gitignore
vendored
Normal file
1
packages/bruno-js/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
src/sandbox/bundle-browser-rollup.js
|
@ -11,7 +11,8 @@
|
||||
"@n8n/vm2": "^3.9.23"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest --testPathIgnorePatterns test.js"
|
||||
"test": "node --experimental-vm-modules $(npx --no-install which jest) --testPathIgnorePatterns test.js",
|
||||
"sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@usebruno/common": "0.1.0",
|
||||
@ -24,12 +25,29 @@
|
||||
"chai": "^4.3.7",
|
||||
"chai-string": "^1.5.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"crypto-js-3.1.9-1": "npm:crypto-js@^3.1.9-1",
|
||||
"json-query": "^2.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "3.3.4",
|
||||
"node-fetch": "2.*",
|
||||
"node-fetch": "^2.7.0",
|
||||
"node-vault": "^0.10.2",
|
||||
"path": "^0.12.7",
|
||||
"quickjs-emscripten": "^0.29.2",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^23.0.2",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"rollup": "3.2.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",
|
||||
"stream": "^0.0.2",
|
||||
"terser": "^5.31.1",
|
||||
"uglify-js": "^3.18.0",
|
||||
"util": "^0.12.5"
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,10 @@ class Bru {
|
||||
setNextRequest(nextRequest) {
|
||||
this.nextRequest = nextRequest;
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bru;
|
||||
|
@ -5,6 +5,7 @@ const Bru = require('../bru');
|
||||
const BrunoRequest = require('../bruno-request');
|
||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
const { interpolateString } = require('../interpolate-string');
|
||||
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
||||
|
||||
const { expect } = chai;
|
||||
chai.use(require('chai-string'));
|
||||
@ -161,7 +162,31 @@ const isUnaryOperator = (operator) => {
|
||||
return unaryOperators.includes(operator);
|
||||
};
|
||||
|
||||
const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
script: literal,
|
||||
context,
|
||||
scriptType: 'template-literal'
|
||||
});
|
||||
}
|
||||
|
||||
return evaluateJsTemplateLiteral(literal, context);
|
||||
};
|
||||
|
||||
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
script: expr,
|
||||
context,
|
||||
scriptType: 'expression'
|
||||
});
|
||||
}
|
||||
|
||||
return evaluateJsExpression(expr, context);
|
||||
};
|
||||
|
||||
const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
|
||||
if (isUnaryOperator(operator)) {
|
||||
return;
|
||||
}
|
||||
@ -181,13 +206,17 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
|
||||
return rhsOperand
|
||||
.split(',')
|
||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||
.map((v) =>
|
||||
evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(v.trim(), interpolationContext), context, runtime)
|
||||
);
|
||||
}
|
||||
|
||||
if (operator === 'between') {
|
||||
const [lhs, rhs] = rhsOperand
|
||||
.split(',')
|
||||
.map((v) => evaluateJsTemplateLiteral(interpolateString(v.trim(), interpolationContext), context));
|
||||
.map((v) =>
|
||||
evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(v.trim(), interpolationContext), context, runtime)
|
||||
);
|
||||
return [lhs, rhs];
|
||||
}
|
||||
|
||||
@ -200,10 +229,14 @@ const evaluateRhsOperand = (rhsOperand, operator, context) => {
|
||||
return interpolateString(rhsOperand, interpolationContext);
|
||||
}
|
||||
|
||||
return evaluateJsTemplateLiteral(interpolateString(rhsOperand, interpolationContext), context);
|
||||
return evaluateJsTemplateLiteralBasedOnRuntime(interpolateString(rhsOperand, interpolationContext), context, runtime);
|
||||
};
|
||||
|
||||
class AssertRuntime {
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
}
|
||||
|
||||
runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars) {
|
||||
const requestVariables = request?.requestVariables || {};
|
||||
const enabledAssertions = _.filter(assertions, (a) => a.enabled);
|
||||
@ -238,8 +271,8 @@ class AssertRuntime {
|
||||
const { operator, value: rhsOperand } = parseAssertionOperator(rhsExpr);
|
||||
|
||||
try {
|
||||
const lhs = evaluateJsExpression(lhsExpr, context);
|
||||
const rhs = evaluateRhsOperand(rhsOperand, operator, context);
|
||||
const lhs = evaluateJsExpressionBasedOnRuntime(lhsExpr, context, this.runtime);
|
||||
const rhs = evaluateRhsOperand(rhsOperand, operator, context, this.runtime);
|
||||
|
||||
switch (operator) {
|
||||
case 'eq':
|
||||
|
@ -28,9 +28,12 @@ const fetch = require('node-fetch');
|
||||
const chai = require('chai');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const NodeVault = require('node-vault');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
|
||||
class ScriptRuntime {
|
||||
constructor() {}
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
}
|
||||
|
||||
// This approach is getting out of hand
|
||||
// Need to refactor this to use a single arg (object) instead of 7
|
||||
@ -86,6 +89,22 @@ class ScriptRuntime {
|
||||
};
|
||||
}
|
||||
|
||||
if (this.runtime === 'quickjs') {
|
||||
await executeQuickJsVmAsync({
|
||||
script: script,
|
||||
context: context,
|
||||
collectionPath
|
||||
});
|
||||
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
runtimeVariables: cleanJson(runtimeVariables),
|
||||
nextRequestName: bru.nextRequest
|
||||
};
|
||||
}
|
||||
|
||||
// default runtime is vm2
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
@ -123,6 +142,7 @@ class ScriptRuntime {
|
||||
});
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
|
||||
return {
|
||||
request,
|
||||
envVariables: cleanJson(envVariables),
|
||||
@ -176,10 +196,27 @@ class ScriptRuntime {
|
||||
log: customLogger('log'),
|
||||
info: customLogger('info'),
|
||||
warn: customLogger('warn'),
|
||||
error: customLogger('error')
|
||||
error: customLogger('error'),
|
||||
debug: customLogger('debug')
|
||||
};
|
||||
}
|
||||
|
||||
if (this.runtime === 'quickjs') {
|
||||
await executeQuickJsVmAsync({
|
||||
script: script,
|
||||
context: context,
|
||||
collectionPath
|
||||
});
|
||||
|
||||
return {
|
||||
response,
|
||||
envVariables: cleanJson(envVariables),
|
||||
runtimeVariables: cleanJson(runtimeVariables),
|
||||
nextRequestName: bru.nextRequest
|
||||
};
|
||||
}
|
||||
|
||||
// default runtime is vm2
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
|
@ -15,7 +15,7 @@ const BrunoRequest = require('../bruno-request');
|
||||
const BrunoResponse = require('../bruno-response');
|
||||
const Test = require('../test');
|
||||
const TestResults = require('../test-results');
|
||||
const { cleanJson } = require('../utils');
|
||||
const { cleanJson, appendAwaitToTestFunc } = require('../utils');
|
||||
|
||||
// Inbuilt Library Support
|
||||
const ajv = require('ajv');
|
||||
@ -30,9 +30,12 @@ const axios = require('axios');
|
||||
const fetch = require('node-fetch');
|
||||
const CryptoJS = require('crypto-js');
|
||||
const NodeVault = require('node-vault');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
|
||||
class TestRuntime {
|
||||
constructor() {}
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
}
|
||||
|
||||
async runTests(
|
||||
testsFile,
|
||||
@ -81,6 +84,9 @@ class TestRuntime {
|
||||
};
|
||||
}
|
||||
|
||||
// add 'await' prefix to the test function calls
|
||||
testsFile = appendAwaitToTestFunc(testsFile);
|
||||
|
||||
const context = {
|
||||
test,
|
||||
bru,
|
||||
@ -101,48 +107,56 @@ class TestRuntime {
|
||||
log: customLogger('log'),
|
||||
info: customLogger('info'),
|
||||
warn: customLogger('warn'),
|
||||
debug: customLogger('debug'),
|
||||
error: customLogger('error')
|
||||
};
|
||||
}
|
||||
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
btoa,
|
||||
atob,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
if (this.runtime === 'quickjs') {
|
||||
await executeQuickJsVmAsync({
|
||||
script: testsFile,
|
||||
context: context
|
||||
});
|
||||
} else {
|
||||
// default runtime is vm2
|
||||
const vm = new NodeVM({
|
||||
sandbox: context,
|
||||
require: {
|
||||
context: 'sandbox',
|
||||
external: true,
|
||||
root: [collectionPath, ...additionalContextRootsAbsolute],
|
||||
mock: {
|
||||
// node libs
|
||||
path,
|
||||
stream,
|
||||
util,
|
||||
url,
|
||||
http,
|
||||
https,
|
||||
punycode,
|
||||
zlib,
|
||||
// 3rd party libs
|
||||
ajv,
|
||||
'ajv-formats': addFormats,
|
||||
btoa,
|
||||
atob,
|
||||
lodash,
|
||||
moment,
|
||||
uuid,
|
||||
nanoid,
|
||||
axios,
|
||||
chai,
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
});
|
||||
const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
|
||||
await asyncVM();
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
|
@ -3,7 +3,38 @@ const Bru = require('../bru');
|
||||
const BrunoRequest = require('../bruno-request');
|
||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||
|
||||
const { executeQuickJsVm } = require('../sandbox/quickjs');
|
||||
|
||||
const evaluateJsTemplateLiteralBasedOnRuntime = (literal, context, runtime) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
script: literal,
|
||||
context,
|
||||
scriptType: 'template-literal'
|
||||
});
|
||||
}
|
||||
|
||||
return evaluateJsTemplateLiteral(literal, context);
|
||||
};
|
||||
|
||||
const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
|
||||
if (runtime === 'quickjs') {
|
||||
return executeQuickJsVm({
|
||||
script: expr,
|
||||
context,
|
||||
scriptType: 'expression'
|
||||
});
|
||||
}
|
||||
|
||||
return evaluateJsExpression(expr, context);
|
||||
};
|
||||
|
||||
class VarsRuntime {
|
||||
constructor(props) {
|
||||
this.runtime = props?.runtime || 'vm2';
|
||||
this.mode = props?.mode || 'developer';
|
||||
}
|
||||
|
||||
runPreRequestVars(vars, request, envVariables, runtimeVariables, collectionPath, processEnvVars) {
|
||||
if (!request?.requestVariables) {
|
||||
request.requestVariables = {};
|
||||
@ -28,7 +59,7 @@ class VarsRuntime {
|
||||
};
|
||||
|
||||
_.each(enabledVars, (v) => {
|
||||
const value = evaluateJsTemplateLiteral(v.value, context);
|
||||
const value = evaluateJsTemplateLiteralBasedOnRuntime(v.value, context, this.runtime);
|
||||
request?.requestVariables && (request.requestVariables[v.name] = value);
|
||||
});
|
||||
}
|
||||
@ -59,7 +90,7 @@ class VarsRuntime {
|
||||
const errors = new Map();
|
||||
_.each(enabledVars, (v) => {
|
||||
try {
|
||||
const value = evaluateJsExpression(v.value, context);
|
||||
const value = evaluateJsExpressionBasedOnRuntime(v.value, context, this.runtime);
|
||||
bru.setVar(v.name, value);
|
||||
} catch (error) {
|
||||
errors.set(v.name, error);
|
||||
|
88
packages/bruno-js/src/sandbox/bundle-libraries.js
Normal file
88
packages/bruno-js/src/sandbox/bundle-libraries.js
Normal file
@ -0,0 +1,88 @@
|
||||
const rollup = require('rollup');
|
||||
const { nodeResolve } = require('@rollup/plugin-node-resolve');
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const fs = require('fs');
|
||||
const { terser } = require('rollup-plugin-terser');
|
||||
|
||||
const bundleLibraries = async () => {
|
||||
const codeScript = `
|
||||
import { expect, assert } from 'chai';
|
||||
import { Buffer } from "buffer";
|
||||
import moment from "moment";
|
||||
import btoa from "btoa";
|
||||
import atob from "atob";
|
||||
import * as CryptoJS from "crypto-js-3.1.9-1";
|
||||
globalThis.expect = expect;
|
||||
globalThis.assert = assert;
|
||||
globalThis.moment = moment;
|
||||
globalThis.btoa = btoa;
|
||||
globalThis.atob = atob;
|
||||
globalThis.Buffer = Buffer;
|
||||
globalThis.CryptoJS = CryptoJS;
|
||||
globalThis.requireObject = {
|
||||
...(globalThis.requireObject || {}),
|
||||
'chai': { expect, assert },
|
||||
'moment': moment,
|
||||
'buffer': { Buffer },
|
||||
'btoa': btoa,
|
||||
'atob': atob,
|
||||
'crypto-js': CryptoJS
|
||||
};
|
||||
`;
|
||||
|
||||
const config = {
|
||||
input: {
|
||||
input: 'inline-code',
|
||||
plugins: [
|
||||
{
|
||||
name: 'inline-code-plugin',
|
||||
resolveId(id) {
|
||||
if (id === 'inline-code') {
|
||||
return id;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
load(id) {
|
||||
if (id === 'inline-code') {
|
||||
return codeScript;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
nodeResolve({
|
||||
preferBuiltins: false,
|
||||
browser: false
|
||||
}),
|
||||
commonjs(),
|
||||
terser()
|
||||
]
|
||||
},
|
||||
output: {
|
||||
file: './src/sandbox/bundle-browser-rollup.js',
|
||||
format: 'iife',
|
||||
name: 'MyBundle'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const bundle = await rollup.rollup(config.input);
|
||||
const { output } = await bundle.generate(config.output);
|
||||
fs.writeFileSync(
|
||||
'./src/sandbox/bundle-browser-rollup.js',
|
||||
`
|
||||
const getBundledCode = () => {
|
||||
return function(){
|
||||
${output?.map((o) => o.code).join('\n')}
|
||||
}()
|
||||
}
|
||||
module.exports = getBundledCode;
|
||||
`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error while bundling:', error);
|
||||
}
|
||||
};
|
||||
|
||||
bundleLibraries();
|
||||
|
||||
module.exports = bundleLibraries;
|
153
packages/bruno-js/src/sandbox/quickjs/index.js
Normal file
153
packages/bruno-js/src/sandbox/quickjs/index.js
Normal file
@ -0,0 +1,153 @@
|
||||
const addBruShimToContext = require('./shims/bru');
|
||||
const addBrunoRequestShimToContext = require('./shims/bruno-request');
|
||||
const addConsoleShimToContext = require('./shims/console');
|
||||
const addBrunoResponseShimToContext = require('./shims/bruno-response');
|
||||
const addTestShimToContext = require('./shims/test');
|
||||
const addLibraryShimsToContext = require('./shims/lib');
|
||||
const addLocalModuleLoaderShimToContext = require('./shims/local-module');
|
||||
const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscripten');
|
||||
|
||||
// execute `npm run sandbox:bundle-libraries` if the below file doesn't exist
|
||||
const getBundledCode = require('../bundle-browser-rollup');
|
||||
const addPathShimToContext = require('./shims/lib/path');
|
||||
|
||||
let QuickJSSyncContext;
|
||||
const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
|
||||
const getContext = (opts) => loader().then((mod) => (QuickJSSyncContext = mod.newContext(opts)));
|
||||
getContext();
|
||||
|
||||
const toNumber = (value) => {
|
||||
const num = Number(value);
|
||||
return Number.isInteger(num) ? parseInt(value, 10) : parseFloat(value);
|
||||
};
|
||||
|
||||
const executeQuickJsVm = ({ script: externalScript, context: externalContext, scriptType = 'template-literal' }) => {
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return Number(externalScript);
|
||||
}
|
||||
|
||||
const vm = QuickJSSyncContext;
|
||||
|
||||
try {
|
||||
const { bru, req, res } = externalContext;
|
||||
|
||||
bru && addBruShimToContext(vm, bru);
|
||||
req && addBrunoRequestShimToContext(vm, req);
|
||||
res && addBrunoResponseShimToContext(vm, res);
|
||||
|
||||
const templateLiteralText = `\`${externalScript}\`;`;
|
||||
const jsExpressionText = `${externalScript};`;
|
||||
|
||||
let scriptText = scriptType === 'template-literal' ? templateLiteralText : jsExpressionText;
|
||||
|
||||
const result = vm.evalCode(scriptText);
|
||||
if (result.error) {
|
||||
let e = vm.dump(result.error);
|
||||
result.error.dispose();
|
||||
return e;
|
||||
} else {
|
||||
let v = vm.dump(result.value);
|
||||
let vString = v.toString();
|
||||
result.value.dispose();
|
||||
return v;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
}
|
||||
};
|
||||
|
||||
const executeQuickJsVmAsync = async ({ script: externalScript, context: externalContext, collectionPath }) => {
|
||||
if (!isNaN(Number(externalScript))) {
|
||||
return toNumber(externalScript);
|
||||
}
|
||||
try {
|
||||
const module = await newQuickJSWASMModule();
|
||||
const vm = module.newContext();
|
||||
|
||||
const bundledCode = getBundledCode?.toString() || '';
|
||||
const moduleLoaderCode = function () {
|
||||
return `
|
||||
globalThis.require = (mod) => {
|
||||
let lib = globalThis.requireObject[mod];
|
||||
let isModuleAPath = (module) => (module?.startsWith('.') || module?.startsWith?.(bru.cwd()))
|
||||
if (lib) {
|
||||
return lib;
|
||||
}
|
||||
else if (isModuleAPath(mod)) {
|
||||
// fetch local module
|
||||
let localModuleCode = globalThis.__brunoLoadLocalModule(mod);
|
||||
|
||||
// compile local module as iife
|
||||
(function (){
|
||||
const initModuleExportsCode = "const module = { exports: {} };"
|
||||
const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;";
|
||||
const patchedRequire = ${`
|
||||
"\\n;" +
|
||||
"let require = (subModule) => isModuleAPath(subModule) ? globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule)) : globalThis.require(subModule)" +
|
||||
"\\n;"
|
||||
`}
|
||||
eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode);
|
||||
})();
|
||||
|
||||
// resolve module
|
||||
return globalThis.requireObject[mod];
|
||||
}
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
(${bundledCode})()
|
||||
${moduleLoaderCode()}
|
||||
`
|
||||
);
|
||||
|
||||
const { bru, req, res, test, __brunoTestResults, console: consoleFn } = externalContext;
|
||||
|
||||
bru && addBruShimToContext(vm, bru);
|
||||
req && addBrunoRequestShimToContext(vm, req);
|
||||
res && addBrunoResponseShimToContext(vm, res);
|
||||
consoleFn && addConsoleShimToContext(vm, consoleFn);
|
||||
addLocalModuleLoaderShimToContext(vm, collectionPath);
|
||||
addPathShimToContext(vm);
|
||||
|
||||
await addLibraryShimsToContext(vm);
|
||||
|
||||
test && __brunoTestResults && addTestShimToContext(vm, __brunoTestResults);
|
||||
|
||||
const script = `
|
||||
(async () => {
|
||||
const setTimeout = async(fn, timer) => {
|
||||
v = await bru.sleep(timer);
|
||||
fn.apply();
|
||||
}
|
||||
await bru.sleep(0);
|
||||
try {
|
||||
${externalScript}
|
||||
}
|
||||
catch(error) {
|
||||
console?.debug?.('quick-js:execution-end:with-error', error?.message);
|
||||
}
|
||||
return 'done';
|
||||
})()
|
||||
`;
|
||||
|
||||
const result = vm.evalCode(script);
|
||||
const promiseHandle = vm.unwrapResult(result);
|
||||
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
||||
promiseHandle.dispose();
|
||||
const resolvedHandle = vm.unwrapResult(resolvedResult);
|
||||
resolvedHandle.dispose();
|
||||
// vm.dispose();
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error('Error executing the script!', error);
|
||||
throw new Error(error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
executeQuickJsVm,
|
||||
executeQuickJsVmAsync
|
||||
};
|
81
packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Normal file
81
packages/bruno-js/src/sandbox/quickjs/shims/bru.js
Normal file
@ -0,0 +1,81 @@
|
||||
const { marshallToVm } = require('../utils');
|
||||
|
||||
const addBruShimToContext = (vm, bru) => {
|
||||
const bruObject = vm.newObject();
|
||||
|
||||
let cwd = vm.newFunction('cwd', function () {
|
||||
return marshallToVm(bru.cwd(), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'cwd', cwd);
|
||||
cwd.dispose();
|
||||
|
||||
let getEnvName = vm.newFunction('getEnvName', function () {
|
||||
return marshallToVm(bru.getEnvName(), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getEnvName', getEnvName);
|
||||
getEnvName.dispose();
|
||||
|
||||
let getProcessEnv = vm.newFunction('getProcessEnv', function (key) {
|
||||
return marshallToVm(bru.getProcessEnv(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getProcessEnv', getProcessEnv);
|
||||
getProcessEnv.dispose();
|
||||
|
||||
let getEnvVar = vm.newFunction('getEnvVar', function (key) {
|
||||
return marshallToVm(bru.getEnvVar(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getEnvVar', getEnvVar);
|
||||
getEnvVar.dispose();
|
||||
|
||||
let setEnvVar = vm.newFunction('setEnvVar', function (key, value) {
|
||||
bru.setEnvVar(vm.dump(key), vm.dump(value));
|
||||
});
|
||||
vm.setProp(bruObject, 'setEnvVar', setEnvVar);
|
||||
setEnvVar.dispose();
|
||||
|
||||
let getVar = vm.newFunction('getVar', function (key) {
|
||||
return marshallToVm(bru.getVar(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getVar', getVar);
|
||||
getVar.dispose();
|
||||
|
||||
let setVar = vm.newFunction('setVar', function (key, value) {
|
||||
bru.setVar(vm.dump(key), vm.dump(value));
|
||||
});
|
||||
vm.setProp(bruObject, 'setVar', setVar);
|
||||
setVar.dispose();
|
||||
|
||||
let setNextRequest = vm.newFunction('setNextRequest', function (nextRequest) {
|
||||
bru.setNextRequest(vm.dump(nextRequest));
|
||||
});
|
||||
vm.setProp(bruObject, 'setNextRequest', setNextRequest);
|
||||
setNextRequest.dispose();
|
||||
|
||||
let visualize = vm.newFunction('visualize', function (htmlString) {
|
||||
bru.visualize(vm.dump(htmlString));
|
||||
});
|
||||
vm.setProp(bruObject, 'visualize', visualize);
|
||||
visualize.dispose();
|
||||
|
||||
let getSecretVar = vm.newFunction('getSecretVar', function (key) {
|
||||
return marshallToVm(bru.getSecretVar(vm.dump(key)), vm);
|
||||
});
|
||||
vm.setProp(bruObject, 'getSecretVar', getSecretVar);
|
||||
getSecretVar.dispose();
|
||||
|
||||
const sleep = vm.newFunction('sleep', (timer) => {
|
||||
const t = vm.getString(timer);
|
||||
const promise = vm.newPromise();
|
||||
setTimeout(() => {
|
||||
promise.resolve(vm.newString('slept'));
|
||||
}, t);
|
||||
promise.settled.then(vm.runtime.executePendingJobs);
|
||||
return promise.handle;
|
||||
});
|
||||
sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
|
||||
|
||||
vm.setProp(vm.global, 'bru', bruObject);
|
||||
bruObject.dispose();
|
||||
};
|
||||
|
||||
module.exports = addBruShimToContext;
|
112
packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js
Normal file
112
packages/bruno-js/src/sandbox/quickjs/shims/bruno-request.js
Normal file
@ -0,0 +1,112 @@
|
||||
const { marshallToVm } = require('../utils');
|
||||
|
||||
const addBrunoRequestShimToContext = (vm, req) => {
|
||||
const reqObject = vm.newObject();
|
||||
|
||||
const url = marshallToVm(req.getUrl(), vm);
|
||||
const method = marshallToVm(req.getMethod(), vm);
|
||||
const headers = marshallToVm(req.getHeaders(), vm);
|
||||
const body = marshallToVm(req.getBody(), vm);
|
||||
const timeout = marshallToVm(req.getTimeout(), vm);
|
||||
|
||||
vm.setProp(reqObject, 'url', url);
|
||||
vm.setProp(reqObject, 'method', method);
|
||||
vm.setProp(reqObject, 'headers', headers);
|
||||
vm.setProp(reqObject, 'body', body);
|
||||
vm.setProp(reqObject, 'timeout', timeout);
|
||||
|
||||
url.dispose();
|
||||
method.dispose();
|
||||
headers.dispose();
|
||||
body.dispose();
|
||||
timeout.dispose();
|
||||
|
||||
let getUrl = vm.newFunction('getUrl', function () {
|
||||
return marshallToVm(req.getUrl(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getUrl', getUrl);
|
||||
getUrl.dispose();
|
||||
|
||||
let setUrl = vm.newFunction('setUrl', function (url) {
|
||||
req.setUrl(vm.dump(url));
|
||||
});
|
||||
vm.setProp(reqObject, 'setUrl', setUrl);
|
||||
setUrl.dispose();
|
||||
|
||||
let getMethod = vm.newFunction('getMethod', function () {
|
||||
return marshallToVm(req.getMethod(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getMethod', getMethod);
|
||||
getMethod.dispose();
|
||||
|
||||
let getAuthMode = vm.newFunction('getAuthMode', function () {
|
||||
return marshallToVm(req.getAuthMode(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getAuthMode', getAuthMode);
|
||||
getAuthMode.dispose();
|
||||
|
||||
let setMethod = vm.newFunction('setMethod', function (method) {
|
||||
req.setMethod(vm.dump(method));
|
||||
});
|
||||
vm.setProp(reqObject, 'setMethod', setMethod);
|
||||
setMethod.dispose();
|
||||
|
||||
let getHeaders = vm.newFunction('getHeaders', function () {
|
||||
return marshallToVm(req.getHeaders(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getHeaders', getHeaders);
|
||||
getHeaders.dispose();
|
||||
|
||||
let setHeaders = vm.newFunction('setHeaders', function (headers) {
|
||||
req.setHeaders(vm.dump(headers));
|
||||
});
|
||||
vm.setProp(reqObject, 'setHeaders', setHeaders);
|
||||
setHeaders.dispose();
|
||||
|
||||
let getHeader = vm.newFunction('getHeader', function (name) {
|
||||
return marshallToVm(req.getHeader(vm.dump(name)), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getHeader', getHeader);
|
||||
getHeader.dispose();
|
||||
|
||||
let setHeader = vm.newFunction('setHeader', function (name, value) {
|
||||
req.setHeader(vm.dump(name), vm.dump(value));
|
||||
});
|
||||
vm.setProp(reqObject, 'setHeader', setHeader);
|
||||
setHeader.dispose();
|
||||
|
||||
let getBody = vm.newFunction('getBody', function () {
|
||||
return marshallToVm(req.getBody(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getBody', getBody);
|
||||
getBody.dispose();
|
||||
|
||||
let setBody = vm.newFunction('setBody', function (data) {
|
||||
req.setBody(vm.dump(data));
|
||||
});
|
||||
vm.setProp(reqObject, 'setBody', setBody);
|
||||
setBody.dispose();
|
||||
|
||||
let setMaxRedirects = vm.newFunction('setMaxRedirects', function (maxRedirects) {
|
||||
req.setMaxRedirects(vm.dump(maxRedirects));
|
||||
});
|
||||
vm.setProp(reqObject, 'setMaxRedirects', setMaxRedirects);
|
||||
setMaxRedirects.dispose();
|
||||
|
||||
let getTimeout = vm.newFunction('getTimeout', function () {
|
||||
return marshallToVm(req.getTimeout(), vm);
|
||||
});
|
||||
vm.setProp(reqObject, 'getTimeout', getTimeout);
|
||||
getTimeout.dispose();
|
||||
|
||||
let setTimeout = vm.newFunction('setTimeout', function (timeout) {
|
||||
req.setTimeout(vm.dump(timeout));
|
||||
});
|
||||
vm.setProp(reqObject, 'setTimeout', setTimeout);
|
||||
setTimeout.dispose();
|
||||
|
||||
vm.setProp(vm.global, 'req', reqObject);
|
||||
reqObject.dispose();
|
||||
};
|
||||
|
||||
module.exports = addBrunoRequestShimToContext;
|
@ -0,0 +1,55 @@
|
||||
const { marshallToVm } = require('../utils');
|
||||
|
||||
const addBrunoResponseShimToContext = (vm, res) => {
|
||||
const resObject = vm.newObject();
|
||||
|
||||
const status = marshallToVm(res?.status, vm);
|
||||
const headers = marshallToVm(res?.headers, vm);
|
||||
const body = marshallToVm(res?.body, vm);
|
||||
const responseTime = marshallToVm(res?.responseTime, vm);
|
||||
|
||||
vm.setProp(resObject, 'status', status);
|
||||
vm.setProp(resObject, 'headers', headers);
|
||||
vm.setProp(resObject, 'body', body);
|
||||
vm.setProp(resObject, 'responseTime', responseTime);
|
||||
|
||||
status.dispose();
|
||||
headers.dispose();
|
||||
body.dispose();
|
||||
responseTime.dispose();
|
||||
|
||||
let getStatus = vm.newFunction('getStatus', function () {
|
||||
return marshallToVm(res.getStatus(), vm);
|
||||
});
|
||||
vm.setProp(resObject, 'getStatus', getStatus);
|
||||
getStatus.dispose();
|
||||
|
||||
let getHeader = vm.newFunction('getHeader', function (name) {
|
||||
return marshallToVm(res.getHeader(vm.dump(name)), vm);
|
||||
});
|
||||
vm.setProp(resObject, 'getHeader', getHeader);
|
||||
getHeader.dispose();
|
||||
|
||||
let getHeaders = vm.newFunction('getHeaders', function () {
|
||||
return marshallToVm(res.getHeaders(), vm);
|
||||
});
|
||||
vm.setProp(resObject, 'getHeaders', getHeaders);
|
||||
getHeaders.dispose();
|
||||
|
||||
let getBody = vm.newFunction('getBody', function () {
|
||||
return marshallToVm(res.getBody(), vm);
|
||||
});
|
||||
vm.setProp(resObject, 'getBody', getBody);
|
||||
getBody.dispose();
|
||||
|
||||
let getResponseTime = vm.newFunction('getResponseTime', function () {
|
||||
return marshallToVm(res.getResponseTime(), vm);
|
||||
});
|
||||
vm.setProp(resObject, 'getResponseTime', getResponseTime);
|
||||
getResponseTime.dispose();
|
||||
|
||||
vm.setProp(vm.global, 'res', resObject);
|
||||
resObject.dispose();
|
||||
};
|
||||
|
||||
module.exports = addBrunoResponseShimToContext;
|
46
packages/bruno-js/src/sandbox/quickjs/shims/console.js
Normal file
46
packages/bruno-js/src/sandbox/quickjs/shims/console.js
Normal file
@ -0,0 +1,46 @@
|
||||
const addConsoleShimToContext = (vm, console) => {
|
||||
if (!console) return;
|
||||
|
||||
const consoleHandle = vm.newObject();
|
||||
|
||||
const logHandle = vm.newFunction('log', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
console?.log?.(...nativeArgs);
|
||||
});
|
||||
|
||||
const debugHandle = vm.newFunction('debug', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
console?.debug?.(...nativeArgs);
|
||||
});
|
||||
|
||||
const infoHandle = vm.newFunction('info', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
console?.info?.(...nativeArgs);
|
||||
});
|
||||
|
||||
const warnHandle = vm.newFunction('warn', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
console?.warn?.(...nativeArgs);
|
||||
});
|
||||
|
||||
const errorHandle = vm.newFunction('error', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
console?.error?.(...nativeArgs);
|
||||
});
|
||||
|
||||
vm.setProp(consoleHandle, 'log', logHandle);
|
||||
vm.setProp(consoleHandle, 'debug', debugHandle);
|
||||
vm.setProp(consoleHandle, 'info', infoHandle);
|
||||
vm.setProp(consoleHandle, 'warn', warnHandle);
|
||||
vm.setProp(consoleHandle, 'error', errorHandle);
|
||||
|
||||
vm.setProp(vm.global, 'console', consoleHandle);
|
||||
consoleHandle.dispose();
|
||||
logHandle.dispose();
|
||||
debugHandle.dispose();
|
||||
infoHandle.dispose();
|
||||
warnHandle.dispose();
|
||||
errorHandle.dispose();
|
||||
};
|
||||
|
||||
module.exports = addConsoleShimToContext;
|
72
packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js
Normal file
72
packages/bruno-js/src/sandbox/quickjs/shims/lib/axios.js
Normal file
@ -0,0 +1,72 @@
|
||||
const axios = require('axios');
|
||||
const { cleanJson } = require('../../../../utils');
|
||||
const { marshallToVm } = require('../../utils');
|
||||
|
||||
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
||||
|
||||
const addAxiosShimToContext = async (vm) => {
|
||||
methods?.forEach((method) => {
|
||||
const axiosHandle = vm.newFunction(method, (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
const promise = vm.newPromise();
|
||||
axios[method](...nativeArgs)
|
||||
.then((response) => {
|
||||
const { status, headers, data } = response || {};
|
||||
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
||||
})
|
||||
.catch((err) => {
|
||||
promise.resolve(
|
||||
marshallToVm(
|
||||
cleanJson({
|
||||
message: err.message
|
||||
}),
|
||||
vm
|
||||
)
|
||||
);
|
||||
});
|
||||
promise.settled.then(vm.runtime.executePendingJobs);
|
||||
return promise.handle;
|
||||
});
|
||||
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios__${method}`, handle));
|
||||
});
|
||||
|
||||
const axiosHandle = vm.newFunction('axios', (...args) => {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
const promise = vm.newPromise();
|
||||
axios(...nativeArgs)
|
||||
.then((response) => {
|
||||
const { status, headers, data } = response || {};
|
||||
promise.resolve(marshallToVm(cleanJson({ status, headers, data }), vm));
|
||||
})
|
||||
.catch((err) => {
|
||||
promise.resolve(
|
||||
marshallToVm(
|
||||
cleanJson({
|
||||
message: err.message
|
||||
}),
|
||||
vm
|
||||
)
|
||||
);
|
||||
});
|
||||
promise.settled.then(vm.runtime.executePendingJobs);
|
||||
return promise.handle;
|
||||
});
|
||||
axiosHandle.consume((handle) => vm.setProp(vm.global, `__bruno__axios`, handle));
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
globalThis.axios = __bruno__axios;
|
||||
${methods
|
||||
?.map((method) => {
|
||||
return `globalThis.axios.${method} = __bruno__axios__${method};`;
|
||||
})
|
||||
?.join('\n')}
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
axios: globalThis.axios,
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addAxiosShimToContext;
|
13
packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js
Normal file
13
packages/bruno-js/src/sandbox/quickjs/shims/lib/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
const addAxiosShimToContext = require('./axios');
|
||||
const addNanoidShimToContext = require('./nanoid');
|
||||
const addPathShimToContext = require('./path');
|
||||
const addUuidShimToContext = require('./uuid');
|
||||
|
||||
const addLibraryShimsToContext = async (vm) => {
|
||||
await addNanoidShimToContext(vm);
|
||||
await addAxiosShimToContext(vm);
|
||||
await addUuidShimToContext(vm);
|
||||
await addPathShimToContext(vm);
|
||||
};
|
||||
|
||||
module.exports = addLibraryShimsToContext;
|
24
packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js
Normal file
24
packages/bruno-js/src/sandbox/quickjs/shims/lib/nanoid.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { nanoid } = require('nanoid');
|
||||
const { marshallToVm } = require('../../utils');
|
||||
|
||||
const addNanoidShimToContext = async (vm) => {
|
||||
let _nanoid = vm.newFunction('nanoid', function () {
|
||||
let v = nanoid();
|
||||
return marshallToVm(v, vm);
|
||||
});
|
||||
vm.setProp(vm.global, '__bruno__nanoid', _nanoid);
|
||||
_nanoid.dispose();
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
globalThis.nanoid = {};
|
||||
globalThis.nanoid.nanoid = globalThis.__bruno__nanoid;
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
'nanoid': globalThis.nanoid
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addNanoidShimToContext;
|
28
packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js
Normal file
28
packages/bruno-js/src/sandbox/quickjs/shims/lib/path.js
Normal file
@ -0,0 +1,28 @@
|
||||
const path = require('path');
|
||||
const { marshallToVm } = require('../../utils');
|
||||
|
||||
const fns = ['resolve'];
|
||||
|
||||
const addPathShimToContext = async (vm) => {
|
||||
fns.forEach((fn) => {
|
||||
let fnHandle = vm.newFunction(fn, function (...args) {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
return marshallToVm(path[fn](...nativeArgs), vm);
|
||||
});
|
||||
vm.setProp(vm.global, `__bruno__path__${fn}`, fnHandle);
|
||||
fnHandle.dispose();
|
||||
});
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
globalThis.path = {};
|
||||
${fns?.map((fn, idx) => `globalThis.path.${fn} = __bruno__path__${fn}`).join('\n')}
|
||||
globalThis.requireObject = {
|
||||
...(globalThis.requireObject || {}),
|
||||
path: globalThis.path,
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addPathShimToContext;
|
30
packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js
Normal file
30
packages/bruno-js/src/sandbox/quickjs/shims/lib/uuid.js
Normal file
@ -0,0 +1,30 @@
|
||||
const uuid = require('uuid');
|
||||
const { marshallToVm } = require('../../utils');
|
||||
|
||||
const fns = ['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate'];
|
||||
|
||||
const addUuidShimToContext = async (vm) => {
|
||||
fns.forEach((fn) => {
|
||||
let fnHandle = vm.newFunction(fn, function (...args) {
|
||||
const nativeArgs = args.map(vm.dump);
|
||||
return marshallToVm(uuid[fn](...nativeArgs), vm);
|
||||
});
|
||||
vm.setProp(vm.global, `__bruno__uuid__${fn}`, fnHandle);
|
||||
fnHandle.dispose();
|
||||
});
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
globalThis.uuid = {};
|
||||
${['version', 'parse', 'stringify', 'v1', 'v1ToV6', 'v3', 'v4', 'v5', 'v6', 'v6ToV1', 'v7', 'validate']
|
||||
?.map((fn, idx) => `globalThis.uuid.${fn} = __bruno__uuid__${fn}`)
|
||||
.join('\n')}
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
uuid: globalThis.uuid,
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addUuidShimToContext;
|
31
packages/bruno-js/src/sandbox/quickjs/shims/local-module.js
Normal file
31
packages/bruno-js/src/sandbox/quickjs/shims/local-module.js
Normal file
@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { marshallToVm } = require('../utils');
|
||||
|
||||
const addLocalModuleLoaderShimToContext = (vm, collectionPath) => {
|
||||
let loadLocalModuleHandle = vm.newFunction('loadLocalModule', function (module) {
|
||||
const filename = vm.dump(module);
|
||||
|
||||
// Check if the filename has an extension
|
||||
const hasExtension = path.extname(filename) !== '';
|
||||
const resolvedFilename = hasExtension ? filename : `${filename}.js`;
|
||||
|
||||
// Resolve the file path and check if it's within the collectionPath
|
||||
const filePath = path.resolve(collectionPath, resolvedFilename);
|
||||
const relativePath = path.relative(collectionPath, filePath);
|
||||
|
||||
// Ensure the resolved file path is inside the collectionPath
|
||||
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
||||
throw new Error('Access to files outside of the collectionPath is not allowed.');
|
||||
}
|
||||
|
||||
let code = fs.readFileSync(filePath).toString();
|
||||
|
||||
return marshallToVm(code, vm);
|
||||
});
|
||||
|
||||
vm.setProp(vm.global, '__brunoLoadLocalModule', loadLocalModuleHandle);
|
||||
loadLocalModuleHandle.dispose();
|
||||
};
|
||||
|
||||
module.exports = addLocalModuleLoaderShimToContext;
|
63
packages/bruno-js/src/sandbox/quickjs/shims/test.js
Normal file
63
packages/bruno-js/src/sandbox/quickjs/shims/test.js
Normal file
@ -0,0 +1,63 @@
|
||||
const { marshallToVm } = require('../utils');
|
||||
|
||||
const addBruShimToContext = (vm, __brunoTestResults) => {
|
||||
let addResult = vm.newFunction('addResult', function (v) {
|
||||
__brunoTestResults.addResult(vm.dump(v));
|
||||
});
|
||||
vm.setProp(vm.global, '__bruno__addResult', addResult);
|
||||
addResult.dispose();
|
||||
|
||||
let getResults = vm.newFunction('getResults', function () {
|
||||
return marshallToVm(__brunoTestResults.getResults(), vm);
|
||||
});
|
||||
vm.setProp(vm.global, '__bruno__getResults', getResults);
|
||||
getResults.dispose();
|
||||
|
||||
vm.evalCode(
|
||||
`
|
||||
globalThis.expect = require('chai').expect;
|
||||
globalThis.assert = require('chai').assert;
|
||||
|
||||
globalThis.__brunoTestResults = {
|
||||
addResult: globalThis.__bruno__addResult,
|
||||
getResults: globalThis.__bruno__getResults,
|
||||
}
|
||||
|
||||
globalThis.DummyChaiAssertionError = class DummyChaiAssertionError extends Error {
|
||||
constructor(message, props, ssf) {
|
||||
super(message);
|
||||
this.name = "AssertionError";
|
||||
Object.assign(this, props);
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.Test = (__brunoTestResults) => async (description, callback) => {
|
||||
try {
|
||||
await callback();
|
||||
__brunoTestResults.addResult({ description, status: "pass" });
|
||||
} catch (error) {
|
||||
if (error instanceof DummyChaiAssertionError) {
|
||||
const { message, actual, expected } = error;
|
||||
__brunoTestResults.addResult({
|
||||
description,
|
||||
status: "fail",
|
||||
error: message,
|
||||
actual,
|
||||
expected,
|
||||
});
|
||||
} else {
|
||||
globalThis.__bruno__addResult({
|
||||
description,
|
||||
status: "fail",
|
||||
error: error.message || "An unexpected error occurred.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
globalThis.test = Test(__brunoTestResults);
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = addBruShimToContext;
|
33
packages/bruno-js/src/sandbox/quickjs/utils/index.js
Normal file
33
packages/bruno-js/src/sandbox/quickjs/utils/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
const marshallToVm = (value, vm) => {
|
||||
if (value === undefined) {
|
||||
return vm.undefined;
|
||||
}
|
||||
if (value === null) {
|
||||
return vm.null;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return vm.newString(value);
|
||||
} else if (typeof value === 'number') {
|
||||
return vm.newNumber(value);
|
||||
} else if (typeof value === 'boolean') {
|
||||
return vm.newBoolean(value);
|
||||
} else if (typeof value === 'object') {
|
||||
if (Array.isArray(value)) {
|
||||
const arr = vm.newArray();
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
vm.setProp(arr, i, marshallToVm(value[i], vm));
|
||||
}
|
||||
return arr;
|
||||
} else {
|
||||
const obj = vm.newObject();
|
||||
for (const key in value) {
|
||||
vm.setProp(obj, key, marshallToVm(value[key], vm));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
marshallToVm
|
||||
};
|
@ -142,10 +142,15 @@ const cleanJson = (data) => {
|
||||
}
|
||||
};
|
||||
|
||||
const appendAwaitToTestFunc = (str) => {
|
||||
return str.replace(/(?<!\.\s*)(?<!await\s)(test\()/g, 'await $1');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
evaluateJsExpression,
|
||||
evaluateJsTemplateLiteral,
|
||||
createResponseParser,
|
||||
internalExpressionCache,
|
||||
cleanJson
|
||||
cleanJson,
|
||||
appendAwaitToTestFunc
|
||||
};
|
||||
|
@ -35,7 +35,7 @@ describe('runtime', () => {
|
||||
})
|
||||
`;
|
||||
|
||||
const runtime = new TestRuntime();
|
||||
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runTests(
|
||||
testFile,
|
||||
{ ...baseRequest },
|
||||
@ -71,7 +71,7 @@ describe('runtime', () => {
|
||||
})
|
||||
`;
|
||||
|
||||
const runtime = new TestRuntime();
|
||||
const runtime = new TestRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runTests(
|
||||
testFile,
|
||||
{ ...baseRequest },
|
||||
@ -114,7 +114,7 @@ describe('runtime', () => {
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const runtime = new ScriptRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runRequestScript(script, { ...baseRequest }, {}, {}, '.', null, process.env);
|
||||
expect(result.runtimeVariables.validation).toBeTruthy();
|
||||
});
|
||||
@ -160,7 +160,7 @@ describe('runtime', () => {
|
||||
bru.setVar('validation', validate(new Date().toISOString()))
|
||||
`;
|
||||
|
||||
const runtime = new ScriptRuntime();
|
||||
const runtime = new ScriptRuntime({ runtime: 'vm2' });
|
||||
const result = await runtime.runResponseScript(
|
||||
script,
|
||||
{ ...baseRequest },
|
||||
|
@ -1,5 +1,10 @@
|
||||
const { describe, it, expect } = require('@jest/globals');
|
||||
const { evaluateJsExpression, internalExpressionCache: cache, createResponseParser } = require('../src/utils');
|
||||
const {
|
||||
evaluateJsExpression,
|
||||
internalExpressionCache: cache,
|
||||
createResponseParser,
|
||||
appendAwaitToTestFunc
|
||||
} = require('../src/utils');
|
||||
|
||||
describe('utils', () => {
|
||||
describe('expression evaluation', () => {
|
||||
@ -137,4 +142,70 @@ describe('utils', () => {
|
||||
expect(value).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendAwaitToTestFunc function', () => {
|
||||
it('example 1', () => {
|
||||
const inputTestsString = `
|
||||
test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
`;
|
||||
const ouutputTestsString = appendAwaitToTestFunc(inputTestsString);
|
||||
expect(ouutputTestsString).toBe(`
|
||||
await test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
`);
|
||||
});
|
||||
|
||||
it('example 2', () => {
|
||||
const inputTestsString = `
|
||||
await test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
`;
|
||||
const ouutputTestsString = appendAwaitToTestFunc(inputTestsString);
|
||||
expect(ouutputTestsString).toBe(`
|
||||
await test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
await test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
await test("should return json", function() {
|
||||
const data = res.getBody();
|
||||
expect(res.getBody()).to.eql({
|
||||
"hello": "bruno"
|
||||
});
|
||||
});
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user