UNPKG

@yeoman/adapter

Version:

Input Output adapter for yeoman's generator/environment stack

132 lines 4.56 kB
import process from 'node:process'; import { format } from 'node:util'; import ora from 'ora'; import PQueue from 'p-queue'; import { TerminalAdapter } from './adapter.js'; const BLOCKING_PRIORITY = 10; const PROMPT_PRIORITY = 10; const LOG_PRIORITY = 20; const MAIN_ADAPTER_PRIORITY = 1000; export class QueuedAdapter { #queue; actualAdapter; delta; log; #nextChildPriority; #ora; separator; /** * `TerminalAdapter` is the default implementation of `Adapter`, an abstraction * layer that defines the I/O interactions. * * It provides a CLI interaction * * @constructor * @param {terminalAdapter} [import('./adapter.js').default] */ constructor(options) { const { adapter, queue, delta, ...adapterOptions } = options ?? {}; this.#queue = queue ?? new PQueue({ concurrency: 1 }); this.actualAdapter = adapter ?? new TerminalAdapter(adapterOptions); this.separator = this.actualAdapter.separator; // Deffered logger const defferredLogger = (...arguments_) => { this.queueLog(() => { this.actualAdapter.log(...arguments_); }); return defferredLogger; }; Object.assign(defferredLogger, this.actualAdapter.log); defferredLogger.write = (...arguments_) => { this.queueLog(() => { this.actualAdapter.log.write(...arguments_); }).catch(console.error); return defferredLogger; }; this.log = defferredLogger; this.delta = (delta ?? MAIN_ADAPTER_PRIORITY) * 100; this.#nextChildPriority = MAIN_ADAPTER_PRIORITY - 1; this.#ora = ora({ stream: options?.stdout ?? options?.stderr ?? process.stderr, }); } newAdapter(delta) { return new QueuedAdapter({ adapter: this.actualAdapter, delta: delta ?? this.#nextChildPriority--, queue: this.#queue }); } close() { this.actualAdapter.close(); this.#queue.clear(); } /** * Prompt a user for one or more questions and pass * the answer(s) to the provided callback. * * It shares its interface with `Base.prompt` * * (Defined inside the constructor to keep interfaces separated between * instances) * * @param {Object|Object[]} questions * @param {Object} [answers] Answers to be passed to inquirer * @return {Object} promise answers */ async prompt(questions, initialAnswers) { return this.#queue.add(async () => this.actualAdapter.prompt(questions, initialAnswers), { priority: PROMPT_PRIORITY + this.delta, throwOnTimeout: true, }); } async onIdle() { return this.#queue.onIdle(); } /** * Basic queue is recommended for blocking calls. * @param fn * @returns */ async queue(function_) { return this.#queue.add(() => function_(this.actualAdapter), { priority: BLOCKING_PRIORITY + this.delta, throwOnTimeout: true }); } /** * Log has a highest priority and should be not blocking. * @param fn * @returns */ async queueLog(function_) { return this.#queue.add(() => function_(this.actualAdapter), { priority: LOG_PRIORITY + this.delta, throwOnTimeout: true }); } /** * Progress is blocking, but will be skipped if the queue is not empty. * @param callback * @param options * @returns */ async progress(function_, options) { if (this.#queue.size > 0 || this.#queue.pending > 0 || options?.disabled || this.#ora.isSpinning) { // Don't show progress if queue is not empty or already spinning. return Promise.resolve(function_({ step() { } })).finally(() => { if (options?.name) { this.log.ok(options.name); } }); } try { this.#ora.start(options?.name); } catch { this.#ora.stop(); } const step = (prefix, message, ...arguments_) => { if (this.#ora.isSpinning) { this.#ora.suffixText = `: ${prefix} ${format(message, ...arguments_)}`; } }; return this.queue(() => function_({ step })).finally(() => { if (this.#ora.isSpinning) { this.#ora.suffixText = ''; this.#ora.succeed(options?.name); } }); } } //# sourceMappingURL=queued-adapter.js.map