@mason-api/cli
Version:
CLI assistant for Mason builder
153 lines (139 loc) • 6.79 kB
JavaScript
import _ from 'lodash';
import colors from 'colors/safe';
import path from 'path';
import spawn from 'child_process';
import { formats, getDatasourceIds } from '@mason-api/generator';
import { getAPIKey, getIdToken, getUserId } from './login';
import { fetchProjects, prompt, promptChoice } from '../utils';
export const command = 'guide';
export const desc = 'Guide through template generation';
export const handler = async (argv) => {
console.log(colors.bold('Welcome to Mason wizard!'));
console.log(`It will guide you through a process to generate static templates
out of your components to be used with a server-side application`);
const idToken = await getIdToken();
const apiKey = await getAPIKey(false);
const userId = await getUserId(false);
console.log(colors.bold('\nWhich format would you like to use?'));
const format = await promptChoice('Format: ', 'format name', {
html: 'Static HTML',
ejs: 'EJS (JavaScript)',
pug: 'PUG (JavaScript)',
php: 'PHP native templating (PHP)',
twig: 'TWIG (PHP)',
erb: 'ERB (Ruby)',
haml: 'HAML (Ruby)',
handlebars: 'Handlebars',
mustache: 'Mustache',
});
console.log(colors.bold('\nWhich project would you like to work with?'));
const projects = await fetchProjects(idToken, argv.verbose);
const projectId = await promptChoice('Project: ', 'project id', _.mapValues(
_.keyBy(_.filter(projects, p => !p.isArchived && _.findIndex(p.members, ['id', userId]) > -1), 'id'),
project => project.name,
));
const project = _.find(projects, ['id', projectId]);
console.log(colors.bold(`\nWhich ${colors.italic('published')} component would you like to generate templates for?`));
const componentId = (await promptChoice(
'Component: ',
'component id',
_.mapValues(_.keyBy(project.publishedComponents, 'id'), component => component.name),
'Leave blank to generate templates for all components',
)) || '*';
const component = _.find(project.components, ['id', componentId]);
let page = '';
if (component) {
if (_.keys(_.get(component, 'config.data', []).length) > 1) {
console.log(colors.bold(`\nWhich ${colors.italic('published')} component would you like to generate templates for?`));
page = (await promptChoice(
'Page: ',
'page name',
_.mapValues(_.keyBy(_.keys(component.config.data)), _.startCase),
'Leave blank to generate templates for all pages',
)) || '*';
} else {
page = 'default';
}
}
const params = [];
// if (componentId === '*' || page === '*') {
console.log(colors.bold('\nProvide an output directory for the generated files '));
console.log(colors.italic(colors.gray('Leave blank to place files in the current directory')));
const outputDir = _.trim(await prompt('Output: ')) || '.';
params.push(`-o ${outputDir}`);
// }
if (format !== 'html') {
console.log('\nWe\'re almost there!');
console.log(colors.bold('Would you like to adjust settings specific to generated templates?'));
console.log(colors.gray(colors.italic('Example: setting prefix for data paths, embedding data into a template, toggling safe mode')));
if (!_.startsWith(_.toLower(await prompt(`${colors.bold('y')}/n: `)), 'n')) {
const components = component ? [component] : project.publishedComponents;
const datasourceIds = [];
_.forEach(components, (comp) => {
_.forEach(_.get(comp, 'config.data'), (page, name) => {
_.forEach(getDatasourceIds(comp.config, name), (datasourceId) => {
if (!_.includes(datasourceIds, datasourceId)) {
datasourceIds.push(datasourceId);
}
});
});
});
const datasources = _.compact(_.map(datasourceIds, id => project.datasources[id]));
if (datasources.length) {
console.log(colors.bold('\nShould some of datasources be embedded into template directly?'));
console.log('Datasources in component turn into loops and variables in the template.');
console.log('Data for those variables needs to be provided when template is rendered.');
console.log('You can pick certain datasources to inline their data into template statically.');
console.log('Chosen templates use the following datasources:');
const embedded = (await promptChoice(
'Page: ',
'page name',
_.mapValues(_.keyBy(datasources, 'id'), datasource => datasource.name || datasource.id),
'Leave blank, choose one, or provide multiple choices separated with comma (e.g. 1,2)',
true,
)) || '';
if (embedded.length) {
params.push(`--embed-data=${_.join(embedded, ' ')}`);
}
}
const chosenFormat = formats[format];
const formatOptions = _.get(chosenFormat, 'options', {});
// eslint-disable-next-line
for (const optionName in formatOptions) {
const option = formatOptions[optionName];
console.log(colors.bold(`\n${option.description}`));
if (option.before) {
console.log(colors.gray('Before: '), option.before({}));
console.log(colors.gray('After: '), option.after({}));
}
if (typeof option.defaultValue === 'boolean') {
const optionPrompt = option.defaultValue ? `${colors.bold('y')}/n: ` : `y/${colors.bold('n')}: `;
const optionValue = await prompt(optionPrompt);
if (optionValue) {
const optionTrue = _.startsWith(_.toLower(optionValue), 'y');
if (option.defaultValue !== optionTrue) {
params.push(`--${_.kebabCase(optionName)}=${optionTrue}`);
}
}
} else {
const optionValue = await prompt(`${_.startCase(optionName)}: `);
if (optionValue) {
params.push(`--${_.kebabCase(optionName)}=${optionValue}`);
}
}
}
}
}
params.push(`-k ${apiKey}`);
console.log(colors.bold('\nGenerated command: '));
console.log(colors.italic(colors.gray('Use this command next time to avoid going through wizard again\nFor example you can use this as a part of your build process')));
const argList = [format, projectId, componentId, page, ...params];
const args = _.join(_.compact(argList), ' ');
console.log(colors.green(`mason ${_.replace(args, '*', '\\*')}`));
console.log(colors.bold('\nExecute command now?'));
console.log(colors.gray(`It will output to ${colors.white(path.resolve(__dirname, outputDir))} directory.`));
if (!_.startsWith(_.trim(await prompt(`${colors.bold('y')}/n: `)), 'n')) {
const { spawn } = require('child_process');
yargs.parse(`generate ${args}`);
}
};