@parallel-park/in-child-process 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/child-process-worker.d.ts +1 -0
- package/dist/in-child-process.d.ts +8 -0
- package/dist/read-until-end.d.ts +2 -0
- package/package.json +2 -2
- package/src/child-process-worker.ts +0 -86
- package/src/in-child-process.ts +0 -180
- package/src/read-until-end.ts +0 -25
- /package/{src/index.ts → dist/index.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type InChildProcess = {
|
|
2
|
+
<Inputs extends {
|
|
3
|
+
[key: string]: any;
|
|
4
|
+
}, Result>(inputs: Inputs, functionToRun: (inputs: Inputs) => Result | Promise<Result>): Promise<Result>;
|
|
5
|
+
<Result>(functionToRun: () => Result | Promise<Result>): Promise<Result>;
|
|
6
|
+
};
|
|
7
|
+
export declare const inChildProcess: InChildProcess;
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallel-park/in-child-process",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Run code within a child process",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"types": "
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "vitest run",
|
|
9
9
|
"build": "rm -rf ./dist && tsc"
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import vm from "vm";
|
|
3
|
-
import makeModuleEnv from "make-module-env";
|
|
4
|
-
import makeDebug from "debug";
|
|
5
|
-
import { readUntilEnd } from "./read-until-end";
|
|
6
|
-
import path from "path";
|
|
7
|
-
|
|
8
|
-
const debug = makeDebug("parallel-park:child-process-worker");
|
|
9
|
-
|
|
10
|
-
const commsIn = fs.createReadStream(
|
|
11
|
-
// @ts-ignore
|
|
12
|
-
null,
|
|
13
|
-
{ fd: 3 }
|
|
14
|
-
);
|
|
15
|
-
const commsOut = fs.createWriteStream(
|
|
16
|
-
// @ts-ignore
|
|
17
|
-
null,
|
|
18
|
-
{ fd: 4 }
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
debug("reading input data...");
|
|
22
|
-
readUntilEnd(commsIn)
|
|
23
|
-
.then((data) => {
|
|
24
|
-
debug("parsing input data...");
|
|
25
|
-
try {
|
|
26
|
-
const [inputs, fnString, callingFile] = JSON.parse(data);
|
|
27
|
-
onReady(inputs, fnString, callingFile);
|
|
28
|
-
} catch (err) {
|
|
29
|
-
onError(err as Error);
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
.catch(onError);
|
|
33
|
-
|
|
34
|
-
function onReady(inputs: any, fnString: string, callingFile: string) {
|
|
35
|
-
debug("in onReady %o", { inputs, fnString, callingFile });
|
|
36
|
-
|
|
37
|
-
// Relevant when callingFile is eg. "REPL2" (from Node.js repl)
|
|
38
|
-
if (!path.isAbsolute(callingFile)) {
|
|
39
|
-
callingFile = path.join(process.cwd(), "fake-path.js");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const wrapperFn = vm.runInThisContext(
|
|
43
|
-
`(function moduleWrapper(exports, require, module, __filename, __dirname) {
|
|
44
|
-
return ${fnString};})`
|
|
45
|
-
);
|
|
46
|
-
const env = makeModuleEnv(callingFile);
|
|
47
|
-
const fn = wrapperFn(
|
|
48
|
-
env.exports,
|
|
49
|
-
env.require,
|
|
50
|
-
env.module,
|
|
51
|
-
env.__filename,
|
|
52
|
-
env.__dirname
|
|
53
|
-
);
|
|
54
|
-
const result = fn(inputs);
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
typeof result === "object" &&
|
|
58
|
-
result != null &&
|
|
59
|
-
typeof result.then === "function"
|
|
60
|
-
) {
|
|
61
|
-
result.then(onSuccess, onError);
|
|
62
|
-
} else {
|
|
63
|
-
onSuccess(result);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function onSuccess(data: any) {
|
|
68
|
-
debug("in onSuccess %o", { data });
|
|
69
|
-
|
|
70
|
-
commsOut.end(JSON.stringify({ type: "success", data }));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function onError(error: Error) {
|
|
74
|
-
debug("in onError %o", { error });
|
|
75
|
-
|
|
76
|
-
commsOut.end(
|
|
77
|
-
JSON.stringify({
|
|
78
|
-
type: "error",
|
|
79
|
-
error: {
|
|
80
|
-
name: error.name,
|
|
81
|
-
message: error.message,
|
|
82
|
-
stack: error.stack,
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
);
|
|
86
|
-
}
|
package/src/in-child-process.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import child_process from "child_process";
|
|
2
|
-
import type * as stream from "stream";
|
|
3
|
-
import { ParsedError } from "@suchipi/error-utils";
|
|
4
|
-
import makeDebug from "debug";
|
|
5
|
-
import util from "util";
|
|
6
|
-
import { readUntilEnd } from "./read-until-end";
|
|
7
|
-
|
|
8
|
-
const debug = makeDebug("parallel-park:in-child-process");
|
|
9
|
-
|
|
10
|
-
const runnerPath = require.resolve("../dist/child-process-worker");
|
|
11
|
-
|
|
12
|
-
type InChildProcess = {
|
|
13
|
-
<Inputs extends { [key: string]: any }, Result>(
|
|
14
|
-
inputs: Inputs,
|
|
15
|
-
functionToRun: (inputs: Inputs) => Result | Promise<Result>
|
|
16
|
-
): Promise<Result>;
|
|
17
|
-
|
|
18
|
-
<Result>(functionToRun: () => Result | Promise<Result>): Promise<Result>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const inChildProcess: InChildProcess = (...args: Array<any>) => {
|
|
22
|
-
const inputs = typeof args[0] === "function" ? {} : args[0];
|
|
23
|
-
const functionToRun = typeof args[0] === "function" ? args[0] : args[1];
|
|
24
|
-
|
|
25
|
-
if (typeof inputs !== "object") {
|
|
26
|
-
throw new Error(
|
|
27
|
-
"The first argument to inChildProcess should be an object of input data to pass to the child process."
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (typeof functionToRun !== "function") {
|
|
32
|
-
throw new Error(
|
|
33
|
-
"The second argument to inChildProcess should be a function to run in the child process."
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const here = new ParsedError(new Error("here"));
|
|
38
|
-
|
|
39
|
-
const callingFrame = here.stackFrames[1];
|
|
40
|
-
const callingFile = callingFrame?.fileName ?? "unknown file";
|
|
41
|
-
|
|
42
|
-
debug("spawning child process: %o", [process.argv[0], runnerPath]);
|
|
43
|
-
|
|
44
|
-
const child = child_process.spawn(process.argv[0], [runnerPath], {
|
|
45
|
-
stdio: ["inherit", "inherit", "inherit", "pipe", "pipe"],
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
child.on("error", reject);
|
|
50
|
-
|
|
51
|
-
const commsOut: stream.Writable = child.stdio![3] as any;
|
|
52
|
-
const commsIn: stream.Readable = child.stdio![4] as any;
|
|
53
|
-
|
|
54
|
-
child.on("spawn", () => {
|
|
55
|
-
const dataToSend = JSON.stringify([
|
|
56
|
-
inputs,
|
|
57
|
-
functionToRun.toString(),
|
|
58
|
-
callingFile,
|
|
59
|
-
]);
|
|
60
|
-
debug("sending inputs to child process: %o", dataToSend);
|
|
61
|
-
commsOut.end(dataToSend, "utf-8");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
let receivedData = "";
|
|
65
|
-
readUntilEnd(commsIn).then((data) => {
|
|
66
|
-
debug("received data from child process: %o", data);
|
|
67
|
-
receivedData = data;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
child.on("close", (code, signal) => {
|
|
71
|
-
debug("child process closed: %o", { code, signal });
|
|
72
|
-
|
|
73
|
-
if (code !== 0) {
|
|
74
|
-
reject(
|
|
75
|
-
new Error(
|
|
76
|
-
`Child process exited with nonzero status code: ${JSON.stringify({
|
|
77
|
-
code,
|
|
78
|
-
signal,
|
|
79
|
-
})}`
|
|
80
|
-
)
|
|
81
|
-
);
|
|
82
|
-
} else {
|
|
83
|
-
debug("parsing received data from child process...");
|
|
84
|
-
let result: any;
|
|
85
|
-
try {
|
|
86
|
-
result = JSON.parse(receivedData);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
reject(
|
|
89
|
-
new Error(
|
|
90
|
-
`parallel-park error: failed to parse received data as JSON. data was: ${util.inspect(
|
|
91
|
-
receivedData,
|
|
92
|
-
{ colors: true, depth: Infinity }
|
|
93
|
-
)}`
|
|
94
|
-
)
|
|
95
|
-
);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
switch (result.type) {
|
|
99
|
-
case "success": {
|
|
100
|
-
debug(
|
|
101
|
-
"child process finished successfully with result: %o",
|
|
102
|
-
result.data
|
|
103
|
-
);
|
|
104
|
-
resolve(result.data);
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
case "error": {
|
|
108
|
-
debug("child process errored: %o", result.error);
|
|
109
|
-
const error = new Error(result.error.message);
|
|
110
|
-
Object.defineProperty(error, "name", { value: result.error.name });
|
|
111
|
-
Object.defineProperty(error, "stack", {
|
|
112
|
-
value:
|
|
113
|
-
result.error.name +
|
|
114
|
-
": " +
|
|
115
|
-
result.error.message +
|
|
116
|
-
"\n" +
|
|
117
|
-
result.error.stack
|
|
118
|
-
.split("\n")
|
|
119
|
-
.slice(1)
|
|
120
|
-
.filter((line) => !/node:internal|node:events/.test(line))
|
|
121
|
-
.map((line) => {
|
|
122
|
-
if (/evalmachine/.test(line)) {
|
|
123
|
-
const lineWithoutEvalMachine = line.replace(
|
|
124
|
-
/evalmachine(?:\.<anonymous>)?/,
|
|
125
|
-
"<function passed into inChildProcess>"
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const matches = line.match(/:(\d+):(\d+)\)?$/);
|
|
129
|
-
if (!matches) {
|
|
130
|
-
return lineWithoutEvalMachine;
|
|
131
|
-
} else {
|
|
132
|
-
let [_, row, col] = matches;
|
|
133
|
-
// subtract 1 from row to skip the module wrapper function line
|
|
134
|
-
row = row - 1;
|
|
135
|
-
|
|
136
|
-
// subtract the length of the `return ` keywords in front of the function
|
|
137
|
-
if (row === 1) {
|
|
138
|
-
col = col - `return `.length;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const hadParen = /\)$/.test(lineWithoutEvalMachine);
|
|
142
|
-
|
|
143
|
-
return lineWithoutEvalMachine.replace(
|
|
144
|
-
/:\d+:\d+\)?$/,
|
|
145
|
-
`:${row}:${col - 1}${hadParen ? ")" : ""}`
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
return line;
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
.join("\n") +
|
|
153
|
-
"\n" +
|
|
154
|
-
error
|
|
155
|
-
.stack!.split("\n")
|
|
156
|
-
.slice(1)
|
|
157
|
-
.filter((line) => !/node:internal|node:events/.test(line))
|
|
158
|
-
.join("\n") +
|
|
159
|
-
"\n" +
|
|
160
|
-
here
|
|
161
|
-
.stack!.split("\n")
|
|
162
|
-
.slice(2)
|
|
163
|
-
.filter((line) => !/node:internal|node:events/.test(line))
|
|
164
|
-
.join("\n"),
|
|
165
|
-
});
|
|
166
|
-
reject(error);
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
default: {
|
|
170
|
-
reject(
|
|
171
|
-
new Error(
|
|
172
|
-
`Internal parallel-park error: unhandled result type: ${result.type}`
|
|
173
|
-
)
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
};
|
package/src/read-until-end.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type stream from "stream";
|
|
2
|
-
import makeDebug from "debug";
|
|
3
|
-
const debug = makeDebug("parallel-park:read-until-end");
|
|
4
|
-
|
|
5
|
-
let streamId = 0;
|
|
6
|
-
|
|
7
|
-
export function readUntilEnd(stream: stream.Readable): Promise<string> {
|
|
8
|
-
const id = `${process.pid}-${streamId}`;
|
|
9
|
-
streamId++;
|
|
10
|
-
|
|
11
|
-
return new Promise((resolve) => {
|
|
12
|
-
let data = "";
|
|
13
|
-
|
|
14
|
-
stream.on("data", (chunk) => {
|
|
15
|
-
const chunkStr = chunk.toString("utf-8");
|
|
16
|
-
debug("received data chunk from stream %s: %o", id, chunkStr);
|
|
17
|
-
data += chunkStr;
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
stream.on("close", () => {
|
|
21
|
-
debug("stream %s closed; resolving with: %o", id, data);
|
|
22
|
-
resolve(data);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
File without changes
|