Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

node-retrieve-globals

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-retrieve-globals - npm Package Compare versions

Comparing version 4.0.0 to 5.0.0

vmModules.js

9

package.json
{
"name": "node-retrieve-globals",
"version": "4.0.0",
"version": "5.0.0",
"description": "Execute a string of JavaScript using Node.js and return the global variable values and functions.",

@@ -8,3 +8,3 @@ "type": "module",

"scripts": {
"test": "npx ava"
"test": "npx ava && cross-env NODE_OPTIONS='--experimental-vm-modules' npx ava"
},

@@ -23,9 +23,10 @@ "repository": {

"@zachleat/noop": "^1.0.3",
"ava": "^5.2.0"
"ava": "^6.0.1",
"cross-env": "^7.0.3"
},
"dependencies": {
"acorn": "^8.8.2",
"acorn-walk": "^8.2.0",
"acorn-walk": "^8.3.1",
"esm-import-transformer": "^3.0.2"
}
}

@@ -7,10 +7,12 @@ # node-retrieve-globals

* Uses `var`, `let`, `const`, `function`, Array and Object [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
* Async-friendly but synchronous methods are available.
* Async-only as of v5.0.
* Can return any valid JS data type (including functions).
* Can provide an external data object as context to the local execution scope
* Transforms ESM import statements to work with current CommonJS limitations in Node’s `vm`.
* Uses [Node’s `vm` module to execute JavaScript](https://nodejs.org/api/vm.html#vmruninthiscontextcode-options)
* ⚠️ The `node:vm` module is not a security mechanism. Do not use it to run untrusted code.
* `codeGeneration` (e.g. `eval`) is disabled by default; use `setCreateContextOptions({codeGeneration: { strings: true, wasm: true } })` to re-enable.
* Works _with or without_ `--experimental-vm-modules` flag (for `vm.Module` support). _(v5.0.0 and newer)_
* Future-friendly feature tests for when `vm.Module` is stable and `--experimental-vm-modules` is no longer necessary. _(v5.0.0 and newer)_
* In use on:
* [JavaScript in Eleventy Front Matter](https://www.11ty.dev/docs/data-frontmatter-customize/#example-use-javascript-in-your-front-matter) (and [Demo](https://github.com/11ty/demo-eleventy-js-front-matter))

@@ -47,5 +49,2 @@ * [WebC’s `<script webc:setup>`](https://www.11ty.dev/docs/languages/webc/#using-javascript-to-setup-your-component-webcsetup)

await vm.getGlobalContext();
// or sync:
// vm.getGlobalContextSync();
```

@@ -67,5 +66,2 @@

await vm.getGlobalContext({ myData: "hello" });
// or sync:
// vm.getGlobalContextSync({ myData: "hello" });
```

@@ -86,2 +82,4 @@

dynamicImport: false, // allows `import()`
addRequire: false, // allows `require()`
experimentalModuleApi: false, // uses Module#_compile instead of `vm` (you probably don’t want this and it is bypassed by default when vm.Module is supported)
};

@@ -88,0 +86,0 @@

@@ -7,3 +7,7 @@ import vm from "vm";

// TODO option to change `require` home base
import { isSupported } from "./vmModules.js";
const IS_VM_MODULES_SUPPORTED = isSupported();
// TODO (feature) option to change `require` home base
const customRequire = createRequire(import.meta.url);

@@ -27,13 +31,17 @@

if(IS_VM_MODULES_SUPPORTED) {
// Override: no code transformations if vm.Module works
this.options.transformEsmImports = false;
}
// set defaults
let acornOptions = {};
if(this.options.transformEsmImports) {
if(IS_VM_MODULES_SUPPORTED || this.options.transformEsmImports) {
acornOptions.sourceType = "module";
}
this.setAcornOptions(acornOptions);
this.setCreateContextOptions();
// transform `import ___ from ___`
// to `const ___ = await import(___)`
// to emulate *some* import syntax.
// transform `import ___ from ___` to `const ___ = await import(___)` to emulate *some* import syntax.
// Doesn’t currently work with aliases (mod as name) or namespaced imports (* as name).

@@ -88,8 +96,8 @@ if(this.options.transformEsmImports) {

// (our acorn walker could be improved to skip non-global declarations, but this method is easier for now)
static _getGlobalVariablesReturnString(names) {
let s = [`let globals = {};`];
static _getGlobalVariablesReturnString(names, mode = "cjs") {
let s = [`let __globals = {};`];
for(let name of names) {
s.push(`if( typeof ${name} !== "undefined") { globals.${name} = ${name}; }`);
s.push(`if( typeof ${name} !== "undefined") { __globals.${name} = ${name}; }`);
}
return `${s.join("\n")}; return globals;`
return `${s.join("\n")};${mode === "esm" ? "\nexport default __globals;" : "return __globals;"}`
}

@@ -114,3 +122,3 @@

static _getCode(code, options) {
_getCode(code, options) {
let { async: isAsync, globalNames, experimentalModuleApi, data } = Object.assign({

@@ -120,3 +128,9 @@ async: true

let prefix = "";
if(IS_VM_MODULES_SUPPORTED) {
return `${code}
${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames, "esm") : ""}`;
}
let prefix = [];
let argKeys = "";

@@ -143,11 +157,65 @@ let argValues = "";

let finalCode = `${prefix}(${isAsync ? "async " : ""}function(${argKeys}) {
return `${prefix}(${isAsync ? "async " : ""}function(${argKeys}) {
${code}
${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames) : ""}
${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames, "cjs") : ""}
})(${argValues});`;
return finalCode;
}
_getGlobalContext(data, options) {
getGlobalNames(parsedAst) {
let globalNames = new Set();
let types = {
FunctionDeclaration(node) {
globalNames.add(node.id.name);
},
VariableDeclarator(node) {
// destructuring assignment Array
if(node.id.type === "ArrayPattern") {
for(let prop of node.id.elements) {
if(prop.type === "Identifier") {
globalNames.add(prop.name);
}
}
} else if(node.id.type === "ObjectPattern") {
// destructuring assignment Object
for(let prop of node.id.properties) {
if(prop.type === "Property") {
globalNames.add(prop.value.name);
}
}
} else if(node.id.name) {
globalNames.add(node.id.name);
}
},
// if imports aren’t being transformed to variables assignment, we need those too
ImportSpecifier(node) {
globalNames.add(node.imported.name);
}
};
walk.simple(parsedAst, types);
return globalNames;
}
_getParseError(code, err) {
// Acorn parsing error on script
let metadata = [];
if(this.options.filePath) {
metadata.push(`file: ${this.options.filePath}`);
}
if(err?.loc?.line) {
metadata.push(`line: ${err.loc.line}`);
}
if(err?.loc?.column) {
metadata.push(`column: ${err.loc.column}`);
}
return new Error(`Had trouble parsing with "acorn"${metadata.length ? ` (${metadata.join(", ")})` : ""}:
Message: ${err.message}
${code}`);
}
async _getGlobalContext(data, options) {
let {

@@ -176,7 +244,13 @@ async: isAsync,

// Warning: This method requires input `data` to be JSON stringify friendly.
// Only use this if the code has `import`:
experimentalModuleApi: this.transformer.hasImports(),
// Don’t use this if vm.Module is supported
// Don’t use this if the code does not contain `import`s
experimentalModuleApi: !IS_VM_MODULES_SUPPORTED && this.transformer.hasImports(),
}, options);
// these things are already supported by Module._compile
if(IS_VM_MODULES_SUPPORTED) {
// Override: don’t use this when modules are allowed.
experimentalModuleApi = false;
}
// These options are already supported by Module._compile
if(experimentalModuleApi) {

@@ -193,6 +267,8 @@ addRequire = false;

});
} else {
data = data || {};
}
if(!data) {
data = {};
}
let context;

@@ -209,55 +285,14 @@ if(experimentalModuleApi || vm.isContext(data)) {

try {
parseCode = RetrieveGlobals._getCode(this.code, {
parseCode = this._getCode(this.code, {
async: isAsync,
}, this.cache);
});
let parsed = acorn.parse(parseCode, this.acornOptions);
globalNames = new Set();
walk.simple(parsed, {
FunctionDeclaration(node) {
globalNames.add(node.id.name);
},
VariableDeclarator(node) {
// destructuring assignment Array
if(node.id.type === "ArrayPattern") {
for(let prop of node.id.elements) {
if(prop.type === "Identifier") {
globalNames.add(prop.name);
}
}
} else if(node.id.type === "ObjectPattern") {
// destructuring assignment Object
for(let prop of node.id.properties) {
if(prop.type === "Property") {
globalNames.add(prop.value.name);
}
}
} else if(node.id.name) {
globalNames.add(node.id.name);
}
}
});
let parsedAst = acorn.parse(parseCode, this.acornOptions);
globalNames = this.getGlobalNames(parsedAst);
} catch(e) {
// Acorn parsing error on script
let metadata = [];
if(this.options.filePath) {
metadata.push(`file: ${this.options.filePath}`);
}
if(e?.loc?.line) {
metadata.push(`line: ${e.loc.line}`);
}
if(e?.loc?.column) {
metadata.push(`column: ${e.loc.column}`);
}
throw new Error(`Had trouble parsing with "acorn"${metadata.length ? ` (${metadata.join(", ")})` : ""}:
Message: ${e.message}
${parseCode}`);
throw this._getParseError(parseCode, e);
}
try {
let execCode = RetrieveGlobals._getCode(this.code, {
let execCode = this._getCode(this.code, {
async: isAsync,

@@ -269,4 +304,9 @@ globalNames,

if(experimentalModuleApi) {
let m = new Module();
m._compile(execCode, import.meta.url);
return m.exports;
}
let execOptions = {};
if(dynamicImport) {

@@ -279,10 +319,46 @@ // Warning: this option is part of the experimental modules API

if(experimentalModuleApi) {
let m = new Module();
m._compile(execCode, import.meta.url);
return m.exports;
if(IS_VM_MODULES_SUPPORTED) {
// options.initializeImportMeta
let m = new vm.SourceTextModule(execCode, {
context,
initializeImportMeta: (meta, module) => {
meta.url = this.options.filePath || module.identifier;
},
...execOptions,
});
// Thank you! https://stackoverflow.com/a/73282303/16711
await m.link(async (specifier, referencingModule) => {
const mod = await import(specifier);
const exportNames = Object.keys(mod);
return new vm.SyntheticModule(
exportNames,
function () {
exportNames.forEach(key => {
this.setExport(key, mod[key])
});
},
{
identifier: specifier,
context: referencingModule.context
}
);
});
await m.evaluate();
// TODO (feature) incorporate other esm `exports` here
return m.namespace.default;
}
return vm.runInContext(execCode, context, execOptions);
} catch(e) {
throw new Error(`Had trouble executing script in Node:
let type = "cjs";
if(IS_VM_MODULES_SUPPORTED) {
type = "esm";
} else if(experimentalModuleApi) {
type = "cjs-experimental";
}
throw new Error(`Had trouble executing Node script (type: ${type}):
Message: ${e.message}

@@ -294,14 +370,6 @@

getGlobalContextSync(data, options) {
let ret = this._getGlobalContext(data, Object.assign({
async: false,
}, options));
this._setContextPrototype(ret);
return ret;
}
async getGlobalContext(data, options) {
let ret = await this._getGlobalContext(data, Object.assign({
// whether or not the target code is executed asynchronously
// note that vm.Module will always be async-friendly
async: true,

@@ -308,0 +376,0 @@ }, options));

import test from "ava";
import { RetrieveGlobals } from "../retrieveGlobals.js";
import { isSupported } from "../vmModules.js";
test("var", t => {
let vm = new RetrieveGlobals("var a = 1;");
t.deepEqual(vm.getGlobalContextSync(), { a: 1 });
const IS_VM_MODULES_SUPPORTED = isSupported();
test("var", async t => {
let vm = new RetrieveGlobals(`var a = 1;`);
t.deepEqual(await vm.getGlobalContext(), { a: 1 });
});
test("isPlainObject", t => {
test("isPlainObject", async t => {
// from eleventy-utils

@@ -20,6 +23,6 @@ function isPlainObject(value) {

let vm = new RetrieveGlobals("var a = 1;");
t.true(isPlainObject(vm.getGlobalContextSync()));
t.true(isPlainObject(await vm.getGlobalContext()));
});
test("isPlainObject deep", t => {
test("isPlainObject deep", async t => {
// from eleventy-utils

@@ -35,3 +38,3 @@ function isPlainObject(value) {

let vm = new RetrieveGlobals("var a = { b: 1, c: { d: {} } };");
let obj = vm.getGlobalContextSync();
let obj = await vm.getGlobalContext();
t.true(isPlainObject(obj.a.c));

@@ -41,3 +44,3 @@ t.true(isPlainObject(obj.a.c.d));

test("isPlainObject deep circular", t => {
test("isPlainObject deep circular", async t => {
// from eleventy-utils

@@ -57,3 +60,3 @@ function isPlainObject(value) {

`);
let obj = vm.getGlobalContextSync();
let obj = await vm.getGlobalContext();
t.true(isPlainObject(obj.a.b));

@@ -64,20 +67,20 @@ t.true(isPlainObject(obj.b.b));

test("var with data", t => {
test("var with data", async t => {
let vm = new RetrieveGlobals("var a = b;");
t.deepEqual(vm.getGlobalContextSync({ b: 2 }), { a: 2 });
t.deepEqual(await vm.getGlobalContext({ b: 2 }), { a: 2 });
});
test("let with data", t => {
test("let with data", async t => {
let vm = new RetrieveGlobals("let a = b;");
t.deepEqual(vm.getGlobalContextSync({ b: 2 }), { a: 2 });
t.deepEqual(await vm.getGlobalContext({ b: 2 }), { a: 2 });
});
test("const with data", t => {
test("const with data", async t => {
let vm = new RetrieveGlobals("const a = b;");
t.deepEqual(vm.getGlobalContextSync({ b: 2 }), { a: 2 });
t.deepEqual(await vm.getGlobalContext({ b: 2 }), { a: 2 });
});
test("function", t => {
test("function", async t => {
let vm = new RetrieveGlobals("function testFunction() {}");
let ret = vm.getGlobalContextSync();
let ret = await vm.getGlobalContext();
t.true(typeof ret.testFunction === "function");

@@ -122,3 +125,3 @@ });

test("global: Same URL", async t => {
let vm = new RetrieveGlobals(`const b = URL`);
let vm = new RetrieveGlobals(`const b = URL;`);
let ret = await vm.getGlobalContext(undefined, {

@@ -130,5 +133,5 @@ reuseGlobal: true

test("return array", t => {
test("return array", async t => {
let vm = new RetrieveGlobals("let b = [1,2,3];");
let globals = vm.getGlobalContextSync();
let globals = await vm.getGlobalContext();
t.true(Array.isArray(globals.b));

@@ -148,10 +151,12 @@ t.deepEqual(globals.b, [1,2,3]);

// 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
// Works with --experimental-vm-modules, remove this when modules are stable
if(IS_VM_MODULES_SUPPORTED) {
test("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");
});
t.is(typeof ret.noop, "function");
});
}

@@ -161,2 +166,3 @@ test("dynamic import (no code transformation) (experimentalModuleApi explicit true)", async t => {

let ret = await vm.getGlobalContext(undefined, {
dynamicImport: true, // irrelevant for fallback case, important for --experimental-vm-modules support case
experimentalModuleApi: true, // Needs to be true here because there are no top level `import`

@@ -196,8 +202,9 @@ });

test("ESM import (experimentalModuleApi explicit true)", async t => {
// let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop";
let vm = new RetrieveGlobals(`import { noop } from "@zachleat/noop";
const b = 1;`, {
transformEsmImports: true,
transformEsmImports: true, // overridden to false when --experimental-vm-modules
});
let ret = await vm.getGlobalContext(undefined, {
experimentalModuleApi: true,
experimentalModuleApi: true, // overridden to false when --experimental-vm-modules
});

@@ -237,7 +244,28 @@ t.is(typeof ret.noop, "function");

});
await t.throwsAsync(async () => {
if(IS_VM_MODULES_SUPPORTED) {
// Works fine with --experimental-vm-modules
let ret = await vm.getGlobalContext({ fn: function() {} }, {
// experimentalModuleApi: true, // implied true
// experimentalModuleApi: true, // implied false
});
t.is(typeof ret.b, "function");
} else {
// This throws if --experimental-vm-modules not set
await t.throwsAsync(async () => {
let ret = await vm.getGlobalContext({ fn: function() {} }, {
// experimentalModuleApi: true, // implied true
});
});
}
});
if(IS_VM_MODULES_SUPPORTED) {
test("import.meta.url works", async t => {
let vm = new RetrieveGlobals(`const b = import.meta.url;`, {
filePath: import.meta.url
});
let ret = await vm.getGlobalContext();
t.is(ret.b, import.meta.url);
});
});
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc