Skip to content

chore: migrate from ora to nanospinner #2640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions docs/healthChecks.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Health Check Plugins

Plugins can be used to extend the health checks that `npx react-native doctor` runs. This can be used to add additional checks for out of tree platforms, or other checks that are specific to a community module.
Plugins can be used to extend the health checks that `npx react-native doctor` runs. This can be used to add additional checks for out of tree platforms, or other checks that are specific to a community module.

See [`Plugins`](./plugins.md) for information about how plugins work.
See [`Plugins`](./plugins.md) for information about how plugins work.

## How does it work?

@@ -21,7 +21,7 @@ module.exports = {
}),
runAutomaticFix: async ({loader}) => {
await installBar();
loader.succeed();
loader.success();
},
},
],
@@ -61,9 +61,7 @@ type HealthCheckInterface = {
visible?: boolean | void;
isRequired?: boolean;
description: string;
getDiagnostics: (
environmentInfo: EnvironmentInfo,
) => Promise<{
getDiagnostics: (environmentInfo: EnvironmentInfo) => Promise<{
version?: string;
versions?: [string];
versionRange?: string;
@@ -94,7 +92,7 @@ Longer description of this health check

##### `getDiagnostics`

Functions which performs the actual check. Simple checks can just return `needsToBeFixed`. Checks which are looking at versions of an installed component (such as the version of node), can also return `version`, `versions` and `versionRange` to provide better information to be displayed in `react-native doctor` when running the check
Functions which performs the actual check. Simple checks can just return `needsToBeFixed`. Checks which are looking at versions of an installed component (such as the version of node), can also return `version`, `versions` and `versionRange` to provide better information to be displayed in `react-native doctor` when running the check

##### `win32AutomaticFix`

@@ -116,7 +114,7 @@ This function will be used to try to fix the issue when `react-native doctor` is

```ts
type RunAutomaticFix = (args: {
loader: Ora;
loader: Spinner;
logManualInstallation: ({
healthcheck,
url,
@@ -134,7 +132,7 @@ type RunAutomaticFix = (args: {

##### `loader`

A reference to a [`ora`](https://www.npmjs.com/package/ora) instance which should be used to report success / failure, and progress of the fix. The fix function should always call either `loader.succeed()` or `loader.fail()` before returning.
A reference to a [`nanospinner`](https://www.npmjs.com/package/nanospinner) instance which should be used to report success / failure, and progress of the fix. The fix function should always call either `loader.success()` or `loader.error()` before returning.

##### `logManualInstallation`

@@ -146,26 +144,27 @@ Provides information about the current system

### Examples of RunAutomaticFix implementations

A health check that requires the user to manually go download/install something. This check will immediately display a message to notify the user how to fix the issue.
A health check that requires the user to manually go download/install something. This check will immediately display a message to notify the user how to fix the issue.

```ts
async function needToInstallFoo({loader, logManualInstallation}) {
loader.fail();
loader.error();

return logManualInstallation({
healthcheck: 'Foo',
url: 'https:/foo.com/download',
});
return logManualInstallation({
healthcheck: 'Foo',
url: 'https:/foo.com/download',
});
}
```

A health check that runs some commands locally which may fix the issue. This check will display a spinner while the exec commands are running. Then once the commands are complete, the spinner will change to a checkmark.
A health check that runs some commands locally which may fix the issue. This check will display a spinner while the exec commands are running. Then once the commands are complete, the spinner will change to a checkmark.

```ts
import { exec } from 'promisify-child-process';
import {exec} from 'promisify-child-process';
async function fixFoo({loader}) {
await exec(`foo --install`);
await exec(`foo --fix`);

loader.succeed();
loader.success();
}
```
24 changes: 14 additions & 10 deletions docs/init.md
Original file line number Diff line number Diff line change
@@ -78,25 +78,29 @@ module.exports = {

## Post init script loading

The responsibility of showing the user progress of the "Executing post init script" goes to the implementor. In the cli, the `ora` package is used to display progress.
For a simple usage in a custom template, `ora` can be used like this in a postInitScript :
The responsibility of showing the user progress of the "Executing post init script" goes to the implementor. In the cli, the `nanospinner` package is used to display progress.
For a simple usage in a custom template, `nanospinner` can be used like this in a postInitScript :

```javascript
#!/usr/bin/env node
const ora = require('ora');
const {createSpinner} = require('nanospinner');

const spinner = ora('Executing post init script ');
const spinner = createSpinner('Executing post init script ');

new Promise((resolve) => {
spinner.start();
// do something
resolve();
}).then(() => {
spinner.succeed();
}).catch(() => {
spinner.fail();
throw new Error('Something went wrong during the post init script execution');
});
})
.then(() => {
spinner.success();
})
.catch(() => {
spinner.error();
throw new Error(
'Something went wrong during the post init script execution',
);
});
```

You can find example custom template [here](https://github.com/Esemesek/react-native-new-template).
4 changes: 2 additions & 2 deletions packages/cli-clean/src/clean.ts
Original file line number Diff line number Diff line change
@@ -235,10 +235,10 @@ export async function clean(
spinner.start(label);
await action()
.then(() => {
spinner.succeed();
spinner.success();
})
.catch((e) => {
spinner.fail(`${label} » ${e}`);
spinner.error(`${label} » ${e}`);
});
}
}
4 changes: 2 additions & 2 deletions packages/cli-config-apple/package.json
Original file line number Diff line number Diff line change
@@ -13,8 +13,8 @@
"fast-glob": "^3.3.2"
},
"devDependencies": {
"@react-native-community/cli-types": "19.0.0",
"ora": "^5.4.1"
"@react-native-community/cli-types": "19.0.0-alpha.0",
"nanospinner": "^1.0.0"
},
"files": [
"build",
18 changes: 9 additions & 9 deletions packages/cli-config-apple/src/tools/installPods.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import execa from 'execa';
import type {Ora} from 'ora';
import type {Spinner} from 'nanospinner';
import chalk from 'chalk';
import {
logger,
@@ -22,7 +22,7 @@ interface RunPodInstallOptions {
newArchEnabled?: boolean;
}

async function runPodInstall(loader: Ora, options: RunPodInstallOptions) {
async function runPodInstall(loader: Spinner, options: RunPodInstallOptions) {
const shouldHandleRepoUpdate = options?.shouldHandleRepoUpdate || true;
try {
loader.start(
@@ -56,7 +56,7 @@ async function runPodInstall(loader: Ora, options: RunPodInstallOptions) {
newArchEnabled: options?.newArchEnabled,
});
} else {
loader.fail();
loader.error();
logger.error(stderr);

throw new CLIError(
@@ -70,7 +70,7 @@ async function runPodInstall(loader: Ora, options: RunPodInstallOptions) {
}
}

async function runPodUpdate(loader: Ora) {
async function runPodUpdate(loader: Spinner) {
try {
loader.start(
`Updating CocoaPods repositories ${chalk.dim(
@@ -81,7 +81,7 @@ async function runPodUpdate(loader: Ora) {
} catch (error) {
// "pod" command outputs errors to stdout (at least some of them)
logger.log((error as any).stderr || (error as any).stdout);
loader.fail();
loader.error();

throw new CLIError(
`Failed to update CocoaPods repositories for iOS project.\nPlease try again manually: "pod repo update".\nCocoaPods documentation: ${chalk.dim.underline(
@@ -103,17 +103,17 @@ async function installCocoaPodsWithGem() {
}
}

async function installCocoaPods(loader: Ora) {
async function installCocoaPods(loader: Spinner) {
loader.stop();

loader.start('Installing CocoaPods');

try {
await installCocoaPodsWithGem();

return loader.succeed();
return loader.success();
} catch (error) {
loader.fail();
loader.error();
logger.error((error as any).stderr);

throw new CLIError(
@@ -124,7 +124,7 @@ async function installCocoaPods(loader: Ora) {
}
}

async function installPods(loader?: Ora, options?: PodInstallOptions) {
async function installPods(loader?: Spinner, options?: PodInstallOptions) {
loader = loader || new NoopLoader();
try {
if (!options?.iosFolderPath && !fs.existsSync('ios')) {
12 changes: 6 additions & 6 deletions packages/cli-config-apple/src/tools/pods.ts
Original file line number Diff line number Diff line change
@@ -94,7 +94,7 @@ async function install(
root: string,
reactNativePath: string,
) {
const loader = getLoader('Installing CocoaPods...');
const loader = getLoader({text: 'Installing CocoaPods...'});
try {
await runCodegen({
root,
@@ -106,9 +106,9 @@ async function install(
iosFolderPath,
});
cacheManager.set(packageJson.name, 'dependencies', currentDependenciesHash);
loader.succeed();
loader.success();
} catch (error) {
loader.fail();
loader.error();
throw new CLIError(
`Something when wrong while installing CocoaPods. Please run ${chalk.bold(
'pod install',
@@ -182,7 +182,7 @@ export default async function resolvePods(
currentPodfileLockChecksum ?? '',
);
} else {
const loader = getLoader('Installing CocoaPods...');
const loader = getLoader({text: 'Installing CocoaPods...'});
try {
await installPods(loader, {
skipBundleInstall: !!cachedDependenciesHash,
@@ -202,9 +202,9 @@ export default async function resolvePods(
'podfileLock',
currentPodfileLockChecksum ?? '',
);
loader.succeed();
loader.success();
} catch (error) {
loader.fail();
loader.error();
throw new CLIError(
`Something when wrong while installing CocoaPods. Please run ${chalk.bold(
'pod install',
8 changes: 4 additions & 4 deletions packages/cli-config-apple/src/tools/runBundleInstall.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import execa from 'execa';
import {CLIError, logger, link} from '@react-native-community/cli-tools';
import type {Ora} from 'ora';
import type {Spinner} from 'nanospinner';

async function runBundleInstall(loader: Ora) {
async function runBundleInstall(loader: Spinner) {
try {
loader.start('Installing Ruby Gems');

await execa('bundle', ['install']);
} catch (error) {
loader.fail();
loader.error();
logger.error((error as any).stderr || (error as any).stdout);
throw new CLIError(
`Looks like your iOS environment is not properly set. Please go to ${link.docs(
@@ -19,7 +19,7 @@ async function runBundleInstall(loader: Ora) {
);
}

loader.succeed();
loader.success();
}

export default runBundleInstall;
2 changes: 1 addition & 1 deletion packages/cli-doctor/package.json
Original file line number Diff line number Diff line change
@@ -18,8 +18,8 @@
"deepmerge": "^4.3.0",
"envinfo": "^7.13.0",
"execa": "^5.0.0",
"nanospinner": "^1.0.0",
"node-stream-zip": "^1.9.1",
"ora": "^5.4.1",
"semver": "^7.5.2",
"wcwidth": "^1.0.1",
"yaml": "^2.2.1"
2 changes: 1 addition & 1 deletion packages/cli-doctor/src/tools/brewInstall.ts
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ async function brewInstall({
return onSuccess();
}

return loader.succeed();
return loader.success();
} catch (error) {
if (typeof onFail === 'function') {
return onFail();
2 changes: 1 addition & 1 deletion packages/cli-doctor/src/tools/downloadAndUnzip.ts
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ export const downloadAndUnzip = async ({

const installer = await fetchToTemp(downloadUrl);

loader.text = `Installing ${component} in "${installPath}"`;
loader.update(`Installing ${component} in "${installPath}"`);
try {
await unzip(installer, installPath);
} finally {
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@ describe('androidSDK', () => {
'android/build.gradle': `
buildscript {
ext {
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0'
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '34.0.0'
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
@@ -123,8 +123,8 @@ describe('androidSDK', () => {

it('installs the SDK if it is missing on Windows', async () => {
const loader = new tools.NoopLoader();
const loaderSucceedSpy = jest.spyOn(loader, 'succeed');
const loaderFailSpy = jest.spyOn(loader, 'fail');
const loaderSuccessSpy = jest.spyOn(loader, 'success');
const loaderErrorSpy = jest.spyOn(loader, 'error');
const downloadAndUnzipSpy = jest
.spyOn(downloadAndUnzip, 'downloadAndUnzip')
.mockImplementation(() => Promise.resolve());
@@ -178,10 +178,10 @@ describe('androidSDK', () => {
expect(requiredComponents.includes(call[0])).toBeTruthy();
}

expect(loaderFailSpy).toHaveBeenCalledTimes(0);
expect(loaderErrorSpy).toHaveBeenCalledTimes(0);
expect(logSpy).toHaveBeenCalledTimes(0);

expect(loaderSucceedSpy).toBeCalledWith(
expect(loaderSuccessSpy).toBeCalledWith(
'Android SDK configured. You might need to restart your PC for all changes to take effect.',
);
});
Original file line number Diff line number Diff line change
@@ -53,8 +53,8 @@ describe('androidStudio', () => {

it('downloads and unzips Android Studio on Windows when missing', async () => {
const loader = new NoopLoader();
const loaderSucceedSpy = jest.spyOn(loader, 'succeed');
const loaderFailSpy = jest.spyOn(loader, 'fail');
const loaderSuccessSpy = jest.spyOn(loader, 'success');
const loaderErrorSpy = jest.spyOn(loader, 'error');
const downloadAndUnzipSpy = jest
.spyOn(downloadAndUnzip, 'downloadAndUnzip')
.mockImplementation(() => Promise.resolve());
@@ -65,10 +65,10 @@ describe('androidStudio', () => {
environmentInfo,
});

expect(loaderFailSpy).toHaveBeenCalledTimes(0);
expect(loaderErrorSpy).toHaveBeenCalledTimes(0);
expect(logSpy).toHaveBeenCalledTimes(0);
expect(downloadAndUnzipSpy).toBeCalledTimes(1);
expect(loaderSucceedSpy).toBeCalledWith(
expect(loaderSuccessSpy).toBeCalledWith(
`Android Studio installed successfully in "${
downloadAndUnzipSpy.mock.calls[0][0].installPath || ''
}".`,
Loading

Unchanged files with check annotations Beta

} from '@react-native-community/cli-platform-apple';
export const dependencyConfig = getDependencyConfig({platformName: 'ios'});
export const projectConfig = getProjectConfig({platformName: 'ios'});

Check failure on line 7 in packages/cli-platform-ios/src/config/index.ts

GitHub Actions / Unit tests (20)

The inferred type of 'projectConfig' cannot be named without a reference to '@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-types'. This is likely not portable. A type annotation is necessary.

Check failure on line 7 in packages/cli-platform-ios/src/config/index.ts

GitHub Actions / Lint

The inferred type of 'projectConfig' cannot be named without a reference to '@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-types'. This is likely not portable. A type annotation is necessary.

Check failure on line 7 in packages/cli-platform-ios/src/config/index.ts

GitHub Actions / E2E Tests (20, ubuntu-latest)

The inferred type of 'projectConfig' cannot be named without a reference to '@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-types'. This is likely not portable. A type annotation is necessary.

Check failure on line 7 in packages/cli-platform-ios/src/config/index.ts

GitHub Actions / E2E Tests (18, ubuntu-latest)

The inferred type of 'projectConfig' cannot be named without a reference to '@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-types'. This is likely not portable. A type annotation is necessary.

Check failure on line 7 in packages/cli-platform-ios/src/config/index.ts

GitHub Actions / Unit tests (18)

The inferred type of 'projectConfig' cannot be named without a reference to '@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-types'. This is likely not portable. A type annotation is necessary.