@@ -22,6 +22,9 @@ const ANDROID_BUILD_TYPES = ['debug', 'release'];
2222const IOS_BUILD_TYPES = [ 'development' , 'ad-hoc' , 'app-store' , 'enterprise' ] ;
2323const APP_STORE_COMPATIBLE_TYPES = [ 'release' , 'app-store' , 'enterprise' ] ;
2424const BUILD_TYPES = ANDROID_BUILD_TYPES . concat ( IOS_BUILD_TYPES ) ;
25+ const ANDROID_ARTIFACT_TYPES = [ 'aab' , 'apk' ] ;
26+ const IOS_ARTIFACT_TYPES = [ 'ipa' , 'dsym' ] ;
27+ const ARTIFACT_TYPES = ANDROID_ARTIFACT_TYPES . concat ( IOS_ARTIFACT_TYPES ) ;
2528const TARGET_PLATFORM = [ 'Android' , 'iOS - Xcode 11 (Preferred)' , 'iOS - Xcode 10' ] ;
2629
2730export interface PackageBuild {
@@ -74,6 +77,12 @@ Customizing the build:
7477Deploying the build to an App Store:
7578- The ${ input ( '--destination' ) } option can be used to deliver the app created by the build to the configured App Store. \
7679This can be used only together with build type ${ input ( 'release' ) } for Android and build types ${ input ( 'app-store' ) } or ${ input ( 'enterprise' ) } for iOS.
80+
81+ Downloading build artifacts:
82+ - By default once the build is complete, all artifacts are downloaded for the selected platform. ${ input ( 'aab' ) } and ${ input ( 'apk' ) } for Android \
83+ ${ input ( 'ipa' ) } and ${ input ( 'dsym' ) } for iOS.
84+ - The ${ input ( '--artifact-type' ) } option can be used to limit artifact downloads to only of that type. For instance, with Android, you can specify ${ input ( 'aab' ) } \
85+ if you do not wish to download ${ input ( 'apk' ) } .
7786` ,
7887 footnotes : [
7988 {
@@ -87,8 +96,10 @@ This can be used only together with build type ${input('release')} for Android a
8796 'android debug --environment="My Custom Environment Name"' ,
8897 'android debug --native-config="My Custom Native Config Name"' ,
8998 'android debug --commit=2345cd3305a1cf94de34e93b73a932f25baac77c' ,
99+ 'android debug --artifact-type=aab' ,
100+ 'android debug --aab-name="my-app-prod.aab" --apk-name="my-app-prod.apk"' ,
90101 'ios development --signing-certificate="iOS Signing Certificate Name" --build-stack="iOS - Xcode 9"' ,
91- 'ios development --signing-certificate="iOS Signing Certificate Name" --build-file -name=my_custom_file_name.ipa' ,
102+ 'ios development --signing-certificate="iOS Signing Certificate Name" --ipa -name=my_custom_file_name.ipa' ,
92103 'ios app-store --signing-certificate="iOS Signing Certificate Name" --destination="Apple App Store Destination"' ,
93104 ] ,
94105 inputs : [
@@ -146,9 +157,43 @@ This can be used only together with build type ${input('release')} for Android a
146157 name : 'build-file-name' ,
147158 summary : 'The name for the downloaded build file' ,
148159 type : String ,
160+ groups : [ MetadataGroup . DEPRECATED ] ,
161+ spec : { value : 'name' } ,
162+ } ,
163+ {
164+ name : 'ipa-name' ,
165+ summary : 'The name for the downloaded ipa file' ,
166+ type : String ,
149167 groups : [ MetadataGroup . ADVANCED ] ,
150168 spec : { value : 'name' } ,
151169 } ,
170+ {
171+ name : 'dsym-name' ,
172+ summary : 'The name for the downloaded dsym file' ,
173+ type : String ,
174+ groups : [ MetadataGroup . ADVANCED ] ,
175+ spec : { value : 'name' } ,
176+ } ,
177+ {
178+ name : 'apk-name' ,
179+ summary : 'The name for the downloaded apk file' ,
180+ type : String ,
181+ groups : [ MetadataGroup . ADVANCED ] ,
182+ spec : { value : 'name' } ,
183+ } ,
184+ {
185+ name : 'aab-name' ,
186+ summary : 'The name for the downloaded aab file' ,
187+ type : String ,
188+ groups : [ MetadataGroup . ADVANCED ] ,
189+ spec : { value : 'name' } ,
190+ } ,
191+ {
192+ name : 'artifact-type' ,
193+ summary : `The artifact type (${ ARTIFACT_TYPES . map ( v => input ( v ) ) . join ( ', ' ) } )` ,
194+ type : String ,
195+ spec : { value : 'name' } ,
196+ } ,
152197 ] ,
153198 } ;
154199 }
@@ -169,14 +214,14 @@ This can be used only together with build type ${input('release')} for Android a
169214 const buildTypes = inputs [ 0 ] === 'ios' ? IOS_BUILD_TYPES : ANDROID_BUILD_TYPES ;
170215
171216 // validate that the build type is valid for the platform
172- let reenterBuilType = false ;
217+ let reenterBuildType = false ;
173218 if ( inputs [ 1 ] && ! buildTypes . includes ( inputs [ 1 ] ) ) {
174- reenterBuilType = true ;
219+ reenterBuildType = true ;
175220 this . env . log . nl ( ) ;
176221 this . env . log . warn ( `Build type ${ strong ( inputs [ 1 ] ) } incompatible for ${ strong ( inputs [ 0 ] ) } ; please choose a correct one` ) ;
177222 }
178223
179- if ( ! inputs [ 1 ] || reenterBuilType ) {
224+ if ( ! inputs [ 1 ] || reenterBuildType ) {
180225 const typeInput = await this . env . prompt ( {
181226 type : 'list' ,
182227 name : 'type' ,
@@ -218,6 +263,26 @@ This can be used only together with build type ${input('release')} for Android a
218263 if ( options [ 'destination' ] && ! APP_STORE_COMPATIBLE_TYPES . includes ( inputs [ 1 ] ) ) {
219264 throw new FatalException ( `Build with type ${ strong ( String ( inputs [ 1 ] ) ) } cannot be deployed to App Store` ) ;
220265 }
266+
267+ if ( options [ 'artifact-type' ] && ( Array . isArray ( options [ 'artifact-type' ] ) || typeof options [ 'artifact-type' ] === 'string' ) ) {
268+ const artifactTypes = Array . isArray ( options [ 'artifact-type' ] ) ? options [ 'artifact-type' ] : [ options [ 'artifact-type' ] ] ;
269+ let unsupported : string [ ] ;
270+
271+ switch ( inputs [ 0 ] ) {
272+ case 'android' :
273+ unsupported = artifactTypes . filter ( type => ! ANDROID_ARTIFACT_TYPES . includes ( type ) ) ;
274+ break ;
275+ case 'ios' :
276+ unsupported = artifactTypes . filter ( type => ! IOS_ARTIFACT_TYPES . includes ( type ) ) ;
277+ break ;
278+ default :
279+ throw new FatalException ( `Unsupported platform ${ inputs [ 0 ] } ` ) ;
280+ }
281+
282+ if ( unsupported . length ) {
283+ throw new FatalException ( `Unsupported artifact types for platform ${ inputs [ 0 ] } : ${ [ ...unsupported ] } ` ) ;
284+ }
285+ }
221286 }
222287
223288 async run ( inputs : CommandLineInputs , options : CommandLineOptions ) : Promise < void > {
@@ -228,6 +293,16 @@ This can be used only together with build type ${input('release')} for Android a
228293 const token = await this . env . session . getUserToken ( ) ;
229294 const appflowId = await this . project . requireAppflowId ( ) ;
230295 const [ platform , buildType ] = inputs ;
296+ let artifactTypes ;
297+
298+ if ( Array . isArray ( options [ 'artifact-type' ] ) ) {
299+ artifactTypes = options [ 'artifact-type' ] . map ( val => val . toUpperCase ( ) ) ;
300+ } else if ( typeof options [ 'artifact-type' ] == 'string' && ARTIFACT_TYPES . includes ( options [ 'artifact-type' ] ) ) {
301+ artifactTypes = [ options [ 'artifact-type' ] . toUpperCase ( ) ]
302+ } else {
303+ const useOriginalDefault = ! ! ( options [ 'build-file-name' ] ) ;
304+ artifactTypes = await this . getDefaultArtifactType ( platform , useOriginalDefault ) ;
305+ }
231306
232307 if ( ! options . commit ) {
233308 options . commit = ( await this . env . shell . output ( 'git' , [ 'rev-parse' , 'HEAD' ] , { cwd : this . project . directory } ) ) . trim ( ) ;
@@ -237,20 +312,13 @@ This can be used only together with build type ${input('release')} for Android a
237312 let build = await this . createPackageBuild ( appflowId , token , platform , buildType , options ) ;
238313 const buildId = build . job_id ;
239314
240- let customBuildFileName = '' ;
241- if ( options [ 'build-file-name' ] ) {
242- if ( typeof ( options [ 'build-file-name' ] ) !== 'string' || ! fileUtils . isValidFileName ( options [ 'build-file-name' ] ) ) {
243- throw new FatalException ( `${ strong ( String ( options [ 'build-file-name' ] ) ) } is not a valid file name` ) ;
244- }
245- customBuildFileName = String ( options [ 'build-file-name' ] ) ;
246- }
247-
248315 const details = columnar ( [
249316 [ 'App ID' , strong ( appflowId ) ] ,
250317 [ 'Build ID' , strong ( buildId . toString ( ) ) ] ,
251318 [ 'Commit' , strong ( `${ build . commit . sha . substring ( 0 , 6 ) } ${ build . commit . note } ` ) ] ,
252319 [ 'Target Platform' , strong ( build . stack . friendly_name ) ] ,
253320 [ 'Build Type' , strong ( build . build_type ) ] ,
321+ [ 'Artifact Type(s)' , strong ( `${ artifactTypes } ` ) ] ,
254322 [ 'Security Profile' , build . profile_tag ? strong ( build . profile_tag ) : weak ( 'not set' ) ] ,
255323 [ 'Environment' , build . environment_name ? strong ( build . environment_name ) : weak ( 'not set' ) ] ,
256324 [ 'Native Config' , build . native_config_name ? strong ( build . native_config_name ) : weak ( 'not set' ) ] ,
@@ -267,13 +335,49 @@ This can be used only together with build type ${input('release')} for Android a
267335 throw new Error ( `Build ${ build . state } ` ) ;
268336 }
269337
270- const url = await this . getDownloadUrl ( appflowId , buildId , token ) ;
271- if ( ! url . url ) {
272- throw new Error ( 'Missing URL in response' ) ;
338+ for ( const artifactType of artifactTypes ) {
339+ const url = await this . getDownloadUrl ( appflowId , buildId , artifactType , token ) ;
340+
341+ if ( ! url . url ) {
342+ throw new Error ( 'Missing URL in response' ) ;
343+ }
344+
345+ let customBuildFileName = '' ;
346+
347+ if ( options [ 'build-file-name' ] ) {
348+ this . env . log . warn ( `The ${ input ( '--build-file-name' ) } option has been deprecated. Please use ${ input ( '--ipa-name' ) } , ${ input ( '--apk-name' ) } or ${ input ( '--{ artifact }-name' ) } .` ) ;
349+ customBuildFileName = await this . sanitizeString ( options [ 'build-file-name' ] ) ;
350+ } else {
351+ customBuildFileName = await this . sanitizeString ( options [ `${ artifactType . toLowerCase ( ) } -name` ] ) ;
352+ }
353+
354+ const filename = await this . downloadBuild ( url . url , customBuildFileName ) ;
355+ this . env . log . ok ( `Artifact downloaded: ${ filename } ` ) ;
356+ }
357+ }
358+
359+ async sanitizeString ( value : string | string [ ] | boolean | null | undefined ) : Promise < string > {
360+
361+ if ( ! value || typeof ( value ) !== 'string' ) {
362+ return '' ;
363+ }
364+
365+ if ( ! fileUtils . isValidFileName ( value ) ) {
366+ throw new FatalException ( `${ strong ( String ( value ) ) } is not a valid file name` ) ;
273367 }
274368
275- const filename = await this . downloadBuild ( url . url , customBuildFileName ) ;
276- this . env . log . ok ( `Build completed: ${ filename } ` ) ;
369+ return String ( value ) ;
370+ }
371+
372+ async getDefaultArtifactType ( platform : string , useOriginalDefault ?: boolean ) : Promise < Array < string > > {
373+ switch ( platform ) {
374+ case 'ios' :
375+ return useOriginalDefault ? [ 'IPA' ] : [ 'IPA' , 'DSYM' ] ;
376+ case 'android' :
377+ return useOriginalDefault ? [ 'APK' ] : [ 'APK' , 'AAB' ] ;
378+ default :
379+ throw new Error ( `No default artifact type for platform '${ platform } '` ) ;
380+ }
277381 }
278382
279383 async createPackageBuild ( appflowId : string , token : string , platform : string , buildType : string , options : CommandLineOptions ) : Promise < PackageBuild > {
@@ -325,8 +429,8 @@ This can be used only together with build type ${input('release')} for Android a
325429 }
326430 }
327431
328- async getDownloadUrl ( appflowId : string , buildId : number , token : string ) : Promise < DownloadUrl > {
329- const { req } = await this . env . client . make ( 'GET' , `/apps/${ appflowId } /packages/${ buildId } /download` ) ;
432+ async getDownloadUrl ( appflowId : string , buildId : number , artifactType : string , token : string ) : Promise < DownloadUrl > {
433+ const { req } = await this . env . client . make ( 'GET' , `/apps/${ appflowId } /packages/${ buildId } /download?artifact_type= ${ artifactType } ` ) ;
330434 req . set ( 'Authorization' , `Bearer ${ token } ` ) . send ( ) ;
331435
332436 try {
0 commit comments