UNPKG

7.15 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const cross_spawn_1 = __importDefault(require("cross-spawn"));
7const stream_1 = require("stream");
8var Pipe;
9(function (Pipe) {
10 Pipe[Pipe["STDOUT"] = 1] = "STDOUT";
11 Pipe[Pipe["STDERR"] = 2] = "STDERR";
12})(Pipe || (Pipe = {}));
13function sigintHandler() {
14 // We don't want SIGINT to kill our process; we want it to kill the
15 // innermost process, whose end will cause our own to exit.
16}
17// Rather than attaching one SIGINT handler for each process, we
18// attach a single one and use a refcount to detect once it's no
19// longer needed.
20let sigintRefCount = 0;
21function makeProcess(name, args, opts, spawnOpts) {
22 return (stdio) => {
23 const stdin = stdio[0] instanceof stream_1.Transform
24 ? `pipe`
25 : stdio[0];
26 const stdout = stdio[1] instanceof stream_1.Transform
27 ? `pipe`
28 : stdio[1];
29 const stderr = stdio[2] instanceof stream_1.Transform
30 ? `pipe`
31 : stdio[2];
32 const child = cross_spawn_1.default(name, args, { ...spawnOpts, stdio: [
33 stdin,
34 stdout,
35 stderr,
36 ] });
37 if (sigintRefCount++ === 0)
38 process.on(`SIGINT`, sigintHandler);
39 if (stdio[0] instanceof stream_1.Transform)
40 stdio[0].pipe(child.stdin);
41 if (stdio[1] instanceof stream_1.Transform)
42 child.stdout.pipe(stdio[1], { end: false });
43 if (stdio[2] instanceof stream_1.Transform)
44 child.stderr.pipe(stdio[2], { end: false });
45 return {
46 stdin: child.stdin,
47 promise: new Promise(resolve => {
48 child.on(`error`, error => {
49 if (--sigintRefCount === 0)
50 process.off(`SIGINT`, sigintHandler);
51 // @ts-ignore
52 switch (error.code) {
53 case `ENOENT`:
54 {
55 stdio[2].write(`command not found: ${name}\n`);
56 resolve(127);
57 }
58 break;
59 case `EACCESS`:
60 {
61 stdio[2].write(`permission denied: ${name}\n`);
62 resolve(128);
63 }
64 break;
65 default:
66 {
67 stdio[2].write(`uncaught error: ${error.message}\n`);
68 resolve(1);
69 }
70 break;
71 }
72 });
73 child.on(`exit`, code => {
74 if (--sigintRefCount === 0)
75 process.off(`SIGINT`, sigintHandler);
76 if (code !== null) {
77 resolve(code);
78 }
79 else {
80 resolve(129);
81 }
82 });
83 }),
84 };
85 };
86}
87exports.makeProcess = makeProcess;
88function makeBuiltin(builtin) {
89 return (stdio) => {
90 const stdin = stdio[0] === `pipe`
91 ? new stream_1.PassThrough()
92 : stdio[0];
93 return {
94 stdin,
95 promise: Promise.resolve().then(() => builtin({
96 stdin,
97 stdout: stdio[1],
98 stderr: stdio[2],
99 })),
100 };
101 };
102}
103exports.makeBuiltin = makeBuiltin;
104class ProtectedStream {
105 constructor(stream) {
106 this.stream = stream;
107 }
108 close() {
109 // Ignore close request
110 }
111 get() {
112 return this.stream;
113 }
114}
115exports.ProtectedStream = ProtectedStream;
116class PipeStream {
117 constructor() {
118 this.stream = null;
119 }
120 close() {
121 if (this.stream === null) {
122 throw new Error(`Assertion failed: No stream attached`);
123 }
124 else {
125 this.stream.end();
126 }
127 }
128 attach(stream) {
129 this.stream = stream;
130 }
131 get() {
132 if (this.stream === null) {
133 throw new Error(`Assertion failed: No stream attached`);
134 }
135 else {
136 return this.stream;
137 }
138 }
139}
140class Handle {
141 constructor(ancestor, implementation) {
142 this.stdin = null;
143 this.stdout = null;
144 this.stderr = null;
145 this.pipe = null;
146 this.ancestor = ancestor;
147 this.implementation = implementation;
148 }
149 static start(implementation, { stdin, stdout, stderr }) {
150 const chain = new Handle(null, implementation);
151 chain.stdin = stdin;
152 chain.stdout = stdout;
153 chain.stderr = stderr;
154 return chain;
155 }
156 pipeTo(implementation, source = Pipe.STDOUT) {
157 const next = new Handle(this, implementation);
158 const pipe = new PipeStream();
159 next.pipe = pipe;
160 next.stdout = this.stdout;
161 next.stderr = this.stderr;
162 if ((source & Pipe.STDOUT) === Pipe.STDOUT)
163 this.stdout = pipe;
164 else if (this.ancestor !== null)
165 this.stderr = this.ancestor.stdout;
166 if ((source & Pipe.STDERR) === Pipe.STDERR)
167 this.stderr = pipe;
168 else if (this.ancestor !== null)
169 this.stderr = this.ancestor.stderr;
170 return next;
171 }
172 async exec() {
173 const stdio = [
174 `ignore`,
175 `ignore`,
176 `ignore`,
177 ];
178 if (this.pipe) {
179 stdio[0] = `pipe`;
180 }
181 else {
182 if (this.stdin === null) {
183 throw new Error(`Assertion failed: No input stream registered`);
184 }
185 else {
186 stdio[0] = this.stdin.get();
187 }
188 }
189 let stdoutLock;
190 if (this.stdout === null) {
191 throw new Error(`Assertion failed: No output stream registered`);
192 }
193 else {
194 stdoutLock = this.stdout;
195 stdio[1] = stdoutLock.get();
196 }
197 let stderrLock;
198 if (this.stderr === null) {
199 throw new Error(`Assertion failed: No error stream registered`);
200 }
201 else {
202 stderrLock = this.stderr;
203 stdio[2] = stderrLock.get();
204 }
205 const child = this.implementation(stdio);
206 if (this.pipe)
207 this.pipe.attach(child.stdin);
208 return await child.promise.then(code => {
209 stdoutLock.close();
210 stderrLock.close();
211 return code;
212 });
213 }
214 async run() {
215 const promises = [];
216 for (let handle = this; handle; handle = handle.ancestor)
217 promises.push(handle.exec());
218 const exitCodes = await Promise.all(promises);
219 return exitCodes[0];
220 }
221}
222exports.Handle = Handle;
223function start(p, opts) {
224 return Handle.start(p, opts);
225}
226exports.start = start;