@expo/spawn-async
Advanced tools
@@ -6,2 +6,3 @@ /// <reference types="node" /> | ||
| ignoreStdio?: boolean; | ||
| maxBuffer?: number; | ||
| } | ||
@@ -8,0 +9,0 @@ interface SpawnPromise<T> extends Promise<T> { |
+119
-49
@@ -5,77 +5,147 @@ "use strict"; | ||
| }; | ||
| const buffer_1 = require("buffer"); | ||
| const cross_spawn_1 = __importDefault(require("cross-spawn")); | ||
| const DEFAULT_MAX_BUFFER = buffer_1.constants.MAX_STRING_LENGTH; | ||
| function spawnAsync(command, args, options = {}) { | ||
| const stubError = new Error(); | ||
| const callerStack = stubError.stack ? stubError.stack.replace(/^.*/, ' ...') : null; | ||
| let child; | ||
| const { ignoreStdio: optionsIgnoreStdio, maxBuffer: optionsMaxBuffer, ...nodeOptions } = options; | ||
| // NOTE(@kitten): When `maxBuffer` is set explicitly, we enforce it strictly | ||
| // and don't produce a result without it being strictly enforced | ||
| const enforceMaxBufferStrictly = optionsMaxBuffer != null; | ||
| const ignoreStdio = !!optionsIgnoreStdio; | ||
| const maxBuffer = Math.min(optionsMaxBuffer !== null && optionsMaxBuffer !== void 0 ? optionsMaxBuffer : DEFAULT_MAX_BUFFER, buffer_1.constants.MAX_STRING_LENGTH); | ||
| let child = (0, cross_spawn_1.default)(command, args, nodeOptions); | ||
| let promise = new Promise((resolve, reject) => { | ||
| let { ignoreStdio, ...nodeOptions } = options; | ||
| // @ts-ignore: cross-spawn declares "args" to be a regular array instead of a read-only one | ||
| child = (0, cross_spawn_1.default)(command, args, nodeOptions); | ||
| let stdout = ''; | ||
| let stderr = ''; | ||
| if (!ignoreStdio) { | ||
| if (child.stdout) { | ||
| child.stdout.on('data', (data) => { | ||
| stdout += data; | ||
| }); | ||
| var _a, _b; | ||
| const stdoutChunks = { buffer: [], maxExceeded: false }; | ||
| const stderrChunks = { buffer: [], maxExceeded: false }; | ||
| function makeHandler(chunks) { | ||
| let length = 0; | ||
| return (chunk) => { | ||
| chunks.buffer.push(chunk); | ||
| length += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength; | ||
| while (chunks.buffer.length > 0 && length > maxBuffer) { | ||
| chunks.maxExceeded = true; | ||
| chunk = chunks.buffer[0]; | ||
| const chunkLength = typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength; | ||
| if (length - chunkLength < maxBuffer) { | ||
| const replacement = typeof chunk === 'string' ? Buffer.from(chunk) : chunk; | ||
| const excess = length - maxBuffer; | ||
| chunks.buffer[0] = replacement.subarray(excess); | ||
| length -= excess; | ||
| break; | ||
| } | ||
| else { | ||
| chunks.buffer.shift(); | ||
| length -= chunkLength; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| function attachResult(target, assign, stdoutChunks, stderrChunks, skipMaxBufferCheck) { | ||
| function makeMaxBufferError() { | ||
| const argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : ''; | ||
| const error = new Error(`${command}${argumentString} exceeded maxBuffer of ${maxBuffer} bytes`); | ||
| error.code = 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'; | ||
| return attachResult(error, assign, stdoutChunks, stderrChunks, true); | ||
| } | ||
| if (child.stderr) { | ||
| child.stderr.on('data', (data) => { | ||
| stderr += data; | ||
| }); | ||
| let _stdout; | ||
| let _stderr; | ||
| const map = { | ||
| stdout: { | ||
| enumerable: true, | ||
| configurable: true, | ||
| get() { | ||
| if (!skipMaxBufferCheck && stdoutChunks.maxExceeded) { | ||
| throw makeMaxBufferError(); | ||
| } | ||
| else if (_stdout === undefined) { | ||
| _stdout = Buffer.concat(stdoutChunks.buffer.map((chunk) => typeof chunk === 'string' ? Buffer.from(chunk) : chunk)).toString('utf8'); | ||
| } | ||
| return _stdout; | ||
| }, | ||
| }, | ||
| stderr: { | ||
| enumerable: true, | ||
| configurable: true, | ||
| get() { | ||
| if (!skipMaxBufferCheck && stderrChunks.maxExceeded) { | ||
| throw makeMaxBufferError(); | ||
| } | ||
| else if (_stderr === undefined) { | ||
| _stderr = Buffer.concat(stderrChunks.buffer.map((chunk) => typeof chunk === 'string' ? Buffer.from(chunk) : chunk)).toString('utf8'); | ||
| } | ||
| return _stderr; | ||
| }, | ||
| }, | ||
| output: { | ||
| enumerable: true, | ||
| configurable: true, | ||
| get: () => [target.stdout, target.stderr], | ||
| }, | ||
| }; | ||
| for (const key in assign) { | ||
| map[key] = { | ||
| value: assign[key], | ||
| enumerable: true, | ||
| writable: true, | ||
| configurable: true, | ||
| }; | ||
| } | ||
| Object.defineProperties(target, map); | ||
| return target; | ||
| } | ||
| if (!ignoreStdio) { | ||
| (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', makeHandler(stdoutChunks)); | ||
| (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', makeHandler(stderrChunks)); | ||
| } | ||
| // Use 'exit' instead of 'close' when there are no piped stdio streams for us to drain; | ||
| // 'close' can be deferred past 'exit' when the child has grandchildren that inherit its | ||
| // stdio fds, so waiting on it without anything to read just stalls | ||
| const completionEvent = ignoreStdio || (!child.stdout && !child.stderr) ? 'exit' : 'close'; | ||
| let completionListener = (code, signal) => { | ||
| child.removeListener('error', errorListener); | ||
| let result = { | ||
| const argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : ''; | ||
| let error = null; | ||
| if (code !== 0) { | ||
| error = signal | ||
| ? new Error(`${command}${argumentString} exited with signal: ${signal}`) | ||
| : new Error(`${command}${argumentString} exited with non-zero code: ${code}`); | ||
| } | ||
| const assignResult = { | ||
| pid: child.pid, | ||
| output: [stdout, stderr], | ||
| stdout, | ||
| stderr, | ||
| status: code, | ||
| signal, | ||
| }; | ||
| if (code !== 0) { | ||
| let argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : ''; | ||
| let error = signal | ||
| ? new Error(`${command}${argumentString} exited with signal: ${signal}`) | ||
| : new Error(`${command}${argumentString} exited with non-zero code: ${code}`); | ||
| if (error.stack && callerStack) { | ||
| if (error) { | ||
| if (error.stack && callerStack) | ||
| error.stack += `\n${callerStack}`; | ||
| } | ||
| Object.assign(error, result); | ||
| reject(error); | ||
| // When we're already rejecting, we don't enforce the max buffer error, and accept that we | ||
| // may truncate stderr/stdout | ||
| reject(attachResult(error, assignResult, stdoutChunks, stderrChunks, true)); | ||
| } | ||
| else if (enforceMaxBufferStrictly && (stdoutChunks.maxExceeded || stderrChunks.maxExceeded)) { | ||
| // When a `maxBuffer` is passed, we enforce the maximum on stdout and stderr strictly | ||
| const error = new Error(`${command}${argumentString} exceeded maxBuffer of ${maxBuffer} bytes`); | ||
| error.code = 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'; | ||
| reject(attachResult(error, assignResult, stdoutChunks, stderrChunks, true)); | ||
| } | ||
| else { | ||
| resolve(result); | ||
| const result = {}; | ||
| resolve(attachResult(result, assignResult, stdoutChunks, stderrChunks)); | ||
| } | ||
| }; | ||
| let errorListener = (error) => { | ||
| if (ignoreStdio) { | ||
| child.removeListener('exit', completionListener); | ||
| } | ||
| else { | ||
| child.removeListener('close', completionListener); | ||
| } | ||
| Object.assign(error, { | ||
| child.removeListener(completionEvent, completionListener); | ||
| const assignResult = { | ||
| pid: child.pid, | ||
| output: [stdout, stderr], | ||
| stdout, | ||
| stderr, | ||
| status: null, | ||
| signal: null, | ||
| }); | ||
| reject(error); | ||
| }; | ||
| reject(attachResult(error, assignResult, stdoutChunks, stderrChunks)); | ||
| }; | ||
| if (ignoreStdio) { | ||
| child.once('exit', completionListener); | ||
| } | ||
| else { | ||
| child.once('close', completionListener); | ||
| } | ||
| child.once(completionEvent, completionListener); | ||
| child.once('error', errorListener); | ||
| }); | ||
| // @ts-ignore: TypeScript isn't aware the Promise constructor argument runs synchronously and | ||
| // thinks `child` is not yet defined | ||
| promise.child = child; | ||
@@ -82,0 +152,0 @@ return promise; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"spawnAsync.js","sourceRoot":"","sources":["../src/spawnAsync.ts"],"names":[],"mappings":"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","sourcesContent":["import { ChildProcess, SpawnOptions as NodeSpawnOptions } from 'child_process';\nimport spawn from 'cross-spawn';\n\nnamespace spawnAsync {\n export interface SpawnOptions extends NodeSpawnOptions {\n ignoreStdio?: boolean;\n }\n\n export interface SpawnPromise<T> extends Promise<T> {\n child: ChildProcess;\n }\n\n export interface SpawnResult {\n pid?: number;\n output: string[];\n stdout: string;\n stderr: string;\n status: number | null;\n signal: string | null;\n }\n}\n\nfunction spawnAsync(\n command: string,\n args?: ReadonlyArray<string>,\n options: spawnAsync.SpawnOptions = {}\n): spawnAsync.SpawnPromise<spawnAsync.SpawnResult> {\n const stubError = new Error();\n const callerStack = stubError.stack ? stubError.stack.replace(/^.*/, ' ...') : null;\n\n let child: ChildProcess;\n let promise = new Promise((resolve, reject) => {\n let { ignoreStdio, ...nodeOptions } = options;\n // @ts-ignore: cross-spawn declares \"args\" to be a regular array instead of a read-only one\n child = spawn(command, args, nodeOptions);\n let stdout = '';\n let stderr = '';\n\n if (!ignoreStdio) {\n if (child.stdout) {\n child.stdout.on('data', (data) => {\n stdout += data;\n });\n }\n\n if (child.stderr) {\n child.stderr.on('data', (data) => {\n stderr += data;\n });\n }\n }\n\n let completionListener = (code: number | null, signal: string | null) => {\n child.removeListener('error', errorListener);\n let result: spawnAsync.SpawnResult = {\n pid: child.pid,\n output: [stdout, stderr],\n stdout,\n stderr,\n status: code,\n signal,\n };\n if (code !== 0) {\n let argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : '';\n let error = signal\n ? new Error(`${command}${argumentString} exited with signal: ${signal}`)\n : new Error(`${command}${argumentString} exited with non-zero code: ${code}`);\n if (error.stack && callerStack) {\n error.stack += `\\n${callerStack}`;\n }\n Object.assign(error, result);\n reject(error);\n } else {\n resolve(result);\n }\n };\n\n let errorListener = (error: Error) => {\n if (ignoreStdio) {\n child.removeListener('exit', completionListener);\n } else {\n child.removeListener('close', completionListener);\n }\n Object.assign(error, {\n pid: child.pid,\n output: [stdout, stderr],\n stdout,\n stderr,\n status: null,\n signal: null,\n });\n reject(error);\n };\n\n if (ignoreStdio) {\n child.once('exit', completionListener);\n } else {\n child.once('close', completionListener);\n }\n child.once('error', errorListener);\n }) as spawnAsync.SpawnPromise<spawnAsync.SpawnResult>;\n // @ts-ignore: TypeScript isn't aware the Promise constructor argument runs synchronously and\n // thinks `child` is not yet defined\n promise.child = child;\n return promise;\n}\n\nexport = spawnAsync;\n"]} | ||
| {"version":3,"file":"spawnAsync.js","sourceRoot":"","sources":["../src/spawnAsync.ts"],"names":[],"mappings":";;;;AACA,mCAAsD;AACtD,8DAAgC;AA6BhC,MAAM,kBAAkB,GAAG,kBAAe,CAAC,iBAAiB,CAAC;AAE7D,SAAS,UAAU,CACjB,OAAe,EACf,IAA4B,EAC5B,UAAmC,EAAE;IAErC,MAAM,SAAS,GAAG,IAAI,KAAK,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEvF,MAAM,EACJ,WAAW,EAAE,kBAAkB,EAC/B,SAAS,EAAE,gBAAgB,EAC3B,GAAG,WAAW,EACf,GAAG,OAAO,CAAC;IAEZ,4EAA4E;IAC5E,gEAAgE;IAChE,MAAM,wBAAwB,GAAG,gBAAgB,IAAI,IAAI,CAAC;IAE1D,MAAM,WAAW,GAAG,CAAC,CAAC,kBAAkB,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,gBAAgB,aAAhB,gBAAgB,cAAhB,gBAAgB,GAAI,kBAAkB,EACtC,kBAAe,CAAC,iBAAiB,CAClC,CAAC;IAEF,IAAI,KAAK,GAAiB,IAAA,qBAAK,EAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAC5D,IAAI,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;;QAC5C,MAAM,YAAY,GAAkB,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QACvE,MAAM,YAAY,GAAkB,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAEvE,SAAS,WAAW,CAAC,MAAqB;YACxC,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,OAAO,CAAC,KAAc,EAAE,EAAE;gBACxB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAC1B,MAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;gBAClF,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,SAAS,EAAE;oBACrD,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC;oBAC1B,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;oBACzB,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;oBAC5F,IAAI,MAAM,GAAG,WAAW,GAAG,SAAS,EAAE;wBACpC,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;wBAC3E,MAAM,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;wBAClC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;wBAChD,MAAM,IAAI,MAAM,CAAC;wBACjB,MAAM;qBACP;yBAAM;wBACL,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;wBACtB,MAAM,IAAI,WAAW,CAAC;qBACvB;iBACF;YACH,CAAC,CAAC;QACJ,CAAC;QAED,SAAS,YAAY,CACnB,MAAS,EACT,MAAuC,EACvC,YAA2B,EAC3B,YAA2B,EAC3B,kBAA4B;YAE5B,SAAS,kBAAkB;gBACzB,MAAM,cAAc,GAAG,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3E,MAAM,KAAK,GAA8B,IAAI,KAAK,CAAC,GAAG,OAAO,GAAG,cAAc,0BAA0B,SAAS,QAAQ,CAAC,CAAC;gBAC3H,KAAK,CAAC,IAAI,GAAG,mCAAmC,CAAC;gBACjD,OAAO,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;YACvE,CAAC;YAED,IAAI,OAA2B,CAAC;YAChC,IAAI,OAA2B,CAAC;YAChC,MAAM,GAAG,GAA0B;gBACjC,MAAM,EAAE;oBACN,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,IAAI;oBAClB,GAAG;wBACD,IAAI,CAAC,kBAAkB,IAAI,YAAY,CAAC,WAAW,EAAE;4BACnD,MAAM,kBAAkB,EAAE,CAAC;yBAC5B;6BAAM,IAAI,OAAO,KAAK,SAAS,EAAE;4BAChC,OAAO,GAAG,MAAM,CAAC,MAAM,CACrB,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAC3F,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;yBACpB;wBACD,OAAO,OAAO,CAAC;oBACjB,CAAC;iBACF;gBACD,MAAM,EAAE;oBACN,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,IAAI;oBAClB,GAAG;wBACD,IAAI,CAAC,kBAAkB,IAAI,YAAY,CAAC,WAAW,EAAE;4BACnD,MAAM,kBAAkB,EAAE,CAAC;yBAC5B;6BAAM,IAAI,OAAO,KAAK,SAAS,EAAE;4BAChC,OAAO,GAAG,MAAM,CAAC,MAAM,CACrB,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAC3F,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;yBACpB;wBACD,OAAO,OAAO,CAAC;oBACjB,CAAC;iBACF;gBACD,MAAM,EAAE;oBACN,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,IAAI;oBAClB,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;iBAC1C;aACF,CAAC;YACF,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE;gBACxB,GAAG,CAAC,GAAG,CAAC,GAAG;oBACT,KAAK,EAAE,MAAM,CAAC,GAAmC,CAAC;oBAClD,UAAU,EAAE,IAAI;oBAChB,QAAQ,EAAE,IAAI;oBACd,YAAY,EAAE,IAAI;iBACnB,CAAC;aACH;YACD,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACrC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,CAAC,WAAW,EAAE;YAChB,MAAA,KAAK,CAAC,MAAM,0CAAE,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC;YACpD,MAAA,KAAK,CAAC,MAAM,0CAAE,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC;SACrD;QAED,uFAAuF;QACvF,wFAAwF;QACxF,mEAAmE;QACnE,MAAM,eAAe,GACnB,WAAW,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAErE,IAAI,kBAAkB,GAAG,CAAC,IAAmB,EAAE,MAAqB,EAAE,EAAE;YACtE,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;YAC7C,MAAM,cAAc,GAAG,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,IAAI,KAAK,GAAuC,IAAI,CAAC;YACrD,IAAI,IAAI,KAAK,CAAC,EAAE;gBACd,KAAK,GAAG,MAAM;oBACZ,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,GAAG,cAAc,wBAAwB,MAAM,EAAE,CAAC;oBACxE,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,GAAG,cAAc,+BAA+B,IAAI,EAAE,CAAC,CAAC;aACjF;YACD,MAAM,YAAY,GAAoC;gBACpD,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,MAAM,EAAE,IAAI;gBACZ,MAAM;aACP,CAAC;YACF,IAAI,KAAK,EAAE;gBACT,IAAI,KAAK,CAAC,KAAK,IAAI,WAAW;oBAAE,KAAK,CAAC,KAAK,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClE,0FAA0F;gBAC1F,6BAA6B;gBAC7B,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC;aAC7E;iBAAM,IAAI,wBAAwB,IAAI,CAAC,YAAY,CAAC,WAAW,IAAI,YAAY,CAAC,WAAW,CAAC,EAAE;gBAC7F,qFAAqF;gBACrF,MAAM,KAAK,GAA8B,IAAI,KAAK,CAAC,GAAG,OAAO,GAAG,cAAc,0BAA0B,SAAS,QAAQ,CAAC,CAAC;gBAC3H,KAAK,CAAC,IAAI,GAAG,mCAAmC,CAAC;gBACjD,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC;aAC7E;iBAAM;gBACL,MAAM,MAAM,GAAG,EAA4B,CAAC;gBAC5C,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;aACzE;QACH,CAAC,CAAC;QAEF,IAAI,aAAa,GAAG,CAAC,KAAY,EAAE,EAAE;YACnC,KAAK,CAAC,cAAc,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;YAC1D,MAAM,YAAY,GAAoC;gBACpD,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;aACb,CAAC;YACF,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;QACxE,CAAC,CAAC;QAEF,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACrC,CAAC,CAAoD,CAAC;IAEtD,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iBAAS,UAAU,CAAC","sourcesContent":["import { ChildProcess, SpawnOptions as NodeSpawnOptions } from 'child_process';\nimport { constants as bufferConstants } from 'buffer';\nimport spawn from 'cross-spawn';\n\nnamespace spawnAsync {\n export interface SpawnOptions extends NodeSpawnOptions {\n ignoreStdio?: boolean;\n maxBuffer?: number;\n }\n\n export interface SpawnPromise<T> extends Promise<T> {\n child: ChildProcess;\n }\n\n export interface SpawnResult {\n pid?: number;\n output: string[];\n stdout: string;\n stderr: string;\n status: number | null;\n signal: string | null;\n }\n}\n\ntype IOChunk = string | Buffer;\n\ninterface IOChunksState {\n buffer: IOChunk[];\n maxExceeded: boolean;\n}\n\nconst DEFAULT_MAX_BUFFER = bufferConstants.MAX_STRING_LENGTH;\n\nfunction spawnAsync(\n command: string,\n args?: ReadonlyArray<string>,\n options: spawnAsync.SpawnOptions = {}\n): spawnAsync.SpawnPromise<spawnAsync.SpawnResult> {\n const stubError = new Error();\n const callerStack = stubError.stack ? stubError.stack.replace(/^.*/, ' ...') : null;\n\n const {\n ignoreStdio: optionsIgnoreStdio,\n maxBuffer: optionsMaxBuffer,\n ...nodeOptions\n } = options;\n\n // NOTE(@kitten): When `maxBuffer` is set explicitly, we enforce it strictly\n // and don't produce a result without it being strictly enforced\n const enforceMaxBufferStrictly = optionsMaxBuffer != null;\n\n const ignoreStdio = !!optionsIgnoreStdio;\n const maxBuffer = Math.min(\n optionsMaxBuffer ?? DEFAULT_MAX_BUFFER,\n bufferConstants.MAX_STRING_LENGTH,\n );\n\n let child: ChildProcess = spawn(command, args, nodeOptions);\n let promise = new Promise((resolve, reject) => {\n const stdoutChunks: IOChunksState = { buffer: [], maxExceeded: false };\n const stderrChunks: IOChunksState = { buffer: [], maxExceeded: false };\n\n function makeHandler(chunks: IOChunksState) {\n let length = 0;\n return (chunk: IOChunk) => {\n chunks.buffer.push(chunk);\n length += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength;\n while (chunks.buffer.length > 0 && length > maxBuffer) {\n chunks.maxExceeded = true;\n chunk = chunks.buffer[0];\n const chunkLength = typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength;\n if (length - chunkLength < maxBuffer) {\n const replacement = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;\n const excess = length - maxBuffer;\n chunks.buffer[0] = replacement.subarray(excess);\n length -= excess;\n break;\n } else {\n chunks.buffer.shift();\n length -= chunkLength;\n }\n }\n };\n }\n\n function attachResult<T extends spawnAsync.SpawnResult | (Error & Partial<spawnAsync.SpawnResult>)>(\n target: T,\n assign: Partial<spawnAsync.SpawnResult>,\n stdoutChunks: IOChunksState,\n stderrChunks: IOChunksState,\n skipMaxBufferCheck?: boolean,\n ): T {\n function makeMaxBufferError() {\n const argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : '';\n const error: Error & { code?: string } = new Error(`${command}${argumentString} exceeded maxBuffer of ${maxBuffer} bytes`);\n error.code = 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n return attachResult(error, assign, stdoutChunks, stderrChunks, true);\n }\n\n let _stdout: string | undefined;\n let _stderr: string | undefined;\n const map: PropertyDescriptorMap = {\n stdout: {\n enumerable: true,\n configurable: true,\n get() {\n if (!skipMaxBufferCheck && stdoutChunks.maxExceeded) {\n throw makeMaxBufferError();\n } else if (_stdout === undefined) {\n _stdout = Buffer.concat(\n stdoutChunks.buffer.map((chunk) => typeof chunk === 'string' ? Buffer.from(chunk) : chunk)\n ).toString('utf8');\n }\n return _stdout;\n },\n },\n stderr: {\n enumerable: true,\n configurable: true,\n get() {\n if (!skipMaxBufferCheck && stderrChunks.maxExceeded) {\n throw makeMaxBufferError();\n } else if (_stderr === undefined) {\n _stderr = Buffer.concat(\n stderrChunks.buffer.map((chunk) => typeof chunk === 'string' ? Buffer.from(chunk) : chunk)\n ).toString('utf8');\n }\n return _stderr;\n },\n },\n output: {\n enumerable: true,\n configurable: true,\n get: () => [target.stdout, target.stderr],\n },\n };\n for (const key in assign) {\n map[key] = {\n value: assign[key as keyof spawnAsync.SpawnResult],\n enumerable: true,\n writable: true,\n configurable: true,\n };\n }\n Object.defineProperties(target, map);\n return target;\n }\n\n if (!ignoreStdio) {\n child.stdout?.on('data', makeHandler(stdoutChunks));\n child.stderr?.on('data', makeHandler(stderrChunks));\n }\n\n // Use 'exit' instead of 'close' when there are no piped stdio streams for us to drain;\n // 'close' can be deferred past 'exit' when the child has grandchildren that inherit its\n // stdio fds, so waiting on it without anything to read just stalls\n const completionEvent =\n ignoreStdio || (!child.stdout && !child.stderr) ? 'exit' : 'close';\n\n let completionListener = (code: number | null, signal: string | null) => {\n child.removeListener('error', errorListener);\n const argumentString = args && args.length > 0 ? ` ${args.join(' ')}` : '';\n let error: (Error & { code?: string }) | null = null;\n if (code !== 0) {\n error = signal\n ? new Error(`${command}${argumentString} exited with signal: ${signal}`)\n : new Error(`${command}${argumentString} exited with non-zero code: ${code}`);\n }\n const assignResult: Partial<spawnAsync.SpawnResult> = {\n pid: child.pid,\n status: code,\n signal,\n };\n if (error) {\n if (error.stack && callerStack) error.stack += `\\n${callerStack}`;\n // When we're already rejecting, we don't enforce the max buffer error, and accept that we\n // may truncate stderr/stdout\n reject(attachResult(error, assignResult, stdoutChunks, stderrChunks, true));\n } else if (enforceMaxBufferStrictly && (stdoutChunks.maxExceeded || stderrChunks.maxExceeded)) {\n // When a `maxBuffer` is passed, we enforce the maximum on stdout and stderr strictly\n const error: Error & { code?: string } = new Error(`${command}${argumentString} exceeded maxBuffer of ${maxBuffer} bytes`);\n error.code = 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n reject(attachResult(error, assignResult, stdoutChunks, stderrChunks, true));\n } else {\n const result = {} as spawnAsync.SpawnResult;\n resolve(attachResult(result, assignResult, stdoutChunks, stderrChunks));\n }\n };\n\n let errorListener = (error: Error) => {\n child.removeListener(completionEvent, completionListener);\n const assignResult: Partial<spawnAsync.SpawnResult> = {\n pid: child.pid,\n status: null,\n signal: null,\n };\n reject(attachResult(error, assignResult, stdoutChunks, stderrChunks));\n };\n\n child.once(completionEvent, completionListener);\n child.once('error', errorListener);\n }) as spawnAsync.SpawnPromise<spawnAsync.SpawnResult>;\n\n promise.child = child;\n return promise;\n}\n\nexport = spawnAsync;\n"]} |
+2
-2
| { | ||
| "name": "@expo/spawn-async", | ||
| "version": "1.7.2", | ||
| "version": "1.8.0", | ||
| "description": "A Promise-based interface into processes created by child_process.spawn", | ||
@@ -43,3 +43,3 @@ "main": "./build/spawnAsync.js", | ||
| "dependencies": { | ||
| "cross-spawn": "^7.0.3" | ||
| "cross-spawn": "^7.0.6" | ||
| }, | ||
@@ -46,0 +46,0 @@ "devDependencies": { |
+11
-0
@@ -33,2 +33,3 @@ # spawn-async [](https://github.com/expo/spawn-async/actions/workflows/main.yml) | ||
| - `ignoreStdio`: whether to ignore waiting for the child process's stdio streams to close before resolving the result promise. When ignoring stdio, the returned values for `stdout` and `stderr` will be empty strings. The default value of this option is `false`. | ||
| - `maxBuffer`: the maximum bytes retained from `stdout` and `stderr` (independently). Output is collected with a sliding window. When set explicitly, exceeding it rejects the promise with an error whose `code` is `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` and whose `stdout`/`stderr` carry the truncated tail. When omitted, the default is `buffer.constants.MAX_STRING_LENGTH` (~512 MiB). | ||
@@ -68,1 +69,11 @@ It returns a promise whose result is an object with these properties: | ||
| ``` | ||
| ## Notes | ||
| ### `maxBuffer` | ||
| `maxBuffer` is a later addition to the API. Set it when child output could exhaust memory and crash the parent process, or when the command or arguments are influenced by untrusted input — an attacker can otherwise force unbounded output to crash the parent. | ||
| The default of `buffer.constants.MAX_STRING_LENGTH` (~512 MiB) is a crash-safe floor, not a memory bound: at that size the materialized string itself can still exhaust process memory. | ||
| When `maxBuffer` is set explicitly, exceeding it rejects the promise immediately with `ERR_CHILD_PROCESS_STDIO_MAXBUFFER`. When left at the default, exceeding it doesn't reject; the sliding-window tail is still readable, but reading `stdout`/`stderr` throws `ERR_CHILD_PROCESS_STDIO_MAXBUFFER` with the truncated tail attached. |
Unstable ownership
Supply chain riskA new collaborator has begun publishing package versions. Package stability and security risk may be elevated.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
28306
95.16%174
68.93%78
16.42%1
Infinity%2
100%Updated