Skip to content

Commit d2948de

Browse files
authored
feat: new looks that makes important information easier to parse (#435)
* chore: more colorful UI; reinstall when incompatible update * more changes * update links; use relativeToCwd * remove getInfo spinner * include app id in build settings for android * fix lint * changeset * fix lint * simplify loader to logger * fix test
1 parent 7157d23 commit d2948de

File tree

34 files changed

+246
-232
lines changed

34 files changed

+246
-232
lines changed

.changeset/chatty-experts-shout.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@rnef/platform-apple-helpers': patch
3+
'@rnef/plugin-brownfield-ios': patch
4+
'@rnef/platform-android': patch
5+
'@rnef/provider-github': patch
6+
'@rnef/plugin-repack': patch
7+
'@rnef/plugin-metro': patch
8+
'@rnef/create-app': patch
9+
'@rnef/config': patch
10+
'@rnef/tools': patch
11+
'@rnef/cli': patch
12+
---
13+
14+
feat: new looks that makes important information easier to parse

packages/cli/src/lib/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
22
import { dirname } from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { type CommandType, getConfig } from '@rnef/config';
5-
import { color, logger, resolveFilenameUp, RnefError } from '@rnef/tools';
5+
import { colorLink, logger, resolveFilenameUp, RnefError } from '@rnef/tools';
66
import { Command } from 'commander';
77
import { checkDeprecatedOptions } from './checkDeprecatedOptions.js';
88
import { cleanPlugin } from './plugins/clean.js';
@@ -104,12 +104,12 @@ function ensureUniqueCommands(commands: CommandType[] | undefined) {
104104
command.name
105105
}" registered twice by the same "${
106106
command.__origin
107-
}" plugin in ${color.cyan('rnef.config.mjs')} file.
107+
}" plugin in ${colorLink('rnef.config.mjs')} file.
108108
Please declare the plugin only once.`);
109109
} else {
110110
logger.error(`Found duplicated command "${
111111
command.name
112-
}" registered by 2 plugins in ${color.cyan('rnef.config.mjs')} file:
112+
}" registered by 2 plugins in ${colorLink('rnef.config.mjs')} file:
113113
1. Added by "${command.__origin}" plugin
114114
2. Added by "${duplicate.__origin}" plugin
115115
Command names must be unique. Please check if you import a plugin multiple times or use incompatible plugins.`);

packages/config/src/lib/config.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createRequire } from 'node:module';
33
import * as path from 'node:path';
44
import { pathToFileURL } from 'node:url';
55
import type { FingerprintSources, RemoteBuildCache } from '@rnef/tools';
6-
import { color, logger } from '@rnef/tools';
6+
import { colorLink, logger } from '@rnef/tools';
77
import type { ValidationError } from 'joi';
88
import { ConfigTypeSchema } from './schema.js';
99
import { formatValidationError } from './utils.js';
@@ -67,7 +67,7 @@ export type ConfigType = {
6767
plugins?: PluginType[];
6868
platforms?: Record<string, PlatformType>;
6969
commands?: Array<CommandType>;
70-
remoteCacheProvider?: null | 'github-actions' |(() => RemoteBuildCache);
70+
remoteCacheProvider?: null | 'github-actions' | (() => RemoteBuildCache);
7171
fingerprint?: {
7272
extraSources?: string[];
7373
ignorePaths?: string[];
@@ -98,7 +98,9 @@ const importUp = async (
9898
let config: ConfigType;
9999

100100
if (ext === '.mjs') {
101-
config = await import(pathToFileURL(filePathWithExt).href).then((module) => module.default);
101+
config = await import(pathToFileURL(filePathWithExt).href).then(
102+
(module) => module.default
103+
);
102104
} else {
103105
const require = createRequire(import.meta.url);
104106
config = require(filePathWithExt);
@@ -139,7 +141,7 @@ export async function getConfig(
139141

140142
if (error) {
141143
logger.error(
142-
`Invalid ${color.cyan(
144+
`Invalid ${colorLink(
143145
path.relative(configDir, filePathWithExt)
144146
)} file:\n` + formatValidationError(config, error)
145147
);

packages/create-app/src/lib/utils/prompts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import {
88
promptMultiselect,
99
promptSelect,
1010
promptText,
11+
relativeToCwd,
1112
RnefError,
1213
type SupportedRemoteCacheProviders,
1314
} from '@rnef/tools';
1415
import { vice } from 'gradient-string';
15-
import path from 'path';
1616
import type { TemplateInfo } from '../templates.js';
1717
import { validateProjectName } from './project-name.js';
1818
import { getRnefVersion } from './version.js';
@@ -59,7 +59,7 @@ export function printByeMessage(
5959
pkgManager: string,
6060
installDeps: boolean
6161
) {
62-
const relativeDir = path.relative(process.cwd(), targetDir);
62+
const relativeDir = relativeToCwd(targetDir);
6363

6464
const nextSteps = [
6565
`cd ${relativeDir}`,

packages/platform-android/src/lib/commands/buildAndroid/__tests__/buildAndroid.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,7 @@ test('buildAndroid runs gradle build with correct configuration for debug and ou
7979
expect(spawn).toBeCalledWith('./gradlew', ['app:bundleDebug', '-x', 'lint'], {
8080
cwd: '/android',
8181
});
82-
expect(spinnerMock.stop).toBeCalledWith(
83-
expect.stringContaining(
84-
'android/app/build/outputs/bundle/debug/app-debug.aab'
85-
)
86-
);
82+
expect(spinnerMock.stop).toBeCalledWith('Built the app');
8783
});
8884

8985
test('buildAndroid fails gracefully when gradle errors', async () => {

packages/platform-android/src/lib/commands/buildAndroid/buildAndroid.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import path from 'node:path';
21
import type { AndroidProjectConfig } from '@react-native-community/cli-types';
3-
import type { FingerprintSources } from '@rnef/tools';
42
import {
5-
color,
3+
colorLink,
4+
type FingerprintSources,
65
formatArtifactName,
76
logger,
87
outro,
98
parseArgs,
10-
spinner,
9+
relativeToCwd,
1110
} from '@rnef/tools';
1211
import { findOutputFile } from '../runAndroid/findOutputFile.js';
1312
import { runGradle } from '../runGradle.js';
@@ -42,12 +41,8 @@ export async function buildAndroid(
4241
const outputFilePath = await findOutputFile(androidProject, tasks);
4342

4443
if (outputFilePath) {
45-
const loader = spinner();
46-
loader.start('');
47-
loader.stop(
48-
`Build available at: ${color.cyan(
49-
path.relative(process.cwd(), outputFilePath)
50-
)}`
44+
logger.log(
45+
`Build available at: ${colorLink(relativeToCwd(outputFilePath))}`
5146
);
5247
}
5348
outro('Success 🎉.');

packages/platform-android/src/lib/commands/generateKeystore.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { PluginApi } from '@rnef/config';
44
import type { SubprocessError } from '@rnef/tools';
55
import {
66
color,
7+
colorLink,
78
intro,
89
logger,
910
outro,
@@ -78,9 +79,9 @@ async function runKeytool(androidProject: AndroidProjectConfig, args: Flags) {
7879
{ stdio: 'inherit' }
7980
);
8081

81-
logger.success(`Keystore generated at: ${color.cyan(keystoreOutputPath)}`);
82+
logger.success(`Keystore generated at: ${colorLink(keystoreOutputPath)}`);
8283
logger.warn(
83-
`Edit the ${color.bold('~/.gradle/gradle.properties')} or ${color.bold(
84+
`Edit the ${colorLink('~/.gradle/gradle.properties')} or ${colorLink(
8485
'./android/gradle.properties'
8586
)} file, and add the following (replace ***** with the correct keystore password):`
8687
);
@@ -94,7 +95,7 @@ async function runKeytool(androidProject: AndroidProjectConfig, args: Flags) {
9495
);
9596
} catch (error) {
9697
throw new RnefError(
97-
`Failed to generate keystore. Please try manually by following instructions at: ${color.cyan(
98+
`Failed to generate keystore. Please try manually by following instructions at: ${colorLink(
9899
'https://reactnative.dev/docs/signed-apk-android'
99100
)}`,
100101
{ cause: (error as SubprocessError).stderr }

packages/platform-android/src/lib/commands/runAndroid/listAndroidUsers.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { promptSelect, spawn, spinner } from '@rnef/tools';
1+
import { logger, promptSelect, spawn } from '@rnef/tools';
22
import { getAdbPath } from './adb.js';
33

44
type User = {
@@ -13,14 +13,12 @@ const regex = new RegExp(
1313
export async function checkUsers(device: string): Promise<User[]> {
1414
const adbPath = getAdbPath();
1515
const adbArgs = ['-s', device, 'shell', 'pm', 'list', 'users'];
16-
const loader = spinner();
17-
loader.start(`Checking users on "${device}"`);
1816

1917
try {
2018
const { stdout, stderr } = await spawn(adbPath, adbArgs, { stdio: 'pipe' });
2119

2220
if (stderr) {
23-
loader.stop(`Failed to check users of "${device}". ${stderr}`, 1);
21+
logger.debug(`Failed to check users on the device. ${stderr}`, 1);
2422
return [];
2523
}
2624

@@ -34,11 +32,10 @@ export async function checkUsers(device: string): Promise<User[]> {
3432
}
3533
}
3634

37-
loader.stop(`Found ${users.length} users on "${device}".`);
3835
return users;
3936
} catch (error) {
40-
loader.stop(
41-
`Unexpected error while checking users of "${device}". Continuing without user selection. Error details: ${
37+
logger.debug(
38+
`Unexpected error while checking users on the device. Continuing without user selection. Error details: ${
4239
(error as { message: string }).message
4340
}.`,
4441
1

packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
} from '@react-native-community/cli-types';
77
import type { FingerprintSources, RemoteBuildCache } from '@rnef/tools';
88
import {
9+
color,
910
fetchCachedBuild,
1011
formatArtifactName,
1112
getLocalBuildCacheBinaryPath,
@@ -15,6 +16,7 @@ import {
1516
outro,
1617
promptSelect,
1718
RnefError,
19+
spinner,
1820
} from '@rnef/tools';
1921
import type { BuildFlags } from '../buildAndroid/buildAndroid.js';
2022
import { options } from '../buildAndroid/buildAndroid.js';
@@ -105,14 +107,7 @@ export async function runAndroid(
105107
if (!binaryPath) {
106108
await runGradle({ tasks, androidProject, args, artifactName });
107109
}
108-
await tryInstallAppOnDevice(
109-
device,
110-
androidProject,
111-
args,
112-
tasks,
113-
binaryPath
114-
);
115-
await tryLaunchAppOnDevice(device, androidProject, args);
110+
await runOnDevice({ device, androidProject, args, tasks, binaryPath });
116111
}
117112
} else {
118113
if ((await getDevices()).length === 0) {
@@ -131,14 +126,7 @@ export async function runAndroid(
131126
}
132127

133128
for (const device of await listAndroidDevices()) {
134-
await tryInstallAppOnDevice(
135-
device,
136-
androidProject,
137-
args,
138-
tasks,
139-
binaryPath
140-
);
141-
await tryLaunchAppOnDevice(device, androidProject, args);
129+
await runOnDevice({ device, androidProject, args, tasks, binaryPath });
142130
}
143131
}
144132

@@ -239,6 +227,41 @@ async function promptForDeviceSelection(
239227
return selected;
240228
}
241229

230+
async function runOnDevice({
231+
device,
232+
androidProject,
233+
args,
234+
tasks,
235+
binaryPath,
236+
}: {
237+
device: DeviceData;
238+
androidProject: AndroidProject;
239+
args: Flags;
240+
tasks: string[];
241+
binaryPath: string | undefined;
242+
}) {
243+
const loader = spinner();
244+
loader.start('Installing the app');
245+
await tryInstallAppOnDevice(device, androidProject, args, tasks, binaryPath);
246+
loader.message('Launching the app');
247+
const { applicationIdWithSuffix } = await tryLaunchAppOnDevice(
248+
device,
249+
androidProject,
250+
args
251+
);
252+
if (applicationIdWithSuffix) {
253+
loader.stop(
254+
`Installed and launched the app on ${color.bold(device.readableName)}`
255+
);
256+
} else {
257+
loader.stop(
258+
`Failed: installing and launching the app on ${color.bold(
259+
device.readableName
260+
)}`
261+
);
262+
}
263+
}
264+
242265
export const runOptions = [
243266
...options,
244267
{

packages/platform-android/src/lib/commands/runAndroid/tryInstallAppOnDevice.ts

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {
2+
color,
23
logger,
34
RnefError,
45
spawn,
5-
spinner,
66
type SubprocessError,
77
} from '@rnef/tools';
88
import { getAdbPath } from './adb.js';
@@ -18,7 +18,7 @@ export async function tryInstallAppOnDevice(
1818
tasks: string[],
1919
binaryPath: string | undefined
2020
) {
21-
let deviceId;
21+
let deviceId: string;
2222
if (!device.deviceId) {
2323
logger.debug(
2424
`No device with id "${device.deviceId}", skipping launching the app.`
@@ -27,6 +27,7 @@ export async function tryInstallAppOnDevice(
2727
} else {
2828
deviceId = device.deviceId;
2929
}
30+
logger.debug(`Connected to device ${color.bold(device.readableName)}`);
3031
let pathToApk: string;
3132
if (!binaryPath) {
3233
const outputFilePath = await findOutputFile(
@@ -55,45 +56,37 @@ export async function tryInstallAppOnDevice(
5556
adbArgs.push(pathToApk);
5657

5758
const adbPath = getAdbPath();
58-
const loader = spinner();
59-
loader.start(
60-
`Installing the app on ${device.readableName} (id: ${deviceId})`
61-
);
6259
try {
63-
await spawn(adbPath, adbArgs);
64-
loader.stop(
65-
`Installed the app on ${device.readableName} (id: ${deviceId}).`
66-
);
60+
await spawn(adbPath, adbArgs, { stdio: 'pipe' });
6761
} catch (error) {
62+
logger.debug(`Failed: Installing the app`, error);
6863
const errorMessage =
6964
(error as SubprocessError).stderr || (error as SubprocessError).stdout;
70-
if (errorMessage.includes('INSTALL_FAILED_INSUFFICIENT_STORAGE')) {
65+
const isInsufficientStorage = errorMessage.includes(
66+
'INSTALL_FAILED_INSUFFICIENT_STORAGE'
67+
);
68+
const isUpdateIncompatible = errorMessage.includes(
69+
'INSTALL_FAILED_UPDATE_INCOMPATIBLE'
70+
);
71+
if (isInsufficientStorage || isUpdateIncompatible) {
7172
try {
72-
loader.message('Trying to install again due to insufficient storage');
73-
const appId = args.appId ?? androidProject.applicationId;
73+
const message = isInsufficientStorage
74+
? 'Recovery: Trying to re-install the app due to insufficient storage'
75+
: 'Recovery: Trying to re-install the app due to binary incompatibility';
76+
logger.debug(message);
77+
const appId = args.appId || androidProject.applicationId;
7478
await spawn(adbPath, ['-s', deviceId, 'uninstall', appId]);
7579
await spawn(adbPath, adbArgs);
76-
loader.stop(
77-
`Installed the app on ${device.readableName} (id: ${deviceId}).`
78-
);
80+
logger.debug(`Recovery: Re-installed the app`);
7981
return;
8082
} catch (error) {
81-
loader.stop(
82-
`Failed: Uninstalling and installing the app on ${device.readableName} (id: ${deviceId})`,
83-
1
84-
);
8583
const errorMessage =
8684
(error as SubprocessError).stderr ||
8785
(error as SubprocessError).stdout;
88-
throw new RnefError(
89-
`The "adb" command failed with: ${errorMessage}. \nPlease uninstall the app manually and try again.`
90-
);
86+
throw new RnefError(`The "adb" command failed with: ${errorMessage}.`);
9187
}
9288
}
93-
loader.stop(
94-
`Failed: Installing the app on ${device.readableName} (id: ${deviceId})`,
95-
1
96-
);
89+
9790
throw new RnefError(`The "adb" command failed with: ${errorMessage}.`);
9891
}
9992
}

0 commit comments

Comments
 (0)