@cloudflare/sandbox 0.0.0-603d05f → 0.0.0-66cc85b
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/CHANGELOG.md +36 -0
- package/Dockerfile +23 -24
- package/README.md +59 -6
- package/container_src/bun.lock +31 -77
- package/container_src/control-process.ts +1 -1
- package/container_src/handler/exec.ts +2 -2
- package/container_src/handler/file.ts +51 -0
- package/container_src/handler/session.ts +2 -2
- package/container_src/index.ts +43 -43
- package/container_src/interpreter-service.ts +276 -0
- package/container_src/isolation.ts +207 -33
- package/container_src/mime-processor.ts +1 -1
- package/container_src/package.json +4 -4
- package/container_src/runtime/executors/javascript/node_executor.ts +123 -0
- package/container_src/runtime/executors/python/ipython_executor.py +338 -0
- package/container_src/runtime/executors/typescript/ts_executor.ts +138 -0
- package/container_src/runtime/process-pool.ts +464 -0
- package/container_src/startup.sh +6 -79
- package/package.json +2 -2
- package/src/client.ts +39 -0
- package/src/errors.ts +15 -14
- package/src/file-stream.ts +162 -0
- package/src/index.ts +22 -5
- package/src/{jupyter-client.ts → interpreter-client.ts} +6 -3
- package/src/interpreter-types.ts +102 -95
- package/src/interpreter.ts +8 -8
- package/src/sandbox.ts +18 -5
- package/src/types.ts +69 -0
- package/container_src/jupyter-server.ts +0 -579
- package/container_src/jupyter-service.ts +0 -461
- package/container_src/jupyter_config.py +0 -48
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements PID namespace isolation to secure the sandbox environment.
|
|
5
5
|
* Executed commands run in isolated namespaces, preventing them from:
|
|
6
|
-
* - Seeing or killing control plane processes (
|
|
6
|
+
* - Seeing or killing control plane processes (Bun)
|
|
7
7
|
* - Accessing platform secrets in /proc
|
|
8
8
|
* - Hijacking control plane ports
|
|
9
9
|
*
|
|
@@ -35,11 +35,11 @@ import type { ProcessRecord, ProcessStatus } from './types';
|
|
|
35
35
|
// Configuration constants
|
|
36
36
|
const CONFIG = {
|
|
37
37
|
// Timeouts (in milliseconds)
|
|
38
|
-
COMMAND_TIMEOUT_MS: 30000, // 30 seconds for command execution
|
|
39
38
|
READY_TIMEOUT_MS: 5000, // 5 seconds for control process to initialize
|
|
40
|
-
CLEANUP_INTERVAL_MS: 30000, // Run cleanup every 30 seconds
|
|
41
|
-
TEMP_FILE_MAX_AGE_MS: 60000, // Delete temp files older than 60 seconds
|
|
42
39
|
SHUTDOWN_GRACE_PERIOD_MS: 500, // Grace period for cleanup on shutdown
|
|
40
|
+
COMMAND_TIMEOUT_MS: parseInt(process.env.COMMAND_TIMEOUT_MS || '30000'), // 30 seconds for command execution
|
|
41
|
+
CLEANUP_INTERVAL_MS: parseInt(process.env.CLEANUP_INTERVAL_MS || '30000'), // Run cleanup every 30 seconds
|
|
42
|
+
TEMP_FILE_MAX_AGE_MS: parseInt(process.env.TEMP_FILE_MAX_AGE_MS || '60000'), // Delete temp files older than 60 seconds
|
|
43
43
|
|
|
44
44
|
// Default paths
|
|
45
45
|
DEFAULT_CWD: '/workspace',
|
|
@@ -121,13 +121,20 @@ export class Session {
|
|
|
121
121
|
timeout: NodeJS.Timeout;
|
|
122
122
|
}>();
|
|
123
123
|
private processes = new Map<string, ProcessRecord>(); // Session-specific processes
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
constructor(private options: SessionOptions) {
|
|
126
126
|
this.canIsolate = (options.isolation === true) && hasNamespaceSupport();
|
|
127
127
|
if (options.isolation === true && !this.canIsolate) {
|
|
128
128
|
console.log(`[Session] Isolation requested for '${options.id}' but not available`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if the session is ready for command execution
|
|
134
|
+
*/
|
|
135
|
+
isReady(): boolean {
|
|
136
|
+
return this.ready && this.control !== null && !this.control.killed;
|
|
137
|
+
}
|
|
131
138
|
|
|
132
139
|
async initialize(): Promise<void> {
|
|
133
140
|
// Use the proper TypeScript control process file
|
|
@@ -423,12 +430,11 @@ export class Session {
|
|
|
423
430
|
|
|
424
431
|
// File Operations - Execute as shell commands to inherit session context
|
|
425
432
|
async writeFileOperation(path: string, content: string, encoding: string = 'utf-8'): Promise<{ success: boolean; exitCode: number; path: string }> {
|
|
426
|
-
// Escape content for safe heredoc usage
|
|
427
|
-
const safeContent = content.replace(/'/g, "'\\''");
|
|
428
|
-
|
|
429
433
|
// Create parent directory if needed, then write file using heredoc
|
|
434
|
+
// Note: The quoted heredoc delimiter 'SANDBOX_EOF' prevents variable expansion
|
|
435
|
+
// and treats the content literally, so no escaping is required
|
|
430
436
|
const command = `mkdir -p "$(dirname "${path}")" && cat > "${path}" << 'SANDBOX_EOF'
|
|
431
|
-
${
|
|
437
|
+
${content}
|
|
432
438
|
SANDBOX_EOF`;
|
|
433
439
|
|
|
434
440
|
const result = await this.exec(command);
|
|
@@ -440,16 +446,180 @@ SANDBOX_EOF`;
|
|
|
440
446
|
};
|
|
441
447
|
}
|
|
442
448
|
|
|
443
|
-
async readFileOperation(path: string, encoding: string = 'utf-8'): Promise<{
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
449
|
+
async readFileOperation(path: string, encoding: string = 'utf-8'): Promise<{
|
|
450
|
+
success: boolean;
|
|
451
|
+
exitCode: number;
|
|
452
|
+
content: string;
|
|
453
|
+
path: string;
|
|
454
|
+
encoding?: 'utf-8' | 'base64';
|
|
455
|
+
isBinary?: boolean;
|
|
456
|
+
mimeType?: string;
|
|
457
|
+
size?: number;
|
|
458
|
+
}> {
|
|
459
|
+
// Step 1: Check if file exists and get metadata
|
|
460
|
+
const statCommand = `stat -c '%s' "${path}" 2>/dev/null || echo "FILE_NOT_FOUND"`;
|
|
461
|
+
const statResult = await this.exec(statCommand);
|
|
462
|
+
|
|
463
|
+
if (statResult.stdout.trim() === 'FILE_NOT_FOUND') {
|
|
464
|
+
// File doesn't exist - return error
|
|
465
|
+
return {
|
|
466
|
+
success: false,
|
|
467
|
+
exitCode: 1,
|
|
468
|
+
content: '',
|
|
469
|
+
path
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const fileSize = parseInt(statResult.stdout.trim(), 10);
|
|
474
|
+
|
|
475
|
+
// Step 2: Detect MIME type using file command
|
|
476
|
+
const mimeCommand = `file --mime-type -b "${path}"`;
|
|
477
|
+
const mimeResult = await this.exec(mimeCommand);
|
|
478
|
+
const mimeType = mimeResult.stdout.trim();
|
|
479
|
+
|
|
480
|
+
// Step 3: Determine if file is binary based on MIME type
|
|
481
|
+
// Text MIME types: text/*, application/json, application/xml, application/javascript, etc.
|
|
482
|
+
const isBinary = !mimeType.startsWith('text/') &&
|
|
483
|
+
!mimeType.includes('json') &&
|
|
484
|
+
!mimeType.includes('xml') &&
|
|
485
|
+
!mimeType.includes('javascript') &&
|
|
486
|
+
!mimeType.includes('x-empty');
|
|
487
|
+
|
|
488
|
+
// Step 4: Read file with appropriate encoding
|
|
489
|
+
let content: string;
|
|
490
|
+
let actualEncoding: 'utf-8' | 'base64';
|
|
491
|
+
|
|
492
|
+
if (isBinary) {
|
|
493
|
+
// Use base64 for binary files
|
|
494
|
+
const base64Command = `base64 -w 0 "${path}"`;
|
|
495
|
+
const base64Result = await this.exec(base64Command);
|
|
496
|
+
content = base64Result.stdout;
|
|
497
|
+
actualEncoding = 'base64';
|
|
498
|
+
} else {
|
|
499
|
+
// Use cat for text files
|
|
500
|
+
const catCommand = `cat "${path}"`;
|
|
501
|
+
const catResult = await this.exec(catCommand);
|
|
502
|
+
content = catResult.stdout;
|
|
503
|
+
actualEncoding = 'utf-8';
|
|
504
|
+
}
|
|
505
|
+
|
|
447
506
|
return {
|
|
448
|
-
success:
|
|
449
|
-
exitCode:
|
|
450
|
-
content
|
|
451
|
-
path
|
|
507
|
+
success: true,
|
|
508
|
+
exitCode: 0,
|
|
509
|
+
content,
|
|
510
|
+
path,
|
|
511
|
+
encoding: actualEncoding,
|
|
512
|
+
isBinary,
|
|
513
|
+
mimeType,
|
|
514
|
+
size: fileSize
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async readFileStreamOperation(path: string): Promise<ReadableStream<Uint8Array>> {
|
|
519
|
+
const encoder = new TextEncoder();
|
|
520
|
+
|
|
521
|
+
// Helper to send SSE event
|
|
522
|
+
const sseEvent = (event: any): Uint8Array => {
|
|
523
|
+
return encoder.encode(`data: ${JSON.stringify(event)}\n\n`);
|
|
452
524
|
};
|
|
525
|
+
|
|
526
|
+
// Create streaming response
|
|
527
|
+
return new ReadableStream({
|
|
528
|
+
start: async (controller) => {
|
|
529
|
+
try {
|
|
530
|
+
// Step 1: Get file metadata (same logic as readFileOperation)
|
|
531
|
+
const statCommand = `stat -c '%s' "${path}" 2>/dev/null || echo "FILE_NOT_FOUND"`;
|
|
532
|
+
const statResult = await this.exec(statCommand);
|
|
533
|
+
|
|
534
|
+
if (statResult.stdout.trim() === 'FILE_NOT_FOUND') {
|
|
535
|
+
// File doesn't exist - send error event
|
|
536
|
+
controller.enqueue(sseEvent({
|
|
537
|
+
type: 'error',
|
|
538
|
+
error: `File not found: ${path}`
|
|
539
|
+
}));
|
|
540
|
+
controller.close();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const fileSize = parseInt(statResult.stdout.trim(), 10);
|
|
545
|
+
|
|
546
|
+
// Step 2: Detect MIME type
|
|
547
|
+
const mimeCommand = `file --mime-type -b "${path}"`;
|
|
548
|
+
const mimeResult = await this.exec(mimeCommand);
|
|
549
|
+
const mimeType = mimeResult.stdout.trim();
|
|
550
|
+
|
|
551
|
+
// Step 3: Determine if binary
|
|
552
|
+
const isBinary = !mimeType.startsWith('text/') &&
|
|
553
|
+
!mimeType.includes('json') &&
|
|
554
|
+
!mimeType.includes('xml') &&
|
|
555
|
+
!mimeType.includes('javascript') &&
|
|
556
|
+
!mimeType.includes('x-empty');
|
|
557
|
+
|
|
558
|
+
const encoding: 'utf-8' | 'base64' = isBinary ? 'base64' : 'utf-8';
|
|
559
|
+
|
|
560
|
+
// Step 4: Send metadata event
|
|
561
|
+
controller.enqueue(sseEvent({
|
|
562
|
+
type: 'metadata',
|
|
563
|
+
mimeType,
|
|
564
|
+
size: fileSize,
|
|
565
|
+
isBinary,
|
|
566
|
+
encoding
|
|
567
|
+
}));
|
|
568
|
+
|
|
569
|
+
// Step 5: Stream file in chunks
|
|
570
|
+
// IMPORTANT: Chunk size MUST be divisible by 3 for base64 encoding!
|
|
571
|
+
// Base64 encodes 3 bytes at a time. If chunks aren't aligned,
|
|
572
|
+
// concatenating separately-encoded base64 strings corrupts the data.
|
|
573
|
+
const CHUNK_SIZE = 65535;
|
|
574
|
+
let bytesRead = 0;
|
|
575
|
+
|
|
576
|
+
while (bytesRead < fileSize) {
|
|
577
|
+
const remainingBytes = fileSize - bytesRead;
|
|
578
|
+
const chunkSize = Math.min(CHUNK_SIZE, remainingBytes);
|
|
579
|
+
|
|
580
|
+
// Use dd to read chunk at specific offset
|
|
581
|
+
// bs=1 means 1 byte block size, skip=offset, count=chunkSize
|
|
582
|
+
let chunkCommand: string;
|
|
583
|
+
if (isBinary) {
|
|
584
|
+
// For binary, read and encode as base64
|
|
585
|
+
chunkCommand = `dd if="${path}" bs=1 skip=${bytesRead} count=${chunkSize} 2>/dev/null | base64 -w 0`;
|
|
586
|
+
} else {
|
|
587
|
+
// For text, just read
|
|
588
|
+
chunkCommand = `dd if="${path}" bs=1 skip=${bytesRead} count=${chunkSize} 2>/dev/null`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const chunkResult = await this.exec(chunkCommand);
|
|
592
|
+
|
|
593
|
+
// Send chunk event
|
|
594
|
+
controller.enqueue(sseEvent({
|
|
595
|
+
type: 'chunk',
|
|
596
|
+
data: chunkResult.stdout
|
|
597
|
+
}));
|
|
598
|
+
|
|
599
|
+
bytesRead += chunkSize;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Step 6: Send complete event
|
|
603
|
+
controller.enqueue(sseEvent({
|
|
604
|
+
type: 'complete',
|
|
605
|
+
bytesRead
|
|
606
|
+
}));
|
|
607
|
+
|
|
608
|
+
controller.close();
|
|
609
|
+
} catch (error) {
|
|
610
|
+
// Send error event
|
|
611
|
+
controller.enqueue(sseEvent({
|
|
612
|
+
type: 'error',
|
|
613
|
+
error: error instanceof Error ? error.message : String(error)
|
|
614
|
+
}));
|
|
615
|
+
controller.close();
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
cancel() {
|
|
620
|
+
console.log(`[Session] File stream cancelled for: ${path}`);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
453
623
|
}
|
|
454
624
|
|
|
455
625
|
async mkdirOperation(path: string, recursive: boolean = false): Promise<{ success: boolean; exitCode: number; path: string; recursive: boolean }> {
|
|
@@ -947,17 +1117,25 @@ export class SessionManager {
|
|
|
947
1117
|
throw new Error(`cwd must be an absolute path starting with '/', got: ${options.cwd}`);
|
|
948
1118
|
}
|
|
949
1119
|
}
|
|
950
|
-
|
|
951
|
-
//
|
|
1120
|
+
|
|
1121
|
+
// Check if session already exists
|
|
952
1122
|
const existing = this.sessions.get(options.id);
|
|
953
1123
|
if (existing) {
|
|
954
|
-
existing
|
|
1124
|
+
// If the existing session is healthy and ready, reuse it
|
|
1125
|
+
if (existing.isReady()) {
|
|
1126
|
+
console.log(`[SessionManager] Reusing existing session '${options.id}'`);
|
|
1127
|
+
return existing;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// If the session exists but is not ready, clean it up and create a new one
|
|
1131
|
+
console.log(`[SessionManager] Destroying unhealthy session '${options.id}' before recreating`);
|
|
1132
|
+
await existing.destroy();
|
|
955
1133
|
}
|
|
956
|
-
|
|
1134
|
+
|
|
957
1135
|
// Create new session
|
|
958
1136
|
const session = new Session(options);
|
|
959
1137
|
await session.initialize();
|
|
960
|
-
|
|
1138
|
+
|
|
961
1139
|
this.sessions.set(options.id, session);
|
|
962
1140
|
console.log(`[SessionManager] Created session '${options.id}'`);
|
|
963
1141
|
return session;
|
|
@@ -973,15 +1151,11 @@ export class SessionManager {
|
|
|
973
1151
|
|
|
974
1152
|
// Helper to get or create default session - reduces duplication
|
|
975
1153
|
async getOrCreateDefaultSession(): Promise<Session> {
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
isolation: true
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
return defaultSession;
|
|
1154
|
+
return await this.createSession({
|
|
1155
|
+
id: 'default',
|
|
1156
|
+
cwd: '/workspace', // Consistent default working directory
|
|
1157
|
+
isolation: true
|
|
1158
|
+
});
|
|
985
1159
|
}
|
|
986
1160
|
|
|
987
1161
|
async exec(command: string, options?: { cwd?: string }): Promise<ExecResult> {
|
|
@@ -1000,7 +1174,7 @@ export class SessionManager {
|
|
|
1000
1174
|
return defaultSession.writeFileOperation(path, content, encoding);
|
|
1001
1175
|
}
|
|
1002
1176
|
|
|
1003
|
-
async readFile(path: string, encoding?: string): Promise<{ success: boolean; exitCode: number; content: string; path: string }> {
|
|
1177
|
+
async readFile(path: string, encoding?: string): Promise<{ success: boolean; exitCode: number; content: string; path: string; encoding?: 'utf-8' | 'base64'; isBinary?: boolean; mimeType?: string; size?: number }> {
|
|
1004
1178
|
const defaultSession = await this.getOrCreateDefaultSession();
|
|
1005
1179
|
return defaultSession.readFileOperation(path, encoding);
|
|
1006
1180
|
}
|
|
@@ -1036,4 +1210,4 @@ export class SessionManager {
|
|
|
1036
1210
|
}
|
|
1037
1211
|
this.sessions.clear();
|
|
1038
1212
|
}
|
|
1039
|
-
}
|
|
1213
|
+
}
|
|
@@ -28,7 +28,7 @@ export interface ChartData {
|
|
|
28
28
|
library?: 'matplotlib' | 'plotly' | 'altair' | 'seaborn' | 'unknown';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export function
|
|
31
|
+
export function processMessage(msg: any): ExecutionResult | null {
|
|
32
32
|
const msgType = msg.header?.msg_type || msg.msg_type;
|
|
33
33
|
|
|
34
34
|
switch (msgType) {
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"start": "bun run index.ts"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"
|
|
11
|
-
"ws": "^8.16.0",
|
|
10
|
+
"esbuild": "^0.21.5",
|
|
12
11
|
"uuid": "^9.0.1"
|
|
13
12
|
},
|
|
14
13
|
"devDependencies": {
|
|
15
|
-
"@types/
|
|
16
|
-
"@types/uuid": "^9.0.7"
|
|
14
|
+
"@types/node": "^20.0.0",
|
|
15
|
+
"@types/uuid": "^9.0.7",
|
|
16
|
+
"typescript": "^5.3.0"
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as readline from 'node:readline';
|
|
4
|
+
import * as util from 'node:util';
|
|
5
|
+
import * as vm from 'node:vm';
|
|
6
|
+
import type { RichOutput } from '../../process-pool';
|
|
7
|
+
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout,
|
|
11
|
+
terminal: false
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const sandbox = {
|
|
15
|
+
console: console,
|
|
16
|
+
process: process,
|
|
17
|
+
require: require,
|
|
18
|
+
Buffer: Buffer,
|
|
19
|
+
setTimeout: setTimeout,
|
|
20
|
+
setInterval: setInterval,
|
|
21
|
+
clearTimeout: clearTimeout,
|
|
22
|
+
clearInterval: clearInterval,
|
|
23
|
+
setImmediate: setImmediate,
|
|
24
|
+
clearImmediate: clearImmediate,
|
|
25
|
+
global: global,
|
|
26
|
+
__dirname: __dirname,
|
|
27
|
+
__filename: __filename
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const context = vm.createContext(sandbox);
|
|
31
|
+
|
|
32
|
+
console.log(JSON.stringify({ status: "ready" }));
|
|
33
|
+
|
|
34
|
+
rl.on('line', async (line: string) => {
|
|
35
|
+
try {
|
|
36
|
+
const request = JSON.parse(line);
|
|
37
|
+
const { code, executionId } = request;
|
|
38
|
+
|
|
39
|
+
const originalStdoutWrite = process.stdout.write;
|
|
40
|
+
const originalStderrWrite = process.stderr.write;
|
|
41
|
+
|
|
42
|
+
let stdout = '';
|
|
43
|
+
let stderr = '';
|
|
44
|
+
|
|
45
|
+
(process.stdout.write as any) = (chunk: string | Buffer, encoding?: BufferEncoding, callback?: () => void) => {
|
|
46
|
+
stdout += chunk.toString();
|
|
47
|
+
if (callback) callback();
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
(process.stderr.write as any) = (chunk: string | Buffer, encoding?: BufferEncoding, callback?: () => void) => {
|
|
52
|
+
stderr += chunk.toString();
|
|
53
|
+
if (callback) callback();
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
let result: unknown;
|
|
58
|
+
let success = true;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
result = vm.runInContext(code, context, {
|
|
62
|
+
filename: `<execution-${executionId}>`,
|
|
63
|
+
timeout: 30000
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
} catch (error: unknown) {
|
|
67
|
+
const err = error as Error;
|
|
68
|
+
stderr += err.stack || err.toString();
|
|
69
|
+
success = false;
|
|
70
|
+
} finally {
|
|
71
|
+
process.stdout.write = originalStdoutWrite;
|
|
72
|
+
process.stderr.write = originalStderrWrite;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const outputs: RichOutput[] = [];
|
|
76
|
+
|
|
77
|
+
if (result !== undefined) {
|
|
78
|
+
if (typeof result === 'object' && result !== null) {
|
|
79
|
+
outputs.push({
|
|
80
|
+
type: 'json',
|
|
81
|
+
data: JSON.stringify(result, null, 2),
|
|
82
|
+
metadata: {}
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
outputs.push({
|
|
86
|
+
type: 'text',
|
|
87
|
+
data: util.inspect(result, { showHidden: false, depth: null, colors: false }),
|
|
88
|
+
metadata: {}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const response = {
|
|
94
|
+
stdout,
|
|
95
|
+
stderr,
|
|
96
|
+
success,
|
|
97
|
+
executionId,
|
|
98
|
+
outputs
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
console.log(JSON.stringify(response));
|
|
102
|
+
|
|
103
|
+
} catch (error: unknown) {
|
|
104
|
+
const err = error as Error;
|
|
105
|
+
console.log(JSON.stringify({
|
|
106
|
+
stdout: '',
|
|
107
|
+
stderr: `Error processing request: ${err.message}`,
|
|
108
|
+
success: false,
|
|
109
|
+
executionId: 'unknown',
|
|
110
|
+
outputs: []
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
process.on('SIGTERM', () => {
|
|
116
|
+
rl.close();
|
|
117
|
+
process.exit(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
process.on('SIGINT', () => {
|
|
121
|
+
rl.close();
|
|
122
|
+
process.exit(0);
|
|
123
|
+
});
|