tywrap 0.1.0 → 0.1.2
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/README.md +54 -221
- package/dist/cli.js +70 -3
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +56 -12
- package/dist/config/index.js.map +1 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +6 -2
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +20 -5
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/bridge-core.d.ts +64 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -0
- package/dist/runtime/bridge-core.js +344 -0
- package/dist/runtime/bridge-core.js.map +1 -0
- package/dist/runtime/node.d.ts +5 -6
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +95 -193
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +4 -7
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +117 -186
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +20 -0
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -0
- package/dist/runtime/timed-out-request-tracker.js +48 -0
- package/dist/runtime/timed-out-request-tracker.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +17 -4
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +68 -35
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/codec.d.ts +22 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +245 -55
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +7 -4
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/python.d.ts.map +1 -1
- package/dist/utils/python.js.map +1 -1
- package/package.json +11 -2
- package/runtime/python_bridge.py +450 -69
- package/src/cli.ts +81 -5
- package/src/config/index.ts +67 -16
- package/src/core/generator.ts +6 -2
- package/src/core/mapper.ts +21 -8
- package/src/index.ts +2 -1
- package/src/runtime/bridge-core.ts +454 -0
- package/src/runtime/node.ts +117 -224
- package/src/runtime/optimized-node.ts +160 -235
- package/src/runtime/timed-out-request-tracker.ts +58 -0
- package/src/types/index.ts +3 -0
- package/src/tywrap.ts +91 -36
- package/src/utils/codec.ts +306 -83
- package/src/utils/logger.ts +11 -7
- package/src/utils/python.ts +0 -1
package/src/runtime/node.ts
CHANGED
|
@@ -5,34 +5,25 @@
|
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
import { delimiter, isAbsolute, join, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
|
|
9
|
-
import { decodeValueAsync } from '../utils/codec.js';
|
|
10
|
+
import { autoRegisterArrowDecoder, decodeValueAsync } from '../utils/codec.js';
|
|
10
11
|
import { getDefaultPythonPath } from '../utils/python.js';
|
|
11
12
|
import { getVenvBinDir, getVenvPythonExe } from '../utils/runtime.js';
|
|
12
13
|
import type { BridgeInfo } from '../types/index.js';
|
|
13
14
|
|
|
14
15
|
import { RuntimeBridge } from './base.js';
|
|
16
|
+
import { BridgeDisposedError, BridgeProtocolError } from './errors.js';
|
|
15
17
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
protocol: string;
|
|
26
|
-
method: 'call' | 'instantiate' | 'call_method' | 'dispose_instance' | 'meta';
|
|
27
|
-
params: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface RpcResponse<T = unknown> {
|
|
31
|
-
id: number;
|
|
32
|
-
protocol: string;
|
|
33
|
-
result?: T;
|
|
34
|
-
error?: { type: string; message: string; traceback?: string };
|
|
35
|
-
}
|
|
18
|
+
BridgeCore,
|
|
19
|
+
type RpcRequest,
|
|
20
|
+
ensureJsonFallback,
|
|
21
|
+
ensurePythonEncoding,
|
|
22
|
+
getMaxLineLengthFromEnv,
|
|
23
|
+
getPathKey,
|
|
24
|
+
normalizeEnv,
|
|
25
|
+
validateBridgeInfo,
|
|
26
|
+
} from './bridge-core.js';
|
|
36
27
|
|
|
37
28
|
export interface NodeBridgeOptions {
|
|
38
29
|
pythonPath?: string;
|
|
@@ -40,6 +31,8 @@ export interface NodeBridgeOptions {
|
|
|
40
31
|
virtualEnv?: string;
|
|
41
32
|
cwd?: string;
|
|
42
33
|
timeoutMs?: number;
|
|
34
|
+
maxLineLength?: number;
|
|
35
|
+
inheritProcessEnv?: boolean;
|
|
43
36
|
/**
|
|
44
37
|
* When true, sets TYWRAP_CODEC_FALLBACK=json for the Python process to prefer JSON encoding
|
|
45
38
|
* for rich types (ndarray/dataframe/series). Default: false for fast-fail on Arrow path issues.
|
|
@@ -57,6 +50,8 @@ interface ResolvedNodeBridgeOptions {
|
|
|
57
50
|
virtualEnv?: string;
|
|
58
51
|
cwd: string;
|
|
59
52
|
timeoutMs: number;
|
|
53
|
+
maxLineLength?: number;
|
|
54
|
+
inheritProcessEnv: boolean;
|
|
60
55
|
enableJsonFallback: boolean;
|
|
61
56
|
env: Record<string, string | undefined>;
|
|
62
57
|
}
|
|
@@ -85,15 +80,9 @@ function resolveVirtualEnv(
|
|
|
85
80
|
|
|
86
81
|
export class NodeBridge extends RuntimeBridge {
|
|
87
82
|
private child?: import('child_process').ChildProcess;
|
|
88
|
-
private
|
|
89
|
-
private readonly pending = new Map<
|
|
90
|
-
number,
|
|
91
|
-
{ resolve: (v: unknown) => void; reject: (e: unknown) => void; timer?: NodeJS.Timeout }
|
|
92
|
-
>();
|
|
83
|
+
private core?: BridgeCore;
|
|
93
84
|
private readonly options: ResolvedNodeBridgeOptions;
|
|
94
|
-
private stderrBuffer = '';
|
|
95
85
|
private disposed = false;
|
|
96
|
-
private protocolError = false;
|
|
97
86
|
private initPromise?: Promise<void>;
|
|
98
87
|
private bridgeInfo?: BridgeInfo;
|
|
99
88
|
|
|
@@ -105,14 +94,13 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
105
94
|
const scriptPath = options.scriptPath ?? resolveDefaultScriptPath();
|
|
106
95
|
const resolvedScriptPath = isAbsolute(scriptPath) ? scriptPath : resolve(cwd, scriptPath);
|
|
107
96
|
this.options = {
|
|
108
|
-
pythonPath:
|
|
109
|
-
options.pythonPath ??
|
|
110
|
-
venv?.pythonPath ??
|
|
111
|
-
getDefaultPythonPath(),
|
|
97
|
+
pythonPath: options.pythonPath ?? venv?.pythonPath ?? getDefaultPythonPath(),
|
|
112
98
|
scriptPath: resolvedScriptPath,
|
|
113
99
|
virtualEnv,
|
|
114
100
|
cwd,
|
|
115
101
|
timeoutMs: options.timeoutMs ?? 30000,
|
|
102
|
+
maxLineLength: options.maxLineLength,
|
|
103
|
+
inheritProcessEnv: options.inheritProcessEnv ?? false,
|
|
116
104
|
enableJsonFallback: options.enableJsonFallback ?? false,
|
|
117
105
|
env: options.env ?? {},
|
|
118
106
|
};
|
|
@@ -184,111 +172,30 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
184
172
|
|
|
185
173
|
async dispose(): Promise<void> {
|
|
186
174
|
this.disposed = true;
|
|
187
|
-
this.
|
|
188
|
-
this.
|
|
189
|
-
if (!this.child) {
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
this.child.kill('SIGTERM');
|
|
193
|
-
this.child = undefined;
|
|
175
|
+
this.core?.handleProcessExit();
|
|
176
|
+
this.resetProcess();
|
|
194
177
|
}
|
|
195
178
|
|
|
196
179
|
private async send<T>(payload: Omit<RpcRequest, 'id' | 'protocol'>): Promise<T> {
|
|
197
180
|
if (this.disposed) {
|
|
198
181
|
throw new BridgeDisposedError('Bridge has been disposed');
|
|
199
182
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const text = `${JSON.stringify(message)}\n`;
|
|
203
|
-
let timer: NodeJS.Timeout | undefined;
|
|
204
|
-
const promise = new Promise<T>((resolvePromise, reject) => {
|
|
205
|
-
timer = setTimeout(() => {
|
|
206
|
-
this.pending.delete(id);
|
|
207
|
-
const stderrTail = this.stderrBuffer.trim();
|
|
208
|
-
const msg = stderrTail
|
|
209
|
-
? `Python call timed out. Recent stderr from Python:\n${stderrTail}`
|
|
210
|
-
: 'Python call timed out';
|
|
211
|
-
reject(new BridgeTimeoutError(msg));
|
|
212
|
-
}, this.options.timeoutMs);
|
|
213
|
-
const resolveWrapped = (v: unknown): void => {
|
|
214
|
-
resolvePromise(v as T);
|
|
215
|
-
};
|
|
216
|
-
this.pending.set(id, { resolve: resolveWrapped, reject, timer });
|
|
217
|
-
});
|
|
218
|
-
try {
|
|
219
|
-
if (!this.child?.stdin) {
|
|
220
|
-
throw new BridgeProtocolError('Python process not available');
|
|
221
|
-
}
|
|
222
|
-
this.child.stdin.write(text);
|
|
223
|
-
} catch (err) {
|
|
224
|
-
this.pending.delete(id);
|
|
225
|
-
if (timer) {
|
|
226
|
-
clearTimeout(timer);
|
|
227
|
-
}
|
|
228
|
-
throw new BridgeProtocolError(`IPC failure: ${(err as Error).message}`);
|
|
229
|
-
}
|
|
230
|
-
return promise;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private errorFrom(err: { type: string; message: string; traceback?: string }): Error {
|
|
234
|
-
const e = new BridgeExecutionError(`${err.type}: ${err.message}`);
|
|
235
|
-
e.traceback = err.traceback;
|
|
236
|
-
return e;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private handleProtocolError(details: string, line?: string): void {
|
|
240
|
-
if (this.protocolError) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
this.protocolError = true;
|
|
244
|
-
const snippet = line ? (line.length > 500 ? `${line.slice(0, 500)}…` : line) : undefined;
|
|
245
|
-
const hint =
|
|
246
|
-
'Ensure your Python code does not print to stdout and that the bridge outputs only JSON lines.';
|
|
247
|
-
const msg = snippet
|
|
248
|
-
? `Protocol error from Python bridge. ${details}\n${hint}\nOffending line: ${snippet}`
|
|
249
|
-
: `Protocol error from Python bridge. ${details}\n${hint}`;
|
|
250
|
-
const error = new BridgeProtocolError(msg);
|
|
251
|
-
for (const [, p] of this.pending) {
|
|
252
|
-
p.reject(error);
|
|
183
|
+
if (!this.core) {
|
|
184
|
+
throw new BridgeProtocolError('Python process not available');
|
|
253
185
|
}
|
|
254
|
-
this.
|
|
255
|
-
this.child?.kill('SIGTERM');
|
|
256
|
-
this.child = undefined;
|
|
257
|
-
this.initPromise = undefined;
|
|
258
|
-
this.bridgeInfo = undefined;
|
|
186
|
+
return this.core.send<T>(payload);
|
|
259
187
|
}
|
|
260
188
|
|
|
261
189
|
private async startProcess(): Promise<void> {
|
|
262
190
|
try {
|
|
191
|
+
const require = createRequire(import.meta.url);
|
|
192
|
+
await autoRegisterArrowDecoder({
|
|
193
|
+
loader: () => require('apache-arrow'),
|
|
194
|
+
});
|
|
263
195
|
const { spawn } = await import('child_process');
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
268
|
-
if (allowedKeys.has(k) || allowedPrefixes.some(p => k.startsWith(p))) {
|
|
269
|
-
baseEnv.set(k, v);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const env: NodeJS.ProcessEnv = {
|
|
273
|
-
...(Object.fromEntries(baseEnv) as NodeJS.ProcessEnv),
|
|
274
|
-
...this.options.env,
|
|
275
|
-
};
|
|
276
|
-
if (this.options.virtualEnv) {
|
|
277
|
-
const venv = resolveVirtualEnv(this.options.virtualEnv, this.options.cwd);
|
|
278
|
-
env.VIRTUAL_ENV = venv.venvPath;
|
|
279
|
-
const currentPath = env.PATH ?? process.env.PATH ?? '';
|
|
280
|
-
env.PATH = `${venv.binDir}${delimiter}${currentPath}`;
|
|
281
|
-
}
|
|
282
|
-
if (!env.PYTHONUTF8) {
|
|
283
|
-
env.PYTHONUTF8 = '1';
|
|
284
|
-
}
|
|
285
|
-
if (!env.PYTHONIOENCODING) {
|
|
286
|
-
env.PYTHONIOENCODING = 'UTF-8';
|
|
287
|
-
}
|
|
288
|
-
// Respect explicit request for JSON fallback only; otherwise fast-fail by default
|
|
289
|
-
if (this.options.enableJsonFallback && !env.TYWRAP_CODEC_FALLBACK) {
|
|
290
|
-
env.TYWRAP_CODEC_FALLBACK = 'json';
|
|
291
|
-
}
|
|
196
|
+
|
|
197
|
+
const env = this.buildEnv();
|
|
198
|
+
const maxLineLength = this.options.maxLineLength ?? getMaxLineLengthFromEnv(env);
|
|
292
199
|
|
|
293
200
|
let child: ReturnType<typeof spawn>;
|
|
294
201
|
try {
|
|
@@ -310,122 +217,108 @@ export class NodeBridge extends RuntimeBridge {
|
|
|
310
217
|
}
|
|
311
218
|
|
|
312
219
|
this.child = child;
|
|
313
|
-
this.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
220
|
+
this.core = new BridgeCore(
|
|
221
|
+
{
|
|
222
|
+
write: (data: string): void => {
|
|
223
|
+
if (!this.child?.stdin) {
|
|
224
|
+
throw new BridgeProtocolError('Python process not available');
|
|
225
|
+
}
|
|
226
|
+
this.child.stdin.write(data);
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
timeoutMs: this.options.timeoutMs,
|
|
231
|
+
maxLineLength,
|
|
232
|
+
decodeValue: decodeValueAsync,
|
|
233
|
+
onFatalError: (): void => this.resetProcess(),
|
|
319
234
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
this.
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
this.child.stdout?.on('data', chunk => {
|
|
238
|
+
this.core?.handleStdoutData(chunk);
|
|
324
239
|
});
|
|
325
240
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
buffer += chunk.toString();
|
|
329
|
-
let idx: number;
|
|
330
|
-
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
331
|
-
const line = buffer.slice(0, idx);
|
|
332
|
-
buffer = buffer.slice(idx + 1);
|
|
333
|
-
if (!line.trim()) {
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
(async (): Promise<void> => {
|
|
337
|
-
try {
|
|
338
|
-
const msg = JSON.parse(line) as RpcResponse;
|
|
339
|
-
if (msg.protocol !== TYWRAP_PROTOCOL) {
|
|
340
|
-
this.handleProtocolError(
|
|
341
|
-
`Invalid protocol. Expected ${TYWRAP_PROTOCOL} but received ${String(
|
|
342
|
-
msg.protocol
|
|
343
|
-
)}`,
|
|
344
|
-
line
|
|
345
|
-
);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (typeof msg.id !== 'number') {
|
|
349
|
-
this.handleProtocolError('Invalid response id', line);
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const pending = this.pending.get(msg.id);
|
|
353
|
-
if (!pending) {
|
|
354
|
-
this.handleProtocolError(`Unexpected response id ${msg.id}`, line);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
this.pending.delete(msg.id);
|
|
358
|
-
if (pending.timer) {
|
|
359
|
-
clearTimeout(pending.timer);
|
|
360
|
-
}
|
|
361
|
-
if (msg.error) {
|
|
362
|
-
pending.reject(this.errorFrom(msg.error));
|
|
363
|
-
} else {
|
|
364
|
-
try {
|
|
365
|
-
const decoded = await decodeValueAsync(msg.result);
|
|
366
|
-
pending.resolve(decoded);
|
|
367
|
-
} catch (err) {
|
|
368
|
-
pending.reject(
|
|
369
|
-
new BridgeProtocolError(
|
|
370
|
-
`Failed to decode Python response: ${
|
|
371
|
-
err instanceof Error ? err.message : String(err)
|
|
372
|
-
}`
|
|
373
|
-
)
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
} catch (err) {
|
|
378
|
-
const parseMessage = err instanceof Error ? err.message : String(err);
|
|
379
|
-
this.handleProtocolError(`Invalid JSON: ${parseMessage}`, line);
|
|
380
|
-
}
|
|
381
|
-
})().catch(() => {
|
|
382
|
-
/* ignore */
|
|
383
|
-
});
|
|
384
|
-
}
|
|
241
|
+
this.child.stderr?.on('data', chunk => {
|
|
242
|
+
this.core?.handleStderrData(chunk);
|
|
385
243
|
});
|
|
386
244
|
|
|
387
|
-
this.child
|
|
388
|
-
|
|
389
|
-
try {
|
|
390
|
-
this.stderrBuffer += chunk.toString();
|
|
391
|
-
// Truncate to last 8KB to avoid unbounded growth
|
|
392
|
-
const MAX = 8 * 1024;
|
|
393
|
-
if (this.stderrBuffer.length > MAX) {
|
|
394
|
-
this.stderrBuffer = this.stderrBuffer.slice(this.stderrBuffer.length - MAX);
|
|
395
|
-
}
|
|
396
|
-
} catch {
|
|
397
|
-
// ignore
|
|
398
|
-
}
|
|
245
|
+
this.child.on('error', err => {
|
|
246
|
+
this.core?.handleProcessError(err);
|
|
399
247
|
});
|
|
400
248
|
|
|
401
|
-
this.child
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const msg = stderrTail
|
|
405
|
-
? `Python process exited. Stderr:\n${stderrTail}`
|
|
406
|
-
: 'Python process exited';
|
|
407
|
-
p.reject(new BridgeProtocolError(msg));
|
|
408
|
-
}
|
|
409
|
-
this.pending.clear();
|
|
410
|
-
this.child = undefined;
|
|
411
|
-
this.initPromise = undefined;
|
|
412
|
-
this.bridgeInfo = undefined;
|
|
249
|
+
this.child.on('exit', () => {
|
|
250
|
+
this.core?.handleProcessExit();
|
|
251
|
+
this.resetProcess();
|
|
413
252
|
});
|
|
414
253
|
|
|
415
254
|
await this.refreshBridgeInfo();
|
|
416
255
|
} catch (err) {
|
|
417
|
-
this.
|
|
418
|
-
this.child = undefined;
|
|
419
|
-
this.initPromise = undefined;
|
|
256
|
+
this.resetProcess();
|
|
420
257
|
throw err;
|
|
421
258
|
}
|
|
422
259
|
}
|
|
423
260
|
|
|
261
|
+
private buildEnv(): NodeJS.ProcessEnv {
|
|
262
|
+
const allowedPrefixes = ['TYWRAP_'];
|
|
263
|
+
const allowedKeys = new Set(['path', 'pythonpath', 'virtual_env', 'pythonhome']);
|
|
264
|
+
const baseEnv: Record<string, string | undefined> = {};
|
|
265
|
+
if (this.options.inheritProcessEnv) {
|
|
266
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
267
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
268
|
+
baseEnv[key] = value;
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
272
|
+
if (
|
|
273
|
+
allowedKeys.has(key.toLowerCase()) ||
|
|
274
|
+
allowedPrefixes.some(prefix => key.startsWith(prefix))
|
|
275
|
+
) {
|
|
276
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
277
|
+
baseEnv[key] = value;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let env = normalizeEnv(baseEnv, this.options.env);
|
|
283
|
+
|
|
284
|
+
if (this.options.virtualEnv) {
|
|
285
|
+
const venv = resolveVirtualEnv(this.options.virtualEnv, this.options.cwd);
|
|
286
|
+
env.VIRTUAL_ENV = venv.venvPath;
|
|
287
|
+
const pathKey = getPathKey(env);
|
|
288
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
289
|
+
const currentPath = env[pathKey] ?? '';
|
|
290
|
+
// eslint-disable-next-line security/detect-object-injection -- env keys are dynamic by design
|
|
291
|
+
env[pathKey] = `${venv.binDir}${delimiter}${currentPath}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
ensurePythonEncoding(env);
|
|
295
|
+
// Respect explicit request for JSON fallback only; otherwise fast-fail by default
|
|
296
|
+
ensureJsonFallback(env, this.options.enableJsonFallback);
|
|
297
|
+
|
|
298
|
+
env = normalizeEnv(env, {});
|
|
299
|
+
return env;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private resetProcess(): void {
|
|
303
|
+
this.core?.clear();
|
|
304
|
+
this.core = undefined;
|
|
305
|
+
if (this.child) {
|
|
306
|
+
try {
|
|
307
|
+
if (this.child.exitCode === null) {
|
|
308
|
+
this.child.kill('SIGTERM');
|
|
309
|
+
}
|
|
310
|
+
} catch {
|
|
311
|
+
// ignore
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
this.child = undefined;
|
|
315
|
+
this.initPromise = undefined;
|
|
316
|
+
this.bridgeInfo = undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
424
319
|
private async refreshBridgeInfo(): Promise<void> {
|
|
425
320
|
const info = await this.send<BridgeInfo>({ method: 'meta', params: {} });
|
|
426
|
-
|
|
427
|
-
throw new BridgeProtocolError('Invalid bridge info payload');
|
|
428
|
-
}
|
|
321
|
+
validateBridgeInfo(info);
|
|
429
322
|
this.bridgeInfo = info;
|
|
430
323
|
}
|
|
431
324
|
}
|