@yeoman/adapter
Version:
Input Output adapter for yeoman's generator/environment stack
132 lines • 4.56 kB
JavaScript
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