node-retrieve-globals
Advanced tools
Comparing version 3.0.1 to 4.0.0
{ | ||
"name": "node-retrieve-globals", | ||
"version": "3.0.1", | ||
"version": "4.0.0", | ||
"description": "Execute a string of JavaScript using Node.js and return the global variable values and functions.", | ||
@@ -27,4 +27,4 @@ "type": "module", | ||
"acorn-walk": "^8.2.0", | ||
"esm-import-transformer": "^3.0.0" | ||
"esm-import-transformer": "^3.0.2" | ||
} | ||
} |
@@ -5,3 +5,3 @@ # node-retrieve-globals | ||
* Supported on Node.js 14 and newer. | ||
* Supported on Node.js 16 and newer. | ||
* Uses `var`, `let`, `const`, `function`, Array and Object [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). | ||
@@ -8,0 +8,0 @@ * Async-friendly but synchronous methods are available. |
@@ -5,3 +5,7 @@ import vm from "vm"; | ||
import { ImportTransformer } from "esm-import-transformer"; | ||
import { createRequire, Module } from "module"; | ||
// TODO option to change `require` home base | ||
const customRequire = createRequire(import.meta.url); | ||
class RetrieveGlobals { | ||
@@ -36,4 +40,3 @@ constructor(code, options) { | ||
if(this.options.transformEsmImports) { | ||
let it = new ImportTransformer(this.originalCode); | ||
this.code = it.transformToDynamicImport(); | ||
this.code = this.transformer.transformToDynamicImport(); | ||
} else { | ||
@@ -44,2 +47,9 @@ this.code = this.originalCode; | ||
get transformer() { | ||
if(!this._transformer) { | ||
this._transformer = new ImportTransformer(this.originalCode); | ||
} | ||
return this._transformer; | ||
} | ||
setAcornOptions(acornOptions) { | ||
@@ -60,3 +70,3 @@ this.acornOptions = Object.assign({ | ||
static _getProxiedContext(context = {}) { | ||
static _getProxiedContext(context = {}, options = {}) { | ||
return new Proxy(context, { | ||
@@ -68,4 +78,8 @@ get(target, propertyName) { | ||
// Re-use the parent `global` https://nodejs.org/api/globals.html | ||
return global[propertyName] || undefined; | ||
if(options.reuseGlobal && Reflect.has(global, propertyName)) { | ||
return global[propertyName]; | ||
} | ||
if(options.addRequire && propertyName === "require") { | ||
return customRequire; | ||
} | ||
} | ||
@@ -103,10 +117,34 @@ }); | ||
static _getCode(code, options) { | ||
let { async: isAsync, globalNames } = Object.assign({ | ||
let { async: isAsync, globalNames, experimentalModuleApi, data } = Object.assign({ | ||
async: true | ||
}, options); | ||
return `(${isAsync ? "async " : ""}function() { | ||
let prefix = ""; | ||
let argKeys = ""; | ||
let argValues = ""; | ||
// Don’t use this when vm.Module is stable (or if the code doesn’t have any imports!) | ||
if(experimentalModuleApi) { | ||
prefix = "module.exports = "; | ||
if(typeof data === "object") { | ||
let dataKeys = Object.keys(data); | ||
if(dataKeys) { | ||
argKeys = `{${dataKeys.join(",")}}`; | ||
argValues = JSON.stringify(data, function replacer(key, value) { | ||
if(typeof value === "function") { | ||
throw new Error(`When using \`experimentalModuleApi\`, context data must be JSON.stringify friendly. The "${key}" property was type \`function\`.`); | ||
} | ||
return value; | ||
}); | ||
} | ||
} | ||
} | ||
let finalCode = `${prefix}(${isAsync ? "async " : ""}function(${argKeys}) { | ||
${code} | ||
${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames) : ""} | ||
})();`; | ||
})(${argValues});`; | ||
return finalCode; | ||
} | ||
@@ -119,11 +157,37 @@ | ||
dynamicImport, | ||
addRequire, | ||
experimentalModuleApi, | ||
} = Object.assign({ | ||
// defaults | ||
async: true, | ||
reuseGlobal: false, | ||
// adds support for `require` | ||
addRequire: false, | ||
// allows dynamic import in `vm` (requires --experimental-vm-modules in Node v20.10+) | ||
// https://github.com/nodejs/node/issues/51154 | ||
// TODO Another workaround possibility: We could use `import` outside of `vm` and inject the dependencies into context `data` | ||
dynamicImport: false, | ||
// Use Module._compile instead of vm | ||
// Workaround for: https://github.com/zachleat/node-retrieve-globals/issues/2 | ||
// Warning: This method requires input `data` to be JSON stringify friendly. | ||
// Only use this if the code has `import`: | ||
experimentalModuleApi: this.transformer.hasImports(), | ||
}, options); | ||
if(reuseGlobal) { | ||
data = RetrieveGlobals._getProxiedContext(data || {}); | ||
// these things are already supported by Module._compile | ||
if(experimentalModuleApi) { | ||
addRequire = false; | ||
dynamicImport = false; | ||
} | ||
if(reuseGlobal || addRequire) { | ||
// Re-use the parent `global` https://nodejs.org/api/globals.html | ||
data = RetrieveGlobals._getProxiedContext(data || {}, { | ||
reuseGlobal, | ||
addRequire, | ||
}); | ||
} else { | ||
@@ -134,3 +198,3 @@ data = data || {}; | ||
let context; | ||
if(vm.isContext(data)) { | ||
if(experimentalModuleApi || vm.isContext(data)) { | ||
context = data; | ||
@@ -146,3 +210,3 @@ } else { | ||
parseCode = RetrieveGlobals._getCode(this.code, { | ||
async: isAsync | ||
async: isAsync, | ||
}, this.cache); | ||
@@ -201,2 +265,4 @@ | ||
globalNames, | ||
experimentalModuleApi, | ||
data: context, | ||
}); | ||
@@ -213,2 +279,7 @@ | ||
if(experimentalModuleApi) { | ||
let m = new Module(); | ||
m._compile(execCode, import.meta.url); | ||
return m.exports; | ||
} | ||
return vm.runInContext(execCode, context, execOptions); | ||
@@ -215,0 +286,0 @@ } catch(e) { |
102
test/test.js
@@ -103,11 +103,2 @@ import test from "ava"; | ||
test("dynamic import", async t => { | ||
let vm = new RetrieveGlobals(`const { noop } = await import("@zachleat/noop");`); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
dynamicImport: true | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
}); | ||
test("global: same console.log", async t => { | ||
@@ -141,2 +132,30 @@ let vm = new RetrieveGlobals(`const b = console.log`); | ||
test("`require` Compatibility", async t => { | ||
let vm = new RetrieveGlobals(`const { noop } = require("@zachleat/noop"); | ||
const b = 1;`); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
addRequire: true, | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
t.is(ret.b, 1); | ||
}); | ||
// TODO we detect `await import()` and use `experimentalModuleApi: true` | ||
test.skip("dynamic import (no code transformation) (requires --experimental-vm-modules in Node v20.10)", async t => { | ||
let vm = new RetrieveGlobals(`const { noop } = await import("@zachleat/noop");`); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
dynamicImport: true | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
}); | ||
test("dynamic import (no code transformation) (experimentalModuleApi explicit true)", async t => { | ||
let vm = new RetrieveGlobals(`const { noop } = await import("@zachleat/noop");`); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
experimentalModuleApi: true, // Needs to be true here because there are no top level `import` | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
}); | ||
// This would require --experimental-vm-modules in Node v20.10, but instead falls back to `experimentalModuleApi` automatically | ||
test("ESM import", async t => { | ||
@@ -148,6 +167,69 @@ let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop"; | ||
let ret = await vm.getGlobalContext(undefined, { | ||
dynamicImport: true | ||
// experimentalModuleApi: true, // implied | ||
dynamicImport: true, | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
t.is(ret.b, 1); | ||
}); | ||
// This would require --experimental-vm-modules in Node v20.10, but instead falls back to `experimentalModuleApi` automatically | ||
test("ESM import (experimentalModuleApi implied true)", async t => { | ||
let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop"; | ||
const b = 1;`, { | ||
transformEsmImports: true, | ||
}); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
// experimentalModuleApi: true, | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
t.is(ret.b, 1); | ||
}); | ||
// This would require --experimental-vm-modules in Node v20.10, but instead falls back to `experimentalModuleApi` automatically | ||
test("ESM import (experimentalModuleApi explicit true)", async t => { | ||
let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop"; | ||
const b = 1;`, { | ||
transformEsmImports: true, | ||
}); | ||
let ret = await vm.getGlobalContext(undefined, { | ||
experimentalModuleApi: true, | ||
}); | ||
t.is(typeof ret.noop, "function"); | ||
t.is(ret.b, 1); | ||
}); | ||
// This does not require --experimental-vm-modules in Node v20.10+ as it has no imports | ||
test("No imports, with data", async t => { | ||
let vm = new RetrieveGlobals(`const b = inputData;`, { | ||
transformEsmImports: true, | ||
}); | ||
let ret = await vm.getGlobalContext({ inputData: "hi" }, { | ||
// experimentalModuleApi: true, // implied false | ||
}); | ||
t.is(ret.b, "hi"); | ||
}); | ||
// This does not require --experimental-vm-modules in Node v20.10+ as it has no imports | ||
test("No imports, with JSON unfriendly data", async t => { | ||
let vm = new RetrieveGlobals(`const b = fn;`, { | ||
transformEsmImports: true, | ||
}); | ||
let ret = await vm.getGlobalContext({ fn: function() {} }, { | ||
// experimentalModuleApi: true, // implied false | ||
}); | ||
t.is(typeof ret.b, "function"); | ||
}); | ||
// This requires --experimental-vm-modules in Node v20.10+ and uses the experimentalModuleApi because it has imports | ||
test("With imports, with JSON unfriendly data", async t => { | ||
let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop"; | ||
const b = fn;`, { | ||
transformEsmImports: true, | ||
}); | ||
await t.throwsAsync(async () => { | ||
let ret = await vm.getGlobalContext({ fn: function() {} }, { | ||
// experimentalModuleApi: true, // implied true | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19690
458
2