forked from aws/agentcore-cli
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsubprocess.ts
More file actions
159 lines (142 loc) · 4.5 KB
/
Copy pathsubprocess.ts
File metadata and controls
159 lines (142 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { isWindows } from './platform';
import { spawn, spawnSync } from 'child_process';
import type { StdioOptions } from 'child_process';
/**
* Subprocess utilities for AgentCore.
*
* IMPORTANT: Async functions (runSubprocess, checkSubprocess, runSubprocessCapture)
* are safe for TUI contexts and are exported from lib.
*
* Sync functions (runSubprocessCaptureSync, checkSubprocessSync) block the event loop
* and are ONLY safe in CDK bundling contexts (which run in a subprocess). They are
* intentionally NOT exported from the public API to prevent accidental UI freezes.
*/
export interface SubprocessOptions {
cwd?: string;
env?: NodeJS.ProcessEnv;
stdio?: StdioOptions;
shell?: boolean;
}
/**
* When shell mode is enabled, merge args into the command string so that
* Node.js does not receive both a non-empty args array and `shell: true`.
* Passing both triggers DEP0190 on Node ≥ 22 (and a warning on earlier
* versions) because the arguments are concatenated without escaping.
*/
function resolveCommand(command: string, args: string[], useShell: boolean): { cmd: string; cmdArgs: string[] } {
if (useShell) {
return { cmd: [command, ...args].join(' '), cmdArgs: [] };
}
return { cmd: command, cmdArgs: args };
}
export async function runSubprocess(command: string, args: string[], options: SubprocessOptions = {}): Promise<void> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise((resolve, reject) => {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'inherit',
shell,
});
child.on('error', reject);
child.on('close', (code, signal) => {
if (code === 0) {
resolve();
return;
}
const reason = code !== null ? `code ${code}` : `signal ${String(signal)}`;
reject(new Error(`${command} exited with ${reason}`));
});
});
}
export async function checkSubprocess(
command: string,
args: string[],
options: SubprocessOptions = {}
): Promise<boolean> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise(resolve => {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'ignore',
shell,
});
child.on('error', () => resolve(false));
child.on('close', code => resolve(code === 0));
});
}
export interface SubprocessResult {
stdout: string;
stderr: string;
code: number | null;
signal: NodeJS.Signals | null;
}
export async function runSubprocessCapture(
command: string,
args: string[],
options: SubprocessOptions = {}
): Promise<SubprocessResult> {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
return new Promise(resolve => {
const child = spawn(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: 'pipe',
shell,
});
let stdout = '';
let stderr = '';
child.stdout?.on('data', (chunk: unknown) => {
stdout += typeof chunk === 'string' ? chunk : Buffer.from(chunk as Uint8Array).toString();
});
child.stderr?.on('data', (chunk: unknown) => {
stderr += typeof chunk === 'string' ? chunk : Buffer.from(chunk as Uint8Array).toString();
});
child.on('close', (code, signal) => {
resolve({ stdout, stderr, code, signal });
});
child.on('error', () => {
resolve({ stdout, stderr, code: -1, signal: null });
});
});
}
export function runSubprocessCaptureSync(
command: string,
args: string[],
options: SubprocessOptions = {}
): SubprocessResult {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
const result = spawnSync(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: 'pipe',
shell,
encoding: 'utf-8',
});
return {
stdout: result.stdout ?? '',
stderr: result.stderr ?? '',
code: result.status,
signal: result.signal,
};
}
export function checkSubprocessSync(command: string, args: string[], options: SubprocessOptions = {}): boolean {
const shell = options.shell ?? isWindows;
const { cmd, cmdArgs } = resolveCommand(command, args, shell);
try {
const result = spawnSync(cmd, cmdArgs, {
cwd: options.cwd,
env: options.env,
stdio: options.stdio ?? 'ignore',
shell,
});
return result.status === 0;
} catch {
return false;
}
}