roc
Version:
Build modern web applications easily
667 lines (544 loc) • 25.2 kB
JavaScript
;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildCompleteConfig = buildCompleteConfig;
exports.generateCommandsDocumentation = generateCommandsDocumentation;
exports.getCommandArgumentsAsString = getCommandArgumentsAsString;
exports.generateCommandDocumentation = generateCommandDocumentation;
exports.getDefaultOptions = getDefaultOptions;
exports.parseArguments = parseArguments;
exports.getMappings = getMappings;
exports.parseOptions = parseOptions;
var _chalk = require('chalk');
var _chalk2 = _interopRequireDefault(_chalk);
var _lodash = require('lodash');
var _trimNewlines = require('trim-newlines');
var _trimNewlines2 = _interopRequireDefault(_trimNewlines);
var _redent = require('redent');
var _redent2 = _interopRequireDefault(_redent);
var _configuration = require('../configuration');
var _keyboardDistance = require('./keyboard-distance');
var _keyboardDistance2 = _interopRequireDefault(_keyboardDistance);
var _buildDocumentationObject = require('../documentation/build-documentation-object');
var _buildDocumentationObject2 = _interopRequireDefault(_buildDocumentationObject);
var _generateTable = require('../documentation/generate-table');
var _generateTable2 = _interopRequireDefault(_generateTable);
var _helpers = require('../documentation/helpers');
var _helpers2 = require('../helpers');
var _onProperty = require('../helpers/on-property');
var _onProperty2 = _interopRequireDefault(_onProperty);
var _validation = require('../validation');
var _style = require('../helpers/style');
var _actions = require('../hooks/actions');
var _getSuggestions = require('../helpers/get-suggestions');
var _getSuggestions2 = _interopRequireDefault(_getSuggestions);
var _extensions = require('./extensions');
var _extensions2 = _interopRequireDefault(_extensions);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Builds the complete configuration objects.
*
* @param {boolean} [verbose=true] - If verbose mode should be enabled, logs some extra information.
* @param {rocConfig} newConfig - The new configuration to base the merge on.
* @param {rocMetaConfig} newMeta - The new meta configuration to base the merge on.
* @param {rocConfig} baseConfig - The base configuration.
* @param {rocMetaConfig} baseMeta - The base meta configuration.
* @param {string} [directory=process.cwd()] - The directory to resolve relative paths from.
* @param {boolean} [validate=true] - If the newConfig and the newMeta structure should be validated.
* @param {boolean} [checkDependencies=true] - If dependencies should be verified in extensions.
*
* @returns {Object} - The result of with the built configurations.
* @property {rocConfig} packageConfig - The packages merged configurations.
* @property {rocConfig} config - The final configuration, with application configuration.
* @property {rocMetaConfig} meta - The merged meta configuration.
*/
function buildCompleteConfig() {
let verbose = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0];
let newConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
let newMeta = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
let baseConfig = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
let baseMeta = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4];
let directory = arguments.length <= 5 || arguments[5] === undefined ? process.cwd() : arguments[5];
let validate = arguments.length <= 6 || arguments[6] === undefined ? true : arguments[6];
let checkDependencies = arguments.length <= 7 || arguments[7] === undefined ? true : arguments[7];
let finalConfig = _extends({}, baseConfig);
let finalMeta = _extends({}, baseMeta);
if ((0, _helpers2.fileExists)('package.json', directory)) {
const packageJson = (0, _helpers2.getPackageJson)(directory);
const packages = newConfig.packages && newConfig.packages.length ? newConfig.packages : (0, _helpers2.getRocPackageDependencies)(packageJson);
const plugins = newConfig.plugins && newConfig.plugins.length ? newConfig.plugins : (0, _helpers2.getRocPluginDependencies)(packageJson);
var _buildExtensionTree = (0, _extensions2.default)(packages, plugins, baseConfig, baseMeta, directory, verbose, checkDependencies);
const projectExtensions = _buildExtensionTree.projectExtensions;
const config = _buildExtensionTree.config;
const meta = _buildExtensionTree.meta;
finalConfig = (0, _configuration.merge)(finalConfig, config);
finalMeta = (0, _configuration.merge)(finalMeta, meta);
if (projectExtensions.length && verbose) {
console.log((0, _style.feedbackMessage)((0, _style.infoLabel)('Info', 'Extensions Used'), projectExtensions.map(extn => `${ extn.name }${ extn.version ? ' - ' + extn.version : '' }`).join('\n')));
}
// Check for a mismatch between application configuration and packages.
if (validate) {
if (Object.keys(newConfig).length) {
const validationFeedback = validateConfigurationStructure(finalConfig, newConfig);
if (validationFeedback) {
console.log(validationFeedback);
}
}
if (Object.keys(newMeta).length) {
const validationFeedback = validateConfigurationStructure(finalMeta, newMeta);
if (validationFeedback) {
console.log(validationFeedback);
}
}
}
// Add project action
if ((0, _lodash.isFunction)(newConfig.action)) {
// TODO Update how action is used
(0, _actions.registerAction)(newConfig.action, 'default', packageJson.name, true);
}
}
return {
packageConfig: finalConfig,
config: (0, _configuration.merge)(finalConfig, newConfig),
meta: (0, _configuration.merge)(finalMeta, newMeta)
};
}
function validateConfigurationStructure(config, applicationConfig) {
const getKeys = function getKeys(obj) {
let oldPath = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];
let allKeys = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
Object.keys(obj).forEach(key => {
const value = obj[key];
const newPath = oldPath + key;
if ((0, _lodash.isPlainObject)(value)) {
getKeys(value, newPath + '.', allKeys);
} else {
allKeys.push(newPath);
}
});
return allKeys;
};
const info = [];
const keys = getKeys(config);
const diff = (0, _lodash.difference)(getKeys(applicationConfig), keys);
if (diff.length > 0) {
info.push((0, _style.feedbackMessage)((0, _style.warningLabel)('Warning', 'Configuration'), 'There was a mismatch in the application configuration structure, make sure this is correct.\n' + (0, _getSuggestions2.default)(diff, keys)));
}
// }
return info.join('\n');
}
/**
* Generates a string with information about all the possible commands.
*
* @param {rocConfig} commands - The Roc config object, uses commands from it.
* @param {rocMetaConfig} commandsmeta - The Roc meta config object, uses commands from it.
*
* @returns {string} - A string with documentation based on the available commands.
*/
function generateCommandsDocumentation(_ref, _ref2) {
let commands = _ref.commands;
let commandsMeta = _ref2.commands;
const header = {
name: true,
description: true
};
const noCommands = { 'No commands available.': '' };
commandsMeta = commandsMeta || {};
// We will sort the commands
let body = [{
name: 'Commands',
level: 0,
objects: Object.keys(commands || noCommands).sort().map(command => {
const options = commandsMeta[command] ? getCommandArgumentsAsString(commandsMeta[command]) : '';
const description = commandsMeta[command] && commandsMeta[command].description ? commandsMeta[command].description : '';
return {
name: command + options,
description
};
})
}];
return generateCommandDocsHelper(body, header, 'General options', 'name');
}
/**
* Generates arguments as a information string.
*
* @param {rocCommandMeta} command - The command meta object.
* @returns {string} - The arguments as a string.
*/
function getCommandArgumentsAsString() {
let command = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
let args = '';
(command.arguments || []).forEach(argument => {
args += argument.required ? ` <${ argument.name }> ` : ` [${ argument.name }]`;
});
return args;
}
/**
* Generates a string with information about a specific command.
*
* @param {rocConfig} settings - The Roc config object, uses settings from it.
* @param {rocMetaConfig} commands+meta - The Roc meta config object, uses commands and settings from it.
* @param {string} command - The selected command.
* @param {string} name - The name of the cli.
*
* @returns {string} - A string with documentation based on the selected commands.
*/
function generateCommandDocumentation(_ref3, _ref4, command, name) {
let settings = _ref3.settings;
var _ref4$commands = _ref4.commands;
let commands = _ref4$commands === undefined ? {} : _ref4$commands;
let meta = _ref4.settings;
const rows = [];
rows.push('Usage: ' + name + ' ' + command + getCommandArgumentsAsString(commands[command]));
rows.push('');
if (commands[command] && (commands[command].description || commands[command].help)) {
if (commands[command].help) {
rows.push((0, _redent2.default)((0, _trimNewlines2.default)(commands[command].help)));
} else {
rows.push(commands[command].description);
}
rows.push('');
}
let body = [];
// Generate the arguments table
if (commands[command] && commands[command].arguments) {
const objects = commands[command].arguments.map(argument => ({
cli: `${ argument.name }`,
description: createDescription(argument)
}));
if (objects.length > 0) {
body = body.concat({
objects,
name: 'Arguments',
level: 0
});
}
}
// Generate the options table
if (commands[command] && commands[command].options) {
const objects = commands[command].options.sort((0, _onProperty2.default)('name')).map(option => ({
cli: option.shortname ? `-${ option.shortname }, --${ option.name }` : `--${ option.name }`,
description: createDescription(option)
}));
if (objects.length > 0) {
body = body.concat({
name: 'Command options',
level: 0,
objects: objects
});
}
}
// Generate the settings table
if (commands[command] && commands[command].settings) {
const filter = commands[command].settings === true ? [] : commands[command].settings;
body = body.concat({
name: 'Settings options',
children: (0, _buildDocumentationObject.sortOnProperty)('name', (0, _buildDocumentationObject2.default)(settings, meta, filter))
});
}
const header = {
cli: true,
description: {
padding: false
},
defaultValue: {
padding: false,
renderer: input => {
input = (0, _helpers.getDefaultValue)(input);
if (input === undefined) {
return '';
}
if (!input) {
return (0, _style.warning)('No default value');
}
return _chalk2.default.cyan(input);
}
},
required: {
padding: false,
renderer: (input, object) => {
if (input && !object.defaultValue) {
return _chalk2.default.green('Required');
}
return '';
}
}
};
rows.push(generateCommandDocsHelper(body, header, 'General options', 'cli'));
return rows.join('\n');
}
function createDescription(param) {
return `${ param.description && param.description + ' ' || '' }` + `${ param.required && _chalk2.default.green('Required') + ' ' || '' }` + `${ param.default && _chalk2.default.cyan(JSON.stringify(param.default)) + ' ' || '' }` + `${ !param.default && param.validation ? _chalk2.default.dim('(' + param.validation(null, true).type + ')') : '' }`;
}
function generateCommandDocsHelper(body, header, options, name) {
body.push({
name: options,
level: 0,
objects: getDefaultOptions(name)
});
return (0, _generateTable2.default)(body, header, {
compact: true,
titleWrapper: input => input + ':',
cellDivider: '',
rowWrapper: input => `${ input }`,
header: false,
groupTitleWrapper: input => input + ':'
});
}
/**
* Gets and array with the default options for the cli.
* Will be formatted to work with {@link generateTable}
*
* @param {string} name - What property the option/flag name should be set.
*
* @returns {Object[]} - Array with the default options formatted for {@link generateTable}.
*/
function getDefaultOptions(name) {
return [{
[name]: '-c, --config',
description: `Path to configuration file, will default to ${ _chalk2.default.bold('roc.config.js') } in current ` + `working directory.`
}, {
[name]: '-d, --directory',
description: 'Path to working directory, will default to the current working directory. Can be either ' + 'absolute or relative.'
}, {
[name]: '-h, --help',
description: 'Output usage information.'
}, {
[name]: '-V, --verbose',
description: 'Enable verbose mode.'
}, {
[name]: '-v, --version',
description: 'Output version number.'
}];
}
/**
* Parses arguments and validates them.
*
* @param {string} command - The command to parse arguments for.
* @param {Object} commands - commands from {@link rocMetaConfig}.
* @param {Object[]} args - Arguments parsed by minimist.
*
* @returns {Object} - Parsed arguments.
* @property {object[]} options - The parsed arguments that was matched against the meta configuration for the command.
* @property {object[]} rest - The rest of the arguments that could not be matched against the configuration.
*/
function parseArguments(command) {
let commands = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
let args = arguments[2];
// If the command supports options
if (commands[command] && commands[command].arguments) {
let parsedArguments = {};
commands[command].arguments.forEach((argument, index) => {
let value = args[index];
if (value === undefined && argument.default) {
value = argument.default;
}
if (value === undefined && argument.required) {
console.log((0, _style.feedbackMessage)((0, _style.errorLabel)('Error', 'Arguments Problem'), `Required argument ${ _chalk2.default.bold(argument.name) } was not provided.`));
/* eslint-disable no-process-exit */
process.exit(1);
/* eslint-enable */
}
if (value !== undefined && argument.converter) {
value = argument.converter(value);
}
if (value !== undefined && argument.validation) {
const validationResult = (0, _validation.isValid)(value, argument.validation);
if (validationResult !== true) {
try {
(0, _validation.throwError)(argument.name, validationResult, value, 'argument');
} catch (err) {
console.log((0, _style.feedbackMessage)((0, _style.errorLabel)('Error', 'Arguments Problem'), 'An argument was not valid.\n\n' + err.message));
/* eslint-disable no-process-exit */
process.exit(1);
/* eslint-enable */
}
}
}
parsedArguments[argument.name] = value;
});
return {
arguments: parsedArguments,
rest: args.splice(Object.keys(parsedArguments).length)
};
}
return {
arguments: {},
rest: args
};
}
/**
* Creates mappings between cli commands to their "path" in the configuration structure, their validator and type
* converter.
*
* @param {rocDocumentationObject} documentationObject - Documentation object to create mappings for.
*
* @returns {Object} - Properties are the cli command without leading dashes that maps to a {@link rocMapObject}.
*/
function getMappings() {
let documentationObject = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
const recursiveHelper = groups => {
let mappings = {};
groups.forEach(group => {
group.objects.forEach(element => {
// Remove the two dashes in the beginning to match correctly
mappings[element.cli.substr(2)] = {
name: element.cli,
path: element.path,
converter: element.converter,
validator: element.validator
};
});
mappings = Object.assign({}, mappings, recursiveHelper(group.children));
});
return mappings;
};
return recursiveHelper(documentationObject);
}
/**
* Converts a set of options to {@link rocConfigSettings} object and command specific options.
*
* @param {Object} options - Options parsed from minimist.
* @param {Object} mappings - Result from {@link getMappings}.
* @param {string} command - The command to parse arguments for.
* @param {Object} commands - commands from {@link rocMetaConfig}.
*
* @returns {{settings: rocConfigSettings, parseOptions: Object}} - The mapped Roc configuration settings object.
*/
function parseOptions(options, mappings, command) {
let commands = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
const infoSettings = [];
var _parseSettingsOptions = parseSettingsOptions(options, mappings);
const settings = _parseSettingsOptions.settings;
const notManaged = _parseSettingsOptions.notManaged;
var _parseCommandOptions = parseCommandOptions(commands[command], notManaged);
const possibleCommandOptions = _parseCommandOptions.possibleCommandOptions;
const possibleCommandOptionsShort = _parseCommandOptions.possibleCommandOptionsShort;
const infoOptions = _parseCommandOptions.infoOptions;
const parsedOptions = _parseCommandOptions.parsedOptions;
const finalNotManaged = _parseCommandOptions.finalNotManaged;
const defaultOptions = ['help', 'config', 'verbose', 'directory', 'version'];
const defaultOptionsShort = ['h', 'c', 'V', 'd', 'v'];
Object.keys(finalNotManaged).forEach(key => {
if (key.length > 1) {
infoSettings.push((0, _getSuggestions2.default)([key], Object.keys(mappings).concat(defaultOptions, possibleCommandOptions), '--'));
} else {
infoSettings.push((0, _getSuggestions2.default)([key], [(0, _keyboardDistance2.default)(key, defaultOptionsShort.concat(possibleCommandOptionsShort))], '-'));
}
});
if (infoSettings.length > 0) {
console.log((0, _style.feedbackMessage)((0, _style.warningLabel)('Warning', 'Option Problem'), 'Some options were not understood.\n\n' + infoSettings.join('\n')));
}
if (infoOptions.length > 0) {
console.log((0, _style.feedbackMessage)((0, _style.errorLabel)('Error', 'Command Options Problem'), 'Some command options were not provided.\n\n' + infoSettings.join('\n')));
/* eslint-disable no-process-exit */
process.exit(1);
/* eslint-enable */
}
return {
settings,
parsedOptions
};
}
function parseSettingsOptions(options, mappings) {
const settings = {};
let notManaged = {};
Object.keys(options).forEach(key => {
if (mappings[key]) {
const value = convert(options[key], mappings[key]);
(0, _lodash.set)(settings, mappings[key].path, value);
} else {
// We did not find a match
notManaged = _extends({}, notManaged, {
[key]: options[key]
});
}
});
return {
settings,
notManaged
};
}
function parseCommandOptions(command, notManaged) {
const infoOptions = [];
let possibleCommandOptions = [];
let possibleCommandOptionsShort = [];
const parsedOptions = {
options: {},
rest: {}
};
const getName = name => name.length === 1 ? '-' + name : '--' + name;
if (command && command.options) {
possibleCommandOptions = command.options.map(option => option.name);
possibleCommandOptionsShort = command.options.reduce((previous, option) => {
if (option.shortname) {
return previous.concat(option.shortname);
}
return previous;
}, []);
command.options.forEach(option => {
let value;
let name;
// See if we can match the option against anything.
if (notManaged[option.name]) {
value = notManaged[option.name];
delete notManaged[option.name];
name = option.name;
} else if (notManaged[option.shortname]) {
value = notManaged[option.shortname];
delete notManaged[option.shortname];
name = option.shortname;
}
if (value === undefined && option.default) {
value = option.default;
}
// The option is required but no value was found
if (value === undefined && option.required) {
const getOptions = () => {
const shortOption = option.shortname ? ' or ' + _chalk2.default.bold('-' + option.shortname) : '';
return _chalk2.default.bold('--' + option.name) + shortOption;
};
infoOptions.push(`Required option ${ getOptions() } was not provided.`);
}
if (value !== undefined && option.converter) {
value = option.converter(value);
}
// If we have a value and a validator
if (value !== undefined && option.validation) {
const validationResult = (0, _validation.isValid)(value, option.validation);
if (validationResult !== true) {
try {
(0, _validation.throwError)(getName(name), validationResult, value, 'option');
} catch (err) {
console.log((0, _style.feedbackMessage)((0, _style.errorLabel)('Error', 'Command Options Problem'), 'A option was not valid..\n\n' + err.message));
/* eslint-disable no-process-exit */
process.exit(1);
/* eslint-enable */
}
}
}
parsedOptions.options[option.name] = value;
});
}
parsedOptions.rest = notManaged;
return {
possibleCommandOptions,
possibleCommandOptionsShort,
infoOptions,
parsedOptions,
finalNotManaged: notManaged
};
}
function convert(value, mapping) {
const val = mapping.converter(value);
const validationResult = (0, _validation.isValid)(val, mapping.validator);
if (validationResult === true) {
return val;
}
// Make sure that we got something from the conversion.
const message = val !== undefined && val !== null && val.toString().length > 0 ? `Received ${ _chalk2.default.underline(JSON.stringify(value)) } and it was converted to ` + `${ _chalk2.default.underline(JSON.stringify(val)) }. ` : '';
console.log((0, _style.feedbackMessage)((0, _style.warningLabel)('Warning', 'Conversion Problem'), `There was a problem when trying to automatically convert ${ _chalk2.default.bold(mapping.name) }. This ` + `value will be ignored.\n\n` + message + validationResult));
}
//# sourceMappingURL=helpers.js.map