From 628cb39f9dbaedd8e22e1fb0fcd5e8e68fb9a7df Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 3 Apr 2023 14:18:41 -0400 Subject: [PATCH 1/4] feat, support multi-spinners --- package.json | 1 + src/cli/loader/loader.ts | 96 +++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 5bfde9f18252..0716f00b6cf9 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "detective-amd": "3.0.1", "detective-stylus": "1.0.0", "doctrine": "2.1.0", + "dreidels": "0.6.1", "enhanced-resolve": "4.5.0", "execa": "2.1.0", "find-up": "5.0.0", diff --git a/src/cli/loader/loader.ts b/src/cli/loader/loader.ts index d4e4526855a3..57162f01d152 100644 --- a/src/cli/loader/loader.ts +++ b/src/cli/loader/loader.ts @@ -1,9 +1,17 @@ -import ora, { Ora, PersistOptions } from 'ora'; +import Spinnies from 'dreidels'; import { SPINNER_TYPE } from '../../constants'; +const DEFAULT_SPINNER = 'default'; + +/** + * previous loader was "ora" (which doesn't support multi spinners) + * some differences: + * 1) when starting a new spinner, it immediately shows a loader that can't be easily "inactive". + * 2) when calling "stop()", it doesn't only stop the loader, but also persist the previous text. + */ export class Loader { - private spinner: Ora | null; + private spinner: Spinnies | null; get isStarted() { return !!this.spinner; @@ -17,69 +25,77 @@ export class Loader { } off(): Loader { - this.stop(); + Object.keys(this.spinner?.spinners || {}).forEach((spinner) => { + this.spinner?.spinners[spinner].remove(); + }); this.spinner = null; return this; } - start(text?: string): Loader { - if (this.spinner) { - this.spinner.start(text); - } - return this; + /** + * @deprecated + * no need to start a spinner. just log/spin whenever needed + */ + start(text?: string, spinnerName = DEFAULT_SPINNER) { + this.spin(text || ' ', spinnerName); } - setText(text: string): Loader { - if (this.spinner) this.spinner.text = text; - return this; + private spin(text: string, spinnerName = DEFAULT_SPINNER) { + if (!this.spinner) return; + if (this.getSpinner(spinnerName)) this.getSpinner(spinnerName)?.text(text); + else this.spinner.add(spinnerName, { text }); } - setTextAndRestart(text: string): Loader { - if (this.spinner) { - this.spinner.stop(); - this.spinner.text = text; - this.spinner.start(); - } - return this; + setText(text: string, spinnerName = DEFAULT_SPINNER) { + this.spin(text, spinnerName); + } + + setTextAndRestart(text: string, spinnerName = DEFAULT_SPINNER) { + this.spin(text, spinnerName); } - get(): Ora | null { + get(): Spinnies | null { return this.spinner; } - stop(): Loader { - if (this.spinner) this.spinner.stop(); - return this; + /** + * avoid using `this.spinner?.spinners.get()` method. it throws when the spinner was removed. + * (which automatically happens when calling fail/stop/succeed/warn/info) + */ + getSpinner(spinnerName = DEFAULT_SPINNER): ReturnType | undefined { + return this.spinner?.spinners[spinnerName]; } - succeed(text?: string): Loader { - if (this.spinner) this.spinner.succeed(text); - return this; + /** + * avoid calling `.stop()`, it persists the last message. + */ + stop(spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.remove(); } - fail(text?: string): Loader { - if (this.spinner) this.spinner.fail(text); - return this; + succeed(text?: string, spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.succeed({ text }); } - warn(text?: string): Loader { - if (this.spinner) this.spinner.warn(text); - return this; + fail(text?: string, spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.fail({ text }); } - info(text?: string): Loader { - if (this.spinner) this.spinner.info(text); - return this; + warn(text?: string, spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.warn({ text }); } - stopAndPersist(options?: PersistOptions): Loader { - if (this.spinner) this.spinner.stopAndPersist(options); - return this; + info(text?: string, spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.info({ text }); + } + + stopAndPersist(text?: string, spinnerName = DEFAULT_SPINNER) { + this.getSpinner(spinnerName)?.static({ text }); } - private createNewSpinner(): Ora { - // for some reason the stream defaults to stderr. - return ora({ spinner: SPINNER_TYPE, text: '', stream: process.stdout, discardStdin: false, hideCursor: false }); + private createNewSpinner(): Spinnies { + const spinnies = new Spinnies({ spinner: SPINNER_TYPE }); + return spinnies; } } From 7a551c5d0b661ad59cc8394566a904bf589d604d Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 3 Apr 2023 16:03:59 -0400 Subject: [PATCH 2/4] fix types --- scopes/harmony/logger/logger.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scopes/harmony/logger/logger.ts b/scopes/harmony/logger/logger.ts index 5b08ad556929..50a9b25c40c1 100644 --- a/scopes/harmony/logger/logger.ts +++ b/scopes/harmony/logger/logger.ts @@ -62,7 +62,7 @@ export class Logger implements IBitLogger { // eslint-disable-next-line no-console console.log(message, ...meta); } else { - loader.stopAndPersist({ text: message }); + loader.stopAndPersist(message); } } @@ -72,7 +72,7 @@ export class Logger implements IBitLogger { // eslint-disable-next-line no-console console.warn(message, ...meta); } else { - loader.stopAndPersist({ text: message }); + loader.stopAndPersist(message); } } @@ -82,7 +82,7 @@ export class Logger implements IBitLogger { // eslint-disable-next-line no-console console.error(message, ...meta); } else { - loader.stopAndPersist({ text: message }); + loader.stopAndPersist(message); } } @@ -91,7 +91,7 @@ export class Logger implements IBitLogger { */ consoleTitle(message: string) { this.info(message); - loader.stopAndPersist({ text: chalk.bold(message) }); + loader.stopAndPersist(message); } /** * print to the screen with a green `✔` prefix. if message is empty, print the last logged message. From 96b4a1fda73e6361a9298951600de968d9e83181 Mon Sep 17 00:00:00 2001 From: David First Date: Mon, 3 Apr 2023 18:03:23 -0400 Subject: [PATCH 3/4] add the new API into logger aspect --- scopes/harmony/logger/logger.ts | 85 ++++++++++++++++++--------------- src/cli/loader/loader.ts | 18 +++++-- 2 files changed, 60 insertions(+), 43 deletions(-) diff --git a/scopes/harmony/logger/logger.ts b/scopes/harmony/logger/logger.ts index 50a9b25c40c1..dcd86946aa5a 100644 --- a/scopes/harmony/logger/logger.ts +++ b/scopes/harmony/logger/logger.ts @@ -1,4 +1,4 @@ -import loader from '@teambit/legacy/dist/cli/loader'; +import loader, { DEFAULT_SPINNER } from '@teambit/legacy/dist/cli/loader/loader'; import logger, { IBitLogger } from '@teambit/legacy/dist/logger/logger'; import chalk from 'chalk'; @@ -44,14 +44,14 @@ export class Logger implements IBitLogger { * single status-line on the bottom of the screen. * the text is replaced every time this method is called. */ - setStatusLine(text: string) { - loader.setTextAndRestart(text); + setStatusLine(text: string, spinner = DEFAULT_SPINNER) { + loader.setTextAndRestart(text, spinner); } /** * remove the text from the last line on the screen. */ - clearStatusLine() { - loader.stop(); + clearStatusLine(spinner = DEFAULT_SPINNER) { + loader.stop(spinner); } /** * print to the screen. if message is empty, print the last logged message. @@ -66,39 +66,36 @@ export class Logger implements IBitLogger { } } - consoleWarn(message?: string, ...meta: any[]) { - if (message) this.warn(message, ...meta); - if (!loader.isStarted && logger.shouldWriteToConsole) { - // eslint-disable-next-line no-console - console.warn(message, ...meta); - } else { - loader.stopAndPersist(message); - } - } - - consoleError(message?: string, ...meta: any[]) { - if (message) this.error(message, ...meta); - if (!loader.isStarted && logger.shouldWriteToConsole) { - // eslint-disable-next-line no-console - console.error(message, ...meta); - } else { - loader.stopAndPersist(message); - } - } - /** * print to the screen as a title, with bold text. */ - consoleTitle(message: string) { + consoleTitle(message: string, spinner = DEFAULT_SPINNER) { this.info(message); - loader.stopAndPersist(message); + loader.stopAndPersist(message, spinner); } /** * print to the screen with a green `✔` prefix. if message is empty, print the last logged message. */ - consoleSuccess(message?: string) { + consoleSuccess(message?: string, spinner = DEFAULT_SPINNER) { if (message) this.info(message); - loader.succeed(message); + loader.succeed(message, spinner); + } + /** + * print to the screen with a red `✖` prefix. if message is empty, print the last logged message. + */ + consoleFailure(message?: string, spinner = DEFAULT_SPINNER) { + if (message) this.error(message); + loader.fail(message, spinner); + } + /** + * print to the screen with a red `⚠` prefix. if message is empty, print the last logged message. + */ + consoleWarning(message?: string, spinner = DEFAULT_SPINNER) { + if (message) { + this.warn(message); + message = chalk.yellow(message); + } + loader.warn(message, spinner); } /** @@ -117,21 +114,31 @@ export class Logger implements IBitLogger { } /** - * print to the screen with a red `✖` prefix. if message is empty, print the last logged message. + * @deprecated + * try using consoleWarning. if not possible, rename this method to a clearer one */ - consoleFailure(message?: string) { - if (message) this.error(message); - loader.fail(message); + consoleWarn(message?: string, ...meta: any[]) { + if (message) this.warn(message, ...meta); + if (!loader.isStarted && logger.shouldWriteToConsole) { + // eslint-disable-next-line no-console + console.warn(message, ...meta); + } else { + loader.stopAndPersist(message); + } } + /** - * print to the screen with a red `⚠` prefix. if message is empty, print the last logged message. + * @deprecated + * try using consoleFailure. if not possible, rename this method to a clearer one */ - consoleWarning(message?: string) { - if (message) { - this.warn(message); - message = chalk.yellow(message); + consoleError(message?: string, ...meta: any[]) { + if (message) this.error(message, ...meta); + if (!loader.isStarted && logger.shouldWriteToConsole) { + // eslint-disable-next-line no-console + console.error(message, ...meta); + } else { + loader.stopAndPersist(message); } - loader.warn(message); } private colorMessage(message: string) { diff --git a/src/cli/loader/loader.ts b/src/cli/loader/loader.ts index 57162f01d152..385015724d7f 100644 --- a/src/cli/loader/loader.ts +++ b/src/cli/loader/loader.ts @@ -2,7 +2,7 @@ import Spinnies from 'dreidels'; import { SPINNER_TYPE } from '../../constants'; -const DEFAULT_SPINNER = 'default'; +export const DEFAULT_SPINNER = 'default'; /** * previous loader was "ora" (which doesn't support multi spinners) @@ -25,13 +25,23 @@ export class Loader { } off(): Loader { - Object.keys(this.spinner?.spinners || {}).forEach((spinner) => { - this.spinner?.spinners[spinner].remove(); - }); + if (!this.spinner) return this; + this.removeAll(); this.spinner = null; return this; } + private removeAll() { + if (!this.spinner) return; + const spinners = this.spinner; + Object.keys(spinners.spinners).forEach((name) => { + spinners.spinners[name].removeAllListeners(); + delete spinners.spinners[name]; + }); + spinners.checkIfActiveSpinners(); + spinners.updateSpinnerState(); + } + /** * @deprecated * no need to start a spinner. just log/spin whenever needed From 65d56cf3726e24662f59860ea275f0cbec523174 Mon Sep 17 00:00:00 2001 From: David First Date: Fri, 7 Apr 2023 12:56:11 -0400 Subject: [PATCH 4/4] stream to stdout --- src/cli/loader/loader.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cli/loader/loader.ts b/src/cli/loader/loader.ts index 385015724d7f..917fecf8f4f4 100644 --- a/src/cli/loader/loader.ts +++ b/src/cli/loader/loader.ts @@ -1,4 +1,5 @@ import Spinnies from 'dreidels'; +import { stdout } from 'process'; import { SPINNER_TYPE } from '../../constants'; @@ -104,7 +105,7 @@ export class Loader { } private createNewSpinner(): Spinnies { - const spinnies = new Spinnies({ spinner: SPINNER_TYPE }); + const spinnies = new Spinnies({ spinner: SPINNER_TYPE, stream: stdout }); return spinnies; } }