From b15afed0177b004fb36120d6fa3ee415668dea51 Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Wed, 31 Jul 2024 14:35:58 +0530 Subject: [PATCH 1/5] feat: install system-images --- src/commands/android/constants.ts | 34 ++++- src/commands/android/interfaces.ts | 12 ++ .../subcommands/install/system-image.ts | 126 ++++++++++++++++++ 3 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/commands/android/subcommands/install/system-image.ts diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index 0fe09f4..fa7b2f4 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -1,8 +1,8 @@ import inquirer from 'inquirer'; -import path from 'path'; import os from 'os'; +import path from 'path'; -import {AvailableOptions, AvailableSubcommands, SdkBinary} from './interfaces'; +import {ApiLevelNames, AvailableOptions, AvailableSubcommands, SdkBinary} from './interfaces'; export const AVAILABLE_OPTIONS: AvailableOptions = { help: { @@ -105,3 +105,33 @@ export const BINARY_TO_PACKAGE_NAME: Record { + try { + const sdkmanagerLocation = getBinaryLocation(sdkRoot, platform, 'sdkmanager', true); + if (!sdkmanagerLocation) { + Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('sdkmanager')} binary not found.\n`); + Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); + Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + + return false; + } + + const stdout = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, '--list | grep "system-images;"'); + if (!stdout) { + Logger.log(`${colors.red('Failed to fetch system images!')} Please try again.`); + + return false; + } + + // sdkmanager output has repetitive system image names in different sections (Installed + // packages, Available packages, Available updates). + // Parse the output and store the system images in a Set to avoid duplicates. + const images = new Set(); + // Before removing duplicates, sort the system images to get them in increasing order of API level. + const lines = stdout.split('\n').sort(); + + lines.forEach(line => { + const image = line.split('|')[0].trim(); + images.add(image); + }); + + // System images are represented in the format: system-images;android-;; + // Group all the system image types by API level. Group all the architectures by system image type. + const availableSystemImages: AvailableSystemImages = {}; + + images.forEach(image => { + if (!image.includes('system-image')) { + return; + } + const imageSplit = image.split(';'); + const apiLevel = imageSplit[1]; + const type = imageSplit[2]; + const arch = imageSplit[3]; + + if (!availableSystemImages[apiLevel]) { + availableSystemImages[apiLevel] = []; + } + + const imageType = availableSystemImages[apiLevel].find(image => image.type === type); + if (!imageType) { + availableSystemImages[apiLevel].push({ + type: type, + archs: [arch] + }); + } else { + imageType.archs.push(arch); + } + }); + + const apiLevelsWithNames = Object.keys(availableSystemImages).map(apiLevel => { + if (APILevelNames[apiLevel]) { + return `${apiLevel}: ${APILevelNames[apiLevel]}`; + } + + return apiLevel; + }); + + const androidVersionAnswer = await inquirer.prompt({ + type: 'list', + name: 'androidVersion', + message: 'Select the API level for system image:', + choices: apiLevelsWithNames + }); + const apiLevel = androidVersionAnswer.androidVersion.split(':')[0]; + + const systemImageTypeAnswer = await inquirer.prompt({ + type: 'list', + name: 'systemImageType', + message: `Select the system image type for ${colors.cyan(apiLevel)}:`, + choices: availableSystemImages[apiLevel].map(image => image.type) + }); + const type = systemImageTypeAnswer.systemImageType; + + const systemImageArchAnswer = await inquirer.prompt({ + type: 'list', + name: 'systemImageArch', + message: 'Select the architecture for the system image:', + choices: availableSystemImages[apiLevel].find(image => image.type === systemImageTypeAnswer.systemImageType)?.archs + }); + const arch = systemImageArchAnswer.systemImageArch; + + const systemImageName = `system-images;${apiLevel};${type};${arch}`; + + Logger.log(); + Logger.log(`Downloading ${colors.cyan(systemImageName)}...\n`); + + const downloadStatus = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, `'${systemImageName}'`); + + if (downloadStatus?.includes('100% Unzipping')) { + Logger.log(`${colors.green('System image downloaded successfully!')}\n`); + + return true; + } else if (downloadStatus?.includes('100% Computing updates')) { + Logger.log(`${colors.green('System image already downloaded!')}\n`); + + return true; + } + + return false; + } catch (error) { + Logger.log(colors.red('Error occured while installing system image.')); + console.error(error); + + return false; + } +} + From 9d4994b77b19fdd5c006930094d01738a263f858 Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Thu, 15 Aug 2024 01:06:12 +0530 Subject: [PATCH 2/5] minor refactor --- src/commands/android/subcommands/install/system-image.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/commands/android/subcommands/install/system-image.ts b/src/commands/android/subcommands/install/system-image.ts index 980d78b..2ce7995 100644 --- a/src/commands/android/subcommands/install/system-image.ts +++ b/src/commands/android/subcommands/install/system-image.ts @@ -2,19 +2,17 @@ import colors from 'ansi-colors'; import inquirer from 'inquirer'; import Logger from '../../../../logger'; -import {symbols} from '../../../../utils'; import {APILevelNames} from '../../constants'; import {AvailableSystemImages, Platform} from '../../interfaces'; import {getBinaryLocation} from '../../utils/common'; import {execBinarySync} from '../../utils/sdk'; +import {showMissingBinaryHelp} from '../common'; export async function installSystemImage(sdkRoot: string, platform: Platform): Promise { try { const sdkmanagerLocation = getBinaryLocation(sdkRoot, platform, 'sdkmanager', true); if (!sdkmanagerLocation) { - Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('sdkmanager')} binary not found.\n`); - Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); - Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + showMissingBinaryHelp('sdkmanager'); return false; } From 19d26566761672c8933e66f315576b8defc65b8c Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Sun, 25 Aug 2024 17:01:19 +0530 Subject: [PATCH 3/5] created spawnCommandSync --- src/commands/android/constants.ts | 8 +++- src/commands/android/dotcommands.ts | 39 +++---------------- src/commands/android/interfaces.ts | 7 ---- .../android/subcommands/install/index.ts | 7 +++- .../subcommands/install/system-image.ts | 23 ++++++----- .../android/subcommands/interfaces.ts | 7 ++++ src/commands/android/utils/sdk.ts | 24 +++++++++++- 7 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index 93ba676..85cbd5e 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -54,7 +54,7 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = { }] }, install: { - description: 'Install APK or AVD on a device', + description: 'Install APK, AVD or system image', flags: [ { name: 'avd', @@ -77,13 +77,17 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = { usageHelp: 'device_id' } ] + }, + { + name: 'system-image', + description: 'Install a system image' } ] }, uninstall: { description: 'todo item', flags: [ - {name: 'avd', description: 'todo item'}, + {name: 'avd', description: 'todo item'} ] } }; diff --git a/src/commands/android/dotcommands.ts b/src/commands/android/dotcommands.ts index e9378f0..ac8e1b2 100644 --- a/src/commands/android/dotcommands.ts +++ b/src/commands/android/dotcommands.ts @@ -1,5 +1,4 @@ import colors from 'ansi-colors'; -import {spawnSync} from 'child_process'; import * as dotenv from 'dotenv'; import path from 'path'; @@ -7,7 +6,8 @@ import {ANDROID_DOTCOMMANDS} from '../../constants'; import Logger from '../../logger'; import {getPlatformName} from '../../utils'; import {Platform, SdkBinary} from './interfaces'; -import {checkJavaInstallation, getBinaryLocation, getBinaryNameForOS, getSdkRootFromEnv} from './utils/common'; +import {checkJavaInstallation, getBinaryLocation, getSdkRootFromEnv} from './utils/common'; +import {spawnCommandSync} from './utils/sdk'; export class AndroidDotCommand { dotcmd: string; @@ -55,42 +55,15 @@ export class AndroidDotCommand { } this.sdkRoot = sdkRootEnv; - return this.executeDotCommand(); - } - - loadEnvFromDotEnv(): void { - this.androidHomeInGlobalEnv = 'ANDROID_HOME' in process.env; - dotenv.config({path: path.join(this.rootDir, '.env')}); - } - - buildCommand(): string { const binaryName = this.dotcmd.split('.')[1] as SdkBinary; const binaryLocation = getBinaryLocation(this.sdkRoot, this.platform, binaryName, true); - let cmd: string; - if (binaryLocation === 'PATH') { - const binaryFullName = getBinaryNameForOS(this.platform, binaryName); - cmd = `${binaryFullName}`; - } else { - const binaryFullName = path.basename(binaryLocation); - const binaryDirPath = path.dirname(binaryLocation); - cmd = path.join(binaryDirPath, binaryFullName); - } - - return cmd; + return spawnCommandSync(binaryLocation, binaryName, this.platform, this.args); } - executeDotCommand(): boolean { - const cmd = this.buildCommand(); - const result = spawnSync(cmd, this.args, {stdio: 'inherit'}); - - if (result.error) { - console.error(result.error); - - return false; - } - - return result.status === 0; + loadEnvFromDotEnv(): void { + this.androidHomeInGlobalEnv = 'ANDROID_HOME' in process.env; + dotenv.config({path: path.join(this.rootDir, '.env')}); } } diff --git a/src/commands/android/interfaces.ts b/src/commands/android/interfaces.ts index f2a24be..166813c 100644 --- a/src/commands/android/interfaces.ts +++ b/src/commands/android/interfaces.ts @@ -29,13 +29,6 @@ export interface SetupConfigs { export type SdkBinary = 'sdkmanager' | 'adb' | 'emulator' | 'avdmanager'; -export interface AvailableSystemImages { - [apiLevel: string]: { - type: string; - archs: string[]; - }[] -} - export interface ApiLevelNames { [apiLevel: string]: string } diff --git a/src/commands/android/subcommands/install/index.ts b/src/commands/android/subcommands/install/index.ts index 6387629..9e6445d 100644 --- a/src/commands/android/subcommands/install/index.ts +++ b/src/commands/android/subcommands/install/index.ts @@ -5,6 +5,7 @@ import {Options, Platform} from '../../interfaces'; import {verifyOptions} from '../common'; import {installApp} from './app'; import {createAvd} from './avd'; +import {installSystemImage} from './system-image'; export async function install(options: Options, sdkRoot: string, platform: Platform): Promise { const optionsVerified = verifyOptions('install', options); @@ -19,6 +20,8 @@ export async function install(options: Options, sdkRoot: string, platform: Platf if (subcommandFlag === 'app') { return await installApp(options, sdkRoot, platform); + } else if (subcommandFlag === 'system-image') { + return await installSystemImage(sdkRoot, platform); } else if (subcommandFlag === 'avd') { return await createAvd(sdkRoot, platform); } @@ -31,13 +34,15 @@ async function promptForFlag(): Promise { type: 'list', name: 'flag', message: 'Select what do you want to install:', - choices: ['APK', 'AVD'] + choices: ['APK', 'AVD', 'System image'] }); Logger.log(); const flag = flagAnswer.flag; if (flag === 'APK') { return 'app'; + } else if (flag === 'System image') { + return 'system-image'; } return 'avd'; diff --git a/src/commands/android/subcommands/install/system-image.ts b/src/commands/android/subcommands/install/system-image.ts index 2ce7995..0f55085 100644 --- a/src/commands/android/subcommands/install/system-image.ts +++ b/src/commands/android/subcommands/install/system-image.ts @@ -3,10 +3,11 @@ import inquirer from 'inquirer'; import Logger from '../../../../logger'; import {APILevelNames} from '../../constants'; -import {AvailableSystemImages, Platform} from '../../interfaces'; +import {Platform} from '../../interfaces'; import {getBinaryLocation} from '../../utils/common'; -import {execBinarySync} from '../../utils/sdk'; +import {execBinarySync, spawnCommandSync} from '../../utils/sdk'; import {showMissingBinaryHelp} from '../common'; +import {AvailableSystemImages} from '../interfaces'; export async function installSystemImage(sdkRoot: string, platform: Platform): Promise { try { @@ -92,27 +93,25 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P type: 'list', name: 'systemImageArch', message: 'Select the architecture for the system image:', - choices: availableSystemImages[apiLevel].find(image => image.type === systemImageTypeAnswer.systemImageType)?.archs + choices: availableSystemImages[apiLevel].find(image => image.type === type)?.archs }); const arch = systemImageArchAnswer.systemImageArch; const systemImageName = `system-images;${apiLevel};${type};${arch}`; Logger.log(); - Logger.log(`Downloading ${colors.cyan(systemImageName)}...\n`); - const downloadStatus = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, `'${systemImageName}'`); - - if (downloadStatus?.includes('100% Unzipping')) { - Logger.log(`${colors.green('System image downloaded successfully!')}\n`); - - return true; - } else if (downloadStatus?.includes('100% Computing updates')) { - Logger.log(`${colors.green('System image already downloaded!')}\n`); + const installationStatus = spawnCommandSync(sdkmanagerLocation, 'sdkmanager', platform, [systemImageName]); + if (installationStatus) { + Logger.log(colors.green('System image installed successfully!\n')); return true; } + Logger.log(colors.red('Something went wrong while installing system image')); + Logger.log(`Please run ${colors.cyan('npx @nightwatch/mobile-helper android.sdkmanager --list')} to verify if the system image was installed.`); + Logger.log('If the system image was not installed, please try installing again.\n'); + return false; } catch (error) { Logger.log(colors.red('Error occured while installing system image.')); diff --git a/src/commands/android/subcommands/interfaces.ts b/src/commands/android/subcommands/interfaces.ts index eae016d..80c948d 100644 --- a/src/commands/android/subcommands/interfaces.ts +++ b/src/commands/android/subcommands/interfaces.ts @@ -24,3 +24,10 @@ export interface SubcommandOptionsVerificationResult { subcommandFlag: string; configs: string[]; } + +export interface AvailableSystemImages { + [apiLevel: string]: { + type: string; + archs: string[]; + }[] +} diff --git a/src/commands/android/utils/sdk.ts b/src/commands/android/utils/sdk.ts index 7b7a971..11c15b0 100644 --- a/src/commands/android/utils/sdk.ts +++ b/src/commands/android/utils/sdk.ts @@ -1,5 +1,5 @@ import colors from 'ansi-colors'; -import {exec, execSync} from 'child_process'; +import {exec, execSync, spawnSync} from 'child_process'; import fs from 'fs'; import {homedir} from 'os'; import path from 'path'; @@ -224,6 +224,28 @@ export const execBinaryAsync = ( }); }; +export const spawnCommandSync = (binaryLocation: string, binaryName: string, platform: Platform, args: string[]) => { + let cmd: string; + if (binaryLocation === 'PATH') { + const binaryFullName = getBinaryNameForOS(platform, binaryName); + cmd = `${binaryFullName}`; + } else { + const binaryFullName = path.basename(binaryLocation); + const binaryDirPath = path.dirname(binaryLocation); + cmd = path.join(binaryDirPath, binaryFullName); + } + + const result = spawnSync(cmd, args, {stdio: 'inherit'}); + + if (result.error) { + console.error(result.error); + + return false; + } + + return result.status === 0; +}; + export const getBuildToolsAvailableVersions = (buildToolsPath: string): string[] => { if (!fs.existsSync(buildToolsPath)) { return []; From 6a9d746a636aeb45d053b632a52d52163a2d4de7 Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Wed, 4 Sep 2024 19:01:32 +0530 Subject: [PATCH 4/5] refactors --- src/commands/android/constants.ts | 38 +++---------------- src/commands/android/interfaces.ts | 5 --- .../android/subcommands/apiLevelNames.json | 29 ++++++++++++++ .../subcommands/install/system-image.ts | 25 +++++++----- .../android/subcommands/interfaces.ts | 5 +++ src/commands/android/utils/sdk.ts | 4 +- 6 files changed, 56 insertions(+), 50 deletions(-) create mode 100644 src/commands/android/subcommands/apiLevelNames.json diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index 08b9bff..662b106 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -2,7 +2,7 @@ import inquirer from 'inquirer'; import os from 'os'; import path from 'path'; -import {ApiLevelNames, AvailableOptions, SdkBinary} from './interfaces'; +import {AvailableOptions, SdkBinary} from './interfaces'; import {AvailableSubcommands} from './subcommands/interfaces'; export const AVAILABLE_OPTIONS: AvailableOptions = { @@ -85,9 +85,12 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = { ] }, uninstall: { - description: 'todo item', + description: 'Uninstall system images, AVDs, or apps from a device', flags: [ - {name: 'avd', description: 'todo item'}, + { + name: 'avd', + description: 'Delete an Android Virtual Device' + }, { name: 'app', description: 'Uninstall an APK from a device', @@ -167,32 +170,3 @@ export const BINARY_TO_PACKAGE_NAME: Record { try { @@ -18,9 +18,9 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P return false; } - const stdout = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, '--list | grep "system-images;"'); + const stdout = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, '--list'); if (!stdout) { - Logger.log(`${colors.red('Failed to fetch system images!')} Please try again.`); + Logger.log(`${colors.red('Failed to fetch available system images!')} Please try again.`); return false; } @@ -33,6 +33,9 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P const lines = stdout.split('\n').sort(); lines.forEach(line => { + if (!line.includes('system-images;')) { + return; + } const image = line.split('|')[0].trim(); images.add(image); }); @@ -66,11 +69,12 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P }); const apiLevelsWithNames = Object.keys(availableSystemImages).map(apiLevel => { - if (APILevelNames[apiLevel]) { - return `${apiLevel}: ${APILevelNames[apiLevel]}`; + let name = apiLevel; + if ((apiLevelNames as ApiLevelNames)[apiLevel]) { + name = `${apiLevel}: ${(apiLevelNames as ApiLevelNames)[apiLevel]}`; } - return apiLevel; + return {name, value: apiLevel}; }); const androidVersionAnswer = await inquirer.prompt({ @@ -79,7 +83,7 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P message: 'Select the API level for system image:', choices: apiLevelsWithNames }); - const apiLevel = androidVersionAnswer.androidVersion.split(':')[0]; + const apiLevel = androidVersionAnswer.androidVersion; const systemImageTypeAnswer = await inquirer.prompt({ type: 'list', @@ -100,6 +104,7 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P const systemImageName = `system-images;${apiLevel};${type};${arch}`; Logger.log(); + Logger.log(`Installing system image: ${colors.cyan(systemImageName)}\n`); const installationStatus = spawnCommandSync(sdkmanagerLocation, 'sdkmanager', platform, [systemImageName]); if (installationStatus) { @@ -108,8 +113,8 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P return true; } - Logger.log(colors.red('Something went wrong while installing system image')); - Logger.log(`Please run ${colors.cyan('npx @nightwatch/mobile-helper android.sdkmanager --list')} to verify if the system image was installed.`); + Logger.log(colors.red('Something went wrong while installing system image.')); + Logger.log(`Please run ${colors.cyan('npx @nightwatch/mobile-helper android.sdkmanager --list_installed')} to verify if the system image was installed.`); Logger.log('If the system image was not installed, please try installing again.\n'); return false; diff --git a/src/commands/android/subcommands/interfaces.ts b/src/commands/android/subcommands/interfaces.ts index 80c948d..e8280ad 100644 --- a/src/commands/android/subcommands/interfaces.ts +++ b/src/commands/android/subcommands/interfaces.ts @@ -31,3 +31,8 @@ export interface AvailableSystemImages { archs: string[]; }[] } + +export interface ApiLevelNames { + [apiLevel: string]: string +} + diff --git a/src/commands/android/utils/sdk.ts b/src/commands/android/utils/sdk.ts index 11c15b0..f1e688e 100644 --- a/src/commands/android/utils/sdk.ts +++ b/src/commands/android/utils/sdk.ts @@ -230,9 +230,7 @@ export const spawnCommandSync = (binaryLocation: string, binaryName: string, pla const binaryFullName = getBinaryNameForOS(platform, binaryName); cmd = `${binaryFullName}`; } else { - const binaryFullName = path.basename(binaryLocation); - const binaryDirPath = path.dirname(binaryLocation); - cmd = path.join(binaryDirPath, binaryFullName); + cmd = binaryLocation; } const result = spawnSync(cmd, args, {stdio: 'inherit'}); From 50c63be0a0f6a6f7d83c5b89dc20521145c766fd Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Thu, 5 Sep 2024 00:06:18 +0530 Subject: [PATCH 5/5] Refactors. --- src/commands/android/dotcommands.ts | 7 ++- .../android/subcommands/install/index.ts | 16 +++--- .../subcommands/install/system-image.ts | 54 ++++++++++--------- .../android/subcommands/uninstall/app.ts | 2 +- src/commands/android/utils/common.ts | 4 +- 5 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/commands/android/dotcommands.ts b/src/commands/android/dotcommands.ts index ac8e1b2..0763e30 100644 --- a/src/commands/android/dotcommands.ts +++ b/src/commands/android/dotcommands.ts @@ -6,6 +6,7 @@ import {ANDROID_DOTCOMMANDS} from '../../constants'; import Logger from '../../logger'; import {getPlatformName} from '../../utils'; import {Platform, SdkBinary} from './interfaces'; +import {showMissingBinaryHelp} from './subcommands/common'; import {checkJavaInstallation, getBinaryLocation, getSdkRootFromEnv} from './utils/common'; import {spawnCommandSync} from './utils/sdk'; @@ -57,6 +58,11 @@ export class AndroidDotCommand { const binaryName = this.dotcmd.split('.')[1] as SdkBinary; const binaryLocation = getBinaryLocation(this.sdkRoot, this.platform, binaryName, true); + if (!binaryLocation) { + showMissingBinaryHelp(binaryName); + + return false; + } return spawnCommandSync(binaryLocation, binaryName, this.platform, this.args); } @@ -66,4 +72,3 @@ export class AndroidDotCommand { dotenv.config({path: path.join(this.rootDir, '.env')}); } } - diff --git a/src/commands/android/subcommands/install/index.ts b/src/commands/android/subcommands/install/index.ts index 9e6445d..a8a0593 100644 --- a/src/commands/android/subcommands/install/index.ts +++ b/src/commands/android/subcommands/install/index.ts @@ -34,17 +34,13 @@ async function promptForFlag(): Promise { type: 'list', name: 'flag', message: 'Select what do you want to install:', - choices: ['APK', 'AVD', 'System image'] + choices: [ + {name: 'Android app (APK)', value: 'app'}, + {name: 'Android Virtual Device (AVD)', value: 'avd'}, + {name: 'System image', value: 'system-image'} + ] }); Logger.log(); - const flag = flagAnswer.flag; - if (flag === 'APK') { - return 'app'; - } else if (flag === 'System image') { - return 'system-image'; - } - - return 'avd'; + return flagAnswer.flag; } - diff --git a/src/commands/android/subcommands/install/system-image.ts b/src/commands/android/subcommands/install/system-image.ts index 236131d..521af23 100644 --- a/src/commands/android/subcommands/install/system-image.ts +++ b/src/commands/android/subcommands/install/system-image.ts @@ -20,42 +20,43 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P const stdout = execBinarySync(sdkmanagerLocation, 'sdkmanager', platform, '--list'); if (!stdout) { - Logger.log(`${colors.red('Failed to fetch available system images!')} Please try again.`); + Logger.log(`${colors.red('\nFailed to fetch available system images!')} Please try again.\n`); return false; } - // sdkmanager output has repetitive system image names in different sections (Installed - // packages, Available packages, Available updates). - // Parse the output and store the system images in a Set to avoid duplicates. - const images = new Set(); - // Before removing duplicates, sort the system images to get them in increasing order of API level. + // `sdkmanager --list` output have repetitive system image names in different sections + // (Installed packages, Available packages, Available updates, etc.) + // + // Parse the output and store the system image names in a Set to avoid duplicates. + const availableImageNames = new Set(); + + // Before parsing and removing duplicates, sort the system images + // to get them in increasing order of API level. const lines = stdout.split('\n').sort(); lines.forEach(line => { if (!line.includes('system-images;')) { return; } - const image = line.split('|')[0].trim(); - images.add(image); + + const imageName = line.split('|')[0].trim(); + availableImageNames.add(imageName); }); // System images are represented in the format: system-images;android-;; // Group all the system image types by API level. Group all the architectures by system image type. const availableSystemImages: AvailableSystemImages = {}; - - images.forEach(image => { - if (!image.includes('system-image')) { + availableImageNames.forEach(imageName => { + if (!imageName.includes('system-image')) { return; } - const imageSplit = image.split(';'); + const imageSplit = imageName.split(';'); const apiLevel = imageSplit[1]; const type = imageSplit[2]; const arch = imageSplit[3]; - if (!availableSystemImages[apiLevel]) { - availableSystemImages[apiLevel] = []; - } + availableSystemImages[apiLevel] ||= []; const imageType = availableSystemImages[apiLevel].find(image => image.type === type); if (!imageType) { @@ -68,7 +69,9 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P } }); - const apiLevelsWithNames = Object.keys(availableSystemImages).map(apiLevel => { + // We've got the available system images grouped by API level. + // Now, prompt the user to select the API level, system image type, and architecture. + const apiLevelChoices = Object.keys(availableSystemImages).map(apiLevel => { let name = apiLevel; if ((apiLevelNames as ApiLevelNames)[apiLevel]) { name = `${apiLevel}: ${(apiLevelNames as ApiLevelNames)[apiLevel]}`; @@ -77,13 +80,13 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P return {name, value: apiLevel}; }); - const androidVersionAnswer = await inquirer.prompt({ + const apiLevelAnswer = await inquirer.prompt({ type: 'list', - name: 'androidVersion', + name: 'apiLevel', message: 'Select the API level for system image:', - choices: apiLevelsWithNames + choices: apiLevelChoices }); - const apiLevel = androidVersionAnswer.androidVersion; + const apiLevel = apiLevelAnswer.apiLevel; const systemImageTypeAnswer = await inquirer.prompt({ type: 'list', @@ -108,21 +111,20 @@ export async function installSystemImage(sdkRoot: string, platform: Platform): P const installationStatus = spawnCommandSync(sdkmanagerLocation, 'sdkmanager', platform, [systemImageName]); if (installationStatus) { - Logger.log(colors.green('System image installed successfully!\n')); + Logger.log(colors.green('\nSystem image installed successfully!\n')); return true; } - Logger.log(colors.red('Something went wrong while installing system image.')); - Logger.log(`Please run ${colors.cyan('npx @nightwatch/mobile-helper android.sdkmanager --list_installed')} to verify if the system image was installed.`); - Logger.log('If the system image was not installed, please try installing again.\n'); + Logger.log(colors.red('\nSomething went wrong while installing system image.\n')); + Logger.log(`To verify if the system image was installed, run: ${colors.cyan('npx @nightwatch/mobile-helper android.sdkmanager --list_installed')}`); + Logger.log('If the system image is not found listed, please try installing again.\n'); return false; } catch (error) { - Logger.log(colors.red('Error occured while installing system image.')); + Logger.log(colors.red('\nError occurred while installing system image.')); console.error(error); return false; } } - diff --git a/src/commands/android/subcommands/uninstall/app.ts b/src/commands/android/subcommands/uninstall/app.ts index ddd4e47..f8f8883 100644 --- a/src/commands/android/subcommands/uninstall/app.ts +++ b/src/commands/android/subcommands/uninstall/app.ts @@ -113,7 +113,7 @@ export async function uninstallApp(options: Options, sdkRoot: string, platform: return false; } catch (error) { - Logger.log(colors.red('Error occurred while uninstalling app.')); + Logger.log(colors.red('\nError occurred while uninstalling app.')); console.error(error); return false; diff --git a/src/commands/android/utils/common.ts b/src/commands/android/utils/common.ts index 8fa6485..c66151d 100644 --- a/src/commands/android/utils/common.ts +++ b/src/commands/android/utils/common.ts @@ -41,7 +41,9 @@ export const getBinaryNameForOS = (platform: Platform, binaryName: string) => { return binaryName; }; -export const getBinaryLocation = (sdkRoot: string, platform: Platform, binaryName: SdkBinary, suppressOutput = false) => { +export const getBinaryLocation = ( + sdkRoot: string, platform: Platform, binaryName: SdkBinary, suppressOutput = false +): string => { const failLocations: string[] = []; const binaryFullName = getBinaryNameForOS(platform, binaryName);