diff options
| author | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
|---|---|---|
| committer | Shipwreckt <me@shipwreckt.co.uk> | 2025-10-31 20:02:14 +0000 |
| commit | 7a52ddeba2a68388b544f529d2d92104420f77b0 (patch) | |
| tree | 15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/node-retrieve-globals | |
| parent | 53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff) | |
Changed from static to 11ty!
Diffstat (limited to 'node_modules/node-retrieve-globals')
| -rw-r--r-- | node_modules/node-retrieve-globals/LICENSE | 21 | ||||
| -rw-r--r-- | node_modules/node-retrieve-globals/README.md | 91 | ||||
| -rw-r--r-- | node_modules/node-retrieve-globals/package.json | 30 | ||||
| -rw-r--r-- | node_modules/node-retrieve-globals/retrieveGlobals.js | 376 | ||||
| -rw-r--r-- | node_modules/node-retrieve-globals/util/getWorkingDirectory.js | 18 | ||||
| -rw-r--r-- | node_modules/node-retrieve-globals/util/vmModules.js | 23 |
6 files changed, 559 insertions, 0 deletions
diff --git a/node_modules/node-retrieve-globals/LICENSE b/node_modules/node-retrieve-globals/LICENSE new file mode 100644 index 0000000..8706985 --- /dev/null +++ b/node_modules/node-retrieve-globals/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Zach Leatherman @zachleat + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/node-retrieve-globals/README.md b/node_modules/node-retrieve-globals/README.md new file mode 100644 index 0000000..eaf8f1e --- /dev/null +++ b/node_modules/node-retrieve-globals/README.md @@ -0,0 +1,91 @@ +# node-retrieve-globals + +Execute a string of JavaScript using Node.js and return the global variable values and functions. + +* 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). +* 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)) + * [WebC’s `<script webc:setup>`](https://www.11ty.dev/docs/languages/webc/#using-java-script-to-setup-your-component) + +## Installation + +Available on [npm](https://www.npmjs.com/package/node-retrieve-globals) + +``` +npm install node-retrieve-globals +``` + +## Usage + +Works from Node.js with ESM and CommonJS: + +```js +import { RetrieveGlobals } from "node-retrieve-globals"; +// const { RetrieveGlobals } = await import("node-retrieve-globals"); +``` + +And then: + +```js +let code = `var a = 1; +const b = "hello"; + +function hello() {}`; + +let vm = new RetrieveGlobals(code); + +await vm.getGlobalContext(); +``` + +Returns: + +```js +{ a: 1, b: "hello", hello: function hello() {} } +``` + +### Pass in your own Data and reference it in the JavaScript code + +```js +let code = `let ref = myData;`; + +let vm = new RetrieveGlobals(code); + +await vm.getGlobalContext({ myData: "hello" }); +``` + +Returns: + +```js +{ ref: "hello" } +``` + +### Advanced options + +```js +// Defaults shown +let options = { + reuseGlobal: false, // re-use Node.js `global`, important if you want `console.log` to log to your console as expected. + 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) +}; + +await vm.getGlobalContext({}, options); +``` + +## Changelog + +* `v6.0.0` Changes `import` and `require` to be project relative (not relative to this package on the file system). +* `v5.0.0` Removes sync API, swap to async-only. Better compatibility with `--experimental-vm-modules` Node flag. +* `v4.0.0` Swap to use `Module._compile` as a workaround for #2 (Node regression with experimental modules API in Node v20.10+) +* `v3.0.0` ESM-only package. Node 16+
\ No newline at end of file diff --git a/node_modules/node-retrieve-globals/package.json b/node_modules/node-retrieve-globals/package.json new file mode 100644 index 0000000..b882306 --- /dev/null +++ b/node_modules/node-retrieve-globals/package.json @@ -0,0 +1,30 @@ +{ + "name": "node-retrieve-globals", + "version": "6.0.1", + "description": "Execute a string of JavaScript using Node.js and return the global variable values and functions.", + "type": "module", + "main": "retrieveGlobals.js", + "scripts": { + "test": "npx ava && cross-env NODE_OPTIONS='--experimental-vm-modules' npx ava" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/zachleat/node-retrieve-globals.git" + }, + "author": { + "name": "Zach Leatherman", + "email": "zachleatherman@gmail.com", + "url": "https://zachleat.com/" + }, + "license": "MIT", + "devDependencies": { + "@zachleat/noop": "^1.0.4", + "ava": "^6.2.0", + "cross-env": "^7.0.3" + }, + "dependencies": { + "acorn": "^8.14.1", + "acorn-walk": "^8.3.4", + "esm-import-transformer": "^3.0.3" + } +} diff --git a/node_modules/node-retrieve-globals/retrieveGlobals.js b/node_modules/node-retrieve-globals/retrieveGlobals.js new file mode 100644 index 0000000..64571a8 --- /dev/null +++ b/node_modules/node-retrieve-globals/retrieveGlobals.js @@ -0,0 +1,376 @@ +import vm from "vm"; +import * as acorn from "acorn"; +import * as walk from "acorn-walk"; +import { ImportTransformer } from "esm-import-transformer"; +import { createRequire, Module } from "module"; + +import { getWorkingDirectory } from "./util/getWorkingDirectory.js"; +import { isSupported } from "./util/vmModules.js"; + +const IS_VM_MODULES_SUPPORTED = isSupported(); + +// `import` and `require` should both be relative to working directory (not this file) +const WORKING_DIRECTORY = getWorkingDirectory(); + +// TODO (feature) option to change `require` home base +const customRequire = createRequire(WORKING_DIRECTORY); + +class RetrieveGlobals { + constructor(code, options) { + this.originalCode = code; + + // backwards compat + if(typeof options === "string") { + options = { + filePath: options + }; + } + + this.options = Object.assign({ + filePath: null, + transformEsmImports: false, + }, options); + + if(IS_VM_MODULES_SUPPORTED) { + // Override: no code transformations if vm.Module works + this.options.transformEsmImports = false; + } + + // set defaults + let acornOptions = {}; + 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. + // Doesn’t currently work with aliases (mod as name) or namespaced imports (* as name). + if(this.options.transformEsmImports) { + this.code = this.transformer.transformToDynamicImport(); + } else { + this.code = this.originalCode; + } + } + + get transformer() { + if(!this._transformer) { + this._transformer = new ImportTransformer(this.originalCode); + } + return this._transformer; + } + + setAcornOptions(acornOptions) { + this.acornOptions = Object.assign({ + ecmaVersion: "latest", + }, acornOptions ); + } + + setCreateContextOptions(contextOptions) { + this.createContextOptions = Object.assign({ + codeGeneration: { + strings: false, + wasm: false, + } + }, contextOptions ); + } + + static _getProxiedContext(context = {}, options = {}) { + return new Proxy(context, { + get(target, propertyName) { + if(Reflect.has(target, propertyName)) { + return Reflect.get(target, propertyName); + } + + if(options.reuseGlobal && Reflect.has(global, propertyName)) { + return global[propertyName]; + } + if(options.addRequire && propertyName === "require") { + return customRequire; + } + } + }); + } + + // We prune function and variable declarations that aren’t globally declared + // (our acorn walker could be improved to skip non-global declarations, but this method is easier for now) + static _getGlobalVariablesReturnString(names, mode = "cjs") { + let s = [`let __globals = {};`]; + for(let name of names) { + s.push(`if( typeof ${name} !== "undefined") { __globals.${name} = ${name}; }`); + } + return `${s.join("\n")};${mode === "esm" ? "\nexport default __globals;" : "return __globals;"}` + } + + _setContextPrototype(context) { + // Context will fail isPlainObject and won’t be merged in the data cascade properly without this prototype set + // See https://github.com/11ty/eleventy-utils/blob/main/src/IsPlainObject.js + if(!context || typeof context !== "object" || Array.isArray(context)) { + return; + } + if(context instanceof Date) { + return; + } + + if(!Object.getPrototypeOf(context).isPrototypeOf(Object.create({}))) { + Object.setPrototypeOf(context, Object.prototype); + // Go deep + for(let key in context) { + this._setContextPrototype(context[key]); + } + } + } + + _getCode(code, options) { + let { async: isAsync, globalNames, experimentalModuleApi, data } = Object.assign({ + async: true + }, options); + + if(IS_VM_MODULES_SUPPORTED) { + return `${code} + +${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames, "esm") : ""}`; + } + + 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; + }); + } + } + } + + return `${prefix}(${isAsync ? "async " : ""}function(${argKeys}) { + ${code} + ${globalNames ? RetrieveGlobals._getGlobalVariablesReturnString(globalNames, "cjs") : ""} +})(${argValues});`; + } + + 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 { + async: isAsync, + reuseGlobal, + 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. + // 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); + + 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) { + addRequire = false; + dynamicImport = false; + } + + if(reuseGlobal || addRequire) { + // Re-use the parent `global` https://nodejs.org/api/globals.html + data = RetrieveGlobals._getProxiedContext(data || {}, { + reuseGlobal, + addRequire, + }); + } + + if(!data) { + data = {}; + } + + let context; + if(experimentalModuleApi || vm.isContext(data)) { + context = data; + } else { + context = vm.createContext(data, this.createContextOptions); + } + + let parseCode; + let globalNames; + + try { + parseCode = this._getCode(this.code, { + async: isAsync, + }); + + let parsedAst = acorn.parse(parseCode, this.acornOptions); + globalNames = this.getGlobalNames(parsedAst); + } catch(e) { + throw this._getParseError(parseCode, e); + } + + try { + let execCode = this._getCode(this.code, { + async: isAsync, + globalNames, + experimentalModuleApi, + data: context, + }); + + if(experimentalModuleApi) { + let m = new Module(); + m._compile(execCode, WORKING_DIRECTORY); + return m.exports; + } + + let execOptions = {}; + if(dynamicImport) { + // Warning: this option is part of the experimental modules API + execOptions.importModuleDynamically = (specifier) => import(specifier); + } + + if(IS_VM_MODULES_SUPPORTED) { + // options.initializeImportMeta + let m = new vm.SourceTextModule(execCode, { + context, + initializeImportMeta: (meta, module) => { + meta.url = this.options.filePath || WORKING_DIRECTORY || 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) { + 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} + +${this.code}`); + } + } + + 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, + }, options)); + + this._setContextPrototype(ret); + + return ret; + } +} + +export { RetrieveGlobals }; diff --git a/node_modules/node-retrieve-globals/util/getWorkingDirectory.js b/node_modules/node-retrieve-globals/util/getWorkingDirectory.js new file mode 100644 index 0000000..2231341 --- /dev/null +++ b/node_modules/node-retrieve-globals/util/getWorkingDirectory.js @@ -0,0 +1,18 @@ +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +function addTrailingSlash(path) { + if(path.endsWith("/")) { + return path; + } + return path + "/"; +} + + +function getWorkingDirectory() { + // Trailing slash required + // `import` and `require` should both be relative to working directory (not this file) + return addTrailingSlash(pathToFileURL(path.resolve(".")).toString()); +} + +export { getWorkingDirectory };
\ No newline at end of file diff --git a/node_modules/node-retrieve-globals/util/vmModules.js b/node_modules/node-retrieve-globals/util/vmModules.js new file mode 100644 index 0000000..7643eff --- /dev/null +++ b/node_modules/node-retrieve-globals/util/vmModules.js @@ -0,0 +1,23 @@ +import vm from "vm"; + +function isSupported() { + // node --experimental-vm-modules … + if(process.execArgv.find(entry => entry == "--experimental-vm-modules")) { + return true; + } + // NODE_OPTIONS='--experimental-vm-modules' node … + if((process.env?.NODE_OPTIONS || "").split(" ").find(entry => entry == "--experimental-vm-modules")) { + return true; + } + + // Feature test for a future when --experimental-vm-modules is not needed + // and vm.Module is stable: + try { + new vm.SourceTextModule(`/* hi */`); + return true; + } catch(e) {} + + return false; +} + +export { isSupported };
\ No newline at end of file |
