@usebruno/js 0.51.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/bru.js +15 -23
- package/src/bruno-request.js +4 -0
- package/src/grpc/bruno-grpc-request.js +52 -0
- package/src/grpc/bruno-grpc-response.js +43 -0
- package/src/grpc/grpc-message-list.js +74 -0
- package/src/grpc/grpc-message.js +20 -0
- package/src/grpc/grpc-metadata-list.js +214 -0
- package/src/grpc/grpc-metadata.js +21 -0
- package/src/grpc/grpc-script-runtime.js +302 -0
- package/src/index.js +2 -0
- package/src/interpolate-string.js +2 -1
- package/src/runtime/assert-runtime.js +1 -0
- package/src/runtime/script-runtime.js +18 -2
- package/src/runtime/test-runtime.js +12 -7
- package/src/sandbox/bundle-browser-rollup.js +32 -32
- package/src/sandbox/node-vm/cjs-loader.js +390 -39
- package/src/sandbox/node-vm/index.js +96 -65
- package/src/sandbox/node-vm/index.spec.js +616 -0
- package/src/sandbox/quickjs/index.js +101 -17
- package/src/sandbox/quickjs/shims/bruno-grpc.js +29 -0
- package/src/sandbox/quickjs/shims/grpc/bruno-grpc-request.js +47 -0
- package/src/sandbox/quickjs/shims/grpc/bruno-grpc-response.js +40 -0
- package/src/sandbox/quickjs/shims/grpc/grpc-message-list.js +29 -0
- package/src/sandbox/quickjs/shims/grpc/grpc-metadata-list.js +31 -0
- package/src/sandbox/quickjs/utils/index.js +42 -23
- package/src/utils/error-formatter.js +13 -9
- package/src/utils/error-formatter.spec.js +97 -2
- package/src/utils/results.js +30 -3
|
@@ -4,7 +4,7 @@ const { get } = require('lodash');
|
|
|
4
4
|
const lodash = require('lodash');
|
|
5
5
|
const { wrapConsoleWithSerializers } = require('./console');
|
|
6
6
|
const { ScriptError, resolveVmFilename } = require('./utils');
|
|
7
|
-
const { createCustomRequire } = require('./cjs-loader');
|
|
7
|
+
const { createCustomRequire, runWithScriptContext, getSharedNpmContext, attachRunLoaderState } = require('./cjs-loader');
|
|
8
8
|
const { safeGlobals } = require('./constants');
|
|
9
9
|
const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
|
10
10
|
const { wrapScriptInClosure, SANDBOX } = require('../../utils/sandbox');
|
|
@@ -44,82 +44,113 @@ async function runScriptInNodeVm({
|
|
|
44
44
|
|
|
45
45
|
// Build the script context with Bruno objects and globals
|
|
46
46
|
const scriptContext = buildScriptContext(context, scriptingConfig);
|
|
47
|
-
|
|
48
|
-
// Create truly isolated context - scriptContext becomes the global object
|
|
49
|
-
// Scripts can ONLY access what's explicitly in scriptContext
|
|
50
|
-
const isolatedContext = vm.createContext(scriptContext);
|
|
51
|
-
|
|
52
|
-
// Add global/globalThis pointing to the isolated context (not host global)
|
|
53
|
-
// This allows libraries that reference 'global' to work while maintaining isolation
|
|
54
|
-
scriptContext.global = scriptContext;
|
|
55
|
-
scriptContext.globalThis = scriptContext;
|
|
56
|
-
|
|
57
|
-
// Create module cache for CJS modules
|
|
58
47
|
const localModuleCache = new Map();
|
|
48
|
+
const cacheModules = get(scriptingConfig, 'cacheModules', false) === true;
|
|
49
|
+
|
|
50
|
+
// cacheModules: one shared VM realm for scripts + npm modules so instanceof
|
|
51
|
+
// matches. Per-run bru/req/res/require resolve via ALS facades on that realm.
|
|
52
|
+
// Otherwise: fresh isolated context per script (default).
|
|
53
|
+
let vmContext;
|
|
54
|
+
if (cacheModules) {
|
|
55
|
+
vmContext = getSharedNpmContext();
|
|
56
|
+
} else {
|
|
57
|
+
vmContext = vm.createContext(scriptContext);
|
|
58
|
+
scriptContext.global = scriptContext;
|
|
59
|
+
scriptContext.globalThis = scriptContext;
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
// Add require() function for CJS module loading
|
|
61
62
|
scriptContext.require = createCustomRequire({
|
|
62
63
|
collectionPath,
|
|
63
|
-
isolatedContext,
|
|
64
|
+
isolatedContext: vmContext,
|
|
64
65
|
currentModuleDir: collectionPath,
|
|
65
66
|
localModuleCache,
|
|
66
|
-
additionalContextRootsAbsolute
|
|
67
|
+
additionalContextRootsAbsolute,
|
|
68
|
+
cacheModules
|
|
67
69
|
});
|
|
70
|
+
// Stashed for shared npm requires that outlive this run's createCustomRequire closure.
|
|
71
|
+
// Symbol key so runWithScriptContext does not publish these as sandbox globals.
|
|
72
|
+
attachRunLoaderState(scriptContext, { localModuleCache, vmContext });
|
|
73
|
+
|
|
74
|
+
// cacheModules: onFail runs after the script's ALS store exits, so re-bind
|
|
75
|
+
// registered callbacks to this run's context (bru/req/... facades).
|
|
76
|
+
let restoreOnFail;
|
|
77
|
+
if (cacheModules && typeof scriptContext.req?.onFail === 'function') {
|
|
78
|
+
const req = scriptContext.req;
|
|
79
|
+
const originalOnFail = req.onFail;
|
|
80
|
+
req.onFail = (callback) => {
|
|
81
|
+
if (typeof callback !== 'function') {
|
|
82
|
+
return originalOnFail.call(req, callback);
|
|
83
|
+
}
|
|
84
|
+
return originalOnFail.call(req, (error) => runWithScriptContext(scriptContext, () => callback(error)));
|
|
85
|
+
};
|
|
86
|
+
restoreOnFail = () => {
|
|
87
|
+
req.onFail = originalOnFail;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
68
90
|
|
|
69
|
-
const vmFilename = resolveVmFilename(scriptPath, collectionPath);
|
|
70
|
-
|
|
71
|
-
// Execute the script in the isolated context
|
|
72
|
-
const wrappedScript = wrapScriptInClosure(script, SANDBOX.NODEVM);
|
|
73
|
-
let compiledScript;
|
|
74
91
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
const vmFilename = resolveVmFilename(scriptPath, collectionPath);
|
|
93
|
+
|
|
94
|
+
// Execute the script in the isolated context
|
|
95
|
+
const wrappedScript = wrapScriptInClosure(script, SANDBOX.NODEVM);
|
|
96
|
+
let compiledScript;
|
|
97
|
+
try {
|
|
98
|
+
compiledScript = new vm.Script(wrappedScript, {
|
|
99
|
+
filename: vmFilename
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
// V8 puts "filename:line" as the first line of syntax error stacks.
|
|
103
|
+
// Parse it so the error formatter can map to the correct source location.
|
|
104
|
+
const firstLine = error.stack?.split('\n')[0];
|
|
105
|
+
const match = firstLine?.match(/^(.+):(\d+)$/);
|
|
106
|
+
if (match && match[1] === vmFilename) {
|
|
107
|
+
error.__callSites = [{
|
|
108
|
+
filePath: vmFilename,
|
|
109
|
+
line: parseInt(match[2], 10),
|
|
110
|
+
column: null,
|
|
111
|
+
functionName: null
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
90
115
|
}
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Capture structured call sites for error-formatter line mapping
|
|
95
|
-
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
96
|
-
Error.prepareStackTrace = (error, callSites) => {
|
|
97
|
-
error.__callSites = callSites
|
|
98
|
-
.filter((site) => site.getFileName() === vmFilename)
|
|
99
|
-
.map((site) => ({
|
|
100
|
-
filePath: site.getFileName(),
|
|
101
|
-
line: site.getLineNumber(),
|
|
102
|
-
column: site.getColumnNumber(),
|
|
103
|
-
functionName: site.getFunctionName() || null
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
return error.toString() + '\n' + callSites
|
|
107
|
-
.map((site) => ` at ${site}`)
|
|
108
|
-
.join('\n');
|
|
109
|
-
};
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// Capture structured call sites for error-formatter line mapping
|
|
118
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
119
|
+
Error.prepareStackTrace = (error, callSites) => {
|
|
120
|
+
error.__callSites = callSites
|
|
121
|
+
.filter((site) => site.getFileName() === vmFilename)
|
|
122
|
+
.map((site) => ({
|
|
123
|
+
filePath: site.getFileName(),
|
|
124
|
+
line: site.getLineNumber(),
|
|
125
|
+
column: site.getColumnNumber(),
|
|
126
|
+
functionName: site.getFunctionName() || null
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
return error.toString() + '\n' + callSites
|
|
130
|
+
.map((site) => ` at ${site}`)
|
|
131
|
+
.join('\n');
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const runScript = () => compiledScript.runInContext(vmContext, {
|
|
136
|
+
displayErrors: true
|
|
137
|
+
});
|
|
138
|
+
if (cacheModules) {
|
|
139
|
+
await runWithScriptContext(scriptContext, runScript);
|
|
140
|
+
} else {
|
|
141
|
+
await runScript();
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// V8 invokes prepareStackTrace lazily on first .stack access.
|
|
145
|
+
// Reading .stack here so custom handler runs and populates error.__callSites
|
|
146
|
+
// (used later by the error formatter to map stack frames to the .bru/.yml script)
|
|
147
|
+
void error.stack;
|
|
148
|
+
throw error;
|
|
149
|
+
} finally {
|
|
150
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
151
|
+
}
|
|
121
152
|
} finally {
|
|
122
|
-
|
|
153
|
+
restoreOnFail?.();
|
|
123
154
|
}
|
|
124
155
|
} catch (error) {
|
|
125
156
|
throw new ScriptError(error, script);
|