time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

index.js (7897B)


      1 import {Buffer} from 'node:buffer';
      2 import path from 'node:path';
      3 import childProcess from 'node:child_process';
      4 import process from 'node:process';
      5 import crossSpawn from 'cross-spawn';
      6 import stripFinalNewline from 'strip-final-newline';
      7 import {npmRunPathEnv} from 'npm-run-path';
      8 import onetime from 'onetime';
      9 import {makeError} from './lib/error.js';
     10 import {normalizeStdio, normalizeStdioNode} from './lib/stdio.js';
     11 import {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} from './lib/kill.js';
     12 import {addPipeMethods} from './lib/pipe.js';
     13 import {handleInput, getSpawnedResult, makeAllStream, handleInputSync} from './lib/stream.js';
     14 import {mergePromise, getSpawnedPromise} from './lib/promise.js';
     15 import {joinCommand, parseCommand, parseTemplates, getEscapedCommand} from './lib/command.js';
     16 import {logCommand, verboseDefault} from './lib/verbose.js';
     17 
     18 const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100;
     19 
     20 const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => {
     21 	const env = extendEnv ? {...process.env, ...envOption} : envOption;
     22 
     23 	if (preferLocal) {
     24 		return npmRunPathEnv({env, cwd: localDir, execPath});
     25 	}
     26 
     27 	return env;
     28 };
     29 
     30 const handleArguments = (file, args, options = {}) => {
     31 	const parsed = crossSpawn._parse(file, args, options);
     32 	file = parsed.command;
     33 	args = parsed.args;
     34 	options = parsed.options;
     35 
     36 	options = {
     37 		maxBuffer: DEFAULT_MAX_BUFFER,
     38 		buffer: true,
     39 		stripFinalNewline: true,
     40 		extendEnv: true,
     41 		preferLocal: false,
     42 		localDir: options.cwd || process.cwd(),
     43 		execPath: process.execPath,
     44 		encoding: 'utf8',
     45 		reject: true,
     46 		cleanup: true,
     47 		all: false,
     48 		windowsHide: true,
     49 		verbose: verboseDefault,
     50 		...options,
     51 	};
     52 
     53 	options.env = getEnv(options);
     54 
     55 	options.stdio = normalizeStdio(options);
     56 
     57 	if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') {
     58 		// #116
     59 		args.unshift('/q');
     60 	}
     61 
     62 	return {file, args, options, parsed};
     63 };
     64 
     65 const handleOutput = (options, value, error) => {
     66 	if (typeof value !== 'string' && !Buffer.isBuffer(value)) {
     67 		// When `execaSync()` errors, we normalize it to '' to mimic `execa()`
     68 		return error === undefined ? undefined : '';
     69 	}
     70 
     71 	if (options.stripFinalNewline) {
     72 		return stripFinalNewline(value);
     73 	}
     74 
     75 	return value;
     76 };
     77 
     78 export function execa(file, args, options) {
     79 	const parsed = handleArguments(file, args, options);
     80 	const command = joinCommand(file, args);
     81 	const escapedCommand = getEscapedCommand(file, args);
     82 	logCommand(escapedCommand, parsed.options);
     83 
     84 	validateTimeout(parsed.options);
     85 
     86 	let spawned;
     87 	try {
     88 		spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options);
     89 	} catch (error) {
     90 		// Ensure the returned error is always both a promise and a child process
     91 		const dummySpawned = new childProcess.ChildProcess();
     92 		const errorPromise = Promise.reject(makeError({
     93 			error,
     94 			stdout: '',
     95 			stderr: '',
     96 			all: '',
     97 			command,
     98 			escapedCommand,
     99 			parsed,
    100 			timedOut: false,
    101 			isCanceled: false,
    102 			killed: false,
    103 		}));
    104 		mergePromise(dummySpawned, errorPromise);
    105 		return dummySpawned;
    106 	}
    107 
    108 	const spawnedPromise = getSpawnedPromise(spawned);
    109 	const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise);
    110 	const processDone = setExitHandler(spawned, parsed.options, timedPromise);
    111 
    112 	const context = {isCanceled: false};
    113 
    114 	spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned));
    115 	spawned.cancel = spawnedCancel.bind(null, spawned, context);
    116 
    117 	const handlePromise = async () => {
    118 		const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone);
    119 		const stdout = handleOutput(parsed.options, stdoutResult);
    120 		const stderr = handleOutput(parsed.options, stderrResult);
    121 		const all = handleOutput(parsed.options, allResult);
    122 
    123 		if (error || exitCode !== 0 || signal !== null) {
    124 			const returnedError = makeError({
    125 				error,
    126 				exitCode,
    127 				signal,
    128 				stdout,
    129 				stderr,
    130 				all,
    131 				command,
    132 				escapedCommand,
    133 				parsed,
    134 				timedOut,
    135 				isCanceled: context.isCanceled || (parsed.options.signal ? parsed.options.signal.aborted : false),
    136 				killed: spawned.killed,
    137 			});
    138 
    139 			if (!parsed.options.reject) {
    140 				return returnedError;
    141 			}
    142 
    143 			throw returnedError;
    144 		}
    145 
    146 		return {
    147 			command,
    148 			escapedCommand,
    149 			exitCode: 0,
    150 			stdout,
    151 			stderr,
    152 			all,
    153 			failed: false,
    154 			timedOut: false,
    155 			isCanceled: false,
    156 			killed: false,
    157 		};
    158 	};
    159 
    160 	const handlePromiseOnce = onetime(handlePromise);
    161 
    162 	handleInput(spawned, parsed.options);
    163 
    164 	spawned.all = makeAllStream(spawned, parsed.options);
    165 
    166 	addPipeMethods(spawned);
    167 	mergePromise(spawned, handlePromiseOnce);
    168 	return spawned;
    169 }
    170 
    171 export function execaSync(file, args, options) {
    172 	const parsed = handleArguments(file, args, options);
    173 	const command = joinCommand(file, args);
    174 	const escapedCommand = getEscapedCommand(file, args);
    175 	logCommand(escapedCommand, parsed.options);
    176 
    177 	const input = handleInputSync(parsed.options);
    178 
    179 	let result;
    180 	try {
    181 		result = childProcess.spawnSync(parsed.file, parsed.args, {...parsed.options, input});
    182 	} catch (error) {
    183 		throw makeError({
    184 			error,
    185 			stdout: '',
    186 			stderr: '',
    187 			all: '',
    188 			command,
    189 			escapedCommand,
    190 			parsed,
    191 			timedOut: false,
    192 			isCanceled: false,
    193 			killed: false,
    194 		});
    195 	}
    196 
    197 	const stdout = handleOutput(parsed.options, result.stdout, result.error);
    198 	const stderr = handleOutput(parsed.options, result.stderr, result.error);
    199 
    200 	if (result.error || result.status !== 0 || result.signal !== null) {
    201 		const error = makeError({
    202 			stdout,
    203 			stderr,
    204 			error: result.error,
    205 			signal: result.signal,
    206 			exitCode: result.status,
    207 			command,
    208 			escapedCommand,
    209 			parsed,
    210 			timedOut: result.error && result.error.code === 'ETIMEDOUT',
    211 			isCanceled: false,
    212 			killed: result.signal !== null,
    213 		});
    214 
    215 		if (!parsed.options.reject) {
    216 			return error;
    217 		}
    218 
    219 		throw error;
    220 	}
    221 
    222 	return {
    223 		command,
    224 		escapedCommand,
    225 		exitCode: 0,
    226 		stdout,
    227 		stderr,
    228 		failed: false,
    229 		timedOut: false,
    230 		isCanceled: false,
    231 		killed: false,
    232 	};
    233 }
    234 
    235 const normalizeScriptStdin = ({input, inputFile, stdio}) => input === undefined && inputFile === undefined && stdio === undefined
    236 	? {stdin: 'inherit'}
    237 	: {};
    238 
    239 const normalizeScriptOptions = (options = {}) => ({
    240 	preferLocal: true,
    241 	...normalizeScriptStdin(options),
    242 	...options,
    243 });
    244 
    245 function create$(options) {
    246 	function $(templatesOrOptions, ...expressions) {
    247 		if (!Array.isArray(templatesOrOptions)) {
    248 			return create$({...options, ...templatesOrOptions});
    249 		}
    250 
    251 		const [file, ...args] = parseTemplates(templatesOrOptions, expressions);
    252 		return execa(file, args, normalizeScriptOptions(options));
    253 	}
    254 
    255 	$.sync = (templates, ...expressions) => {
    256 		if (!Array.isArray(templates)) {
    257 			throw new TypeError('Please use $(options).sync`command` instead of $.sync(options)`command`.');
    258 		}
    259 
    260 		const [file, ...args] = parseTemplates(templates, expressions);
    261 		return execaSync(file, args, normalizeScriptOptions(options));
    262 	};
    263 
    264 	return $;
    265 }
    266 
    267 export const $ = create$();
    268 
    269 export function execaCommand(command, options) {
    270 	const [file, ...args] = parseCommand(command);
    271 	return execa(file, args, options);
    272 }
    273 
    274 export function execaCommandSync(command, options) {
    275 	const [file, ...args] = parseCommand(command);
    276 	return execaSync(file, args, options);
    277 }
    278 
    279 export function execaNode(scriptPath, args, options = {}) {
    280 	if (args && !Array.isArray(args) && typeof args === 'object') {
    281 		options = args;
    282 		args = [];
    283 	}
    284 
    285 	const stdio = normalizeStdioNode(options);
    286 	const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect'));
    287 
    288 	const {
    289 		nodePath = process.execPath,
    290 		nodeOptions = defaultExecArgv,
    291 	} = options;
    292 
    293 	return execa(
    294 		nodePath,
    295 		[
    296 			...nodeOptions,
    297 			scriptPath,
    298 			...(Array.isArray(args) ? args : []),
    299 		],
    300 		{
    301 			...options,
    302 			stdin: undefined,
    303 			stdout: undefined,
    304 			stderr: undefined,
    305 			stdio,
    306 			shell: false,
    307 		},
    308 	);
    309 }