inquirer
Version:
A collection of common interactive command line user interfaces.
298 lines (297 loc) • 11.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports._ = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const node_readline_1 = __importDefault(require("node:readline"));
const rxjs_1 = require("rxjs");
const run_async_1 = __importDefault(require("run-async"));
const mute_stream_1 = __importDefault(require("mute-stream"));
const ansi_escapes_1 = __importDefault(require("ansi-escapes"));
exports._ = {
set: (obj, path = '', value) => {
let pointer = obj;
path.split('.').forEach((key, index, arr) => {
if (key === '__proto__' || key === 'constructor')
return;
if (index === arr.length - 1) {
pointer[key] = value;
}
else if (!(key in pointer) || typeof pointer[key] !== 'object') {
pointer[key] = {};
}
pointer = pointer[key];
});
},
get: (obj, path = '', defaultValue) => {
const travel = (regexp) => String.prototype.split
.call(path, regexp)
.filter(Boolean)
.reduce(
// @ts-expect-error implicit any on res[key]
(res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
const result = travel(/[,[\]]+?/) || travel(/[,.[\]]+?/);
return result === undefined || result === obj ? defaultValue : result;
},
};
/**
* Resolve a question property value if it is passed as a function.
* This method will overwrite the property on the question object with the received value.
*/
function fetchAsyncQuestionProperty(question, prop, answers) {
if (prop in question) {
const propGetter = question[prop];
if (typeof propGetter === 'function') {
return (0, rxjs_1.from)((0, run_async_1.default)(propGetter)(answers).then((value) => {
return Object.assign(question, { [prop]: value });
}));
}
}
return (0, rxjs_1.of)(question);
}
class TTYError extends Error {
constructor() {
super(...arguments);
Object.defineProperty(this, "isTtyError", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
}
}
function setupReadlineOptions(opt = {}) {
// Inquirer 8.x:
// opt.skipTTYChecks = opt.skipTTYChecks === undefined ? opt.input !== undefined : opt.skipTTYChecks;
opt.skipTTYChecks = opt.skipTTYChecks === undefined ? true : opt.skipTTYChecks;
// Default `input` to stdin
const input = opt.input || process.stdin;
// Check if prompt is being called in TTY environment
// If it isn't return a failed promise
// @ts-expect-error: ignore isTTY type error
if (!opt.skipTTYChecks && !input.isTTY) {
throw new TTYError('Prompts can not be meaningfully rendered in non-TTY environments');
}
// Add mute capabilities to the output
const ms = new mute_stream_1.default();
ms.pipe(opt.output || process.stdout);
const output = ms;
return Object.assign(Object.assign({ terminal: true }, opt), { input,
output });
}
function isQuestionArray(questions) {
return Array.isArray(questions);
}
function isQuestionMap(questions) {
return Object.values(questions).every((maybeQuestion) => typeof maybeQuestion === 'object' &&
!Array.isArray(maybeQuestion) &&
maybeQuestion != null);
}
function isPromptConstructor(prompt) {
return (prompt.prototype &&
'run' in prompt.prototype &&
typeof prompt.prototype.run === 'function');
}
/**
* Base interface class other can inherits from
*/
class PromptsRunner {
constructor(prompts, opt) {
Object.defineProperty(this, "prompts", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "answers", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "process", {
enumerable: true,
configurable: true,
writable: true,
value: rxjs_1.EMPTY
});
Object.defineProperty(this, "onClose", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "opt", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "rl", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Handle the ^C exit
*/
Object.defineProperty(this, "onForceClose", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.close();
process.kill(process.pid, 'SIGINT');
console.log('');
}
});
/**
* Close the interface and cleanup listeners
*/
Object.defineProperty(this, "close", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
// Remove events listeners
process.removeListener('exit', this.onForceClose);
if (typeof this.onClose === 'function') {
this.onClose();
}
}
});
Object.defineProperty(this, "setDefaultType", {
enumerable: true,
configurable: true,
writable: true,
value: (question) => {
// Default type to input
if (!this.prompts[question.type]) {
question = Object.assign({}, question, { type: 'input' });
}
return (0, rxjs_1.defer)(() => (0, rxjs_1.of)(question));
}
});
Object.defineProperty(this, "filterIfRunnable", {
enumerable: true,
configurable: true,
writable: true,
value: (question) => {
if (question.askAnswered !== true &&
exports._.get(this.answers, question.name) !== undefined) {
return rxjs_1.EMPTY;
}
const { when } = question;
if (when === false) {
return rxjs_1.EMPTY;
}
if (typeof when !== 'function') {
return (0, rxjs_1.of)(question);
}
return (0, rxjs_1.defer)(() => (0, rxjs_1.from)((0, run_async_1.default)(when)(this.answers).then((shouldRun) => {
if (shouldRun) {
return question;
}
return;
})).pipe((0, rxjs_1.filter)((val) => val != null)));
}
});
this.opt = opt;
this.prompts = prompts;
}
run(questions, answers) {
// Keep global reference to the answers
this.answers = typeof answers === 'object' ? Object.assign({}, answers) : {};
let obs;
if (isQuestionArray(questions)) {
obs = (0, rxjs_1.from)(questions);
}
else if ((0, rxjs_1.isObservable)(questions)) {
obs = questions;
}
else if (isQuestionMap(questions)) {
// Case: Called with a set of { name: question }
obs = (0, rxjs_1.from)(Object.entries(questions).map(([name, question]) => {
return Object.assign({}, question, { name });
}));
}
else {
// Case: Called with a single question config
obs = (0, rxjs_1.from)([questions]);
}
this.process = obs.pipe((0, rxjs_1.concatMap)((question) => this.processQuestion(question)));
const promise = (0, rxjs_1.lastValueFrom)(this.process.pipe((0, rxjs_1.reduce)((answersObj, answer) => {
exports._.set(answersObj, answer.name, answer.answer);
return answersObj;
}, this.answers))).then(() => this.onCompletion(), (error) => this.onError(error));
return Object.assign(promise, { ui: this });
}
/**
* Once all prompt are over
*/
onCompletion() {
this.close();
return this.answers;
}
onError(error) {
this.close();
return Promise.reject(error);
}
processQuestion(question) {
question = Object.assign({}, question);
return (0, rxjs_1.defer)(() => {
const obs = (0, rxjs_1.of)(question);
return obs.pipe((0, rxjs_1.concatMap)(this.setDefaultType), (0, rxjs_1.concatMap)(this.filterIfRunnable), (0, rxjs_1.concatMap)((question) => fetchAsyncQuestionProperty(question, 'message', this.answers)), (0, rxjs_1.concatMap)((question) => fetchAsyncQuestionProperty(question, 'default', this.answers)), (0, rxjs_1.concatMap)((question) => fetchAsyncQuestionProperty(question, 'choices', this.answers)), (0, rxjs_1.concatMap)((question) => {
if ('choices' in question) {
// @ts-expect-error question type is too loose
question.choices = question.choices.map((choice) => {
if (typeof choice === 'string') {
return { name: choice, value: choice };
}
return choice;
});
}
return (0, rxjs_1.of)(question);
}), (0, rxjs_1.concatMap)((question) => this.fetchAnswer(question)));
});
}
fetchAnswer(question) {
const prompt = this.prompts[question.type];
if (prompt == null) {
throw new Error(`Prompt for type ${question.type} not found`);
}
return isPromptConstructor(prompt)
? (0, rxjs_1.defer)(() => {
const rl = node_readline_1.default.createInterface(setupReadlineOptions(this.opt));
rl.resume();
const onClose = () => {
rl.removeListener('SIGINT', this.onForceClose);
rl.setPrompt('');
rl.output.unmute();
rl.output.write(ansi_escapes_1.default.cursorShow);
rl.output.end();
rl.close();
};
this.onClose = onClose;
this.rl = rl;
// Make sure new prompt start on a newline when closing
process.on('exit', this.onForceClose);
rl.on('SIGINT', this.onForceClose);
const activePrompt = new prompt(question, rl, this.answers);
return (0, rxjs_1.from)(activePrompt.run().then((answer) => {
onClose();
this.onClose = undefined;
this.rl = undefined;
return { name: question.name, answer };
}));
})
: (0, rxjs_1.defer)(() => (0, rxjs_1.from)(prompt(question, this.opt).then((answer) => ({
name: question.name,
answer,
}))));
}
}
exports.default = PromptsRunner;