UNPKG

africa

Version:

A library to interactively create and read configuration files.

136 lines (117 loc) 3.96 kB
import ask from 'reloquent' import bosom from 'bosom' import Group from './Group' /** * Ask questions and write answers to the RC file. * @param {_reloquent.Questions} questions The set of questions. These need to be updated to include defaults. * @param {string} path The path for the RC file. * @param {{ * skipExisting: (boolean|undefined), * config: (!Object|undefined), * timeout: (number|undefined) * }} [options] Additional options. */ export async function askQuestionsAndWrite(questions, path, { skipExisting = false, config = {}, timeout, } = {}) { /** @type {!Object<string, !Group>} */ const groups = {} let current = {} const q = Object.entries(questions).reduce((acc, [key, question]) => { if (question instanceof Group) { groups[key] = question return acc } acc[key] = question return acc }, {}) let answers = await ask(q, timeout) try { current = await bosom(path) } catch (err) { // ok } if (skipExisting) { answers = skipAnswers(answers, config, current) || {} } const ga = await Object.entries(groups).reduce(async (acc, [key, group]) => { acc = await acc let g = await ask(group.questions, timeout) if (skipExisting) g = skipAnswers(g, config[key], current[key]) if (g) acc[key] = g return acc }, {}) // answers not from questions but in config anyhow const extra = skipAnswers(current, config, current) || {} const total = { ...extra, ...answers, ...ga } await bosom(path, total, { space: 2 }) return total } const skipAnswers = (answers, config = {}, current = {}) => { let allSkipped = true const skipped = Object.entries(answers).reduce((acc, [key, val]) => { const CURRENT = current[key] const DEFAULT = config[key] if (val == DEFAULT && val != CURRENT) return acc allSkipped = false acc[key] = val return acc }, {}) if (allSkipped) return null return skipped } /** * Merge two configurations, with `a` as base one, such that its properties won't be overridden by `b`. * @param {!Object} a The base configuration. * @param {!Object} b The extension to configuration. */ export const merge = (a, b) => { return Object.entries(b).reduce((acc, [k, value]) => { if (typeof value == 'object' && value !== null && typeof a[k] == 'object') { acc[k] = merge(a[k], value) } else acc[k] = value return acc }, a) } /** * Adds default value from the config. * @param {_reloquent.Questions} questions A set of questions to extend with default value from the existing config. * @param {!Object} current Current configuration object (answers). * @returns {_reloquent.Questions} Questions with updated defaultValue where answers were present in the passed config object. */ const extendQuestions = (questions, current) => { const q = Object.entries(questions).reduce((acc, [key, question]) => { const defaultValue = current[key] if (!defaultValue) { acc[key] = question return acc } let value = typeof question == 'string' ? { text: question } : question if (question instanceof Group) { question.questions = extendQuestions(question.questions, defaultValue) } else { value = { ...value, defaultValue } } acc[key] = value return acc }, {}) return q } /** * Ask questions while adding default values when asking. * @param {_reloquent.Questions} questions * @param {string} path The path to the rc file. * @param {!Object} config Current answers. * @param {number} [timeout] * @param {{ skipExisting: (boolean|undefined) }} [opts] */ export const forceQuestions = async (questions, path, config, timeout, { skipExisting } = {}) => { const q = extendQuestions(questions, config) const conf = await askQuestionsAndWrite(q, path, { timeout, skipExisting, config }) return conf } /** * @suppress {nonStandardJsDocs} * @typedef {import('../..').Questions} _reloquent.Questions */