11import { ERROR_SHELL_COMMAND_NOT_FOUND , LOGGER_LEVELS , ShellCommandError } from '@ionic/cli-framework' ;
22import { createProcessEnv , killProcessTree , onBeforeExit } from '@ionic/cli-framework/utils/process' ;
3- import { ShellCommand } from '@ionic/cli-framework/utils/shell' ;
3+ import { ShellCommand , which } from '@ionic/cli-framework/utils/shell' ;
44import { combineStreams } from '@ionic/cli-framework/utils/streams' ;
55import chalk from 'chalk' ;
6- import { ChildProcess } from 'child_process' ;
6+ import { ChildProcess , SpawnOptions } from 'child_process' ;
77import * as Debug from 'debug' ;
88import * as path from 'path' ;
99import * as split2 from 'split2' ;
@@ -32,7 +32,9 @@ export class Shell implements IShell {
3232
3333 async run ( command : string , args : string [ ] , { stream, killOnExit = true , showCommand = true , showError = true , fatalOnNotFound = true , fatalOnError = true , truncateErrorOutput, ...crossSpawnOptions } : IShellRunOptions ) : Promise < void > {
3434 this . prepareSpawnOptions ( crossSpawnOptions ) ;
35- const cmd = new ShellCommand ( command , args , crossSpawnOptions ) ;
35+
36+ const cmdpath = await this . resolveCommandPath ( command , crossSpawnOptions ) ;
37+ const cmd = new ShellCommand ( cmdpath , args , crossSpawnOptions ) ;
3638
3739 const fullCmd = cmd . bashify ( ) ;
3840 const truncatedCmd = fullCmd . length > 80 ? fullCmd . substring ( 0 , 80 ) + '...' : fullCmd ;
@@ -121,7 +123,8 @@ export class Shell implements IShell {
121123 }
122124
123125 async output ( command : string , args : string [ ] , { fatalOnNotFound = true , fatalOnError = true , showError = true , showCommand = false , ...crossSpawnOptions } : IShellOutputOptions ) : Promise < string > {
124- const cmd = new ShellCommand ( command , args , crossSpawnOptions ) ;
126+ const cmdpath = await this . resolveCommandPath ( command , crossSpawnOptions ) ;
127+ const cmd = new ShellCommand ( cmdpath , args , crossSpawnOptions ) ;
125128
126129 const fullCmd = cmd . bashify ( ) ;
127130 const truncatedCmd = fullCmd . length > 80 ? fullCmd . substring ( 0 , 80 ) + '...' : fullCmd ;
@@ -159,10 +162,31 @@ export class Shell implements IShell {
159162 }
160163 }
161164
162- spawn ( command : string , args : string [ ] , { showCommand = true , ...crossSpawnOptions } : IShellSpawnOptions ) : ChildProcess {
165+ /**
166+ * When `child_process.spawn` isn't provided a full path to the command
167+ * binary, it behaves differently on Windows than other platforms. For
168+ * Windows, discover the full path to the binary, otherwise fallback to the
169+ * command provided.
170+ *
171+ * @see https://github.com/ionic-team/ionic-cli/issues/3563#issuecomment-425232005
172+ */
173+ async resolveCommandPath ( command : string , options : SpawnOptions ) : Promise < string > {
174+ if ( process . platform === 'win32' ) {
175+ try {
176+ return await which ( command , { PATH : options . env . PATH } ) ;
177+ } catch ( e ) {
178+ // ignore
179+ }
180+ }
181+
182+ return command ;
183+ }
184+
185+ async spawn ( command : string , args : string [ ] , { showCommand = true , ...crossSpawnOptions } : IShellSpawnOptions ) : Promise < ChildProcess > {
163186 this . prepareSpawnOptions ( crossSpawnOptions ) ;
164187
165- const cmd = new ShellCommand ( command , args , crossSpawnOptions ) ;
188+ const cmdpath = await this . resolveCommandPath ( command , crossSpawnOptions ) ;
189+ const cmd = new ShellCommand ( cmdpath , args , crossSpawnOptions ) ;
166190 const p = cmd . spawn ( ) ;
167191
168192 if ( showCommand && this . e . log . level >= LOGGER_LEVELS . INFO ) {
@@ -176,7 +200,8 @@ export class Shell implements IShell {
176200 const opts : IShellSpawnOptions = { } ;
177201 this . prepareSpawnOptions ( opts ) ;
178202
179- const cmd = new ShellCommand ( command , args , opts ) ;
203+ const cmdpath = await this . resolveCommandPath ( command , opts ) ;
204+ const cmd = new ShellCommand ( cmdpath , args , opts ) ;
180205
181206 try {
182207 const out = await cmd . output ( ) ;
0 commit comments