UNPKG

roc

Version:

Build modern web applications easily

552 lines (443 loc) 20 kB
'use strict'; exports.__esModule = true; 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; }; exports.buildCompleteConfig = buildCompleteConfig; exports.getSuggestions = getSuggestions; exports.generateCommandsDocumentation = generateCommandsDocumentation; exports.generateCommandDocumentation = generateCommandDocumentation; exports.parseOptions = parseOptions; exports.getMappings = getMappings; exports.parseArguments = parseArguments; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } require('source-map-support/register'); var _chalk = require('chalk'); var _chalk2 = _interopRequireDefault(_chalk); var _lodash = require('lodash'); var _resolve = require('resolve'); var _resolve2 = _interopRequireDefault(_resolve); var _leven = require('leven'); var _leven2 = _interopRequireDefault(_leven); var _trimNewlines = require('trim-newlines'); var _trimNewlines2 = _interopRequireDefault(_trimNewlines); var _redent = require('redent'); var _redent2 = _interopRequireDefault(_redent); var _configuration = require('../configuration'); var _documentationBuildDocumentationObject = require('../documentation/build-documentation-object'); var _documentationBuildDocumentationObject2 = _interopRequireDefault(_documentationBuildDocumentationObject); var _documentationGenerateTable = require('../documentation/generate-table'); var _documentationGenerateTable2 = _interopRequireDefault(_documentationGenerateTable); var _documentationHelpers = require('../documentation/helpers'); var _helpers = require('../helpers'); var _validation = require('../validation'); var _helpersStyle = require('../helpers/style'); /** * Builds the complete configuration objects. * * @param {boolean} debug - If debug mode should be enabled, logs some extra information. * @param {rocConfig} config - The base configuration. * @param {rocMetaConfig} meta - The base meta configuration. * @param {rocConfig} newConfig - The new configuration to base the merge on. * @param {rocMetaConfig} newMeta - The new meta configuration to base the merge on. * @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. * * @returns {Object} - The result of with the built configurations. * @property {rocConfig} extensionConfig - The extensions merged configurations * @property {rocConfig} config - The final configuration, with application configuration. * @property {rocMetaConfig} meta - The merged meta configuration. */ function buildCompleteConfig(debug) { var config = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; var meta = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var newConfig = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; var newMeta = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4]; var directory = arguments.length <= 5 || arguments[5] === undefined ? process.cwd() : arguments[5]; var validate = arguments.length <= 6 || arguments[6] === undefined ? true : arguments[6]; var finalConfig = _extends({}, config); var finalMeta = _extends({}, meta); var usedExtensions = []; var mergeExtension = function mergeExtension(extensionName) { var _getExtension = getExtension(extensionName, directory); var baseConfig = _getExtension.baseConfig; var _getExtension$metaConfig = _getExtension.metaConfig; var metaConfig = _getExtension$metaConfig === undefined ? {} : _getExtension$metaConfig; if (baseConfig) { usedExtensions.push(extensionName); finalConfig = _configuration.merge(finalConfig, baseConfig); finalMeta = _configuration.merge(finalMeta, metaConfig); } }; if (_helpers.fileExists('package.json', directory)) { // If extensions are defined we will use them to merge the configurations if (newConfig.extensions && newConfig.extensions.length) { newConfig.extensions.forEach(mergeExtension); } else { var packageJson = _helpers.getPackageJson(directory); _helpers.getRocDependencies(packageJson).forEach(mergeExtension); } if (usedExtensions.length && debug) { console.log(_helpersStyle.importantLabel('The following Roc extensions will be used:'), usedExtensions, '\n'); } // Check for a mismatch between application configuration and extensions. if (validate) { if (Object.keys(newConfig).length) { console.log(validateConfigurationStructure(finalConfig, newConfig)); } if (Object.keys(newMeta).length) { console.log(validateConfigurationStructure(finalMeta, newMeta)); } } } return { extensionConfig: finalConfig, config: _configuration.merge(finalConfig, newConfig), meta: _configuration.merge(finalMeta, newMeta) }; } function getExtension(extensionName, directory) { try { var _require = require(_resolve2['default'].sync(extensionName, { basedir: directory })); var baseConfig = _require.baseConfig; var metaConfig = _require.metaConfig; return { baseConfig: baseConfig, metaConfig: metaConfig }; } catch (err) { console.log(_helpersStyle.errorLabel('Failed to load Roc extension ' + _chalk2['default'].bold(extensionName) + '. ' + 'Make sure you have it installed. Try running:') + ' ' + _chalk2['default'].underline('npm install --save ' + extensionName), '\n'); return {}; } } function validateConfigurationStructure(config, applicationConfig) { var getKeys = function getKeys(obj) { var oldPath = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1]; var allKeys = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2]; Object.keys(obj).forEach(function (key) { var value = obj[key]; var newPath = oldPath + key; if (_lodash.isPlainObject(value)) { getKeys(value, newPath + '.', allKeys); } else { allKeys.push(newPath); } }); return allKeys; }; var info = []; var keys = getKeys(config); var diff = _lodash.difference(getKeys(applicationConfig), keys); if (diff.length > 0) { info.push(_helpersStyle.errorLabel('Configuration problem') + ' There was a mismatch in the application configuration structure, make sure this is correct.\n'); info.push(getSuggestions(diff, keys)); info.push(''); } // } return info.join('\n'); } /** * Will create a string with suggestions for possible typos. * * @param {string[]} current - The current values that might be incorrect. * @param {string[]} possible - All the possible correct values. * @param {boolean} [command=false] - If the suggestion should be managed as a command. * * @returns {string} - A string with possible suggestions for typos. */ function getSuggestions(current, possible) { var command = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; var info = []; current.forEach(function (currentKey) { var shortest = 0; var closest = undefined; for (var _iterator = possible, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref; if (_isArray) { if (_i >= _iterator.length) break; _ref = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref = _i.value; } var key = _ref; var distance = _leven2['default'](currentKey, key); if (distance <= 0 || distance > 4) { continue; } if (shortest && distance >= shortest) { continue; } closest = key; shortest = distance; } var extra = command ? '--' : ''; if (closest) { info.push('Did not understand ' + _chalk2['default'].underline(extra + currentKey) + ' - Did you mean ' + _chalk2['default'].underline(extra + closest)); } else { info.push('Did not understand ' + _chalk2['default'].underline(extra + currentKey)); } }); 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(_ref7, _ref8) { var commands = _ref7.commands; var commandsMeta = _ref8.commands; var header = { name: true, description: true }; var noCommands = { 'No commands available.': '' }; commandsMeta = commandsMeta || {}; var body = [{ name: 'Commands', objects: Object.keys(commands || noCommands).map(function (command) { var options = commandsMeta[command] ? ' ' + getCommandOptionsAsString(commandsMeta[command]) : ''; var description = commandsMeta[command] && commandsMeta[command].description ? commandsMeta[command].description : ''; return { name: command + options, description: description }; }) }]; return generateCommandDocsHelper(body, header, 'Options', 'name'); } function getCommandOptionsAsString() { var command = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var options = ''; (command.options || []).forEach(function (option) { options += option.required ? '<' + option.name + '> ' : '[' + option.name + '] '; }); return options; } /** * 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(_ref9, _ref10, command, name) { var settings = _ref9.settings; var _ref10$commands = _ref10.commands; var commands = _ref10$commands === undefined ? {} : _ref10$commands; var meta = _ref10.settings; var rows = []; rows.push('Usage: ' + name + ' ' + command + ' ' + getCommandOptionsAsString(commands[command])); rows.push(''); if (commands[command] && commands[command].help) { rows.push(_redent2['default'](_trimNewlines2['default'](commands[command].help))); rows.push(''); } var body = []; // Generate the options table if (commands[command] && commands[command].settings) { rows.push('Options:'); rows.push(''); var filter = commands[command].settings === true ? [] : commands[command].settings; body = _documentationBuildDocumentationObject2['default'](settings, meta, filter); } var header = { cli: true, description: { name: 'Description', padding: false }, defaultValue: { name: 'Default', renderer: function renderer(input) { input = _documentationHelpers.getDefaultValue(input); if (input === undefined) { return ''; } if (!input) { return _helpersStyle.warning('No default value'); } return _chalk2['default'].cyan(input); } } }; rows.push(generateCommandDocsHelper(body, header, 'CLI options', 'cli')); return rows.join('\n'); } function generateCommandDocsHelper(body, header, options, name) { var _ref2, _ref3, _ref4, _ref5, _ref6; body.push({ name: options, objects: [(_ref2 = {}, _ref2[name] = '-h, --help', _ref2.description = 'Output usage information.', _ref2), (_ref3 = {}, _ref3[name] = '-v, --version', _ref3.description = 'Output version number.', _ref3), (_ref4 = {}, _ref4[name] = '-d, --debug', _ref4.description = 'Enable debug mode.', _ref4), (_ref5 = {}, _ref5[name] = '-c, --config', _ref5.description = 'Path to configuration file, will default to ' + _chalk2['default'].bold('roc.config.js') + ' in current ' + 'working directory.', _ref5), (_ref6 = {}, _ref6[name] = '-D, --directory', _ref6.description = 'Path to working directory, will default to the current working directory. Can be either ' + 'absolute or relative.', _ref6)] }); return _documentationGenerateTable2['default'](body, header, { compact: true, titleWrapper: function titleWrapper(input) { return input + ':'; }, cellDivider: '', rowWrapper: function rowWrapper(input) { return '' + input; }, header: false, groupTitleWrapper: function groupTitleWrapper(input) { return input + ':'; } }); } /** * Parses options and validates them. * * @param {string} command - The command to parse options for. * @param {Object} commands - commands from {@link rocMetaConfig}. * @param {Object[]} options - Options parsed by minimist. * * @returns {Object} - Parsed options. * @property {object[]} options - The parsed options that was matched against the meta configuration for the command. * @property {object[]} rest - The rest of the options that could not be matched against the configuration. */ function parseOptions(command, commands, options) { // If the command supports options if (commands[command] && commands[command].options) { var _ret = (function () { var parsedOptions = {}; commands[command].options.forEach(function (option, index) { var value = options[index]; if (option.required && !value) { throw new Error('Required option "' + option.name + '" was not provided.'); } if (value && option.validation) { var validationResult = _validation.isValid(value, option.validation); if (validationResult !== true) { try { _validation.throwError(option.name, validationResult, value, 'option'); } catch (err) { /* eslint-disable no-process-exit, no-console */ console.log(_helpersStyle.errorLabel('Arguments problem') + ' An option was not valid.\n'); console.log(err.message); process.exit(1); /* eslint-enable */ } } } parsedOptions[option.name] = value; }); return { v: { options: parsedOptions, rest: options.splice(Object.keys(parsedOptions).length) } }; })(); if (typeof _ret === 'object') return _ret.v; } return { options: undefined, rest: options }; } /** * Creates mappings between cli commands to their "path" in the configuration structure, their validator and type * convertor. * * @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(documentationObject) { var recursiveHelper = function recursiveHelper(groups) { var mappings = {}; groups.forEach(function (group) { group.objects.forEach(function (element) { // Remove the two dashes in the beginning to match correctly mappings[element.cli.substr(2)] = { name: element.cli, path: element.path, convertor: getConvertor(element.defaultValue, element.cli), validator: element.validator }; }); mappings = Object.assign({}, mappings, recursiveHelper(group.children)); }); return mappings; }; return recursiveHelper(documentationObject); } // Convert values based on their default value function getConvertor(value, name) { if (_lodash.isBoolean(value)) { return function (input) { if (_lodash.isBoolean(input)) { return input; } if (input === 'true' || input === 'false') { return input === 'true'; } console.log(_helpersStyle.warningLabel('Invalid value given for ' + _chalk2['default'].bold(name) + '.'), 'Will use the default ' + _chalk2['default'].bold(value) + '.'); return value; }; } else if (Array.isArray(value)) { return function (input) { var parsed = undefined; try { parsed = JSON.parse(input); } catch (err) { // Ignore this case } if (Array.isArray(parsed)) { return parsed; } return input.toString().split(','); }; } else if (Number.isInteger(value)) { return function (input) { return parseInt(input, 10); }; } else if (!_lodash.isString(value) && (!value || Object.keys(value).length === 0)) { return function (input) { return JSON.parse(input); }; } return function (input) { return input; }; } /** * Converts a set of arguments to {@link rocConfigSettings} object. * * @param {Object} args - Arguments parsed from minimist. * @param {Object} mappings - Result from {@link getMappings}. * * @returns {Object} - The mapped Roc configuration settings object. */ function parseArguments(args, mappings) { var config = {}; var info = []; Object.keys(args).forEach(function (key) { if (mappings[key]) { var value = convert(args[key], mappings[key]); _lodash.set(config, mappings[key].path, value); } else { // We did not find a match info.push(getSuggestions([key], Object.keys(mappings), true)); } }); if (info.length > 0) { console.log(_helpersStyle.errorLabel('CLI problem'), 'Some commands were not understood.\n'); console.log(info.join('\n') + '\n'); } return config; } function convert(value, mapping) { var val = mapping.convertor(value); var validationResult = _validation.isValid(val, mapping.validator); if (validationResult === true) { return val; } console.log(_helpersStyle.warning('There was a problem when trying to automatically convert ' + _chalk2['default'].bold(mapping.name) + '. This ' + 'value will be ignored.')); console.log('Received ' + _chalk2['default'].underline(value) + ' and it was converted to ' + _chalk2['default'].underline(val) + '.', validationResult, '\n'); } //# sourceMappingURL=helpers.js.map