Files
wordpress-export-to-markdown/src/intake.js
T
2025-01-28 16:56:37 -05:00

160 lines
4.6 KiB
JavaScript

import camelcase from 'camelcase';
import chalk from 'chalk';
import * as commander from 'commander';
import * as luxon from 'luxon';
import path from 'path';
import * as normalizers from './normalizers.js';
import * as questions from './questions.js';
import * as shared from './shared.js';
// visual formatting for wizard
const promptTheme = {
prefix: {
idle: chalk.gray('\n?'),
done: chalk.green('✓')
},
style: {
description: (text) => chalk.gray('example: ' + text)
}
};
export async function getConfig() {
// check command line for any config options
const commandLineQuestions = questions.all;
const commandLineAnswers = getCommandLineAnswers(commandLineQuestions);
let wizardAnswers;
if (commandLineAnswers.wizard) {
console.log('\nStarting wizard...');
// run wizard for remaining config options
const wizardQuestions = questions.all.filter((question) => !(camelcase(question.name) in commandLineAnswers));
wizardAnswers = await getWizardAnswers(wizardQuestions, commandLineAnswers);
} else {
console.log('\nSkipping wizard...');
}
return { ...commandLineAnswers, ...wizardAnswers };
}
function getCommandLineAnswers(questions) {
// show errors in red
commander.program.configureOutput({
outputError: (str, write) => write(chalk.red(str))
});
questions.forEach((question) => {
const option = new commander.Option('--' + question.name + ' <' + question.type + '>', question.description);
option.default(question.default);
if (question.choices && question.type !== 'boolean') {
// let commander handle non-boolean multiple choice validation
option.choices(question.choices.map((choice) => choice.value));
} else {
option.argParser((value) => normalize(value, question.type, (errorMessage) => {
throw new commander.InvalidArgumentError(errorMessage);
}));
}
commander.program.addOption(option);
});
const answers = commander.program.parse().opts();
// do some post-processing on the answers
for (const [key, value] of Object.entries(answers)) {
// the "wizard" answer and any user-provided (not defaulted) answers are left alone
if (key === 'wizard' || commander.program.getOptionValueSource(key) !== 'default') {
continue;
}
if (answers.wizard) {
// remove this default answer so the wizard will ask about it later
delete answers[key];
} else {
// normalize and validate default answer
const question = questions.find((question) => camelcase(question.name) === key);
answers[key] = normalize(value, question.type, (errorMessage) => {
// this is formatted to match how commander displays other errors
commander.program.error(`error: option '--${question.name} <${question.type}>' argument '${value}' is invalid. ${errorMessage}`);
});
}
}
return answers;
}
export async function getWizardAnswers(questions, commandLineAnswers) {
const answers = {};
for (const question of questions) {
let answerKey = camelcase(question.name);
let normalizedAnswer; // holds normalized answer value potentially returned during validation
const promptConfig = {
theme: promptTheme,
message: question.description + '?',
default: question.default,
};
if (question.choices) {
promptConfig.choices = question.choices;
promptConfig.loop = false;
if (question.isPathQuestion) {
// create a snapshot config of command line answers and wizard answers so far
const config = { ...commandLineAnswers, ...answers };
promptConfig.choices.forEach((choice) => {
// show example path if this choice is selected
config[answerKey] = choice.value;
choice.description = buildSamplePostPath(config);
});
}
} else {
promptConfig.validate = (value) => {
let validationErrorMessage;
normalizedAnswer = normalize(value, question.type, (errorMessage) => {
validationErrorMessage = errorMessage;
});
return validationErrorMessage ?? true;
}
}
const answer = await question.prompt(promptConfig).catch((ex) => {
// exit gracefully if user hits ctrl + c during wizard
if (ex instanceof Error && ex.name === 'ExitPromptError') {
console.log('\nUser quit wizard early.');
process.exit(0);
} else {
throw ex;
}
});
answers[answerKey] = normalizedAnswer ?? answer;
}
return answers;
}
function normalize(value, type, onError) {
const normalizer = normalizers[camelcase(type)];
if (!normalizer) {
return value;
}
try {
return normalizer(value);
} catch (ex) {
onError(ex.message);
}
}
export function buildSamplePostPath(config) {
const outputDir = path.sep;
const type = '';
const date = luxon.DateTime.now().toFormat('yyyy-LL-dd');
const slug = 'my-post';
return shared.buildPostPath(outputDir, type, date, slug, config);
}