@@ -20,13 +20,28 @@ export interface SubprocessOptions {
2020 shell ?: boolean ;
2121}
2222
23+ /**
24+ * When shell mode is enabled, merge args into the command string so that
25+ * Node.js does not receive both a non-empty args array and `shell: true`.
26+ * Passing both triggers DEP0190 on Node ≥ 22 (and a warning on earlier
27+ * versions) because the arguments are concatenated without escaping.
28+ */
29+ function resolveCommand ( command : string , args : string [ ] , useShell : boolean ) : { cmd : string ; cmdArgs : string [ ] } {
30+ if ( useShell ) {
31+ return { cmd : [ command , ...args ] . join ( ' ' ) , cmdArgs : [ ] } ;
32+ }
33+ return { cmd : command , cmdArgs : args } ;
34+ }
35+
2336export async function runSubprocess ( command : string , args : string [ ] , options : SubprocessOptions = { } ) : Promise < void > {
37+ const shell = options . shell ?? isWindows ;
38+ const { cmd, cmdArgs } = resolveCommand ( command , args , shell ) ;
2439 return new Promise ( ( resolve , reject ) => {
25- const child = spawn ( command , args , {
40+ const child = spawn ( cmd , cmdArgs , {
2641 cwd : options . cwd ,
2742 env : options . env ,
2843 stdio : options . stdio ?? 'inherit' ,
29- shell : options . shell ?? isWindows ,
44+ shell,
3045 } ) ;
3146
3247 child . on ( 'error' , reject ) ;
@@ -46,12 +61,14 @@ export async function checkSubprocess(
4661 args : string [ ] ,
4762 options : SubprocessOptions = { }
4863) : Promise < boolean > {
64+ const shell = options . shell ?? isWindows ;
65+ const { cmd, cmdArgs } = resolveCommand ( command , args , shell ) ;
4966 return new Promise ( resolve => {
50- const child = spawn ( command , args , {
67+ const child = spawn ( cmd , cmdArgs , {
5168 cwd : options . cwd ,
5269 env : options . env ,
5370 stdio : options . stdio ?? 'ignore' ,
54- shell : options . shell ?? isWindows ,
71+ shell,
5572 } ) ;
5673
5774 child . on ( 'error' , ( ) => resolve ( false ) ) ;
@@ -71,12 +88,14 @@ export async function runSubprocessCapture(
7188 args : string [ ] ,
7289 options : SubprocessOptions = { }
7390) : Promise < SubprocessResult > {
91+ const shell = options . shell ?? isWindows ;
92+ const { cmd, cmdArgs } = resolveCommand ( command , args , shell ) ;
7493 return new Promise ( resolve => {
75- const child = spawn ( command , args , {
94+ const child = spawn ( cmd , cmdArgs , {
7695 cwd : options . cwd ,
7796 env : options . env ,
7897 stdio : 'pipe' ,
79- shell : options . shell ?? isWindows ,
98+ shell,
8099 } ) ;
81100
82101 let stdout = '' ;
@@ -105,11 +124,13 @@ export function runSubprocessCaptureSync(
105124 args : string [ ] ,
106125 options : SubprocessOptions = { }
107126) : SubprocessResult {
108- const result = spawnSync ( command , args , {
127+ const shell = options . shell ?? isWindows ;
128+ const { cmd, cmdArgs } = resolveCommand ( command , args , shell ) ;
129+ const result = spawnSync ( cmd , cmdArgs , {
109130 cwd : options . cwd ,
110131 env : options . env ,
111132 stdio : 'pipe' ,
112- shell : options . shell ?? isWindows ,
133+ shell,
113134 encoding : 'utf-8' ,
114135 } ) ;
115136
@@ -122,12 +143,14 @@ export function runSubprocessCaptureSync(
122143}
123144
124145export function checkSubprocessSync ( command : string , args : string [ ] , options : SubprocessOptions = { } ) : boolean {
146+ const shell = options . shell ?? isWindows ;
147+ const { cmd, cmdArgs } = resolveCommand ( command , args , shell ) ;
125148 try {
126- const result = spawnSync ( command , args , {
149+ const result = spawnSync ( cmd , cmdArgs , {
127150 cwd : options . cwd ,
128151 env : options . env ,
129152 stdio : options . stdio ?? 'ignore' ,
130- shell : options . shell ?? isWindows ,
153+ shell,
131154 } ) ;
132155 return result . status === 0 ;
133156 } catch {
0 commit comments