Skip to content

Commit

Permalink
feat: added --user flag to run-android command (#1869)
Browse files Browse the repository at this point in the history
* feat: added userId to run-android command

* Update packages/cli-platform-android/src/commands/runAndroid/listAndroidUsers.ts

Co-authored-by: Szymon Rybczak <szymon.rybczak@gmail.com>

* Update docs/commands.md

Co-authored-by: homersimpsons <guillaume.alabre@gmail.com>

* fix: code review fixes

* chore: update docs

* fix: ts and tests

---------

Co-authored-by: Szymon Rybczak <szymon.rybczak@gmail.com>
Co-authored-by: homersimpsons <guillaume.alabre@gmail.com>
  • Loading branch information
3 people authored and thymikee committed May 16, 2023
1 parent a00a896 commit a73cecc
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,4 @@ Upgrade your app's template files to the specified or latest npm version using [

Using this command is a recommended way of upgrading relatively simple React Native apps with not too many native libraries linked. The more iOS and Android build files are modified, the higher chance for a conflicts. The command will guide you on how to continue upgrade process manually in case of failure.

_Note: If you'd like to upgrade using this method from React Native version lower than 0.59.0, you may use a standalone version of this CLI: `npx @react-native-community/cli upgrade`._
_Note: If you'd like to upgrade using this method from React Native version lower than 0.59.0, you may use a standalone version of this CLI: `npx @react-native-community/cli upgrade`._
3 changes: 3 additions & 0 deletions packages/cli-platform-android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ react-native build-android --extra-params "-x lint -x test"

Installs passed binary instead of building a fresh one. This command is not compatible with `--tasks`.

#### `--user` <number | string>

Id of the User Profile you want to install the app on.
### `log-android`

Usage:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import execa from 'execa';
import {checkUsers} from '../listAndroidUsers';

// output of "adb -s ... shell pm users list" command
const gradleOutput = `
Users:
UserInfo{0:Homersimpsons:c13} running
UserInfo{10:Guest:404}
`;

jest.mock('execa', () => {
return {sync: jest.fn()};
});

describe('check android users', () => {
it('should correctly parse recieved users', () => {
(execa.sync as jest.Mock).mockReturnValueOnce({stdout: gradleOutput});
const users = checkUsers('device', 'adbPath');

expect(users).toStrictEqual([
{id: '0', name: 'Homersimpsons'},
{id: '10', name: 'Guest'},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ export function getTaskNames(
taskPrefix: 'assemble' | 'install' | 'bundle',
sourceDir: string,
): Array<string> {
let appTasks = tasks || [taskPrefix + toPascalCase(mode)];
let appTasks =
tasks && tasks.length ? tasks : [taskPrefix + toPascalCase(mode)];

// Check against build flavors for "install" task ("assemble" don't care about it so much and will build all)
if (!tasks && taskPrefix === 'install') {
if (!tasks?.length && taskPrefix === 'install') {
const actionableInstallTasks = getGradleTasks('install', sourceDir);
if (!actionableInstallTasks.find((t) => t.task.includes(appTasks[0]))) {
const installTasksForMode = actionableInstallTasks.filter((t) =>
Expand Down
24 changes: 22 additions & 2 deletions packages/cli-platform-android/src/commands/runAndroid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import path from 'path';
import {build, runPackager, BuildFlags, options} from '../buildAndroid';
import {promptForTaskSelection} from './listAndroidTasks';
import {getTaskNames} from './getTaskNames';
import {checkUsers, promptForUser} from './listAndroidUsers';

export interface Flags extends BuildFlags {
appId: string;
Expand All @@ -30,6 +31,7 @@ export interface Flags extends BuildFlags {
deviceId?: string;
listDevices?: boolean;
binaryPath?: string;
user?: number | string;
}

export type AndroidProject = NonNullable<Config['project']['android']>;
Expand Down Expand Up @@ -121,6 +123,17 @@ async function buildAndRun(args: Flags, androidProject: AndroidProject) {
);
}

if (args.interactive) {
const users = checkUsers(device.deviceId as string, adbPath);
if (users && users.length > 1) {
const user = await promptForUser(users);

if (user) {
args.user = user.id;
}
}
}

if (device.connected) {
return runOnSpecificDevice(
{...args, deviceId: device.deviceId},
Expand Down Expand Up @@ -167,14 +180,16 @@ function runOnSpecificDevice(
// if coming from run-android command and we have selected task
// from interactive mode we need to create appropriate build task
// eg 'installRelease' -> 'assembleRelease'
const buildTask = selectedTask?.replace('install', 'assemble') ?? 'build';
const buildTask = selectedTask
? [selectedTask.replace('install', 'assemble')]
: [];

if (devices.length > 0 && deviceId) {
if (devices.indexOf(deviceId) !== -1) {
let gradleArgs = getTaskNames(
androidProject.appName,
args.mode || args.variant,
args.tasks ?? [buildTask],
args.tasks ?? buildTask,
'install',
androidProject.sourceDir,
);
Expand Down Expand Up @@ -287,6 +302,11 @@ export default {
description:
'Path relative to project root where pre-built .apk binary lives.',
},
{
name: '--user <number>',
description: 'Id of the User Profile you want to install the app on.',
parse: Number,
},
],
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {logger} from '@react-native-community/cli-tools';
import execa from 'execa';
import prompts from 'prompts';

type User = {
id: string;
name: string;
};

export function checkUsers(device: string, adbPath: string) {
try {
const adbArgs = ['-s', device, 'shell', 'pm', 'list', 'users'];

logger.debug(`Checking users on "${device}"...`);
const {stdout} = execa.sync(adbPath, adbArgs, {encoding: 'utf-8'});
const regex = new RegExp(
/^\s*UserInfo\{(?<userId>\d+):(?<userName>.*):(?<userFlags>[0-9a-f]*)}/,
);
const users: User[] = [];

const lines = stdout.split('\n');
for (const line of lines) {
const res = regex.exec(line);
if (res?.groups) {
users.push({id: res.groups.userId, name: res.groups.userName});
}
}

if (users.length > 1) {
logger.debug(
`Available users are:\n${users
.map((user) => `${user.name} - ${user.id}`)
.join('\n')}`,
);
}

return users;
} catch (error) {
logger.error('Failed to check users of device.', error as any);
return [];
}
}

export async function promptForUser(users: User[]) {
const {selectedUser}: {selectedUser: User} = await prompts({
type: 'select',
name: 'selectedUser',
message: 'Which profile would you like to launch your app into?',
choices: users.map((user: User) => ({
title: user.name,
value: user,
})),
min: 1,
});

return selectedUser;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ function tryInstallAppOnDevice(
pathToApk = args.binaryPath;
}

const adbArgs = ['-s', device, 'install', '-r', '-d', pathToApk];
const installArgs = ['-s', device, 'install', '-r', '-d'];
if (args.user !== undefined) {
installArgs.push('--user', `${args.user}`);
}
const adbArgs = [...installArgs, pathToApk];
logger.info(`Installing the app on the device "${device}"...`);
logger.debug(
`Running command "cd android && adb -s ${device} install -r -d ${pathToApk}"`,
);
logger.debug(`Running command "cd android && adb ${adbArgs.join(' ')}"`);
execa.sync(adbPath, adbArgs, {stdio: 'inherit'});
} catch (error) {
throw new CLIError(
Expand Down Expand Up @@ -82,7 +84,7 @@ function getInstallApkName(
return apkName;
}

throw new CLIError('Could not find the correct install APK file.');
throw new Error('Could not find the correct install APK file.');
}

export default tryInstallAppOnDevice;

0 comments on commit a73cecc

Please sign in to comment.