From 75f4dc0a3e38de3b711661f94c6ac82b516eac92 Mon Sep 17 00:00:00 2001 From: IT-MikeS <20338451+IT-MikeS@users.noreply.github.com> Date: Tue, 30 Aug 2022 09:56:31 -0400 Subject: [PATCH 1/5] feat(cli): add build command for android --- cli/src/android/build.ts | 88 ++++++++++++++++++++++++++++++++++++++++ cli/src/index.ts | 48 +++++++++++++++++++++- cli/src/tasks/build.ts | 62 ++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 cli/src/android/build.ts create mode 100644 cli/src/tasks/build.ts diff --git a/cli/src/android/build.ts b/cli/src/android/build.ts new file mode 100644 index 0000000000..1b0eeae458 --- /dev/null +++ b/cli/src/android/build.ts @@ -0,0 +1,88 @@ +import { join } from 'path'; + +import c from '../colors'; +import { runTask } from '../common'; +import type { Config } from '../definitions'; +import { logSuccess } from '../log'; +import type { BuildCommandOptions } from '../tasks/build'; +import { runCommand } from '../util/subprocess'; + +export async function buildAndroid( + config: Config, + buildOptions: BuildCommandOptions, +): Promise { + const releaseTypeIsAAB = buildOptions.androidreleasetype === 'AAB'; + const arg = releaseTypeIsAAB ? ':app:bundleRelease' : 'assembleRelease'; + const gradleArgs = [arg]; + + if ( + !buildOptions.keystorepath || + !buildOptions.keystorealias || + !buildOptions.keystorealiaspass || + !buildOptions.keystorepass + ) { + throw 'Missing options. Please supply all options for android aab signing. (Keystore Path, Keystore Password, Keystore Key Alias, Keystore Key Password)'; + } + + try { + await runTask('Running Gradle build', async () => + runCommand('./gradlew', gradleArgs, { + cwd: config.android.platformDirAbs, + }), + ); + } catch (e) { + if ((e as any).includes('EACCES')) { + throw `gradlew file does not have executable permissions. This can happen if the Android platform was added on a Windows machine. Please run ${c.strong( + `chmod +x ./${config.android.platformDir}/gradlew`, + )} and try again.`; + } else { + throw e; + } + } + + const releasePath = join( + config.android.appDirAbs, + 'build', + 'outputs', + releaseTypeIsAAB ? 'bundle' : 'apk', + 'release', + ); + + const unsignedReleaseName = `app${ + config.android.flavor ? `-${config.android.flavor}` : '' + }-release${ + releaseTypeIsAAB ? '' : '-unsigned' + }.${buildOptions.androidreleasetype.toLowerCase()}`; + + const signedReleaseName = unsignedReleaseName.replace( + `-release${ + releaseTypeIsAAB ? '' : '-unsigned' + }.${buildOptions.androidreleasetype.toLowerCase()}`, + `-release-signed.${buildOptions.androidreleasetype.toLowerCase()}`, + ); + + const signingArgs = [ + '-sigalg', + 'SHA1withRSA', + '-digestalg', + 'SHA1', + '-keystore', + buildOptions.keystorepath, + '-keypass', + buildOptions.keystorealiaspass, + '-storepass', + buildOptions.keystorepass, + `-signedjar`, + `${join(releasePath, signedReleaseName)}`, + `${join(releasePath, unsignedReleaseName)}`, + buildOptions.keystorealias, + ]; + + await runTask('Signing Release', async () => { + await runCommand('jarsigner', signingArgs, { + cwd: config.android.platformDirAbs, + }); + }); + + logSuccess(`Successfully generated ${signedReleaseName} at: ${releasePath}`); +} diff --git a/cli/src/index.ts b/cli/src/index.ts index f3c4eaf873..7806020ff1 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,4 +1,4 @@ -import { program } from 'commander'; +import { Option, program } from 'commander'; import c from './colors'; import { checkExternalConfig, loadConfig } from './config'; @@ -132,6 +132,52 @@ export function runProgram(config: Config): void { ), ); + program + .command('build ') + .description('builds the release version of the selected platform') + .option('--keystorepath ', 'Path to the keystore') + .option('--keystorepass ', 'Password to the keystore') + .option('--keystorealias ', 'Key Alias in the keystore') + .option( + '--keystorealiaspass ', + 'Password for the Key Alias', + ) + .addOption( + new Option( + '--androidreleasetype ', + 'Android release type; APK or AAB', + ) + .choices(['AAB', 'APK']) + .default('AAB'), + ) + .action( + wrapAction( + telemetryAction( + config, + async ( + platform, + { + keystorepath, + keystorepass, + keystorealias, + keystorealiaspass, + androidreleasetype, + }, + ) => { + const { buildCommand } = await import('./tasks/build'); + console.log(program.args); + await buildCommand(config, platform, { + keystorepath, + keystorepass, + keystorealias, + keystorealiaspass, + androidreleasetype, + }); + }, + ), + ), + ); + program .command(`run [platform]`) .description( diff --git a/cli/src/tasks/build.ts b/cli/src/tasks/build.ts new file mode 100644 index 0000000000..d8cbec6ad6 --- /dev/null +++ b/cli/src/tasks/build.ts @@ -0,0 +1,62 @@ +import { buildAndroid } from '../android/build'; +import c from '../colors'; +import { selectPlatforms, promptForPlatform } from '../common'; +import type { Config } from '../definitions'; +import { fatal, isFatal } from '../errors'; + +export interface BuildCommandOptions { + keystorepath: string; + keystorepass: string; + keystorealias: string; + keystorealiaspass: string; + androidreleasetype: string; +} + +export async function buildCommand( + config: Config, + selectedPlatformName: string, + buildOptions: BuildCommandOptions, +): Promise { + const platforms = await selectPlatforms(config, selectedPlatformName); + let platformName: string; + if (platforms.length === 1) { + platformName = platforms[0]; + } else { + platformName = await promptForPlatform( + platforms.filter(createBuildablePlatformFilter(config)), + `Please choose a platform to build for:`, + ); + } + + try { + await build(config, platformName, buildOptions); + } catch (e) { + if (!isFatal(e)) { + fatal((e as any).stack ?? e); + } + throw e; + } +} + +export async function build( + config: Config, + platformName: string, + buildOptions: BuildCommandOptions, +): Promise { + if (platformName == config.ios.name) { + // await runIOS(config); + } else if (platformName === config.android.name) { + await buildAndroid(config, buildOptions); + } else if (platformName === config.web.name) { + throw `Platform "${platformName}" is not available in the build command.`; + } else { + throw `Platform "${platformName}" is not valid.`; + } +} + +function createBuildablePlatformFilter( + config: Config, +): (platform: string) => boolean { + return platform => + platform === config.ios.name || platform === config.android.name; +} From 1bc76fb94ce681905221a7090cb082a5998ab6de Mon Sep 17 00:00:00 2001 From: IT-MikeS <20338451+IT-MikeS@users.noreply.github.com> Date: Tue, 30 Aug 2022 10:01:32 -0400 Subject: [PATCH 2/5] chore: remove console log --- cli/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/src/index.ts b/cli/src/index.ts index 7806020ff1..a1d84486be 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -165,7 +165,6 @@ export function runProgram(config: Config): void { }, ) => { const { buildCommand } = await import('./tasks/build'); - console.log(program.args); await buildCommand(config, platform, { keystorepath, keystorepass, From a35ce6befc3974847a423dc4a5d1487a9be67484 Mon Sep 17 00:00:00 2001 From: IT-MikeS <20338451+IT-MikeS@users.noreply.github.com> Date: Tue, 30 Aug 2022 10:30:56 -0400 Subject: [PATCH 3/5] chore: mesage if try ios --- cli/src/tasks/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/tasks/build.ts b/cli/src/tasks/build.ts index d8cbec6ad6..7b954dd45e 100644 --- a/cli/src/tasks/build.ts +++ b/cli/src/tasks/build.ts @@ -44,7 +44,7 @@ export async function build( buildOptions: BuildCommandOptions, ): Promise { if (platformName == config.ios.name) { - // await runIOS(config); + throw `Platform "${platformName}" is not available in the build command.`; } else if (platformName === config.android.name) { await buildAndroid(config, buildOptions); } else if (platformName === config.web.name) { From 9484d4c237515c0fcfae2b51b080549264fe24df Mon Sep 17 00:00:00 2001 From: IT-MikeS <20338451+IT-MikeS@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:26:07 -0400 Subject: [PATCH 4/5] chore: remove unused --- cli/src/tasks/build.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/src/tasks/build.ts b/cli/src/tasks/build.ts index 7b954dd45e..d0815b000d 100644 --- a/cli/src/tasks/build.ts +++ b/cli/src/tasks/build.ts @@ -1,5 +1,4 @@ import { buildAndroid } from '../android/build'; -import c from '../colors'; import { selectPlatforms, promptForPlatform } from '../common'; import type { Config } from '../definitions'; import { fatal, isFatal } from '../errors'; From 0b24259ce48e83c4f49227dffbd3121869686ee9 Mon Sep 17 00:00:00 2001 From: IT-MikeS <20338451+IT-MikeS@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:09:56 -0400 Subject: [PATCH 5/5] chore: add cap-config file support --- cli/src/android/build.ts | 13 ++++++------- cli/src/config.ts | 9 +++++++++ cli/src/declarations.ts | 38 ++++++++++++++++++++++++++++++++++++++ cli/src/definitions.ts | 7 +++++++ cli/src/tasks/build.ts | 27 +++++++++++++++++++++------ 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/cli/src/android/build.ts b/cli/src/android/build.ts index 1b0eeae458..de22a2ded1 100644 --- a/cli/src/android/build.ts +++ b/cli/src/android/build.ts @@ -11,7 +11,8 @@ export async function buildAndroid( config: Config, buildOptions: BuildCommandOptions, ): Promise { - const releaseTypeIsAAB = buildOptions.androidreleasetype === 'AAB'; + const releaseType = buildOptions.androidreleasetype ?? 'AAB'; + const releaseTypeIsAAB = releaseType === 'AAB'; const arg = releaseTypeIsAAB ? ':app:bundleRelease' : 'assembleRelease'; const gradleArgs = [arg]; @@ -21,7 +22,7 @@ export async function buildAndroid( !buildOptions.keystorealiaspass || !buildOptions.keystorepass ) { - throw 'Missing options. Please supply all options for android aab signing. (Keystore Path, Keystore Password, Keystore Key Alias, Keystore Key Password)'; + throw 'Missing options. Please supply all options for android signing. (Keystore Path, Keystore Password, Keystore Key Alias, Keystore Key Password)'; } try { @@ -50,15 +51,13 @@ export async function buildAndroid( const unsignedReleaseName = `app${ config.android.flavor ? `-${config.android.flavor}` : '' - }-release${ - releaseTypeIsAAB ? '' : '-unsigned' - }.${buildOptions.androidreleasetype.toLowerCase()}`; + }-release${releaseTypeIsAAB ? '' : '-unsigned'}.${releaseType.toLowerCase()}`; const signedReleaseName = unsignedReleaseName.replace( `-release${ releaseTypeIsAAB ? '' : '-unsigned' - }.${buildOptions.androidreleasetype.toLowerCase()}`, - `-release-signed.${buildOptions.androidreleasetype.toLowerCase()}`, + }.${releaseType.toLowerCase()}`, + `-release-signed.${releaseType.toLowerCase()}`, ); const signingArgs = [ diff --git a/cli/src/config.ts b/cli/src/config.ts index c6f75c34f3..903b3619b7 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -236,6 +236,14 @@ async function loadAndroidConfig( const buildOutputDir = `${apkPath}/debug`; const cordovaPluginsDir = 'capacitor-cordova-android-plugins'; const studioPath = lazy(() => determineAndroidStudioPath(cliConfig.os)); + const buildOptions = { + keystorePath: extConfig.android?.buildOptions?.keystorePath, + keystorePassword: extConfig.android?.buildOptions?.keystorePassword, + keystoreAlias: extConfig.android?.buildOptions?.keystoreAlias, + keystoreAliasPassword: + extConfig.android?.buildOptions?.keystoreAliasPassword, + releaseType: extConfig.android?.buildOptions?.releaseType, + }; return { name, @@ -261,6 +269,7 @@ async function loadAndroidConfig( buildOutputDir, buildOutputDirAbs: resolve(platformDirAbs, buildOutputDir), flavor, + buildOptions, }; } diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts index 63b69d1d4e..1e4432bafc 100644 --- a/cli/src/declarations.ts +++ b/cli/src/declarations.ts @@ -221,6 +221,44 @@ export interface CapacitorConfig { * @default 60 */ minWebViewVersion?: number; + + buildOptions?: { + /** + * Path to your keystore + * + * @since 4.3.0 + */ + keystorePath?: string; + + /** + * Password to your keystore + * + * @since 4.3.0 + */ + keystorePassword?: string; + + /** + * Alias in the keystore to use + * + * @since 4.3.0 + */ + keystoreAlias?: string; + + /** + * Password for the alias in the keystore to use + * + * @since 4.3.0 + */ + keystoreAliasPassword?: string; + + /** + * Bundle type for your release build + * + * @since 4.3.0 + * @default "AAB" + */ + releaseType?: 'AAB' | 'APK'; + }; }; ios?: { diff --git a/cli/src/definitions.ts b/cli/src/definitions.ts index 445d5312b8..a2e0b6d24c 100644 --- a/cli/src/definitions.ts +++ b/cli/src/definitions.ts @@ -91,6 +91,13 @@ export interface AndroidConfig extends PlatformConfig { readonly buildOutputDirAbs: string; readonly apkName: string; readonly flavor: string; + readonly buildOptions: { + keystorePath?: string; + keystorePassword?: string; + keystoreAlias?: string; + keystoreAliasPassword?: string; + releaseType?: 'AAB' | 'APK'; + }; } export interface IOSConfig extends PlatformConfig { diff --git a/cli/src/tasks/build.ts b/cli/src/tasks/build.ts index d0815b000d..e57e54cf8f 100644 --- a/cli/src/tasks/build.ts +++ b/cli/src/tasks/build.ts @@ -4,11 +4,11 @@ import type { Config } from '../definitions'; import { fatal, isFatal } from '../errors'; export interface BuildCommandOptions { - keystorepath: string; - keystorepass: string; - keystorealias: string; - keystorealiaspass: string; - androidreleasetype: string; + keystorepath?: string; + keystorepass?: string; + keystorealias?: string; + keystorealiaspass?: string; + androidreleasetype?: 'AAB' | 'APK'; } export async function buildCommand( @@ -27,8 +27,23 @@ export async function buildCommand( ); } + const buildCommandOptions: BuildCommandOptions = { + keystorepath: + buildOptions.keystorepath || config.android.buildOptions.keystorePath, + keystorepass: + buildOptions.keystorepass || config.android.buildOptions.keystorePassword, + keystorealias: + buildOptions.keystorealias || config.android.buildOptions.keystoreAlias, + keystorealiaspass: + buildOptions.keystorealiaspass || + config.android.buildOptions.keystoreAliasPassword, + androidreleasetype: + buildOptions.androidreleasetype || + config.android.buildOptions.releaseType, + }; + try { - await build(config, platformName, buildOptions); + await build(config, platformName, buildCommandOptions); } catch (e) { if (!isFatal(e)) { fatal((e as any).stack ?? e);