roc
Version:
Build modern web applications easily
552 lines (443 loc) • 20 kB
JavaScript
;
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