From 7ec2f904feb9b85e17b1500d848c9a84a8c8bd95 Mon Sep 17 00:00:00 2001 From: Stian Jensen Date: Thu, 4 Jun 2026 22:29:34 +0200 Subject: [PATCH] chore: migrate from `ora` to `nanospinner` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap out ora for nanospinner (^1.2.2) as the terminal spinner library, using nanospinner's API directly without a compatibility wrapper. ora has 16 transitive deps (one of which being chalk, which was removed as a direct dep of the cli in a previous release). nanospinner is far more modern and lean, with only one transitive dependency. API changes across call sites: - .succeed() → .success() - .fail() → .error() - loader.text = '...' → loader.update('...') Add a Jest auto-mock for nanospinner since Jest 26 cannot resolve node: protocol imports that nanospinner uses internally. --- __mocks__/nanospinner.js | 21 +++++++ docs/healthChecks.md | 10 ++-- docs/init.md | 12 ++-- packages/cli-clean/src/clean.ts | 4 +- packages/cli-config-apple/package.json | 3 +- .../cli-config-apple/src/tools/installPods.ts | 18 +++--- packages/cli-config-apple/src/tools/pods.ts | 8 +-- .../src/tools/runBundleInstall.ts | 8 +-- packages/cli-doctor/package.json | 1 - packages/cli-doctor/src/tools/brewInstall.ts | 2 +- .../cli-doctor/src/tools/downloadAndUnzip.ts | 2 +- .../healthchecks/__tests__/androidSDK.test.ts | 8 +-- .../__tests__/androidStudio.test.ts | 8 +-- .../tools/healthchecks/__tests__/jdk.test.ts | 8 +-- .../healthchecks/__tests__/watchman.test.ts | 10 ++-- .../cli-doctor/src/tools/healthchecks/adb.ts | 4 +- .../healthchecks/androidHomeEnvVariable.ts | 4 +- .../src/tools/healthchecks/androidNDK.ts | 2 +- .../src/tools/healthchecks/androidSDK.ts | 17 +++--- .../src/tools/healthchecks/androidStudio.ts | 4 +- .../src/tools/healthchecks/cocoaPods.ts | 4 +- .../src/tools/healthchecks/common.ts | 2 +- .../cli-doctor/src/tools/healthchecks/jdk.ts | 8 +-- .../src/tools/healthchecks/nodeJS.ts | 2 +- .../src/tools/healthchecks/packager.ts | 4 +- .../cli-doctor/src/tools/healthchecks/ruby.ts | 2 +- .../src/tools/healthchecks/xcode.ts | 2 +- .../src/tools/healthchecks/xcodeEnv.ts | 4 +- packages/cli-doctor/src/tools/install.ts | 2 +- .../cli-doctor/src/tools/runAutomaticFix.ts | 5 +- packages/cli-doctor/src/types.ts | 4 +- .../commands/runAndroid/listAndroidTasks.ts | 4 +- packages/cli-tools/package.json | 2 +- packages/cli-tools/src/loader.ts | 55 ++++++++++--------- packages/cli/src/commands/init/git.ts | 2 +- packages/cli/src/commands/init/init.ts | 14 ++--- yarn.lock | 7 +++ 37 files changed, 153 insertions(+), 124 deletions(-) create mode 100644 __mocks__/nanospinner.js diff --git a/__mocks__/nanospinner.js b/__mocks__/nanospinner.js new file mode 100644 index 000000000..e40d71a11 --- /dev/null +++ b/__mocks__/nanospinner.js @@ -0,0 +1,21 @@ +function createSpinner() { + const spinner = { + start: () => spinner, + stop: () => spinner, + success: () => spinner, + error: () => spinner, + warn: () => spinner, + info: () => spinner, + clear: () => spinner, + render: () => spinner, + update: () => spinner, + reset: () => spinner, + spin: () => spinner, + write: () => spinner, + loop: () => spinner, + isSpinning: () => false, + }; + return spinner; +} + +module.exports = {createSpinner}; diff --git a/docs/healthChecks.md b/docs/healthChecks.md index d47508793..3f806dab8 100644 --- a/docs/healthChecks.md +++ b/docs/healthChecks.md @@ -21,7 +21,7 @@ module.exports = { }), runAutomaticFix: async ({loader}) => { await installBar(); - loader.succeed(); + loader.success(); }, }, ], @@ -116,7 +116,7 @@ This function will be used to try to fix the issue when `react-native doctor` is ```ts type RunAutomaticFix = (args: { - loader: Ora; + loader: Loader; logManualInstallation: ({ healthcheck, url, @@ -134,7 +134,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` @@ -150,7 +150,7 @@ A health check that requires the user to manually go download/install something. ```ts async function needToInstallFoo({loader, logManualInstallation}) { - loader.fail(); + loader.error(); return logManualInstallation({ healthcheck: 'Foo', @@ -167,5 +167,5 @@ async function fixFoo({loader}) { await exec(`foo --install`); await exec(`foo --fix`); - loader.succeed(); + loader.success(); } diff --git a/docs/init.md b/docs/init.md index 138f249d8..74e304459 100644 --- a/docs/init.md +++ b/docs/init.md @@ -78,23 +78,23 @@ 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(); + spinner.success(); }).catch(() => { - spinner.fail(); + spinner.error(); throw new Error('Something went wrong during the post init script execution'); }); ``` diff --git a/packages/cli-clean/src/clean.ts b/packages/cli-clean/src/clean.ts index 943a39525..46c0c96a1 100644 --- a/packages/cli-clean/src/clean.ts +++ b/packages/cli-clean/src/clean.ts @@ -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}`); }); } } diff --git a/packages/cli-config-apple/package.json b/packages/cli-config-apple/package.json index c5949648a..44168ea6f 100644 --- a/packages/cli-config-apple/package.json +++ b/packages/cli-config-apple/package.json @@ -13,8 +13,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { - "@react-native-community/cli-types": "20.1.3", - "ora": "^5.4.1" + "@react-native-community/cli-types": "20.1.3" }, "files": [ "build", diff --git a/packages/cli-config-apple/src/tools/installPods.ts b/packages/cli-config-apple/src/tools/installPods.ts index ec37000e8..66c63eb5a 100644 --- a/packages/cli-config-apple/src/tools/installPods.ts +++ b/packages/cli-config-apple/src/tools/installPods.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import execa from 'execa'; -import type {Ora} from 'ora'; +import type {Loader} from '@react-native-community/cli-tools'; import pico from 'picocolors'; import { logger, @@ -23,7 +23,7 @@ interface RunPodInstallOptions { newArchEnabled?: boolean; } -async function runPodInstall(loader: Ora, options: RunPodInstallOptions) { +async function runPodInstall(loader: Loader, options: RunPodInstallOptions) { const shouldHandleRepoUpdate = options?.shouldHandleRepoUpdate || true; try { loader.start( @@ -66,7 +66,7 @@ async function runPodInstall(loader: Ora, options: RunPodInstallOptions) { newArchEnabled: options?.newArchEnabled, }); } else { - loader.fail(); + loader.error(); logger.error(stderr); throw new CLIError( @@ -80,7 +80,7 @@ async function runPodInstall(loader: Ora, options: RunPodInstallOptions) { } } -async function runPodUpdate(loader: Ora) { +async function runPodUpdate(loader: Loader) { try { loader.start( `Updating CocoaPods repositories ${pico.dim( @@ -91,7 +91,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: ${pico.dim( @@ -113,7 +113,7 @@ async function installCocoaPodsWithGem() { } } -async function installCocoaPods(loader: Ora) { +async function installCocoaPods(loader: Loader) { loader.stop(); loader.start('Installing CocoaPods'); @@ -121,9 +121,9 @@ async function installCocoaPods(loader: Ora) { try { await installCocoaPodsWithGem(); - return loader.succeed(); + return loader.success(); } catch (error) { - loader.fail(); + loader.error(); logger.error((error as any).stderr); throw new CLIError( @@ -134,7 +134,7 @@ async function installCocoaPods(loader: Ora) { } } -async function installPods(loader?: Ora, options?: PodInstallOptions) { +async function installPods(loader?: Loader, options?: PodInstallOptions) { loader = loader || new NoopLoader(); try { if (!options?.iosFolderPath && !fs.existsSync('ios')) { diff --git a/packages/cli-config-apple/src/tools/pods.ts b/packages/cli-config-apple/src/tools/pods.ts index 8be71a23b..46e62ac0d 100644 --- a/packages/cli-config-apple/src/tools/pods.ts +++ b/packages/cli-config-apple/src/tools/pods.ts @@ -108,9 +108,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 went wrong while installing CocoaPods. Please run ${pico.bold( 'pod install', @@ -204,9 +204,9 @@ export default async function resolvePods( 'podfileLock', currentPodfileLockChecksum ?? '', ); - loader.succeed(); + loader.success(); } catch (error) { - loader.fail(); + loader.error(); throw new CLIError( `Something went wrong while installing CocoaPods. Please run ${pico.bold( 'pod install', diff --git a/packages/cli-config-apple/src/tools/runBundleInstall.ts b/packages/cli-config-apple/src/tools/runBundleInstall.ts index 955f9c789..523290403 100644 --- a/packages/cli-config-apple/src/tools/runBundleInstall.ts +++ b/packages/cli-config-apple/src/tools/runBundleInstall.ts @@ -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 {Loader} from '@react-native-community/cli-tools'; -async function runBundleInstall(loader: Ora) { +async function runBundleInstall(loader: Loader) { 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; diff --git a/packages/cli-doctor/package.json b/packages/cli-doctor/package.json index 3bd437330..dbcf425de 100644 --- a/packages/cli-doctor/package.json +++ b/packages/cli-doctor/package.json @@ -18,7 +18,6 @@ "envinfo": "^7.13.0", "execa": "^5.0.0", "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", "picocolors": "^1.1.1", "semver": "^7.5.2", "wcwidth": "^1.0.1", diff --git a/packages/cli-doctor/src/tools/brewInstall.ts b/packages/cli-doctor/src/tools/brewInstall.ts index 3c08ebf51..463f6521c 100644 --- a/packages/cli-doctor/src/tools/brewInstall.ts +++ b/packages/cli-doctor/src/tools/brewInstall.ts @@ -26,7 +26,7 @@ async function brewInstall({ return onSuccess(); } - return loader.succeed(); + return loader.success(); } catch (error) { if (typeof onFail === 'function') { return onFail(); diff --git a/packages/cli-doctor/src/tools/downloadAndUnzip.ts b/packages/cli-doctor/src/tools/downloadAndUnzip.ts index bd4ae20f3..b187f26f6 100644 --- a/packages/cli-doctor/src/tools/downloadAndUnzip.ts +++ b/packages/cli-doctor/src/tools/downloadAndUnzip.ts @@ -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 { diff --git a/packages/cli-doctor/src/tools/healthchecks/__tests__/androidSDK.test.ts b/packages/cli-doctor/src/tools/healthchecks/__tests__/androidSDK.test.ts index 73f25748d..5d84898d2 100644 --- a/packages/cli-doctor/src/tools/healthchecks/__tests__/androidSDK.test.ts +++ b/packages/cli-doctor/src/tools/healthchecks/__tests__/androidSDK.test.ts @@ -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.', ); }); diff --git a/packages/cli-doctor/src/tools/healthchecks/__tests__/androidStudio.test.ts b/packages/cli-doctor/src/tools/healthchecks/__tests__/androidStudio.test.ts index 05d7334b6..4afabf6e3 100644 --- a/packages/cli-doctor/src/tools/healthchecks/__tests__/androidStudio.test.ts +++ b/packages/cli-doctor/src/tools/healthchecks/__tests__/androidStudio.test.ts @@ -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 || '' }".`, diff --git a/packages/cli-doctor/src/tools/healthchecks/__tests__/jdk.test.ts b/packages/cli-doctor/src/tools/healthchecks/__tests__/jdk.test.ts index ae399380d..0355483f8 100644 --- a/packages/cli-doctor/src/tools/healthchecks/__tests__/jdk.test.ts +++ b/packages/cli-doctor/src/tools/healthchecks/__tests__/jdk.test.ts @@ -73,8 +73,8 @@ describe('jdk', () => { it('downloads and unzips JDK on Windows when missing', 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()); @@ -85,10 +85,10 @@ describe('jdk', () => { environmentInfo, }); - expect(loaderFailSpy).toHaveBeenCalledTimes(0); + expect(loaderErrorSpy).toHaveBeenCalledTimes(0); expect(logSpy).toHaveBeenCalledTimes(0); expect(downloadAndUnzipSpy).toBeCalledTimes(1); - expect(loaderSucceedSpy).toBeCalledWith( + expect(loaderSuccessSpy).toBeCalledWith( 'JDK installed successfully. Please restart your shell to see the changes', ); }); diff --git a/packages/cli-doctor/src/tools/healthchecks/__tests__/watchman.test.ts b/packages/cli-doctor/src/tools/healthchecks/__tests__/watchman.test.ts index 82f146aa5..4045522ff 100644 --- a/packages/cli-doctor/src/tools/healthchecks/__tests__/watchman.test.ts +++ b/packages/cli-doctor/src/tools/healthchecks/__tests__/watchman.test.ts @@ -82,12 +82,12 @@ describe('watchman', () => { }); const loaderSpy = new NoopLoader(); - const loaderSucceedSpy = jest.spyOn(loaderSpy, 'succeed'); - const loaderFailSpy = jest.spyOn(loaderSpy, 'fail'); + const loaderSuccessSpy = jest.spyOn(loaderSpy, 'success'); + const loaderErrorSpy = jest.spyOn(loaderSpy, 'error'); const brewInstallSpy = jest .spyOn(brewInstall, 'brewInstall') .mockImplementation(({loader}) => { - loader.succeed(); + loader.success(); return Promise.resolve(); }); @@ -102,9 +102,9 @@ describe('watchman', () => { value: originalPlatform, }); - expect(loaderFailSpy).toHaveBeenCalledTimes(0); + expect(loaderErrorSpy).toHaveBeenCalledTimes(0); expect(logSpy).toHaveBeenCalledTimes(0); expect(brewInstallSpy).toBeCalledTimes(1); - expect(loaderSucceedSpy).toBeCalledTimes(1); + expect(loaderSuccessSpy).toBeCalledTimes(1); }); }); diff --git a/packages/cli-doctor/src/tools/healthchecks/adb.ts b/packages/cli-doctor/src/tools/healthchecks/adb.ts index ff38dbd1b..168211b81 100644 --- a/packages/cli-doctor/src/tools/healthchecks/adb.ts +++ b/packages/cli-doctor/src/tools/healthchecks/adb.ts @@ -38,7 +38,7 @@ export default { } }, runAutomaticFix: async ({loader, logManualInstallation}) => { - loader.fail(); + loader.error(); let hash: string; switch (link.getOS()) { case 'macos': @@ -59,7 +59,7 @@ export default { if (device && device.connected) { tryRunAdbReverse(process.env.RCT_METRO_PORT || 8081, device.deviceId); } - return loader.succeed(); + return loader.success(); } catch (e) { return logManualInstallation({ healthcheck: 'Adb', diff --git a/packages/cli-doctor/src/tools/healthchecks/androidHomeEnvVariable.ts b/packages/cli-doctor/src/tools/healthchecks/androidHomeEnvVariable.ts index 5ca3b00b1..32f8cfccf 100644 --- a/packages/cli-doctor/src/tools/healthchecks/androidHomeEnvVariable.ts +++ b/packages/cli-doctor/src/tools/healthchecks/androidHomeEnvVariable.ts @@ -29,12 +29,12 @@ export default { runAutomaticFix: async ({loader, logManualInstallation}) => { // Variable could have been added if installing Android Studio so double checking if (process.env.ANDROID_HOME) { - loader.succeed(); + loader.success(); return; } - loader.fail(); + loader.error(); logManualInstallation({ message, diff --git a/packages/cli-doctor/src/tools/healthchecks/androidNDK.ts b/packages/cli-doctor/src/tools/healthchecks/androidNDK.ts index 993011b71..7ea897a68 100644 --- a/packages/cli-doctor/src/tools/healthchecks/androidNDK.ts +++ b/packages/cli-doctor/src/tools/healthchecks/androidNDK.ts @@ -25,7 +25,7 @@ export default { const isNDKInstalled = androidSdk !== 'Not Found' && androidSdk['Android NDK'] !== 'Not Found'; - loader.fail(); + loader.error(); if (isNDKInstalled) { return logManualInstallation({ diff --git a/packages/cli-doctor/src/tools/healthchecks/androidSDK.ts b/packages/cli-doctor/src/tools/healthchecks/androidSDK.ts index ab6eefeb8..ce32005e6 100644 --- a/packages/cli-doctor/src/tools/healthchecks/androidSDK.ts +++ b/packages/cli-doctor/src/tools/healthchecks/androidSDK.ts @@ -109,7 +109,7 @@ export default { const androidSDKRoot = getAndroidSdkRootInstallation(); if (androidSDKRoot === '') { - loader.fail('There was an error finding the Android SDK root'); + loader.error('There was an error finding the Android SDK root'); return; } @@ -122,7 +122,7 @@ export default { }); for (const component of componentsToInstall) { - loader.text = `Installing "${component}" (this may take a few minutes)`; + loader.update(`Installing "${component}" (this may take a few minutes)`); try { await installComponent(component, androidSDKRoot); @@ -131,7 +131,7 @@ export default { } } - loader.text = 'Updating environment variables'; + loader.update('Updating environment variables'); // Required for the emulator to work from the CLI await setEnvironment('ANDROID_SDK_ROOT', androidSDKRoot); @@ -142,8 +142,9 @@ export default { path.join(androidSDKRoot, 'platform-tools'), ); - loader.text = - 'Configuring Hypervisor for faster emulation, this might prompt UAC'; + loader.update( + 'Configuring Hypervisor for faster emulation, this might prompt UAC', + ); const {hypervisor, installed} = await getBestHypervisor(androidSDKRoot); @@ -164,15 +165,15 @@ export default { } } - loader.text = 'Creating AVD'; + loader.update('Creating AVD'); await createAVD(androidSDKRoot, 'pixel_9.0', 'pixel', systemImage); - loader.succeed( + loader.success( 'Android SDK configured. You might need to restart your PC for all changes to take effect.', ); }, runAutomaticFix: async ({loader, logManualInstallation, environmentInfo}) => { - loader.fail(); + loader.error(); if (isSDKInstalled(environmentInfo)) { return logManualInstallation({ diff --git a/packages/cli-doctor/src/tools/healthchecks/androidStudio.ts b/packages/cli-doctor/src/tools/healthchecks/androidStudio.ts index 12cdb1ba5..ea1a2d6af 100644 --- a/packages/cli-doctor/src/tools/healthchecks/androidStudio.ts +++ b/packages/cli-doctor/src/tools/healthchecks/androidStudio.ts @@ -96,12 +96,12 @@ export default { ico: join(binFolder, 'studio.ico'), }); - loader.succeed( + loader.success( `Android Studio installed successfully in "${installPath}".`, ); }, runAutomaticFix: async ({loader, logManualInstallation}) => { - loader.fail(); + loader.error(); return logManualInstallation({ healthcheck: 'Android Studio', diff --git a/packages/cli-doctor/src/tools/healthchecks/cocoaPods.ts b/packages/cli-doctor/src/tools/healthchecks/cocoaPods.ts index 64443cd4b..7726d4010 100644 --- a/packages/cli-doctor/src/tools/healthchecks/cocoaPods.ts +++ b/packages/cli-doctor/src/tools/healthchecks/cocoaPods.ts @@ -33,13 +33,13 @@ export default { // First attempt to install `cocoapods` await execa('gem', options); - return loader.succeed(loaderSucceedMessage); + return loader.success(loaderSucceedMessage); } catch (_error) { // If that doesn't work then try with sudo try { await runSudo(`gem ${options.join(' ')}`); - return loader.succeed(loaderSucceedMessage); + return loader.success(loaderSucceedMessage); } catch (error) { logError({ healthcheck: label, diff --git a/packages/cli-doctor/src/tools/healthchecks/common.ts b/packages/cli-doctor/src/tools/healthchecks/common.ts index 1a352263a..a7a8b9bce 100644 --- a/packages/cli-doctor/src/tools/healthchecks/common.ts +++ b/packages/cli-doctor/src/tools/healthchecks/common.ts @@ -68,7 +68,7 @@ const logError = ({ command: string; }) => { if (loader) { - loader.fail(); + loader.error(); } addBlankLine(); diff --git a/packages/cli-doctor/src/tools/healthchecks/jdk.ts b/packages/cli-doctor/src/tools/healthchecks/jdk.ts index 6a27e5961..5b3771d2a 100644 --- a/packages/cli-doctor/src/tools/healthchecks/jdk.ts +++ b/packages/cli-doctor/src/tools/healthchecks/jdk.ts @@ -43,22 +43,22 @@ export default { installPath, }); - loader.text = 'Updating environment variables'; + loader.update('Updating environment variables'); const jdkPath = join(installPath, 'jdk-11.0.2'); await setEnvironment('JAVA_HOME', jdkPath); await updateEnvironment('PATH', join(jdkPath, 'bin')); - loader.succeed( + loader.success( 'JDK installed successfully. Please restart your shell to see the changes', ); } catch (e) { - loader.fail(e as any); + loader.error(e as any); } }, runAutomaticFix: async ({logManualInstallation, loader}) => { - loader.fail(); + loader.error(); logManualInstallation({ healthcheck: 'JDK', url: link.docs('set-up-your-environment', 'android', { diff --git a/packages/cli-doctor/src/tools/healthchecks/nodeJS.ts b/packages/cli-doctor/src/tools/healthchecks/nodeJS.ts index ada2de19c..42a10ec55 100644 --- a/packages/cli-doctor/src/tools/healthchecks/nodeJS.ts +++ b/packages/cli-doctor/src/tools/healthchecks/nodeJS.ts @@ -14,7 +14,7 @@ export default { versionRange: versionRanges.NODE_JS, }), runAutomaticFix: async ({loader, logManualInstallation}) => { - loader.fail(); + loader.error(); logManualInstallation({ healthcheck: 'Node.js', diff --git a/packages/cli-doctor/src/tools/healthchecks/packager.ts b/packages/cli-doctor/src/tools/healthchecks/packager.ts index fa1239adb..5701b3dd3 100644 --- a/packages/cli-doctor/src/tools/healthchecks/packager.ts +++ b/packages/cli-doctor/src/tools/healthchecks/packager.ts @@ -25,7 +25,7 @@ export default { }; }, runAutomaticFix: async ({loader, config}) => { - loader.fail(); + loader.error(); try { const terminal = getDefaultUserTerminal(); const port = Number(process.env.RCT_METRO_PORT) || 8081; @@ -38,7 +38,7 @@ export default { '--terminal', terminal, ]); - return loader.succeed(); + return loader.success(); } return logManualInstallation({ message: diff --git a/packages/cli-doctor/src/tools/healthchecks/ruby.ts b/packages/cli-doctor/src/tools/healthchecks/ruby.ts index ea9162b98..aa90f27a8 100644 --- a/packages/cli-doctor/src/tools/healthchecks/ruby.ts +++ b/packages/cli-doctor/src/tools/healthchecks/ruby.ts @@ -170,7 +170,7 @@ export default { return fallbackResult; }, runAutomaticFix: async ({loader, logManualInstallation}) => { - loader.fail(); + loader.error(); logManualInstallation({ healthcheck: 'Ruby', diff --git a/packages/cli-doctor/src/tools/healthchecks/xcode.ts b/packages/cli-doctor/src/tools/healthchecks/xcode.ts index 1fb9ac014..82c0dbfdd 100644 --- a/packages/cli-doctor/src/tools/healthchecks/xcode.ts +++ b/packages/cli-doctor/src/tools/healthchecks/xcode.ts @@ -18,7 +18,7 @@ export default { }; }, runAutomaticFix: async ({loader, logManualInstallation}) => { - loader.fail(); + loader.error(); logManualInstallation({ healthcheck: 'Xcode', diff --git a/packages/cli-doctor/src/tools/healthchecks/xcodeEnv.ts b/packages/cli-doctor/src/tools/healthchecks/xcodeEnv.ts index 2ac85fbca..7b9de18bf 100644 --- a/packages/cli-doctor/src/tools/healthchecks/xcodeEnv.ts +++ b/packages/cli-doctor/src/tools/healthchecks/xcodeEnv.ts @@ -71,9 +71,9 @@ export default { const destFilePath = path.join(pathString, xcodeEnvFile); await copyFileAsync(src, destFilePath); }); - loader.succeed('.xcode.env file have been created!'); + loader.success('.xcode.env file have been created!'); } catch (e) { - loader.fail(e as any); + loader.error(e as any); } }, } as HealthCheckInterface; diff --git a/packages/cli-doctor/src/tools/install.ts b/packages/cli-doctor/src/tools/install.ts index 4845a827a..569a671cc 100644 --- a/packages/cli-doctor/src/tools/install.ts +++ b/packages/cli-doctor/src/tools/install.ts @@ -19,7 +19,7 @@ async function install({pkg, label, url, loader}: InstallArgs) { throw new Error('Not implemented yet'); } } catch (_error) { - loader.fail(); + loader.error(); logManualInstallation({ healthcheck: label, diff --git a/packages/cli-doctor/src/tools/runAutomaticFix.ts b/packages/cli-doctor/src/tools/runAutomaticFix.ts index 71fbd2463..6ddd57f5c 100644 --- a/packages/cli-doctor/src/tools/runAutomaticFix.ts +++ b/packages/cli-doctor/src/tools/runAutomaticFix.ts @@ -79,10 +79,7 @@ export default async function ({ logger.log(`\n${pico.dim(category.label)}`); for (const healthcheckToRun of healthchecksToRun) { - const spinner = getLoader({ - prefixText: '', - text: healthcheckToRun.label, - }).start(); + const spinner = getLoader(healthcheckToRun.label).start(); try { await healthcheckToRun.runAutomaticFix({ diff --git a/packages/cli-doctor/src/types.ts b/packages/cli-doctor/src/types.ts index 63624817a..52daf4b5e 100644 --- a/packages/cli-doctor/src/types.ts +++ b/packages/cli-doctor/src/types.ts @@ -1,7 +1,7 @@ import type {Config} from '@react-native-community/cli-types'; -import type {Ora} from 'ora'; +import type {Loader} from '@react-native-community/cli-tools'; -export type Loader = Ora; +export type {Loader}; export type NotFound = 'Not Found'; diff --git a/packages/cli-platform-android/src/commands/runAndroid/listAndroidTasks.ts b/packages/cli-platform-android/src/commands/runAndroid/listAndroidTasks.ts index 806056a41..933386052 100644 --- a/packages/cli-platform-android/src/commands/runAndroid/listAndroidTasks.ts +++ b/packages/cli-platform-android/src/commands/runAndroid/listAndroidTasks.ts @@ -38,10 +38,10 @@ export const getGradleTasks = ( const out = execa.sync(cmd, ['tasks', '--group', taskType], { cwd: sourceDir, }).stdout; - loader.succeed(); + loader.success(); return parseTasksFromGradleFile(taskType, out); } catch { - loader.fail(); + loader.error(); return []; } }; diff --git a/packages/cli-tools/package.json b/packages/cli-tools/package.json index cd15858ea..620a9ed9e 100644 --- a/packages/cli-tools/package.json +++ b/packages/cli-tools/package.json @@ -13,7 +13,7 @@ "find-up": "^5.0.0", "launch-editor": "^2.9.1", "mime": "^2.4.1", - "ora": "^5.4.1", + "nanospinner": "^1.2.2", "picocolors": "^1.1.1", "prompts": "^2.4.2", "semver": "^7.5.2" diff --git a/packages/cli-tools/src/loader.ts b/packages/cli-tools/src/loader.ts index f4ef0524d..133e30543 100644 --- a/packages/cli-tools/src/loader.ts +++ b/packages/cli-tools/src/loader.ts @@ -1,36 +1,25 @@ -import ora from 'ora'; -import type {Ora, Options, Spinner, Color} from 'ora'; +import {createSpinner, Spinner} from 'nanospinner'; import logger from './logger'; -export type Loader = Ora; +export type Loader = Spinner; -class OraNoop implements Loader { - spinner: Spinner = {interval: 1, frames: []}; - indent: number = 0; - isSpinning: boolean = false; - text: string = ''; - prefixText: string = ''; - color: Color = 'blue'; - - succeed(_text?: string | undefined) { - return this; - } - fail(_text?: string) { +class NoopSpinner implements Loader { + start() { return this; } - start(_text?: string) { + stop() { return this; } - stop() { + success() { return this; } - warn(_text?: string) { + error() { return this; } - info(_text?: string) { + warn() { return this; } - stopAndPersist() { + info() { return this; } clear() { @@ -39,13 +28,29 @@ class OraNoop implements Loader { render() { return this; } - frame() { - return this.text; + update() { + return this; + } + reset() { + return this; + } + spin() { + return this; + } + write() { + return this; + } + loop() { + return this; + } + isSpinning() { + return false; } } -export function getLoader(options?: string | Options | undefined): Loader { - return logger.isVerbose() ? new OraNoop() : ora(options); +export function getLoader(options?: string | {text?: string}): Loader { + const text = typeof options === 'string' ? options : options?.text; + return logger.isVerbose() ? new NoopSpinner() : createSpinner(text); } -export const NoopLoader = OraNoop; +export const NoopLoader = NoopSpinner; diff --git a/packages/cli/src/commands/init/git.ts b/packages/cli/src/commands/init/git.ts index 10eee2411..40fc169ee 100644 --- a/packages/cli/src/commands/init/git.ts +++ b/packages/cli/src/commands/init/git.ts @@ -59,7 +59,7 @@ export const createGitRepository = async (folder: string) => { cwd: folder, }, ); - loader.succeed(); + loader.success(); } catch (e) { loader.stop(); logger.debug( diff --git a/packages/cli/src/commands/init/init.ts b/packages/cli/src/commands/init/init.ts index 20be2d731..5c7e39599 100644 --- a/packages/cli/src/commands/init/init.ts +++ b/packages/cli/src/commands/init/init.ts @@ -235,7 +235,7 @@ async function createFromTemplate({ yarnConfigOptions, ); - loader.succeed(); + loader.success(); loader.start('Copying template'); const templateName = getTemplateName(templateSourceDir); @@ -246,7 +246,7 @@ async function createFromTemplate({ templateSourceDir, ); - loader.succeed(); + loader.success(); loader.start('Processing template'); await changePlaceholderInTemplate({ @@ -261,7 +261,7 @@ async function createFromTemplate({ await bumpYarnVersion(projectDirectory); } - loader.succeed(); + loader.success(); const {postInitScript} = templateConfig; if (postInitScript) { loader.info('Executing post init script '); @@ -295,7 +295,7 @@ async function createFromTemplate({ iosFolderPath: path.join(projectDirectory, 'ios'), }); await installPods(loader, {}); - loader.succeed(); + loader.success(); setEmptyHashForCachedDependencies(projectName); } else if (installPodsValue === 'undefined') { const {installCocoapods} = await prompt({ @@ -318,7 +318,7 @@ async function createFromTemplate({ iosFolderPath: path.join(projectDirectory, 'ios'), }); await installPods(loader, {}); - loader.succeed(); + loader.success(); setEmptyHashForCachedDependencies(projectName); } } @@ -331,7 +331,7 @@ async function createFromTemplate({ } } else { didInstallPods = false; - loader.succeed('Dependencies installation skipped'); + loader.success('Dependencies installation skipped'); } fs.removeSync(templateSourceDir); @@ -368,7 +368,7 @@ async function installDependencies({ root, }); - loader.succeed(); + loader.success(); } function checkPackageManagerAvailability( diff --git a/yarn.lock b/yarn.lock index 46c005ad3..9fe1ad8d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7919,6 +7919,13 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +nanospinner@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/nanospinner/-/nanospinner-1.2.2.tgz#5a38f4410b5bf7a41585964bee74d32eab3e040b" + integrity sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA== + dependencies: + picocolors "^1.1.1" + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"