/** * Copyright © 2024 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 **/ var cp = require('child_process') var fs = require('fs') var path = require('path') var Promise = require('bluebird') var yauzl = require('yauzl') var logger = require('./logger') module.exports = function(options) { return new Promise(function(resolve, reject) { var log = logger.createLogger('util:bundletool') var bundletoolFilePath = options.bundletoolPath var bundle = options.file var bundlePath = bundle.path var outputPath = bundlePath + '.apks' var keystore = options.keystore function checkIfJava() { return new Promise(function(resolve, reject) { var check = cp.spawn('java', ['-version']) var stderrChunks = [] check.on('error', function(err) { reject(err) }) check.stderr.on('data', function(data) { stderrChunks = stderrChunks.concat(data) }) check.stderr.on('end', function() { var data = Buffer.concat(stderrChunks).toString().split('\n')[0] var regex = new RegExp('(openjdk|java) version') var javaVersion = regex.test(data) ? data.split(' ')[2].replace(/"/g, '') : false if (javaVersion !== false) { resolve(javaVersion) } else { reject(new Error('Java not found'), null) } }) }) } function convert() { var proc = cp.spawn('java', [ '-jar' , bundletoolFilePath , 'build-apks' , `--bundle=${bundlePath}` , `--output=${outputPath}` , `--ks=${keystore.ksPath}` , `--ks-pass=pass:${keystore.ksPass}` , `--ks-key-alias=${keystore.ksKeyAlias}` , `--key-pass=pass:${keystore.ksKeyPass}` , '--overwrite' , '--mode=universal' ]) proc.on('error', function(err) { reject(err) }) proc.on('exit', function(code, signal) { if (signal) { reject(new Error('Exited with signal ' + signal)) } else if (code === 0) { yauzl.open(outputPath, {lazyEntries: true}, function(err, zipfile) { if (err) { reject(err) } zipfile.readEntry() zipfile.on('entry', function(entry) { if (/\/$/.test(entry.fileName)) { zipfile.readEntry() } else { zipfile.openReadStream(entry, function(err, readStream) { if (err) { reject(err) } readStream.on('end', function() { zipfile.readEntry() }) var writeStream = fs.createWriteStream(path.join('/tmp/', entry.fileName)) writeStream.on('error', function(err) { reject(err) }) readStream.pipe(writeStream) }) } }) zipfile.on('error', function(err) { reject(err) }) zipfile.once('end', function() { fs.copyFileSync('/tmp/universal.apk', bundlePath) fs.unlinkSync('/tmp/universal.apk') fs.unlinkSync('/tmp/toc.pb') fs.unlinkSync(outputPath) log.info('AAB -> APK') resolve(bundle) }) }) } else { reject(new Error('Exited with status ' + code)) } }) } if (bundle.isAab === true) { log.info('AAB detected') checkIfJava() .then(function() { if (!fs.existsSync(keystore.ksPath)) { cp.spawnSync('keytool', [ '-genkey' , '-noprompt' , '-keystore', keystore.ksPath , '-alias', keystore.ksKeyAlias , '-keyalg', keystore.ksKeyalg , '-keysize', keystore.ksKeysize , '-storepass', keystore.ksPass , '-keypass', keystore.ksKeyPass , '-dname', keystore.ksDname , '-validity', keystore.ksValidity ]) } }) .then(function() { if(!fs.existsSync(keystore.ksPath)) { reject('Keystore not found') } else if(!fs.existsSync(bundletoolFilePath)) { reject('bundletool not found') } else { convert() } }) .catch(function(err) { reject(err) }) } else { resolve(bundle) } }) }