UNPKG

roc

Version:

Build modern web applications easily

667 lines (544 loc) 25.2 kB
'use strict'; 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