@@ -16,7 +16,7 @@ import { findAndroidBuildTool, getAndroidBuildToolsPath } from '../../paths.js';
1616import { buildJsBundle } from './bundle.js' ;
1717
1818export type SignAndroidOptions = {
19- apkPath : string ;
19+ binaryPath : string ;
2020 keystorePath ?: string ;
2121 keystorePassword ?: string ;
2222 keyAlias ?: string ;
@@ -30,7 +30,9 @@ export type SignAndroidOptions = {
3030export async function signAndroid ( options : SignAndroidOptions ) {
3131 validateOptions ( options ) ;
3232
33- intro ( `Modifying APK file` ) ;
33+ const extension = path . extname ( options . binaryPath ) . slice ( 1 ) ;
34+
35+ intro ( `Modifying ${ extension . toUpperCase ( ) } file` ) ;
3436
3537 const tempPath = getSignOutputPath ( ) ;
3638 if ( fs . existsSync ( tempPath ) ) {
@@ -60,28 +62,28 @@ export async function signAndroid(options: SignAndroidOptions) {
6062 options . jsBundlePath = bundleOutputPath ;
6163 }
6264
63- // 2. Initialize temporary APK file
64- const tempApkPath = path . join ( tempPath , ' output-app.apk' ) ;
65+ // 2. Initialize temporary archive file
66+ const tempArchivePath = path . join ( tempPath , ` output-app.${ extension } ` ) ;
6567
66- loader . start ( ' Initializing output APK...' ) ;
68+ loader . start ( ` Initializing output ${ extension . toUpperCase ( ) } ...` ) ;
6769 try {
68- const zip = new AdmZip ( options . apkPath ) ;
70+ const zip = new AdmZip ( options . binaryPath ) ;
6971 // Remove old signature files
7072 zip . deleteFile ( 'META-INF/*' ) ;
71- zip . writeZip ( tempApkPath ) ;
73+ zip . writeZip ( tempArchivePath ) ;
7274 } catch ( error ) {
7375 throw new RockError (
74- `Failed to initialize output APK file: ${ options . outputPath } ` ,
76+ `Failed to initialize output file: ${ options . outputPath } ` ,
7577 { cause : ( error as SubprocessError ) . stderr } ,
7678 ) ;
7779 }
78- loader . stop ( `Initialized output APK. ` ) ;
80+ loader . stop ( `Initialized output ${ extension . toUpperCase ( ) } ` ) ;
7981
8082 // 3. Replace JS bundle if provided
8183 if ( options . jsBundlePath ) {
8284 loader . start ( 'Replacing JS bundle...' ) ;
8385 await replaceJsBundle ( {
84- apkPath : tempApkPath ,
86+ archivePath : tempArchivePath ,
8587 jsBundlePath : options . jsBundlePath ,
8688 } ) ;
8789 loader . stop (
@@ -91,32 +93,34 @@ export async function signAndroid(options: SignAndroidOptions) {
9193 ) ;
9294 }
9395
94- // 4. Align APK file
95- loader . start ( 'Aligning output APK file...' ) ;
96- const outputApkPath = options . outputPath ?? options . apkPath ;
97- await alignApkFile ( tempApkPath , outputApkPath ) ;
96+ // 4. Align archive
97+ loader . start ( 'Aligning output file...' ) ;
98+ const outputPath = options . outputPath ?? options . binaryPath ;
99+ await alignArchiveFile ( tempArchivePath , outputPath ) ;
98100 loader . stop (
99- `Created output APK file: ${ colorLink ( relativeToCwd ( outputApkPath ) ) } .` ,
101+ `Created output ${ extension . toUpperCase ( ) } file: ${ colorLink ( relativeToCwd ( outputPath ) ) } .` ,
100102 ) ;
101103
102- // 5. Sign APK file
103- loader . start ( ' Signing the APK file...' ) ;
104+ // 5. Sign archive file
105+ loader . start ( ` Signing the ${ extension . toUpperCase ( ) } file...` ) ;
104106 const keystorePath = options . keystorePath ?? 'android/app/debug.keystore' ;
105- await signApkFile ( {
106- apkPath : outputApkPath ,
107+ await signArchive ( {
108+ binaryPath : outputPath ,
107109 keystorePath,
108110 keystorePassword : options . keystorePassword ?? 'pass:android' ,
109111 keyAlias : options . keyAlias ,
110112 keyPassword : options . keyPassword ,
111113 } ) ;
112- loader . stop ( `Signed the APK file with keystore: ${ colorLink ( keystorePath ) } .` ) ;
114+ loader . stop (
115+ `Signed the ${ extension . toUpperCase ( ) } file with keystore: ${ colorLink ( keystorePath ) } .` ,
116+ ) ;
113117
114118 outro ( 'Success 🎉.' ) ;
115119}
116120
117121function validateOptions ( options : SignAndroidOptions ) {
118- if ( ! fs . existsSync ( options . apkPath ) ) {
119- throw new RockError ( `APK file not found "${ options . apkPath } "` ) ;
122+ if ( ! fs . existsSync ( options . binaryPath ) ) {
123+ throw new RockError ( `File not found "${ options . binaryPath } "` ) ;
120124 }
121125
122126 if ( options . buildJsBundle && options . jsBundlePath ) {
@@ -131,22 +135,24 @@ function validateOptions(options: SignAndroidOptions) {
131135}
132136
133137type ReplaceJsBundleOptions = {
134- apkPath : string ;
138+ archivePath : string ;
135139 jsBundlePath : string ;
136140} ;
137141
138142async function replaceJsBundle ( {
139- apkPath ,
143+ archivePath ,
140144 jsBundlePath,
141145} : ReplaceJsBundleOptions ) {
142146 try {
143- const zip = new AdmZip ( apkPath ) ;
144- zip . deleteFile ( 'assets/index.android.bundle' ) ;
145- zip . addLocalFile ( jsBundlePath , 'assets' , 'index.android.bundle' ) ;
146- zip . writeZip ( apkPath ) ;
147+ const zip = new AdmZip ( archivePath ) ;
148+ const assetsPath = isAab ( archivePath ) ? 'base/assets' : 'assets' ;
149+
150+ zip . deleteFile ( path . join ( assetsPath , 'index.android.bundle' ) ) ;
151+ zip . addLocalFile ( jsBundlePath , assetsPath , 'index.android.bundle' ) ;
152+ zip . writeZip ( archivePath ) ;
147153 } catch ( error ) {
148154 throw new RockError (
149- `Failed to replace JS bundle in destination file: ${ apkPath } }` ,
155+ `Failed to replace JS bundle in destination file: ${ archivePath } ` ,
150156 { cause : error } ,
151157 ) ;
152158 }
@@ -159,7 +165,7 @@ function isSdkGTE35(versionString: string) {
159165 return match [ 1 ] . localeCompare ( '35.0.0' , undefined , { numeric : true } ) >= 0 ;
160166}
161167
162- async function alignApkFile ( inputApkPath : string , outputApkPath : string ) {
168+ async function alignArchiveFile ( inputArchivePath : string , outputPath : string ) {
163169 const zipAlignPath = findAndroidBuildTool ( 'zipalign' ) ;
164170 if ( ! zipAlignPath ) {
165171 throw new RockError (
@@ -177,34 +183,34 @@ Please follow instructions at: https://reactnative.dev/docs/set-up-your-environm
177183 '-f' , // Overwrites existing output file.
178184 '-v' , // Overwrites existing output file.
179185 '4' , // alignment in bytes, e.g. '4' provides 32-bit alignment
180- inputApkPath ,
181- outputApkPath ,
186+ inputArchivePath ,
187+ outputPath ,
182188 ] ;
183189 try {
184190 await spawn ( zipAlignPath , zipalignArgs ) ;
185191 } catch ( error ) {
186192 throw new RockError (
187- `Failed to align APK file: ${ zipAlignPath } ${ zipalignArgs . join ( ' ' ) } ` ,
193+ `Failed to align archive file: ${ zipAlignPath } ${ zipalignArgs . join ( ' ' ) } ` ,
188194 { cause : ( error as SubprocessError ) . stderr } ,
189195 ) ;
190196 }
191197}
192198
193- type SignApkOptions = {
194- apkPath : string ;
199+ type SignOptions = {
200+ binaryPath : string ;
195201 keystorePath : string ;
196202 keystorePassword : string ;
197203 keyAlias ?: string ;
198204 keyPassword ?: string ;
199205} ;
200206
201- async function signApkFile ( {
202- apkPath ,
207+ async function signArchive ( {
208+ binaryPath ,
203209 keystorePath,
204210 keystorePassword,
205211 keyAlias,
206212 keyPassword,
207- } : SignApkOptions ) {
213+ } : SignOptions ) {
208214 if ( ! fs . existsSync ( keystorePath ) ) {
209215 throw new RockError (
210216 `Keystore file not found "${ keystorePath } ". Provide a valid keystore path using the "--keystore" option.` ,
@@ -230,7 +236,8 @@ Please follow instructions at: https://reactnative.dev/docs/set-up-your-environm
230236 formatPassword ( keystorePassword ) ,
231237 ...( keyAlias ? [ '--ks-key-alias' , keyAlias ] : [ ] ) ,
232238 ...( keyPassword ? [ '--key-pass' , formatPassword ( keyPassword ) ] : [ ] ) ,
233- apkPath ,
239+ ...( isAab ( binaryPath ) ? [ '--min-sdk-version' , '36' ] : [ ] ) ,
240+ binaryPath ,
234241 ] ;
235242
236243 try {
@@ -264,3 +271,7 @@ function formatPassword(password: string) {
264271function getSignOutputPath ( ) {
265272 return path . join ( getDotRockPath ( ) , 'android/sign' ) ;
266273}
274+
275+ function isAab ( filePath : string ) : boolean {
276+ return path . extname ( filePath ) . toLowerCase ( ) === '.aab' ;
277+ }
0 commit comments