UNPKG

ttsc

Version:

General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.

92 lines 3.78 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.captureProcessOutput = captureProcessOutput; const node_fs_1 = __importDefault(require("node:fs")); const node_os_1 = __importDefault(require("node:os")); const node_path_1 = __importDefault(require("node:path")); /** * A pair of temporary files standing in for a child process's pipes. * * `spawnSync` holds a _piped_ stream in this process's memory and refuses to * keep more than `maxBuffer` bytes, so any piped capture has to name a ceiling * — and a ceiling is a number nobody chose for this machine, deciding on the * user's behalf that a large but legitimate build said too much. Handing the * child a file descriptor instead means the bytes never pass through this heap * on their way out of the child: how much a process may write is the * filesystem's business, and that is the same answer on every machine. Reading * the result back still materializes a string, so V8's own maximum string * length remains the outer bound — but that is a property of the runtime rather * than a budget chosen here, and it is identical everywhere this runs. * * The directory is per-call, so two concurrent spawns cannot read each other's * bytes. */ function captureProcessOutput() { const directory = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "ttsc-spawn-")); const stdoutPath = node_path_1.default.join(directory, "stdout"); const stderrPath = node_path_1.default.join(directory, "stderr"); const stdoutFd = node_fs_1.default.openSync(stdoutPath, "w+"); let stderrFd; try { stderrFd = node_fs_1.default.openSync(stderrPath, "w+"); } catch (error) { // The first descriptor and the directory are already live. Nothing else // will ever hold them, so they are released here rather than left for a // caller that never received a handle to dispose. closeQuietly(stdoutFd); removeQuietly(directory); throw error; } return { dispose() { closeQuietly(stdoutFd); closeQuietly(stderrFd); removeQuietly(directory); }, read(stream, encoding) { const location = stream === "stdout" ? stdoutPath : stderrPath; let raw; try { raw = node_fs_1.default.readFileSync(location); } catch { // A spawn that never launched leaves nothing behind. Report the same // empty output a failed piped capture would have. raw = Buffer.alloc(0); } return encoding === "buffer" ? raw : raw.toString(encoding ?? "utf8"); }, stderrFd, stdoutFd, }; } /** Close a descriptor, ignoring one that is already closed. */ function closeQuietly(fd) { try { node_fs_1.default.closeSync(fd); } catch { // Already closed; removing the directory is what reclaims the space. } } /** * Remove the capture directory without letting cleanup replace a result. * * `dispose` runs from a `finally`, so a throw here would surface instead of the * spawn's own outcome — and on Windows a grandchild that inherited the handle * can hold the file long enough to make removal fail. Leaving bytes in the * system temp directory is the lesser outcome by far. */ function removeQuietly(directory) { try { node_fs_1.default.rmSync(directory, { force: true, recursive: true }); } catch { // Best effort. } } //# sourceMappingURL=captureProcessOutput.js.map