@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
|
@@ -2,8 +2,214 @@ const vm = require('node:vm');
|
|
|
2
2
|
const fs = require('node:fs');
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const nodeModule = require('node:module');
|
|
5
|
+
const { AsyncLocalStorage } = require('node:async_hooks');
|
|
5
6
|
|
|
6
7
|
const { isBuiltinModule, isPathWithinAllowedRoots } = require('./utils');
|
|
8
|
+
const { safeGlobals } = require('./constants');
|
|
9
|
+
const { mixinTypedArrays } = require('../mixins/typed-arrays');
|
|
10
|
+
|
|
11
|
+
// Shared npm context (once per process) so modules aren't re-eval'd per script. #9074
|
|
12
|
+
const activeScriptContext = new AsyncLocalStorage();
|
|
13
|
+
const npmModuleEval = new AsyncLocalStorage();
|
|
14
|
+
const sharedNpmModuleCache = new Map();
|
|
15
|
+
const contextBoundModulePaths = new Set();
|
|
16
|
+
// path -> paths required while that module was evaluating (for context-bound eviction)
|
|
17
|
+
const moduleRequireGraph = new Map();
|
|
18
|
+
let sharedNpmSandbox = null;
|
|
19
|
+
let sharedNpmContext = null;
|
|
20
|
+
|
|
21
|
+
const BRUNO_CONTEXT_KEYS = [
|
|
22
|
+
'bru',
|
|
23
|
+
'req',
|
|
24
|
+
'res',
|
|
25
|
+
'test',
|
|
26
|
+
'expect',
|
|
27
|
+
'assert',
|
|
28
|
+
'__brunoTestResults',
|
|
29
|
+
'__bruSetScope',
|
|
30
|
+
'jwt',
|
|
31
|
+
'console',
|
|
32
|
+
'scriptingConfig'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const facades = new Map();
|
|
36
|
+
|
|
37
|
+
const RUN_LOADER_STATE = Symbol('brunoRunLoaderState');
|
|
38
|
+
|
|
39
|
+
function attachRunLoaderState(scriptContext, { localModuleCache, vmContext }) {
|
|
40
|
+
scriptContext[RUN_LOADER_STATE] = { localModuleCache, vmContext };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getRunLoaderState(store) {
|
|
44
|
+
return store?.[RUN_LOADER_STATE];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Late-bound facade for a Bruno global in the shared npm context.
|
|
49
|
+
* @param {string} key - The Bruno global's name
|
|
50
|
+
* @param {boolean} callable - Whether the current value is a function
|
|
51
|
+
*/
|
|
52
|
+
function facadeFor(key, callable) {
|
|
53
|
+
const cacheKey = `${key}:${callable ? 'fn' : 'obj'}`;
|
|
54
|
+
if (facades.has(cacheKey)) {
|
|
55
|
+
return facades.get(cacheKey);
|
|
56
|
+
}
|
|
57
|
+
const current = () => activeScriptContext.getStore()?.[key];
|
|
58
|
+
const isMissing = (value) => value === undefined || value === null;
|
|
59
|
+
const target = callable ? () => {} : {};
|
|
60
|
+
|
|
61
|
+
// Cache wrappers so `bru.setVar === bru.setVar` remains true.
|
|
62
|
+
const methods = new Map();
|
|
63
|
+
const lateBoundMethod = (prop) => {
|
|
64
|
+
if (!methods.has(prop)) {
|
|
65
|
+
methods.set(prop, (...args) => {
|
|
66
|
+
const value = current();
|
|
67
|
+
const member = isMissing(value) ? undefined : value[prop];
|
|
68
|
+
if (typeof member !== 'function') {
|
|
69
|
+
throw new TypeError(`${key}.${String(prop)} is not available outside of a script execution`);
|
|
70
|
+
}
|
|
71
|
+
return Reflect.apply(member, value, args);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return methods.get(prop);
|
|
75
|
+
};
|
|
76
|
+
const facade = new Proxy(target, {
|
|
77
|
+
get: (_, prop) => {
|
|
78
|
+
const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
79
|
+
if (targetDescriptor && !targetDescriptor.configurable && !targetDescriptor.writable) {
|
|
80
|
+
return targetDescriptor.value;
|
|
81
|
+
}
|
|
82
|
+
const value = current();
|
|
83
|
+
if (isMissing(value)) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const member = value[prop];
|
|
87
|
+
return typeof member === 'function' ? lateBoundMethod(prop) : member;
|
|
88
|
+
},
|
|
89
|
+
set: (_, prop, newValue) => {
|
|
90
|
+
const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
91
|
+
if (targetDescriptor && !targetDescriptor.configurable && !targetDescriptor.writable) {
|
|
92
|
+
return Object.is(targetDescriptor.value, newValue);
|
|
93
|
+
}
|
|
94
|
+
const value = current();
|
|
95
|
+
if (isMissing(value)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
value[prop] = newValue;
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
has: (_, prop) => {
|
|
102
|
+
const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
103
|
+
if (targetDescriptor && !targetDescriptor.configurable) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (!Object.isExtensible(target)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const value = current();
|
|
110
|
+
return !isMissing(value) && prop in Object(value);
|
|
111
|
+
},
|
|
112
|
+
ownKeys: () => {
|
|
113
|
+
// Once freeze/preventExtensions runs, only report keys on the inert target
|
|
114
|
+
// or Proxy throws and the poisoned facade breaks every later script.
|
|
115
|
+
if (!Object.isExtensible(target)) {
|
|
116
|
+
return Reflect.ownKeys(target);
|
|
117
|
+
}
|
|
118
|
+
const value = current();
|
|
119
|
+
const currentKeys = isMissing(value) ? [] : Reflect.ownKeys(Object(value));
|
|
120
|
+
return [...new Set([...Reflect.ownKeys(target), ...currentKeys])];
|
|
121
|
+
},
|
|
122
|
+
getOwnPropertyDescriptor: (_, prop) => {
|
|
123
|
+
const targetDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
124
|
+
if (targetDescriptor && !targetDescriptor.configurable) {
|
|
125
|
+
return targetDescriptor;
|
|
126
|
+
}
|
|
127
|
+
if (!Object.isExtensible(target)) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
const value = current();
|
|
131
|
+
const descriptor = isMissing(value) ? undefined : Object.getOwnPropertyDescriptor(Object(value), prop);
|
|
132
|
+
return descriptor ? { ...descriptor, configurable: true } : undefined;
|
|
133
|
+
},
|
|
134
|
+
getPrototypeOf: () => {
|
|
135
|
+
const value = current();
|
|
136
|
+
return isMissing(value) ? null : Object.getPrototypeOf(Object(value));
|
|
137
|
+
},
|
|
138
|
+
apply: (_, thisArg, args) => {
|
|
139
|
+
const value = current();
|
|
140
|
+
if (typeof value !== 'function') {
|
|
141
|
+
throw new TypeError(`${key} is not available outside of a script execution`);
|
|
142
|
+
}
|
|
143
|
+
return Reflect.apply(value, thisArg, args);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
facades.set(cacheKey, facade);
|
|
147
|
+
return facade;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function defineDynamicGlobal(key) {
|
|
151
|
+
if (Object.prototype.hasOwnProperty.call(sharedNpmSandbox, key)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
Object.defineProperty(sharedNpmSandbox, key, {
|
|
155
|
+
enumerable: true,
|
|
156
|
+
configurable: true,
|
|
157
|
+
get: () => {
|
|
158
|
+
const evalStore = npmModuleEval.getStore();
|
|
159
|
+
if (evalStore) {
|
|
160
|
+
evalStore.touched.add(key);
|
|
161
|
+
}
|
|
162
|
+
const value = activeScriptContext.getStore()?.[key];
|
|
163
|
+
if (value === undefined || value === null) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (typeof value !== 'object' && typeof value !== 'function') {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
// Custom script globals (arrays, plain data, callbacks, require) stay raw so
|
|
170
|
+
// Array.isArray / identity work. Bruno APIs need facades for late-binding.
|
|
171
|
+
if (!BRUNO_CONTEXT_KEYS.includes(key)) {
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
return facadeFor(key, typeof value === 'function');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getSharedNpmContext() {
|
|
180
|
+
if (!sharedNpmContext) {
|
|
181
|
+
sharedNpmSandbox = Object.fromEntries(
|
|
182
|
+
safeGlobals
|
|
183
|
+
.filter((key) => global[key] !== undefined)
|
|
184
|
+
.map((key) => [key, global[key]])
|
|
185
|
+
);
|
|
186
|
+
mixinTypedArrays(sharedNpmSandbox);
|
|
187
|
+
sharedNpmContext = vm.createContext(sharedNpmSandbox);
|
|
188
|
+
sharedNpmSandbox.global = sharedNpmSandbox;
|
|
189
|
+
sharedNpmSandbox.globalThis = sharedNpmSandbox;
|
|
190
|
+
BRUNO_CONTEXT_KEYS.forEach(defineDynamicGlobal);
|
|
191
|
+
}
|
|
192
|
+
return sharedNpmContext;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs `fn` with `scriptContext` as the currently executing script context, so
|
|
197
|
+
* npm modules called from it (or from async work it starts) resolve `bru`,
|
|
198
|
+
* `req`, `res`, ... to it. Safe for interleaved executions; nested executions
|
|
199
|
+
* (`bru.runRequest`) see their own context.
|
|
200
|
+
* @param {Object} scriptContext - The script's vm global object
|
|
201
|
+
* @param {Function} fn - The execution, typically `() => script.runInContext(...)`
|
|
202
|
+
* @returns {*} Whatever `fn` returns
|
|
203
|
+
*/
|
|
204
|
+
function runWithScriptContext(scriptContext, fn) {
|
|
205
|
+
getSharedNpmContext();
|
|
206
|
+
for (const key of Object.keys(scriptContext)) {
|
|
207
|
+
if (key !== 'global' && key !== 'globalThis') {
|
|
208
|
+
defineDynamicGlobal(key);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return activeScriptContext.run(scriptContext, fn);
|
|
212
|
+
}
|
|
7
213
|
|
|
8
214
|
/**
|
|
9
215
|
* Resolve a local module path, handling files and directories
|
|
@@ -74,7 +280,8 @@ function createCustomRequire({
|
|
|
74
280
|
isolatedContext,
|
|
75
281
|
currentModuleDir = collectionPath,
|
|
76
282
|
localModuleCache = new Map(),
|
|
77
|
-
additionalContextRootsAbsolute = []
|
|
283
|
+
additionalContextRootsAbsolute = [],
|
|
284
|
+
cacheModules = false
|
|
78
285
|
}) {
|
|
79
286
|
return (moduleName) => {
|
|
80
287
|
const normalizedModuleName = moduleName.replace(/\\/g, '/');
|
|
@@ -87,7 +294,8 @@ function createCustomRequire({
|
|
|
87
294
|
isolatedContext,
|
|
88
295
|
localModuleCache,
|
|
89
296
|
currentModuleDir,
|
|
90
|
-
additionalContextRootsAbsolute
|
|
297
|
+
additionalContextRootsAbsolute,
|
|
298
|
+
cacheModules
|
|
91
299
|
});
|
|
92
300
|
}
|
|
93
301
|
|
|
@@ -100,7 +308,8 @@ function createCustomRequire({
|
|
|
100
308
|
isolatedContext,
|
|
101
309
|
localModuleCache,
|
|
102
310
|
currentModuleDir,
|
|
103
|
-
additionalContextRootsAbsolute
|
|
311
|
+
additionalContextRootsAbsolute,
|
|
312
|
+
cacheModules
|
|
104
313
|
});
|
|
105
314
|
}
|
|
106
315
|
|
|
@@ -117,7 +326,8 @@ function createCustomRequire({
|
|
|
117
326
|
collectionPath,
|
|
118
327
|
currentModuleDir,
|
|
119
328
|
isolatedContext,
|
|
120
|
-
localModuleCache
|
|
329
|
+
localModuleCache,
|
|
330
|
+
cacheModules
|
|
121
331
|
});
|
|
122
332
|
};
|
|
123
333
|
}
|
|
@@ -134,7 +344,8 @@ function loadLocalModule({
|
|
|
134
344
|
isolatedContext,
|
|
135
345
|
localModuleCache,
|
|
136
346
|
currentModuleDir,
|
|
137
|
-
additionalContextRootsAbsolute = []
|
|
347
|
+
additionalContextRootsAbsolute = [],
|
|
348
|
+
cacheModules = false
|
|
138
349
|
}) {
|
|
139
350
|
// Validate the raw module name doesn't try to escape allowed roots
|
|
140
351
|
const preliminaryPath = path.resolve(currentModuleDir, moduleName);
|
|
@@ -182,7 +393,8 @@ function loadLocalModule({
|
|
|
182
393
|
isolatedContext,
|
|
183
394
|
currentModuleDir: moduleDir,
|
|
184
395
|
localModuleCache,
|
|
185
|
-
additionalContextRootsAbsolute
|
|
396
|
+
additionalContextRootsAbsolute,
|
|
397
|
+
cacheModules
|
|
186
398
|
});
|
|
187
399
|
|
|
188
400
|
try {
|
|
@@ -199,6 +411,38 @@ function loadLocalModule({
|
|
|
199
411
|
}
|
|
200
412
|
}
|
|
201
413
|
|
|
414
|
+
function recordRequireEdge(resolvedPath) {
|
|
415
|
+
const parent = npmModuleEval.getStore();
|
|
416
|
+
if (!parent?.resolvedPath) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (!moduleRequireGraph.has(parent.resolvedPath)) {
|
|
420
|
+
moduleRequireGraph.set(parent.resolvedPath, new Set());
|
|
421
|
+
}
|
|
422
|
+
moduleRequireGraph.get(parent.resolvedPath).add(resolvedPath);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function evictContextBoundGraph(rootPath, rootModuleObj, localModuleCache) {
|
|
426
|
+
const stack = [rootPath];
|
|
427
|
+
const visited = new Set();
|
|
428
|
+
while (stack.length) {
|
|
429
|
+
const modulePath = stack.pop();
|
|
430
|
+
if (visited.has(modulePath)) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
visited.add(modulePath);
|
|
434
|
+
contextBoundModulePaths.add(modulePath);
|
|
435
|
+
const moduleObj = modulePath === rootPath ? rootModuleObj : sharedNpmModuleCache.get(modulePath);
|
|
436
|
+
sharedNpmModuleCache.delete(modulePath);
|
|
437
|
+
if (moduleObj) {
|
|
438
|
+
localModuleCache.set(modulePath, moduleObj);
|
|
439
|
+
}
|
|
440
|
+
for (const dep of moduleRequireGraph.get(modulePath) || []) {
|
|
441
|
+
stack.push(dep);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
202
446
|
/**
|
|
203
447
|
* Executes a module in the VM context with caching and special file handling
|
|
204
448
|
* @param {Object} options - Configuration options
|
|
@@ -208,22 +452,82 @@ function loadLocalModule({
|
|
|
208
452
|
function executeModuleInVmContext({
|
|
209
453
|
resolvedPath,
|
|
210
454
|
moduleName,
|
|
211
|
-
isolatedContext,
|
|
212
455
|
collectionPath,
|
|
213
|
-
|
|
456
|
+
isolatedContext,
|
|
457
|
+
localModuleCache,
|
|
458
|
+
cacheModules = false
|
|
214
459
|
}) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
460
|
+
if (cacheModules) {
|
|
461
|
+
recordRequireEdge(resolvedPath);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (!cacheModules) {
|
|
465
|
+
if (localModuleCache.has(resolvedPath)) {
|
|
466
|
+
return localModuleCache.get(resolvedPath).exports;
|
|
467
|
+
}
|
|
468
|
+
return evaluateNpmModule({
|
|
469
|
+
resolvedPath,
|
|
470
|
+
moduleName,
|
|
471
|
+
collectionPath,
|
|
472
|
+
isolatedContext,
|
|
473
|
+
localModuleCache,
|
|
474
|
+
useSharedContext: false,
|
|
475
|
+
cacheModules: false
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const isContextBound = contextBoundModulePaths.has(resolvedPath);
|
|
480
|
+
|
|
481
|
+
if (isContextBound) {
|
|
482
|
+
if (localModuleCache?.has(resolvedPath)) {
|
|
483
|
+
return localModuleCache.get(resolvedPath).exports;
|
|
484
|
+
}
|
|
485
|
+
return evaluateNpmModule({
|
|
486
|
+
resolvedPath,
|
|
487
|
+
moduleName,
|
|
488
|
+
collectionPath,
|
|
489
|
+
isolatedContext,
|
|
490
|
+
localModuleCache,
|
|
491
|
+
useSharedContext: false,
|
|
492
|
+
cacheModules: true
|
|
493
|
+
});
|
|
218
494
|
}
|
|
219
495
|
|
|
496
|
+
if (sharedNpmModuleCache.has(resolvedPath)) {
|
|
497
|
+
return sharedNpmModuleCache.get(resolvedPath).exports;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return evaluateNpmModule({
|
|
501
|
+
resolvedPath,
|
|
502
|
+
moduleName,
|
|
503
|
+
collectionPath,
|
|
504
|
+
isolatedContext,
|
|
505
|
+
localModuleCache,
|
|
506
|
+
useSharedContext: true,
|
|
507
|
+
cacheModules: true
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function evaluateNpmModule({
|
|
512
|
+
resolvedPath,
|
|
513
|
+
moduleName,
|
|
514
|
+
collectionPath,
|
|
515
|
+
isolatedContext,
|
|
516
|
+
localModuleCache,
|
|
517
|
+
useSharedContext,
|
|
518
|
+
cacheModules = false
|
|
519
|
+
}) {
|
|
220
520
|
// Native modules (.node files) - fall back to host require
|
|
221
521
|
// Note: This bypasses VM isolation for native addons.
|
|
222
522
|
// This is intentional - [`developer` mode] node-vm isolation need not be strict for native modules.
|
|
223
523
|
if (resolvedPath.endsWith('.node')) {
|
|
224
524
|
const result = require(resolvedPath);
|
|
225
|
-
|
|
226
|
-
|
|
525
|
+
const moduleObj = { exports: result };
|
|
526
|
+
if (useSharedContext) {
|
|
527
|
+
sharedNpmModuleCache.set(resolvedPath, moduleObj);
|
|
528
|
+
} else {
|
|
529
|
+
localModuleCache.set(resolvedPath, moduleObj);
|
|
530
|
+
}
|
|
227
531
|
return result;
|
|
228
532
|
}
|
|
229
533
|
|
|
@@ -231,37 +535,50 @@ function executeModuleInVmContext({
|
|
|
231
535
|
if (resolvedPath.endsWith('.json')) {
|
|
232
536
|
const jsonContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
233
537
|
const result = JSON.parse(jsonContent);
|
|
234
|
-
|
|
235
|
-
|
|
538
|
+
const moduleObj = { exports: result };
|
|
539
|
+
if (useSharedContext) {
|
|
540
|
+
sharedNpmModuleCache.set(resolvedPath, moduleObj);
|
|
541
|
+
} else {
|
|
542
|
+
localModuleCache.set(resolvedPath, moduleObj);
|
|
543
|
+
}
|
|
236
544
|
return result;
|
|
237
545
|
}
|
|
238
546
|
|
|
239
|
-
// JavaScript files
|
|
240
547
|
const moduleSource = fs.readFileSync(resolvedPath, 'utf8');
|
|
241
548
|
const moduleDir = path.dirname(resolvedPath);
|
|
242
549
|
const moduleObj = { exports: {} };
|
|
550
|
+
const moduleCache = useSharedContext ? sharedNpmModuleCache : localModuleCache;
|
|
243
551
|
|
|
244
|
-
|
|
245
|
-
// This allows re-entrant requires to get partial exports (Node.js behavior)
|
|
246
|
-
// We cache moduleObj (not moduleObj.exports) so that module.exports reassignment works
|
|
247
|
-
localModuleCache.set(resolvedPath, moduleObj);
|
|
552
|
+
moduleCache.set(resolvedPath, moduleObj);
|
|
248
553
|
|
|
249
554
|
const moduleRequire = createNpmModuleRequire({
|
|
250
555
|
collectionPath,
|
|
251
|
-
isolatedContext,
|
|
252
556
|
currentModuleDir: moduleDir,
|
|
253
|
-
|
|
557
|
+
isolatedContext,
|
|
558
|
+
localModuleCache,
|
|
559
|
+
cacheModules
|
|
254
560
|
});
|
|
255
561
|
|
|
562
|
+
const vmContext = useSharedContext ? getSharedNpmContext() : isolatedContext;
|
|
563
|
+
const evalStore = { resolvedPath, touched: new Set() };
|
|
564
|
+
|
|
256
565
|
try {
|
|
257
|
-
// Wrap module code in a function that receives CJS parameters
|
|
258
566
|
const wrappedCode = `(function(module, exports, require, __filename, __dirname) {\n${moduleSource}\n})`;
|
|
259
567
|
const compiledScript = new vm.Script(wrappedCode, { filename: resolvedPath });
|
|
260
|
-
const moduleFunction = compiledScript.runInContext(
|
|
261
|
-
|
|
568
|
+
const moduleFunction = compiledScript.runInContext(vmContext);
|
|
569
|
+
const runModule = () => {
|
|
570
|
+
moduleFunction(moduleObj, moduleObj.exports, moduleRequire, resolvedPath, moduleDir);
|
|
571
|
+
};
|
|
572
|
+
if (cacheModules && useSharedContext) {
|
|
573
|
+
npmModuleEval.run(evalStore, runModule);
|
|
574
|
+
if (evalStore.touched.size > 0) {
|
|
575
|
+
evictContextBoundGraph(resolvedPath, moduleObj, localModuleCache);
|
|
576
|
+
}
|
|
577
|
+
} else {
|
|
578
|
+
runModule();
|
|
579
|
+
}
|
|
262
580
|
} catch (error) {
|
|
263
|
-
|
|
264
|
-
localModuleCache.delete(resolvedPath);
|
|
581
|
+
moduleCache.delete(resolvedPath);
|
|
265
582
|
const stack = error.stack || '';
|
|
266
583
|
throw new Error(`Error loading module ${moduleName}: ${error.message}\nStack: ${stack}`);
|
|
267
584
|
}
|
|
@@ -286,7 +603,8 @@ function loadNpmModule({
|
|
|
286
603
|
collectionPath,
|
|
287
604
|
currentModuleDir,
|
|
288
605
|
isolatedContext,
|
|
289
|
-
localModuleCache
|
|
606
|
+
localModuleCache,
|
|
607
|
+
cacheModules = false
|
|
290
608
|
}) {
|
|
291
609
|
let resolvedPath;
|
|
292
610
|
|
|
@@ -323,9 +641,10 @@ function loadNpmModule({
|
|
|
323
641
|
return executeModuleInVmContext({
|
|
324
642
|
resolvedPath,
|
|
325
643
|
moduleName,
|
|
326
|
-
isolatedContext,
|
|
327
644
|
collectionPath,
|
|
328
|
-
|
|
645
|
+
isolatedContext,
|
|
646
|
+
localModuleCache,
|
|
647
|
+
cacheModules
|
|
329
648
|
});
|
|
330
649
|
}
|
|
331
650
|
|
|
@@ -338,25 +657,43 @@ function loadNpmModule({
|
|
|
338
657
|
* @param {Object} options - Configuration options
|
|
339
658
|
* @returns {Function} Custom require function for npm module dependencies
|
|
340
659
|
*/
|
|
660
|
+
function markParentContextBoundIfNeeded(resolvedPath) {
|
|
661
|
+
const evalStore = npmModuleEval.getStore();
|
|
662
|
+
if (evalStore && contextBoundModulePaths.has(resolvedPath)) {
|
|
663
|
+
evalStore.touched.add('*');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
341
667
|
function createNpmModuleRequire({
|
|
342
668
|
collectionPath,
|
|
343
|
-
isolatedContext,
|
|
344
669
|
currentModuleDir,
|
|
345
|
-
|
|
670
|
+
isolatedContext,
|
|
671
|
+
localModuleCache,
|
|
672
|
+
cacheModules = false
|
|
346
673
|
}) {
|
|
347
674
|
const moduleRequire = nodeModule.createRequire(path.join(currentModuleDir, 'index.js'));
|
|
348
675
|
|
|
349
676
|
return (moduleName) => {
|
|
677
|
+
// Shared parents keep this require for the process lifetime; always prefer the
|
|
678
|
+
// active script run's cache/context so lazy context-bound leaves re-eval.
|
|
679
|
+
const store = cacheModules ? activeScriptContext.getStore() : null;
|
|
680
|
+
const runState = getRunLoaderState(store);
|
|
681
|
+
const runLocalCache = runState?.localModuleCache ?? localModuleCache;
|
|
682
|
+
const runContext = runState?.vmContext ?? isolatedContext;
|
|
683
|
+
|
|
350
684
|
// Handle relative imports within npm module
|
|
351
685
|
if (moduleName.startsWith('./') || moduleName.startsWith('../')) {
|
|
352
686
|
const resolvedPath = moduleRequire.resolve(moduleName);
|
|
353
|
-
|
|
687
|
+
const exports = executeModuleInVmContext({
|
|
354
688
|
resolvedPath,
|
|
355
689
|
moduleName,
|
|
356
|
-
isolatedContext,
|
|
357
690
|
collectionPath,
|
|
358
|
-
|
|
691
|
+
isolatedContext: runContext,
|
|
692
|
+
localModuleCache: runLocalCache,
|
|
693
|
+
cacheModules
|
|
359
694
|
});
|
|
695
|
+
markParentContextBoundIfNeeded(resolvedPath);
|
|
696
|
+
return exports;
|
|
360
697
|
}
|
|
361
698
|
|
|
362
699
|
// Handle builtins
|
|
@@ -368,16 +705,30 @@ function createNpmModuleRequire({
|
|
|
368
705
|
|
|
369
706
|
// Handle npm dependencies - resolve from current module's directory
|
|
370
707
|
const resolvedPath = moduleRequire.resolve(moduleName);
|
|
371
|
-
|
|
708
|
+
const exports = executeModuleInVmContext({
|
|
372
709
|
resolvedPath,
|
|
373
710
|
moduleName,
|
|
374
|
-
isolatedContext,
|
|
375
711
|
collectionPath,
|
|
376
|
-
|
|
712
|
+
isolatedContext: runContext,
|
|
713
|
+
localModuleCache: runLocalCache,
|
|
714
|
+
cacheModules
|
|
377
715
|
});
|
|
716
|
+
markParentContextBoundIfNeeded(resolvedPath);
|
|
717
|
+
return exports;
|
|
378
718
|
};
|
|
379
719
|
}
|
|
380
720
|
|
|
381
721
|
module.exports = {
|
|
382
|
-
createCustomRequire
|
|
722
|
+
createCustomRequire,
|
|
723
|
+
runWithScriptContext,
|
|
724
|
+
getSharedNpmContext,
|
|
725
|
+
attachRunLoaderState,
|
|
726
|
+
__resetNpmModuleStateForTests: () => {
|
|
727
|
+
sharedNpmModuleCache.clear();
|
|
728
|
+
contextBoundModulePaths.clear();
|
|
729
|
+
moduleRequireGraph.clear();
|
|
730
|
+
facades.clear();
|
|
731
|
+
sharedNpmSandbox = null;
|
|
732
|
+
sharedNpmContext = null;
|
|
733
|
+
}
|
|
383
734
|
};
|