UNPKG

inquirer

Version:

A collection of common interactive command line user interfaces.

298 lines (297 loc) 11.5 kB
"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;