1- import { Footnote , LOGGER_LEVELS , MetadataGroup , createPrefixedFormatter } from '@ionic/cli-framework' ;
2- import { onBeforeExit , processExit , sleepForever } from '@ionic/utils-process' ;
3- import { ERROR_COMMAND_NOT_FOUND , SubprocessError } from '@ionic/utils-subprocess' ;
4- import * as path from 'path' ;
1+ import { Footnote , MetadataGroup } from '@ionic/cli-framework' ;
2+ import { onBeforeExit , sleepForever } from '@ionic/utils-process' ;
53import * as url from 'url' ;
64
75import { CommandInstanceInfo , CommandLineInputs , CommandLineOptions , CommandMetadata , CommandMetadataOption , CommandPreRun , IShellRunOptions } from '../../definitions' ;
86import { COMMON_BUILD_COMMAND_OPTIONS , build } from '../../lib/build' ;
97import { input , strong , weak } from '../../lib/color' ;
108import { FatalException } from '../../lib/errors' ;
119import { loadConfigXml } from '../../lib/integrations/cordova/config' ;
10+ import { getPackagePath } from '../../lib/integrations/cordova/project' ;
1211import { filterArgumentsForCordova , generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils' ;
12+ import { SUPPORTED_PLATFORMS , createNativeRunArgs , createNativeRunListArgs , runNativeRun } from '../../lib/native-run' ;
1313import { COMMON_SERVE_COMMAND_OPTIONS , LOCAL_ADDRESSES , serve } from '../../lib/serve' ;
14- import { createDefaultLoggerHandlers } from '../../lib/utils/logger' ;
15- import { pkgManagerArgs } from '../../lib/utils/npm' ;
14+ import { createPrefixedWriteStream } from '../../lib/utils/logger' ;
1615
1716import { CORDOVA_BUILD_EXAMPLE_COMMANDS , CORDOVA_RUN_OPTIONS , CordovaCommand } from './base' ;
1817
19- const CORDOVA_ANDROID_PACKAGE_PATH = 'platforms/android/app/build/outputs/apk/' ;
20- const CORDOVA_IOS_SIMULATOR_PACKAGE_PATH = 'platforms/ios/build/emulator' ;
21- const CORDOVA_IOS_DEVICE_PACKAGE_PATH = 'platforms/ios/build/device' ;
22-
2318const NATIVE_RUN_OPTIONS : ReadonlyArray < CommandMetadataOption > = [
2419 {
2520 name : 'native-run' ,
26- summary : `Use ${ input ( 'native-run' ) } instead of Cordova for running the app ` ,
21+ summary : `Do not use ${ input ( 'native-run' ) } to run the app; use Cordova instead ` ,
2722 type : Boolean ,
28- groups : [ MetadataGroup . EXPERIMENTAL , 'native-run' ] ,
23+ default : true ,
24+ groups : [ 'native-run' ] ,
2925 hint : weak ( '[native-run]' ) ,
3026 } ,
3127 {
3228 name : 'connect' ,
3329 summary : 'Do not tie the running app to the process' ,
3430 type : Boolean ,
3531 default : true ,
36- groups : [ MetadataGroup . EXPERIMENTAL , 'native-run' ] ,
37- hint : weak ( '[native-run]' ) ,
32+ groups : [ 'native-run' ] ,
33+ hint : weak ( '[native-run] (--livereload) ' ) ,
3834 } ,
3935 {
4036 name : 'json' ,
41- summary : `Output ${ input ( '--list' ) } targets in JSON` ,
37+ summary : `Output targets in JSON` ,
4238 type : Boolean ,
43- groups : [ MetadataGroup . EXPERIMENTAL , 'native-run' ] ,
44- hint : weak ( '[native-run]' ) ,
39+ groups : [ MetadataGroup . ADVANCED , 'native-run' ] ,
40+ hint : weak ( '[native-run] (--list) ' ) ,
4541 } ,
4642] ;
4743
@@ -128,13 +124,15 @@ export class RunCommand extends CordovaCommand implements CommandPreRun {
128124 type : 'project' ,
129125 summary : 'Run an Ionic project on a connected device' ,
130126 description : `
131- Like running ${ input ( 'cordova run' ) } or ${ input ( 'cordova emulate' ) } directly, but performs ${ input ( 'ionic build' ) } before deploying to the device or emulator . Optionally specify the ${ input ( '--livereload' ) } option to use the dev server from ${ input ( 'ionic serve' ) } for livereload functionality.
127+ Build your app and deploy it to devices and emulators using this command . Optionally specify the ${ input ( '--livereload' ) } option to use the dev server from ${ input ( 'ionic serve' ) } for livereload functionality.
132128
133- For Android and iOS, you can setup Remote Debugging on your device with browser development tools using these docs[^remote-debugging-docs].
129+ This command will first use ${ input ( 'ionic build' ) } to build web assets (or ${ input ( 'ionic serve' ) } with the ${ input ( '--livereload' ) } option). Then, ${ input ( 'cordova build' ) } is used to compile and prepare your app. Finally, the ${ input ( 'native-run' ) } utility[^native-run-repo] is used to run your app on a device. To use Cordova for this process instead, use the ${ input ( '--no-native-run' ) } option.
130+
131+ If you have multiple devices and emulators, you can target a specific one with the ${ input ( '--target' ) } option. You can list targets with ${ input ( '--list' ) } .
134132
135- Just like with ${ input ( 'ionic cordova build' ) } , you can pass additional options to the Cordova CLI using the ${ input ( '--' ) } separator. To pass additional options to the dev server, consider using ${ input ( 'ionic serve' ) } and the ${ input ( '--livereload-url' ) } option .
133+ For Android and iOS , you can setup Remote Debugging on your device with browser development tools using these docs[^remote-debugging-docs] .
136134
137- With the experimental ${ input ( '--native-run ' ) } flag, this command will first use Cordova to build your app, and then it will run it on a device using the ${ input ( 'native-run ' ) } utility[^native-run-repo] instead of Cordova .
135+ Just like with ${ input ( 'ionic cordova build ' ) } , you can pass additional options to the Cordova CLI using the ${ input ( '--' ) } separator. To pass additional options to the dev server, consider using ${ input ( 'ionic serve ' ) } separately and using the ${ input ( '--livereload-url' ) } option .
138136 ` ,
139137 footnotes,
140138 exampleCommands,
@@ -181,7 +179,7 @@ With the experimental ${input('--native-run')} flag, this command will first use
181179
182180 if ( options [ 'native-run' ] ) {
183181 const args = createNativeRunListArgs ( inputs , options ) ;
184- await this . nativeRun ( args ) ;
182+ await this . runNativeRun ( args ) ;
185183 } else {
186184 const args = filterArgumentsForCordova ( metadata , options ) ;
187185 await this . runCordova ( [ 'run' , ...args . slice ( 1 ) ] , { } ) ;
@@ -191,180 +189,113 @@ With the experimental ${input('--native-run')} flag, this command will first use
191189 }
192190
193191 if ( ! inputs [ 0 ] ) {
194- const platform = await this . env . prompt ( {
192+ const p = await this . env . prompt ( {
195193 type : 'input' ,
196194 name : 'platform' ,
197195 message : `What platform would you like to run (${ [ 'android' , 'ios' ] . map ( v => input ( v ) ) . join ( ', ' ) } ):` ,
198196 } ) ;
199197
200- inputs [ 0 ] = platform . trim ( ) ;
198+ inputs [ 0 ] = p . trim ( ) ;
201199 }
202200
203- await this . checkForPlatformInstallation ( inputs [ 0 ] ) ;
201+ const [ platform ] = inputs ;
202+
203+ if ( options [ 'native-run' ] && ! SUPPORTED_PLATFORMS . includes ( platform ) ) {
204+ this . env . log . warn ( `${ input ( platform ) } is not supported by ${ input ( 'native-run' ) } . Using Cordova to run the app.` ) ;
205+ options [ 'native-run' ] = false ;
206+ }
207+
208+ await this . checkForPlatformInstallation ( platform ) ;
204209 }
205210
206211 async run ( inputs : CommandLineInputs , options : CommandLineOptions ) : Promise < void > {
212+ if ( options [ 'livereload' ] ) {
213+ await this . runServeDeploy ( inputs , options ) ;
214+ } else {
215+ await this . runBuildDeploy ( inputs , options ) ;
216+ }
217+ }
218+
219+ async runServeDeploy ( inputs : CommandLineInputs , options : CommandLineOptions ) {
207220 if ( ! this . project ) {
208221 throw new FatalException ( `Cannot run ${ input ( 'ionic cordova run/emulate' ) } outside a project directory.` ) ;
209222 }
210223
224+ const conf = await loadConfigXml ( this . integration ) ;
211225 const metadata = await this . getMetadata ( ) ;
212226
213- if ( options [ 'livereload' ] ) {
214- let livereloadUrl = options [ 'livereload-url' ] ? String ( options [ 'livereload-url' ] ) : undefined ;
215-
216- if ( ! livereloadUrl ) {
217- // TODO: use runner directly
218- const details = await serve ( { flags : this . env . flags , config : this . env . config , log : this . env . log , prompt : this . env . prompt , shell : this . env . shell , project : this . project } , inputs , generateOptionsForCordovaBuild ( metadata , inputs , options ) ) ;
227+ let livereloadUrl = options [ 'livereload-url' ] ? String ( options [ 'livereload-url' ] ) : undefined ;
219228
220- if ( details . externallyAccessible === false && ! options [ 'native-run' ] ) {
221- const extra = LOCAL_ADDRESSES . includes ( details . externalAddress ) ? '\nEnsure you have proper port forwarding setup from your device to your computer.' : '' ;
222- this . env . log . warn ( `Your device or emulator may not be able to access ${ strong ( details . externalAddress ) } .${ extra } \n\n` ) ;
223- }
229+ if ( ! livereloadUrl ) {
230+ // TODO: use runner directly
231+ const details = await serve ( { flags : this . env . flags , config : this . env . config , log : this . env . log , prompt : this . env . prompt , shell : this . env . shell , project : this . project } , inputs , generateOptionsForCordovaBuild ( metadata , inputs , options ) ) ;
224232
225- livereloadUrl = `${ details . protocol || 'http' } ://${ options [ 'native-run' ] ? details . localAddress : details . externalAddress } :${ details . port } ` ;
233+ if ( details . externallyAccessible === false && ! options [ 'native-run' ] ) {
234+ const extra = LOCAL_ADDRESSES . includes ( details . externalAddress ) ? '\nEnsure you have proper port forwarding setup from your device to your computer.' : '' ;
235+ this . env . log . warn ( `Your device or emulator may not be able to access ${ strong ( details . externalAddress ) } .${ extra } \n\n` ) ;
226236 }
227237
228- const conf = await loadConfigXml ( this . integration ) ;
229-
230- onBeforeExit ( async ( ) => {
231- conf . resetContentSrc ( ) ;
232- await conf . save ( ) ;
233- } ) ;
238+ livereloadUrl = `${ details . protocol || 'http' } ://${ options [ 'native-run' ] ? details . localAddress : details . externalAddress } :${ details . port } ` ;
239+ }
234240
235- conf . writeContentSrc ( livereloadUrl ) ;
241+ onBeforeExit ( async ( ) => {
242+ conf . resetContentSrc ( ) ;
236243 await conf . save ( ) ;
244+ } ) ;
237245
238- const cordovalog = this . env . log . clone ( ) ;
239- cordovalog . handlers = createDefaultLoggerHandlers ( createPrefixedFormatter ( `${ weak ( `[cordova]` ) } ` ) ) ;
240- const cordovalogws = cordovalog . createWriteStream ( LOGGER_LEVELS . INFO ) ;
246+ conf . writeContentSrc ( livereloadUrl ) ;
247+ await conf . save ( ) ;
241248
242- if ( options [ 'native-run' ] ) {
243- // hack to do just Cordova build instead
244- metadata . name = 'build' ;
249+ const cordovalogws = createPrefixedWriteStream ( this . env . log , weak ( `[cordova]` ) ) ;
245250
246- const buildOpts : IShellRunOptions = { stream : cordovalogws } ;
247- // ignore very verbose compiler output unless --verbose (still pipe stderr)
248- if ( ! options [ 'verbose' ] ) {
249- buildOpts . stdio = [ 'ignore' , 'ignore' , 'pipe' ] ;
250- }
251- await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , buildOpts ) ;
251+ if ( options [ 'native-run' ] ) {
252+ const [ platform ] = inputs ;
253+ const packagePath = await getPackagePath ( conf . getProjectInfo ( ) . name , platform , options [ 'emulator' ] as boolean ) ;
254+ const { port : portForward } = url . parse ( livereloadUrl ) ;
252255
253- const platform = inputs [ 0 ] ;
254- const packagePath = getPackagePath ( conf . getProjectInfo ( ) . name , platform , options [ 'emulator' ] as boolean ) ;
255- const nativeRunArgs = createNativeRunArgs ( packagePath , platform , livereloadUrl , options ) ;
256- await this . nativeRun ( nativeRunArgs ) ;
257- } else {
258- await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , { stream : cordovalogws } ) ;
259- await sleepForever ( ) ;
260- }
261- } else {
262- if ( options . build ) {
263- // TODO: use runner directly
264- await build ( { config : this . env . config , log : this . env . log , shell : this . env . shell , prompt : this . env . prompt , project : this . project } , inputs , generateOptionsForCordovaBuild ( metadata , inputs , options ) ) ;
256+ const buildOpts : IShellRunOptions = { stream : cordovalogws } ;
257+ // ignore very verbose compiler output unless --verbose (still pipe stderr)
258+ if ( ! options [ 'verbose' ] ) {
259+ buildOpts . stdio = [ 'ignore' , 'ignore' , 'pipe' ] ;
265260 }
266261
267- await this . runCordova ( filterArgumentsForCordova ( metadata , options ) ) ;
262+ await this . runCordova ( filterArgumentsForCordova ( { ...metadata , name : 'build' } , options ) , buildOpts ) ;
263+ await this . runNativeRun ( createNativeRunArgs ( { packagePath, platform, portForward } , options ) ) ;
264+ } else {
265+ await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , { stream : cordovalogws } ) ;
266+ await sleepForever ( ) ;
268267 }
269268 }
270269
271- protected async nativeRun ( args : ReadonlyArray < string > ) : Promise < void > {
270+ async runBuildDeploy ( inputs : CommandLineInputs , options : CommandLineOptions ) {
272271 if ( ! this . project ) {
273272 throw new FatalException ( `Cannot run ${ input ( 'ionic cordova run/emulate' ) } outside a project directory.` ) ;
274273 }
275274
276- let ws : NodeJS . WritableStream | undefined ;
275+ const conf = await loadConfigXml ( this . integration ) ;
276+ const metadata = await this . getMetadata ( ) ;
277277
278- if ( ! args . includes ( '--list' ) ) {
279- const log = this . env . log . clone ( ) ;
280- log . handlers = createDefaultLoggerHandlers ( createPrefixedFormatter ( weak ( `[native-run]` ) ) ) ;
281- ws = log . createWriteStream ( LOGGER_LEVELS . INFO ) ;
278+ if ( options . build ) {
279+ // TODO: use runner directly
280+ await build ( { config : this . env . config , log : this . env . log , shell : this . env . shell , prompt : this . env . prompt , project : this . project } , inputs , generateOptionsForCordovaBuild ( metadata , inputs , options ) ) ;
282281 }
283282
284- try {
285- await this . env . shell . run ( 'native-run' , args , { showCommand : ! args . includes ( '--json' ) , fatalOnNotFound : false , cwd : this . project . directory , stream : ws } ) ;
286- } catch ( e ) {
287- if ( e instanceof SubprocessError && e . code === ERROR_COMMAND_NOT_FOUND ) {
288- const cdvInstallArgs = await pkgManagerArgs ( this . env . config . get ( 'npmClient' ) , { command : 'install' , pkg : 'native-run' , global : true } ) ;
289- throw new FatalException (
290- `${ input ( 'native-run' ) } was not found on your PATH. Please install it globally:\n` +
291- `${ input ( cdvInstallArgs . join ( ' ' ) ) } \n`
292- ) ;
293- }
283+ if ( options [ 'native-run' ] ) {
284+ const [ platform ] = inputs ;
285+ const packagePath = await getPackagePath ( conf . getProjectInfo ( ) . name , platform , options [ 'emulator' ] as boolean ) ;
294286
295- throw e ;
296- }
297-
298- // If we connect the `native-run` process to the running app, then we
299- // should also connect the Ionic CLI with the running `native-run` process.
300- // This will exit the Ionic CLI when `native-run` exits.
301- if ( args . includes ( '--connect' ) ) {
302- processExit ( 0 ) ; // tslint:disable-line:no-floating-promises
287+ await this . runCordova ( filterArgumentsForCordova ( { ...metadata , name : 'build' } , options ) ) ;
288+ await this . runNativeRun ( createNativeRunArgs ( { packagePath, platform } , { ...options , connect : false } ) ) ;
289+ } else {
290+ await this . runCordova ( filterArgumentsForCordova ( metadata , options ) ) ;
303291 }
304292 }
305- }
306-
307- function createNativeRunArgs ( packagePath : string , platform : string , livereloadUrl : string , options : CommandLineOptions ) : string [ ] {
308- const opts = [ platform , '--app' , packagePath ] ;
309- const target = options [ 'target' ] ? String ( options [ 'target' ] ) : undefined ;
310-
311- if ( target ) {
312- opts . push ( '--target' , target ) ;
313- } else if ( options [ 'emulator' ] ) {
314- opts . push ( '--virtual' ) ;
315- }
316293
317- if ( options [ 'connect' ] ) {
318- opts . push ( '--connect' ) ;
319- }
320-
321- if ( ! options [ 'livereload-url' ] ) {
322- const { port } = url . parse ( livereloadUrl ) ;
323- opts . push ( '--forward' , `${ port } :${ port } ` ) ;
324- }
325-
326- if ( options [ 'json' ] ) {
327- opts . push ( '--json' ) ;
328- }
329-
330- if ( options [ 'verbose' ] ) {
331- opts . push ( '--verbose' ) ;
332- }
333-
334- return opts ;
335- }
336-
337- function createNativeRunListArgs ( inputs : string [ ] , options : CommandLineOptions ) : string [ ] {
338- const args = [ ] ;
339- if ( inputs [ 0 ] ) {
340- args . push ( inputs [ 0 ] ) ;
341- }
342- args . push ( '--list' ) ;
343- if ( options [ 'json' ] ) {
344- args . push ( '--json' ) ;
345- }
346- if ( options [ 'device' ] ) {
347- args . push ( '--device' ) ;
348- }
349- if ( options [ 'emulator' ] ) {
350- args . push ( '--virtual' ) ;
351- }
352- if ( options [ 'json' ] ) {
353- args . push ( '--json' ) ;
354- }
355-
356- return args ;
357- }
294+ protected async runNativeRun ( args : ReadonlyArray < string > ) : Promise < void > {
295+ if ( ! this . project ) {
296+ throw new FatalException ( `Cannot run ${ input ( 'ionic cordova run/emulate' ) } outside a project directory.` ) ;
297+ }
358298
359- function getPackagePath ( appName : string , platform : string , emulator : boolean ) {
360- if ( platform === 'android' ) {
361- // TODO: don't hardcode this/support multiple build paths (ex: multiple arch builds)
362- // use app/build/outputs/apk/debug/output.json?
363- return path . join ( CORDOVA_ANDROID_PACKAGE_PATH , 'debug' , 'app-debug.apk' ) ;
364- }
365- if ( platform === 'ios' && emulator ) {
366- return path . join ( CORDOVA_IOS_SIMULATOR_PACKAGE_PATH , `${ appName } .app` ) ;
367- } else {
368- return path . join ( CORDOVA_IOS_DEVICE_PACKAGE_PATH , `${ appName } .ipa` ) ;
299+ await runNativeRun ( this . env , args , { cwd : this . project . directory } ) ;
369300 }
370301}
0 commit comments