1
- import { LOGGER_LEVELS , OptionGroup , createPrefixedFormatter } from '@ionic/cli-framework' ;
2
- import { onBeforeExit , sleepForever } from '@ionic/cli-framework/utils/process' ;
1
+ import { ERROR_SHELL_COMMAND_NOT_FOUND , LOGGER_LEVELS , OptionGroup , ShellCommandError , createPrefixedFormatter } from '@ionic/cli-framework' ;
2
+ import { onBeforeExit , processExit , sleepForever } from '@ionic/cli-framework/utils/process' ;
3
3
import chalk from 'chalk' ;
4
+ import * as path from 'path' ;
4
5
5
- import { CommandInstanceInfo , CommandLineInputs , CommandLineOptions , CommandMetadata , CommandMetadataOption , CommandPreRun } from '../../definitions' ;
6
+ import { CommandInstanceInfo , CommandLineInputs , CommandLineOptions , CommandMetadata , CommandMetadataOption , CommandPreRun , IShellRunOptions } from '../../definitions' ;
6
7
import { COMMON_BUILD_COMMAND_OPTIONS , build } from '../../lib/build' ;
7
8
import { FatalException } from '../../lib/errors' ;
8
9
import { loadConfigXml } from '../../lib/integrations/cordova/config' ;
9
10
import { filterArgumentsForCordova , generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils' ;
10
11
import { COMMON_SERVE_COMMAND_OPTIONS , LOCAL_ADDRESSES , serve } from '../../lib/serve' ;
11
12
import { createDefaultLoggerHandlers } from '../../lib/utils/logger' ;
13
+ import { pkgManagerArgs } from '../../lib/utils/npm' ;
12
14
13
15
import { CORDOVA_BUILD_EXAMPLE_COMMANDS , CordovaCommand } from './base' ;
14
16
17
+ const CORDOVA_ANDROID_PACKAGE_PATH = 'platforms/android/app/build/outputs/apk/' ;
18
+ const CORDOVA_IOS_SIMULATOR_PACKAGE_PATH = 'platforms/ios/build/emulator' ;
19
+ const CORDOVA_IOS_DEVICE_PACKAGE_PATH = 'platforms/ios/build/device' ;
20
+
15
21
const CORDOVA_RUN_OPTIONS : ReadonlyArray < CommandMetadataOption > = [
16
22
{
17
23
name : 'debug' ,
@@ -31,21 +37,21 @@ const CORDOVA_RUN_OPTIONS: ReadonlyArray<CommandMetadataOption> = [
31
37
name : 'device' ,
32
38
summary : 'Deploy build to a device' ,
33
39
type : Boolean ,
34
- groups : [ 'cordova' ] ,
40
+ groups : [ 'cordova' , 'native-run' ] ,
35
41
hint : chalk . dim ( '[cordova]' ) ,
36
42
} ,
37
43
{
38
44
name : 'emulator' ,
39
45
summary : 'Deploy build to an emulator' ,
40
46
type : Boolean ,
41
- groups : [ 'cordova' ] ,
47
+ groups : [ 'cordova' , 'native-run' ] ,
42
48
hint : chalk . dim ( '[cordova]' ) ,
43
49
} ,
44
50
{
45
51
name : 'target' ,
46
52
summary : `Deploy build to a device (use ${ chalk . green ( '--list' ) } to see all)` ,
47
53
type : String ,
48
- groups : [ OptionGroup . Advanced , 'cordova' ] ,
54
+ groups : [ OptionGroup . Advanced , 'cordova' , 'native-run' ] ,
49
55
hint : chalk . dim ( '[cordova]' ) ,
50
56
} ,
51
57
{
@@ -57,6 +63,31 @@ const CORDOVA_RUN_OPTIONS: ReadonlyArray<CommandMetadataOption> = [
57
63
} ,
58
64
] ;
59
65
66
+ const NATIVE_RUN_OPTIONS : ReadonlyArray < CommandMetadataOption > = [
67
+ {
68
+ name : 'native-run' ,
69
+ summary : `Use ${ chalk . green ( 'native-run' ) } instead of Cordova for running the app` ,
70
+ type : Boolean ,
71
+ groups : [ OptionGroup . Hidden , 'native-run' ] ,
72
+ hint : chalk . dim ( '[native-run]' ) ,
73
+ } ,
74
+ {
75
+ name : 'connect' ,
76
+ summary : 'Do not tie the running app to the process' ,
77
+ type : Boolean ,
78
+ default : true ,
79
+ groups : [ OptionGroup . Hidden , 'native-run' ] ,
80
+ hint : chalk . dim ( '[native-run]' ) ,
81
+ } ,
82
+ {
83
+ name : 'json' ,
84
+ summary : `Output ${ chalk . green ( '--list' ) } targets in JSON` ,
85
+ type : Boolean ,
86
+ groups : [ OptionGroup . Hidden , 'native-run' ] ,
87
+ hint : chalk . dim ( '[native-run]' ) ,
88
+ } ,
89
+ ] ;
90
+
60
91
export class RunCommand extends CordovaCommand implements CommandPreRun {
61
92
async getMetadata ( ) : Promise < CommandMetadata > {
62
93
let groups : string [ ] = [ ] ;
@@ -69,9 +100,9 @@ export class RunCommand extends CordovaCommand implements CommandPreRun {
69
100
const options : CommandMetadataOption [ ] = [
70
101
{
71
102
name : 'list' ,
72
- summary : 'List all available Cordova targets' ,
103
+ summary : 'List all available targets' ,
73
104
type : Boolean ,
74
- groups : [ 'cordova' ] ,
105
+ groups : [ 'cordova' , 'native-run' ] ,
75
106
} ,
76
107
// Build Options
77
108
{
@@ -114,6 +145,9 @@ export class RunCommand extends CordovaCommand implements CommandPreRun {
114
145
// Cordova Options
115
146
options . push ( ...CORDOVA_RUN_OPTIONS ) ;
116
147
148
+ // `native-run` Options
149
+ options . push ( ...NATIVE_RUN_OPTIONS ) ;
150
+
117
151
return {
118
152
name : 'run' ,
119
153
type : 'project' ,
@@ -168,9 +202,13 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-re
168
202
options [ 'emulator' ] = true ;
169
203
}
170
204
}
171
-
172
- const args = filterArgumentsForCordova ( metadata , options ) ;
173
- await this . runCordova ( [ 'run' , ...args . slice ( 1 ) ] , { } ) ;
205
+ if ( options [ 'native-run' ] ) {
206
+ const args = createNativeRunListArgs ( inputs , options ) ;
207
+ await this . nativeRun ( args ) ;
208
+ } else {
209
+ const args = filterArgumentsForCordova ( metadata , options ) ;
210
+ await this . runCordova ( [ 'run' , ...args . slice ( 1 ) ] , { } ) ;
211
+ }
174
212
throw new FatalException ( '' , 0 ) ;
175
213
}
176
214
@@ -196,7 +234,6 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-re
196
234
197
235
if ( options [ 'livereload' ] ) {
198
236
let livereloadUrl = options [ 'livereload-url' ] ? String ( options [ 'livereload-url' ] ) : undefined ;
199
-
200
237
if ( ! livereloadUrl ) {
201
238
// TODO: use runner directly
202
239
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 ) ) ;
@@ -223,8 +260,25 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-re
223
260
cordovalog . handlers = createDefaultLoggerHandlers ( createPrefixedFormatter ( `${ chalk . dim ( `[cordova]` ) } ` ) ) ;
224
261
const cordovalogws = cordovalog . createWriteStream ( LOGGER_LEVELS . INFO ) ;
225
262
226
- await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , { stream : cordovalogws } ) ;
227
- await sleepForever ( ) ;
263
+ if ( options [ 'native-run' ] ) {
264
+ // hack to do just Cordova build instead
265
+ metadata . name = 'build' ;
266
+
267
+ const buildOpts : IShellRunOptions = { stream : cordovalogws } ;
268
+ // ignore very verbose compiler output unless --verbose (still pipe stderr)
269
+ if ( ! options [ 'verbose' ] ) {
270
+ buildOpts . stdio = [ 'ignore' , 'ignore' , 'pipe' ] ;
271
+ }
272
+ await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , buildOpts ) ;
273
+
274
+ const platform = inputs [ 0 ] ;
275
+ const packagePath = getPackagePath ( conf . getProjectInfo ( ) . name , platform , options [ 'emulator' ] as boolean ) ;
276
+ const nativeRunArgs = createNativeRunArgs ( packagePath , platform , options ) ;
277
+ await this . nativeRun ( nativeRunArgs ) ;
278
+ } else {
279
+ await this . runCordova ( filterArgumentsForCordova ( metadata , options ) , { stream : cordovalogws } ) ;
280
+ await sleepForever ( ) ;
281
+ }
228
282
} else {
229
283
if ( options . build ) {
230
284
// TODO: use runner directly
@@ -234,4 +288,94 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://ionicframework.com/docs/developer-re
234
288
await this . runCordova ( filterArgumentsForCordova ( metadata , options ) ) ;
235
289
}
236
290
}
291
+
292
+ protected async nativeRun ( args : ReadonlyArray < string > ) : Promise < void > {
293
+ if ( ! this . project ) {
294
+ throw new FatalException ( `Cannot run ${ chalk . green ( 'ionic cordova run/emulate' ) } outside a project directory.` ) ;
295
+ }
296
+
297
+ let ws : NodeJS . WritableStream | undefined ;
298
+
299
+ if ( ! args . includes ( '--list' ) ) {
300
+ const log = this . env . log . clone ( ) ;
301
+ log . handlers = createDefaultLoggerHandlers ( createPrefixedFormatter ( chalk . dim ( `[native-run]` ) ) ) ;
302
+ ws = log . createWriteStream ( LOGGER_LEVELS . INFO ) ;
303
+ }
304
+
305
+ try {
306
+ await this . env . shell . run ( 'native-run' , args , { showCommand : ! args . includes ( '--json' ) , fatalOnNotFound : false , cwd : this . project . directory , stream : ws } ) ;
307
+ } catch ( e ) {
308
+ if ( e instanceof ShellCommandError && e . code === ERROR_SHELL_COMMAND_NOT_FOUND ) {
309
+ const cdvInstallArgs = await pkgManagerArgs ( this . env . config . get ( 'npmClient' ) , { command : 'install' , pkg : 'native-run' , global : true } ) ;
310
+ throw new FatalException (
311
+ `${ chalk . green ( 'native-run' ) } was not found on your PATH. Please install it globally:\n` +
312
+ `${ chalk . green ( cdvInstallArgs . join ( ' ' ) ) } \n`
313
+ ) ;
314
+ }
315
+
316
+ throw e ;
317
+ }
318
+
319
+ // If we connect the `native-run` process to the running app, then we
320
+ // should also connect the Ionic CLI with the running `native-run` process.
321
+ // This will exit the Ionic CLI when `native-run` exits.
322
+ if ( args . includes ( '--connect' ) ) {
323
+ processExit ( 0 ) ; // tslint:disable-line:no-floating-promises
324
+ }
325
+ }
326
+ }
327
+
328
+ function createNativeRunArgs ( packagePath : string , platform : string , options : CommandLineOptions ) : string [ ] {
329
+ const opts = [ platform , '--app' , packagePath ] ;
330
+ const target = options [ 'target' ] as string ;
331
+ if ( target ) {
332
+ opts . push ( '--target' , target ) ;
333
+ } else if ( options [ 'emulator' ] ) {
334
+ opts . push ( '--virtual' ) ;
335
+ }
336
+
337
+ if ( options [ 'connect' ] ) {
338
+ opts . push ( '--connect' ) ;
339
+ }
340
+
341
+ if ( options [ 'json' ] ) {
342
+ opts . push ( '--json' ) ;
343
+ }
344
+
345
+ return opts ;
346
+ }
347
+
348
+ function createNativeRunListArgs ( inputs : string [ ] , options : CommandLineOptions ) : string [ ] {
349
+ const args = [ ] ;
350
+ if ( inputs [ 0 ] ) {
351
+ args . push ( inputs [ 0 ] ) ;
352
+ }
353
+ args . push ( '--list' ) ;
354
+ if ( options [ 'json' ] ) {
355
+ args . push ( '--json' ) ;
356
+ }
357
+ if ( options [ 'device' ] ) {
358
+ args . push ( '--device' ) ;
359
+ }
360
+ if ( options [ 'emulator' ] ) {
361
+ args . push ( '--virtual' ) ;
362
+ }
363
+ if ( options [ 'json' ] ) {
364
+ args . push ( '--json' ) ;
365
+ }
366
+
367
+ return args ;
368
+ }
369
+
370
+ function getPackagePath ( appName : string , platform : string , emulator : boolean ) {
371
+ if ( platform === 'android' ) {
372
+ // TODO: don't hardcode this/support multiple build paths (ex: multiple arch builds)
373
+ // use app/build/outputs/apk/debug/output.json?
374
+ return path . join ( CORDOVA_ANDROID_PACKAGE_PATH , 'debug' , 'app-debug.apk' ) ;
375
+ }
376
+ if ( platform === 'ios' && emulator ) {
377
+ return path . join ( CORDOVA_IOS_SIMULATOR_PACKAGE_PATH , `${ appName } .app` ) ;
378
+ } else {
379
+ return path . join ( CORDOVA_IOS_DEVICE_PACKAGE_PATH , `${ appName } .ipa` ) ;
380
+ }
237
381
}
0 commit comments