UNPKG

node-version-call

Version:

Call a function in a specific version of node

112 lines (111 loc) 4.73 kB
import pathKey from 'env-path-key'; import Module from 'module'; import { loadModuleSync } from 'module-compat'; import { spawnOptions } from 'node-version-utils'; import semver from 'semver'; import resolveVersion from './lib/resolveVersion.js'; const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require; const SLEEP_MS = 60; /** * Create a bound async caller for a specific version and worker. * Installs the version on first call (lazy) and caches it. * * @param version - Version spec ('v18.0.0', '>=18', '20') * @param workerPath - Path to the file to execute * @param options - Execution options * @returns A function that calls the worker with callback or Promise */ let functionExec = null; export default function bind(version, workerPath, options) { const opts = options || {}; const callbacks = opts.callbacks; const useSpawnOptions = opts.spawnOptions !== false; // default true const env = opts.env || process.env; const moduleType = opts.moduleType || 'auto'; const interop = opts.interop || 'default'; let initialized = false; let isLocal; let cachedExecPath = null; let cachedInstallPath = null; return function boundAsyncCaller(...args) { // Detect callback vs Promise mode (Node convention) const lastArg = args[args.length - 1]; const isCallbackMode = typeof lastArg === 'function'; const callback = isCallbackMode ? args.pop() : null; const execute = ()=>{ // Initialize on first call if (!initialized) { isLocal = version === process.version || semver.satisfies(process.version, version); if (!isLocal) { const resolved = resolveVersion(version, { storagePath: opts.storagePath }); cachedExecPath = resolved.execPath; cachedInstallPath = resolved.installPath; } initialized = true; } // Local execution - current process satisfies version if (isLocal) { // If worker uses callbacks, we need function-exec-sync to convert to sync if (callbacks) { const PATH_KEY = pathKey(); if (opts.env && !opts.env[PATH_KEY]) { throw new Error(`node-version-call: options.env missing required ${PATH_KEY}`); } if (!functionExec) functionExec = _require('function-exec-sync'); const execOptions = { execPath: process.execPath, sleep: SLEEP_MS, callbacks, env, moduleType, interop }; return functionExec === null || functionExec === void 0 ? void 0 : functionExec(execOptions, workerPath, ...args); } // Use loadModuleSync for ESM support const fn = loadModuleSync(workerPath, { moduleType, interop }); return typeof fn === 'function' ? fn.apply(null, args) : fn; } // Remote execution - spawn child process if (!functionExec) functionExec = _require('function-exec-sync'); if (useSpawnOptions) { const execOptions = spawnOptions(cachedInstallPath, { execPath: cachedExecPath, sleep: SLEEP_MS, callbacks, env, moduleType, interop }); return functionExec === null || functionExec === void 0 ? void 0 : functionExec(execOptions, workerPath, ...args); } const execOptions = { execPath: cachedExecPath, sleep: SLEEP_MS, callbacks, env, moduleType, interop }; return functionExec === null || functionExec === void 0 ? void 0 : functionExec.apply(null, [ execOptions, workerPath, ...args ]); }; const worker = (execute, cb)=>{ try { const result = execute(); cb(null, result); } catch (err) { cb(err); } }; if (callback) return worker(execute, callback); return new Promise((resolve, reject)=>worker(execute, (err, result)=>err ? reject(err) : resolve(result))); }; }