1- import fs from 'fs-extra' ;
2- import path from 'path' ;
1+ import { spawn } from 'child_process' ;
32import os from 'os' ;
3+ import path from 'path' ;
4+ import fs from 'fs-extra' ;
45import { open as openZipFile } from 'yauzl' ;
5- import { Zip } from './zip' ;
66import { t } from '../i18n' ;
7+ import { ResourceFinder } from './resource-finder' ;
8+ import { mapInfoResource } from './utils' ;
9+ import { ManifestParser } from './xml-parser/manifest' ;
10+ import { Zip } from './zip' ;
711
812export class AabParser extends Zip {
913 file : string | File ;
@@ -16,20 +20,40 @@ export class AabParser extends Zip {
1620 async extractApk (
1721 outputPath : string ,
1822 {
19- includeAllSplits = true ,
23+ includeAllSplits,
2024 splits,
2125 } : { includeAllSplits ?: boolean ; splits ?: string [ ] | null } ,
2226 ) {
23- const { exec } = require ( 'child_process' ) ;
24- const util = require ( 'util' ) ;
25- const execAsync = util . promisify ( exec ) ;
2627 const normalizedSplits = Array . isArray ( splits )
2728 ? splits . map ( ( item ) => item . trim ( ) ) . filter ( Boolean )
2829 : [ ] ;
2930 const modules = includeAllSplits
3031 ? null
3132 : Array . from ( new Set ( [ 'base' , ...normalizedSplits ] ) ) ;
32- const modulesArg = modules ? ` --modules="${ modules . join ( ',' ) } "` : '' ;
33+ const modulesArgs = modules ? [ `--modules=${ modules . join ( ',' ) } ` ] : [ ] ;
34+
35+ const runCommand = ( command : string , args : string [ ] ) =>
36+ new Promise < void > ( ( resolve , reject ) => {
37+ const child = spawn ( command , args , {
38+ stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
39+ } ) ;
40+ let stderr = '' ;
41+ child . stderr ?. on ( 'data' , ( chunk ) => {
42+ stderr += chunk . toString ( ) ;
43+ } ) ;
44+ child . on ( 'error' , reject ) ;
45+ child . on ( 'close' , ( code ) => {
46+ if ( code === 0 ) {
47+ resolve ( ) ;
48+ return ;
49+ }
50+ reject (
51+ new Error (
52+ stderr . trim ( ) || `Command failed: ${ command } (code ${ code } )` ,
53+ ) ,
54+ ) ;
55+ } ) ;
56+ } ) ;
3357
3458 // Create a temp file for the .apks output
3559 const tempDir = os . tmpdir ( ) ;
@@ -41,14 +65,28 @@ export class AabParser extends Zip {
4165 // User might need keystore to sign it properly but for simple extraction we stick to default debug key if possible or unsigned?
4266 // actually bundletool build-apks signs with debug key by default if no keystore provided.
4367
44- let cmd = `bundletool build-apks --mode=universal --bundle="${ this . file } " --output="${ tempApksPath } " --overwrite${ modulesArg } ` ;
4568 try {
46- await execAsync ( cmd ) ;
69+ await runCommand ( 'bundletool' , [
70+ 'build-apks' ,
71+ '--mode=universal' ,
72+ `--bundle=${ this . file } ` ,
73+ `--output=${ tempApksPath } ` ,
74+ '--overwrite' ,
75+ ...modulesArgs ,
76+ ] ) ;
4777 } catch ( e ) {
4878 // Fallback to npx node-bundletool if bundletool is not in PATH
4979 // We use -y to avoid interactive prompt for installation
50- cmd = `npx -y node-bundletool build-apks --mode=universal --bundle="${ this . file } " --output="${ tempApksPath } " --overwrite${ modulesArg } ` ;
51- await execAsync ( cmd ) ;
80+ await runCommand ( 'npx' , [
81+ '-y' ,
82+ 'node-bundletool' ,
83+ 'build-apks' ,
84+ '--mode=universal' ,
85+ `--bundle=${ this . file } ` ,
86+ `--output=${ tempApksPath } ` ,
87+ '--overwrite' ,
88+ ...modulesArgs ,
89+ ] ) ;
5290 }
5391
5492 // 2. Extract universal.apk from the .apks (zip) file
@@ -119,7 +157,6 @@ export class AabParser extends Zip {
119157 const resourceBuffer = await this . getEntry ( ResourceName ) ;
120158 if ( resourceBuffer ) {
121159 const resourceMap = this . _parseResourceMap ( resourceBuffer as Buffer ) ;
122- const { mapInfoResource } = require ( './utils' ) ;
123160 apkInfo = mapInfoResource ( apkInfo , resourceMap ) ;
124161 }
125162 } catch ( e : any ) {
@@ -137,8 +174,7 @@ export class AabParser extends Zip {
137174 */
138175 private _parseManifest ( buffer : Buffer ) {
139176 try {
140- const ManifestXmlParser = require ( './xml-parser/manifest' ) ;
141- const parser = new ManifestXmlParser ( buffer , {
177+ const parser = new ManifestParser ( buffer , {
142178 ignore : [
143179 'application.activity' ,
144180 'application.service' ,
@@ -159,7 +195,6 @@ export class AabParser extends Zip {
159195 */
160196 private _parseResourceMap ( buffer : Buffer ) {
161197 try {
162- const ResourceFinder = require ( './resource-finder' ) ;
163198 return new ResourceFinder ( ) . processResourceTable ( buffer ) ;
164199 } catch ( e : any ) {
165200 throw new Error ( t ( 'aabParseResourcesError' , { error : e ?. message ?? e } ) ) ;
0 commit comments