UNPKG

node-version-use

Version:

Cross-platform solution for using multiple versions of node. Useful for compatibility testing

202 lines (201 loc) 9.09 kB
var _process_env_OSTYPE; import spawn from 'cross-spawn-cb'; import fs from 'fs'; import resolveVersions from 'node-resolve-versions'; import { spawnOptions as createSpawnOptions } from 'node-version-utils'; import path from 'path'; import Queue from 'queue-cb'; import resolveBin from 'resolve-bin-sync'; import spawnStreaming from 'spawn-streaming'; import { createSession, formatArguments } from 'spawn-term'; import { objectAssign, stringEndsWith } from './compat.js'; import { storagePath } from './constants.js'; import loadNodeVersionInstall from './lib/loadNodeVersionInstall.js'; import { getPathWithoutNvuBin, resolveSystemBinary } from './lib/resolveSystemBinary.js'; const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test((_process_env_OSTYPE = process.env.OSTYPE) !== null && _process_env_OSTYPE !== void 0 ? _process_env_OSTYPE : ''); const NODE = isWindows ? 'node.exe' : 'node'; // Parse npm-generated .cmd wrapper to extract the JS script path function parseNpmCmdWrapper(cmdPath) { try { const content = fs.readFileSync(cmdPath, 'utf8'); // Match: "%_prog%" "%dp0%\node_modules\...\cli.js" %* // or: "%_prog%" "%dp0%\path\to\script.js" %* const match = content.match(/"%_prog%"\s+"?%dp0%\\([^"]+)"?\s+%\*/); if (match) { const relativePath = match[1]; const cmdDir = path.dirname(cmdPath); return path.join(cmdDir, relativePath); } } catch (_e) { // ignore } return null; } // On Windows, resolve npm bin commands to their JS entry points to bypass .cmd wrappers // This fixes issues with nvm-windows where .cmd wrappers use symlinked node.exe directly function resolveCommand(command, args) { if (!isWindows) return { command, args }; // Case 1: Command is a .cmd file path if (stringEndsWith(command.toLowerCase(), '.cmd')) { const scriptPath = parseNpmCmdWrapper(command); if (scriptPath) { return { command: NODE, args: [ scriptPath ].concat(args) }; } } // Case 2: Try to resolve the command as an npm package bin from node_modules try { const binPath = resolveBin(command); return { command: NODE, args: [ binPath ].concat(args) }; } catch (_e) { // Not an npm package bin, use original command } return { command, args }; } export default function worker(versionExpression, command, args, options, callback) { // Handle "system" as a special version that uses system binaries directly if (versionExpression === 'system') { runWithSystemBinaries(command, args, options, callback); return; } // Load node-version-install lazily loadNodeVersionInstall((loadErr, installVersion)=>{ if (loadErr) return callback(loadErr); resolveVersions(versionExpression, options, (err, result)=>{ const versions = result; if (err) return callback(err); if (!versions || !versions.length) { callback(new Error(`No versions found from expression: ${versionExpression}`)); return; } const installOptions = objectAssign({ storagePath: storagePath }, options); const streamingOptions = options; const results = []; const queue = new Queue(1); // Create session once for all processes (only if multiple versions) const interactive = options.interactive !== false; const session = versions.length >= 2 && process.stdout.isTTY && typeof createSession === 'function' && !streamingOptions.streaming ? createSession({ header: `${command} ${args.join(' ')}`, showStatusBar: true, interactive }) : null; versions.forEach((version, index)=>queue.defer((cb)=>installVersion === null || installVersion === void 0 ? void 0 : installVersion(version, installOptions, (err, installs)=>{ const install = installs && installs.length === 1 ? installs[0] : null; if (err || !install) { const error = err || new Error(`Unexpected version results for version ${version}. Install ${JSON.stringify(installs)}`); results.push({ install, command, version, error, result: undefined }); return cb(); } const spawnOptions = createSpawnOptions(install.installPath, options); const prefix = install.version; function next(err, res) { if (!session && !options.silent) console.log('=============='); if (err && err.message.indexOf('ExperimentalWarning') >= 0) { res = err; err = undefined; } results.push({ install, command, version, error: err !== null && err !== void 0 ? err : undefined, result: res }); cb(); } // On Windows, resolve npm bin commands to bypass .cmd wrappers const resolved = resolveCommand(command, args); // Show command when running single version (no terminal session, unless silent) if (!session && !options.silent) console.log(`${index > 0 ? '\n' : ''}${version}`); if (!session && !options.silent) console.log('--------------'); if (!session && !options.silent) console.log(`$ ${formatArguments([ resolved.command ].concat(resolved.args)).join(' ')}`); if ((versions === null || versions === void 0 ? void 0 : versions.length) < 2) spawn(resolved.command, resolved.args, spawnOptions, next); else if (session) session.spawn(resolved.command, resolved.args, spawnOptions, { group: prefix, expanded: streamingOptions.expanded }, next); else spawnStreaming(resolved.command, resolved.args, spawnOptions, { prefix: process.stdout.isTTY ? prefix : undefined }, next); }))); queue.await((err)=>{ if (session) { session.waitAndClose(()=>{ err ? callback(err) : callback(undefined, results); }); } else { err ? callback(err) : callback(undefined, results); } }); }); }); } /** * Run command using system binaries (bypassing nvu version management) * This handles the "system" version specifier */ function runWithSystemBinaries(command, args, options, callback) { // Find the system binary for the command const systemBinary = resolveSystemBinary(command); if (!systemBinary) { callback(new Error(`System ${command} not found in PATH`)); return; } // Create spawn options with PATH excluding ~/.nvu/bin // This ensures any child processes also use system binaries const cleanPath = getPathWithoutNvuBin(); const spawnOptions = objectAssign({}, options); spawnOptions.env = objectAssign({}, process.env); spawnOptions.env.PATH = cleanPath; spawnOptions.stdio = options.stdio || 'inherit'; // On Windows, resolve npm bin commands to bypass .cmd wrappers const resolved = resolveCommand(command, args); // For system, use the resolved system binary path const finalCommand = resolved.command === command ? systemBinary : resolved.command; const finalArgs = resolved.command === command ? args : resolved.args; if (!options.silent) { console.log(`$ ${formatArguments([ finalCommand ].concat(finalArgs)).join(' ')}`); } spawn(finalCommand, finalArgs, spawnOptions, (err, res)=>{ if (err && err.message && err.message.indexOf('ExperimentalWarning') >= 0) { res = err; err = undefined; } const result = { install: null, command, version: 'system', error: err, result: res }; callback(err, [ result ]); }); }