storyforge 0.5.3 → 0.7.0
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/bridge-poller-JIP6RGMN.js +7033 -0
- package/dist/chunk-2UXQZTSS.js +877 -0
- package/dist/chunk-3ZW2KMKN.js +5657 -0
- package/dist/chunk-56OB3EDN.js +29 -0
- package/dist/chunk-62PNWAA7.js +1020 -0
- package/dist/chunk-C5KB4UV6.js +242 -0
- package/dist/chunk-CDQ3UP6J.js +52 -0
- package/dist/chunk-CQ2IV4Q2.js +14 -0
- package/dist/chunk-L4BNY5R3.js +175 -0
- package/dist/chunk-M6JAFPDI.js +314 -0
- package/dist/chunk-NSPRIPOP.js +36 -0
- package/dist/chunk-U6LFS35L.js +62 -0
- package/dist/chunk-WVPXYQ2G.js +164 -0
- package/dist/chunk-YLY3P6CS.js +39 -0
- package/dist/chunk-ZHDWKDF5.js +44 -0
- package/dist/{cli-usage-FG3YUG3W.js → cli-usage-G762TREV.js} +1 -0
- package/dist/dist-es-2LINLMXN.js +89 -0
- package/dist/dist-es-4NQJAKNU.js +381 -0
- package/dist/dist-es-4RCBWWWT.js +71 -0
- package/dist/dist-es-JQWJEY7F.js +490 -0
- package/dist/dist-es-KZQIITSZ.js +168 -0
- package/dist/dist-es-QJ3UEHQZ.js +325 -0
- package/dist/dist-es-SXKTBYGM.js +47 -0
- package/dist/event-streams-DNC7XM6J.js +245 -0
- package/dist/index.js +339 -7
- package/dist/loadSso-SM6TYICZ.js +591 -0
- package/dist/signin-TZJIHAWM.js +703 -0
- package/dist/sso-oidc-MO5NOHP3.js +828 -0
- package/dist/stitch-7XB7ZA4D.js +446 -0
- package/dist/sts-PUI4IC7G.js +3942 -0
- package/package.json +2 -2
- package/dist/bridge-poller-WEKSUZMT.js +0 -367
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storyforge",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "StoryForge — local bridge for the Forge video production web app.
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "StoryForge — local bridge for the Forge video production web app. Parallel clip-render orchestrator (Remotion 4 + Manim + HyperFrames + ffmpeg) + final video stitcher + dependency doctor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"storyforge": "./dist/index.js"
|
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
log
|
|
4
|
-
} from "./chunk-GJQ45C5W.js";
|
|
5
|
-
|
|
6
|
-
// src/bridge-poller.ts
|
|
7
|
-
import { spawn, spawnSync } from "child_process";
|
|
8
|
-
import * as crypto from "crypto";
|
|
9
|
-
import * as fs from "fs";
|
|
10
|
-
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
11
|
-
var POLL_INTERVAL_MS = 8e3;
|
|
12
|
-
var CLI_TIMEOUT_MS = Number(process.env.SCRIPT_GEN_TIMEOUT_MS) || 20 * 60 * 1e3;
|
|
13
|
-
var IMAGE_GEN_TIMEOUT_MS = Number(process.env.CODEX_IMAGE_GEN_TIMEOUT_MS) || Number(process.env.IMAGE_GEN_TIMEOUT_MS) || 8 * 60 * 1e3;
|
|
14
|
-
var MAX_IN_FLIGHT_JOBS = Math.max(1, Math.min(6, Number(process.env.FORGE_BRIDGE_MAX_IN_FLIGHT) || 4));
|
|
15
|
-
var CODEX_IMAGEGEN_AGENT_MODELS = ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"];
|
|
16
|
-
var CODEX_AGENT_MODEL_RE = /^gpt-5(?:$|[.\-])/i;
|
|
17
|
-
var BridgePoller = class {
|
|
18
|
-
baseUrl;
|
|
19
|
-
token;
|
|
20
|
-
clientVersion;
|
|
21
|
-
instanceId;
|
|
22
|
-
heartbeatTimer = null;
|
|
23
|
-
pollTimer = null;
|
|
24
|
-
inFlight = /* @__PURE__ */ new Set();
|
|
25
|
-
stopped = false;
|
|
26
|
-
constructor(opts) {
|
|
27
|
-
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
28
|
-
this.token = opts.token;
|
|
29
|
-
this.clientVersion = opts.clientVersion;
|
|
30
|
-
this.instanceId = `${process.pid}-${crypto.randomBytes(2).toString("hex")}-${Date.now()}`;
|
|
31
|
-
}
|
|
32
|
-
start() {
|
|
33
|
-
if (this.heartbeatTimer || this.pollTimer) return;
|
|
34
|
-
log.info(`[bridge] poller starting \xB7 ${this.baseUrl} \xB7 instance=${this.instanceId}`);
|
|
35
|
-
void this.heartbeat();
|
|
36
|
-
this.heartbeatTimer = setInterval(() => {
|
|
37
|
-
void this.heartbeat();
|
|
38
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
39
|
-
this.pollTimer = setInterval(() => {
|
|
40
|
-
void this.poll();
|
|
41
|
-
}, POLL_INTERVAL_MS);
|
|
42
|
-
}
|
|
43
|
-
async stop() {
|
|
44
|
-
this.stopped = true;
|
|
45
|
-
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
46
|
-
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
47
|
-
this.heartbeatTimer = null;
|
|
48
|
-
this.pollTimer = null;
|
|
49
|
-
}
|
|
50
|
-
probeBinary(name) {
|
|
51
|
-
if (!/^[a-z][a-z0-9-]*$/i.test(name)) return { available: false, path: null };
|
|
52
|
-
const r = spawnSync("which", [name], { encoding: "utf-8", timeout: 2e3 });
|
|
53
|
-
if (r.status !== 0) return { available: false, path: null };
|
|
54
|
-
const path = (r.stdout ?? "").trim();
|
|
55
|
-
return { available: !!path, path: path || null };
|
|
56
|
-
}
|
|
57
|
-
async heartbeat() {
|
|
58
|
-
if (this.stopped) return;
|
|
59
|
-
try {
|
|
60
|
-
const resp = await fetch(`${this.baseUrl}/api/cli-bridge/heartbeat`, {
|
|
61
|
-
method: "POST",
|
|
62
|
-
headers: {
|
|
63
|
-
Authorization: `Bearer ${this.token}`,
|
|
64
|
-
"Content-Type": "application/json"
|
|
65
|
-
},
|
|
66
|
-
body: JSON.stringify({
|
|
67
|
-
availableClis: {
|
|
68
|
-
claude: this.probeBinary("claude"),
|
|
69
|
-
codex: this.probeBinary("codex")
|
|
70
|
-
},
|
|
71
|
-
clientVersion: this.clientVersion,
|
|
72
|
-
instanceId: this.instanceId
|
|
73
|
-
}),
|
|
74
|
-
signal: AbortSignal.timeout(45e3)
|
|
75
|
-
});
|
|
76
|
-
if (!resp.ok) {
|
|
77
|
-
const text = await resp.text().catch(() => "");
|
|
78
|
-
log.warn(`[bridge] heartbeat HTTP ${resp.status}: ${text.slice(0, 120)}`);
|
|
79
|
-
}
|
|
80
|
-
} catch (err) {
|
|
81
|
-
log.warn(`[bridge] heartbeat failed: ${err.message}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
async poll() {
|
|
85
|
-
if (this.stopped) return;
|
|
86
|
-
const availableSlots = MAX_IN_FLIGHT_JOBS - this.inFlight.size;
|
|
87
|
-
if (availableSlots <= 0) return;
|
|
88
|
-
let jobs = [];
|
|
89
|
-
try {
|
|
90
|
-
const resp = await fetch(`${this.baseUrl}/api/cli-bridge/claim`, {
|
|
91
|
-
method: "POST",
|
|
92
|
-
headers: {
|
|
93
|
-
Authorization: `Bearer ${this.token}`,
|
|
94
|
-
"Content-Type": "application/json"
|
|
95
|
-
},
|
|
96
|
-
body: JSON.stringify({ instanceId: this.instanceId, limit: Math.min(3, availableSlots) }),
|
|
97
|
-
signal: AbortSignal.timeout(45e3)
|
|
98
|
-
});
|
|
99
|
-
if (!resp.ok) {
|
|
100
|
-
if (resp.status === 401) {
|
|
101
|
-
log.warn("[bridge] 401 \u2014 bridge token rejected. Stopping poller. Set BRIDGE_TOKEN to the value Vercel has and restart.");
|
|
102
|
-
await this.stop();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const text = await resp.text().catch(() => "");
|
|
106
|
-
log.warn(`[bridge] claim HTTP ${resp.status}: ${text.slice(0, 120)}`);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
const data = await resp.json();
|
|
110
|
-
jobs = data.jobs ?? [];
|
|
111
|
-
} catch (err) {
|
|
112
|
-
log.warn(`[bridge] claim failed: ${err.message}`);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (jobs.length === 0) return;
|
|
116
|
-
await Promise.all(jobs.map((job) => this.runJob(job)));
|
|
117
|
-
}
|
|
118
|
-
async runJob(job) {
|
|
119
|
-
if (this.inFlight.has(job.id)) return;
|
|
120
|
-
this.inFlight.add(job.id);
|
|
121
|
-
const imagePayload = parseImageGenPayload(job.prompt);
|
|
122
|
-
const modelLabel = imagePayload && job.cli === "codex" ? `codex:imagegen via ${codexImagegenModelCandidates(job.model)[0]}${job.model ? ` (requested ${job.model})` : ""}` : `${job.cli}${job.model ? `:${job.model}` : ""}`;
|
|
123
|
-
log.info(`[bridge] running job ${job.id.slice(0, 8)} \xB7 ${modelLabel}`);
|
|
124
|
-
const startedAt = Date.now();
|
|
125
|
-
try {
|
|
126
|
-
const { text, modelUsed, error } = await this.invokeJob(job);
|
|
127
|
-
const latencyMs = Date.now() - startedAt;
|
|
128
|
-
await this.respond(job.id, { text, modelUsed, latencyMs, error });
|
|
129
|
-
if (error) {
|
|
130
|
-
log.warn(`[bridge] job ${job.id.slice(0, 8)} failed (${latencyMs}ms): ${error.slice(0, 120)}`);
|
|
131
|
-
} else {
|
|
132
|
-
log.success(`[bridge] job ${job.id.slice(0, 8)} done (${latencyMs}ms \xB7 ${modelUsed})`);
|
|
133
|
-
}
|
|
134
|
-
} catch (err) {
|
|
135
|
-
const latencyMs = Date.now() - startedAt;
|
|
136
|
-
await this.respond(job.id, { error: err.message, latencyMs });
|
|
137
|
-
} finally {
|
|
138
|
-
this.inFlight.delete(job.id);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
async invokeCli(cli, model, prompt) {
|
|
142
|
-
const candidates = cli === "codex" ? model && /^[a-z0-9.\-]+$/i.test(model) ? [model] : ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"] : model && /^[a-z0-9-]+$/i.test(model) ? [model] : ["opus", "sonnet"];
|
|
143
|
-
let lastErr = "";
|
|
144
|
-
for (const m of candidates) {
|
|
145
|
-
try {
|
|
146
|
-
const args = cli === "codex" ? ["exec", "-", "--model", m, "--search"] : [
|
|
147
|
-
"-p",
|
|
148
|
-
"--model",
|
|
149
|
-
m,
|
|
150
|
-
"--no-session-persistence",
|
|
151
|
-
"--allowedTools",
|
|
152
|
-
"WebSearch,WebFetch"
|
|
153
|
-
];
|
|
154
|
-
const text = await runSpawn(cli, args, prompt, CLI_TIMEOUT_MS);
|
|
155
|
-
if (text.trim()) return { text, modelUsed: `${cli}:${m}` };
|
|
156
|
-
lastErr = `${cli}/${m} returned empty stdout`;
|
|
157
|
-
} catch (err) {
|
|
158
|
-
lastErr = `${cli}/${m}: ${err.message}`;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return { error: lastErr || "no model produced output" };
|
|
162
|
-
}
|
|
163
|
-
async invokeJob(job) {
|
|
164
|
-
const claudePromptEnvelope = parseClaudePromptEnvelope(job.prompt);
|
|
165
|
-
if (claudePromptEnvelope) {
|
|
166
|
-
if (job.cli !== "claude") return { error: `${claudePromptEnvelope.__forgeJobKind} envelope requires claude CLI` };
|
|
167
|
-
return this.invokeClaudeWithEnvelope(job.model, claudePromptEnvelope);
|
|
168
|
-
}
|
|
169
|
-
const imagePayload = parseImageGenPayload(job.prompt);
|
|
170
|
-
if (imagePayload) {
|
|
171
|
-
if (job.cli !== "codex") return { error: "image-gen bridge jobs require codex CLI" };
|
|
172
|
-
return this.invokeCodexImageGen(job.model, imagePayload);
|
|
173
|
-
}
|
|
174
|
-
return this.invokeCli(job.cli, job.model, job.prompt);
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Run a Claude prompt envelope. Web tools are gated by envelope.webTools:
|
|
178
|
-
* ['WebSearch','WebFetch'] for fact-pack-build (research stage)
|
|
179
|
-
* [] for script-from-pack (pack-only writing)
|
|
180
|
-
*/
|
|
181
|
-
async invokeClaudeWithEnvelope(jobModel, envelope) {
|
|
182
|
-
const requestedModel = envelope.model || jobModel;
|
|
183
|
-
const fallbackChain = envelope.__forgeJobKind === "fact-pack-build" ? ["opus", "sonnet"] : ["sonnet", "haiku"];
|
|
184
|
-
const candidates = requestedModel && /^[a-z0-9-]+$/i.test(requestedModel) ? [requestedModel] : fallbackChain;
|
|
185
|
-
const tools = (envelope.webTools ?? []).filter((t) => t === "WebSearch" || t === "WebFetch");
|
|
186
|
-
const baseArgs = ["-p", "--model", "<m>", "--no-session-persistence"];
|
|
187
|
-
if (tools.length > 0) {
|
|
188
|
-
baseArgs.push("--allowedTools", tools.join(","));
|
|
189
|
-
}
|
|
190
|
-
let lastErr = "";
|
|
191
|
-
for (const m of candidates) {
|
|
192
|
-
try {
|
|
193
|
-
const args = baseArgs.map((a) => a === "<m>" ? m : a);
|
|
194
|
-
const text = await runSpawn("claude", args, envelope.prompt, CLI_TIMEOUT_MS);
|
|
195
|
-
if (text.trim()) return { text, modelUsed: `claude:${m}${tools.length === 0 ? " (no-web)" : ""}` };
|
|
196
|
-
lastErr = `claude/${m} returned empty stdout`;
|
|
197
|
-
} catch (err) {
|
|
198
|
-
lastErr = `claude/${m}: ${err.message}`;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return { error: lastErr || "no model produced output" };
|
|
202
|
-
}
|
|
203
|
-
async invokeCodexImageGen(model, payload) {
|
|
204
|
-
const candidates = codexImagegenModelCandidates(model);
|
|
205
|
-
const count = Math.max(1, Math.min(4, Number(payload.count) || 1));
|
|
206
|
-
const prompt = [
|
|
207
|
-
"Use Codex imagegen to generate raster image assets for Forge.",
|
|
208
|
-
"Use the installed imagegen skill in its default built-in tool mode.",
|
|
209
|
-
`Generate exactly ${count} image${count === 1 ? "" : "s"}.`,
|
|
210
|
-
`Aspect: ${payload.aspect ?? "16:9"}. Quality: ${payload.quality ?? "medium"}.`,
|
|
211
|
-
payload.size ? `Preferred size: ${payload.size}.` : "",
|
|
212
|
-
"",
|
|
213
|
-
"Image prompt:",
|
|
214
|
-
payload.prompt,
|
|
215
|
-
"",
|
|
216
|
-
"After the file(s) are generated, return ONLY strict JSON in this shape:",
|
|
217
|
-
'{"images":[{"path":"/absolute/path/to/generated.png","mimeType":"image/png"}]}',
|
|
218
|
-
"No markdown, no prose, no thumbnails, no relative paths."
|
|
219
|
-
].filter(Boolean).join("\n");
|
|
220
|
-
let lastErr = "";
|
|
221
|
-
for (const m of candidates) {
|
|
222
|
-
try {
|
|
223
|
-
const stdout = await runSpawn("codex", ["exec", "-", "--model", m], prompt, IMAGE_GEN_TIMEOUT_MS);
|
|
224
|
-
const images = collectImagesFromCodexStdout(stdout, count);
|
|
225
|
-
if (images.length === 0) {
|
|
226
|
-
lastErr = `codex/${m} imagegen returned no readable image paths`;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
text: JSON.stringify({ images, model: `codex-cli:${m}` }),
|
|
231
|
-
modelUsed: `codex-cli:${m}`
|
|
232
|
-
};
|
|
233
|
-
} catch (err) {
|
|
234
|
-
lastErr = `codex/${m}: ${err.message}`;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return { error: lastErr || "codex imagegen produced no output" };
|
|
238
|
-
}
|
|
239
|
-
async respond(jobId, body) {
|
|
240
|
-
try {
|
|
241
|
-
const resp = await fetch(`${this.baseUrl}/api/cli-bridge/respond`, {
|
|
242
|
-
method: "POST",
|
|
243
|
-
headers: {
|
|
244
|
-
Authorization: `Bearer ${this.token}`,
|
|
245
|
-
"Content-Type": "application/json"
|
|
246
|
-
},
|
|
247
|
-
body: JSON.stringify({ jobId, ...body }),
|
|
248
|
-
signal: AbortSignal.timeout(45e3)
|
|
249
|
-
});
|
|
250
|
-
if (!resp.ok) {
|
|
251
|
-
const text = await resp.text().catch(() => "");
|
|
252
|
-
log.warn(`[bridge] respond HTTP ${resp.status}: ${text.slice(0, 120)}`);
|
|
253
|
-
}
|
|
254
|
-
} catch (err) {
|
|
255
|
-
log.warn(`[bridge] respond failed for ${jobId.slice(0, 8)}: ${err.message}`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
function codexImagegenModelCandidates(model) {
|
|
260
|
-
const requested = model?.trim();
|
|
261
|
-
if (requested && /^[a-z0-9.\-]+$/i.test(requested) && CODEX_AGENT_MODEL_RE.test(requested)) {
|
|
262
|
-
return [requested];
|
|
263
|
-
}
|
|
264
|
-
return CODEX_IMAGEGEN_AGENT_MODELS;
|
|
265
|
-
}
|
|
266
|
-
function parseImageGenPayload(prompt) {
|
|
267
|
-
try {
|
|
268
|
-
const parsed = JSON.parse(prompt);
|
|
269
|
-
if (parsed.__forgeJobKind === "image-gen" && typeof parsed.prompt === "string" && parsed.prompt.trim()) {
|
|
270
|
-
return parsed;
|
|
271
|
-
}
|
|
272
|
-
} catch {
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
var CLAUDE_JOB_KINDS = [
|
|
277
|
-
"fact-pack-build",
|
|
278
|
-
"script-from-pack",
|
|
279
|
-
"script-evaluate",
|
|
280
|
-
"script-improve"
|
|
281
|
-
];
|
|
282
|
-
function parseClaudePromptEnvelope(prompt) {
|
|
283
|
-
try {
|
|
284
|
-
const parsed = JSON.parse(prompt);
|
|
285
|
-
const kind = parsed.__forgeJobKind;
|
|
286
|
-
if (kind && CLAUDE_JOB_KINDS.includes(kind) && typeof parsed.prompt === "string" && parsed.prompt.length > 0) {
|
|
287
|
-
return parsed;
|
|
288
|
-
}
|
|
289
|
-
} catch {
|
|
290
|
-
}
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
function collectImagesFromCodexStdout(stdout, maxCount) {
|
|
294
|
-
const parsedPaths = parseImagePathsFromJson(stdout);
|
|
295
|
-
const regexPaths = Array.from(stdout.matchAll(/(?:^|["`\s])(\/[^"`\n\r]+\.(?:png|jpe?g|webp))(?:["`\s]|$)/gi)).map((match) => match[1]);
|
|
296
|
-
const paths = Array.from(/* @__PURE__ */ new Set([...parsedPaths, ...regexPaths]));
|
|
297
|
-
const images = [];
|
|
298
|
-
for (const p of paths) {
|
|
299
|
-
if (images.length >= maxCount) break;
|
|
300
|
-
try {
|
|
301
|
-
const stat = fs.statSync(p);
|
|
302
|
-
if (!stat.isFile() || stat.size <= 0) continue;
|
|
303
|
-
images.push({
|
|
304
|
-
base64: fs.readFileSync(p).toString("base64"),
|
|
305
|
-
mimeType: mimeTypeForPath(p),
|
|
306
|
-
model: "codex-cli:imagegen"
|
|
307
|
-
});
|
|
308
|
-
} catch {
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return images;
|
|
312
|
-
}
|
|
313
|
-
function parseImagePathsFromJson(stdout) {
|
|
314
|
-
const trimmed = stdout.replace(/^```(?:json)?\s*/, "").replace(/```\s*$/, "").trim();
|
|
315
|
-
const candidates = [trimmed];
|
|
316
|
-
const objectMatch = trimmed.match(/\{[\s\S]*\}/);
|
|
317
|
-
if (objectMatch) candidates.push(objectMatch[0]);
|
|
318
|
-
for (const candidate of candidates) {
|
|
319
|
-
try {
|
|
320
|
-
const parsed = JSON.parse(candidate);
|
|
321
|
-
if (Array.isArray(parsed.images)) {
|
|
322
|
-
return parsed.images.map((img) => img.path).filter((p) => typeof p === "string");
|
|
323
|
-
}
|
|
324
|
-
} catch {
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return [];
|
|
328
|
-
}
|
|
329
|
-
function mimeTypeForPath(p) {
|
|
330
|
-
const lower = p.toLowerCase();
|
|
331
|
-
if (lower.endsWith(".webp")) return "image/webp";
|
|
332
|
-
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
333
|
-
return "image/png";
|
|
334
|
-
}
|
|
335
|
-
function runSpawn(cmd, args, stdin, timeoutMs) {
|
|
336
|
-
return new Promise((resolve, reject) => {
|
|
337
|
-
const proc = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
338
|
-
let stdout = "";
|
|
339
|
-
let stderr = "";
|
|
340
|
-
const timer = setTimeout(() => {
|
|
341
|
-
try {
|
|
342
|
-
proc.kill("SIGKILL");
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
reject(new Error(`${cmd} timed out after ${timeoutMs}ms`));
|
|
346
|
-
}, timeoutMs);
|
|
347
|
-
proc.stdout.on("data", (d) => {
|
|
348
|
-
stdout += d.toString("utf-8");
|
|
349
|
-
});
|
|
350
|
-
proc.stderr.on("data", (d) => {
|
|
351
|
-
stderr += d.toString("utf-8");
|
|
352
|
-
});
|
|
353
|
-
proc.on("error", (err) => {
|
|
354
|
-
clearTimeout(timer);
|
|
355
|
-
reject(err);
|
|
356
|
-
});
|
|
357
|
-
proc.on("close", (code) => {
|
|
358
|
-
clearTimeout(timer);
|
|
359
|
-
if (code !== 0) reject(new Error(`${cmd} exit ${code}: ${stderr.slice(-300)}`));
|
|
360
|
-
else resolve(stdout);
|
|
361
|
-
});
|
|
362
|
-
proc.stdin.end(stdin);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
export {
|
|
366
|
-
BridgePoller
|
|
367
|
-
};
|