UNPKG

5.54 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.default = sanitizeConfig;
5
6var _fs = _interopRequireDefault(require("fs"));
7
8var _path = _interopRequireDefault(require("path"));
9
10var _castArray = _interopRequireDefault(require("lodash/castArray"));
11
12var _isBoolean = _interopRequireDefault(require("lodash/isBoolean"));
13
14var _isFunction = _interopRequireDefault(require("lodash/isFunction"));
15
16var _isPlainObject = _interopRequireDefault(require("lodash/isPlainObject"));
17
18var _isString = _interopRequireDefault(require("lodash/isString"));
19
20var _isFinite = _interopRequireDefault(require("lodash/isFinite"));
21
22var _map = _interopRequireDefault(require("lodash/map"));
23
24var _listify = _interopRequireDefault(require("listify"));
25
26var _kleur = _interopRequireDefault(require("kleur"));
27
28var _fastestLevenshtein = require("fastest-levenshtein");
29
30var _typeDetect = _interopRequireDefault(require("type-detect"));
31
32var _glogg = _interopRequireDefault(require("glogg"));
33
34var _qI = require("q-i");
35
36var _error = _interopRequireDefault(require("./error"));
37
38function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
39
40const logger = (0, _glogg.default)('rsg');
41const typeCheckers = {
42 number: _isFinite.default,
43 string: _isString.default,
44 boolean: _isBoolean.default,
45 array: Array.isArray,
46 function: _isFunction.default,
47 object: _isPlainObject.default,
48 'file path': _isString.default,
49 'existing file path': _isString.default,
50 'directory path': _isString.default,
51 'existing directory path': _isString.default
52};
53
54const typesList = types => (0, _listify.default)(types, {
55 finalWord: 'or'
56});
57
58const shouldBeFile = types => types.some(type => type.includes('file'));
59
60const shouldBeDirectory = types => types.some(type => type.includes('directory'));
61
62const shouldExist = types => types.some(type => type.includes('existing'));
63
64function isDirectory(path) {
65 try {
66 return _fs.default.lstatSync(path).isDirectory();
67 } catch (e) {
68 if (e.code !== 'ENOENT') {
69 throw e;
70 }
71
72 return false;
73 }
74}
75/**
76 * Validates and normalizes config.
77 *
78 * @param {object} config
79 * @param {object} schema
80 * @param {string} rootDir
81 * @return {object}
82 */
83
84
85function sanitizeConfig(config, schema, rootDir) {
86 // Check for unknown fields
87 (0, _map.default)(config, (value, keyAny) => {
88 const key = keyAny;
89
90 if (!schema[key]) {
91 // Try to guess
92 const possibleOptions = Object.keys(schema);
93 const suggestedOption = possibleOptions.reduce((suggestion, option) => {
94 const steps = (0, _fastestLevenshtein.distance)(option, key);
95
96 if (steps < 2) {
97 return option;
98 }
99
100 return suggestion;
101 }, '');
102 throw new _error.default(`Unknown config option ${_kleur.default.bold(key)} was found, the value is:\n` + (0, _qI.stringify)(value) + (suggestedOption ? `\n\nDid you mean ${_kleur.default.bold(suggestedOption)}?` : ''), suggestedOption);
103 }
104 }); // Check all fields
105
106 const safeConfig = {};
107 (0, _map.default)(schema, (props, keyAny) => {
108 const key = keyAny;
109 let value = config[key]; // Custom processing
110
111 if (props.process) {
112 value = props.process(value, config, rootDir);
113 }
114
115 if (value === undefined) {
116 // Default value
117 value = props.default; // Check if the field is required
118
119 const isRequired = (0, _isFunction.default)(props.required) ? props.required(config) : props.required;
120
121 if (isRequired) {
122 const message = (0, _isString.default)(isRequired) ? isRequired : `${_kleur.default.bold(key)} config option is required.`;
123 throw new _error.default(message, key);
124 }
125 } else if (props.deprecated) {
126 logger.warn(`${key} config option is deprecated. ${props.deprecated}`);
127 } else if (props.removed) {
128 throw new _error.default(`${_kleur.default.bold(key)} config option was removed. ${props.removed}`);
129 }
130
131 if (value !== undefined && props.type) {
132 const types = (0, _castArray.default)(props.type); // Check type
133
134 const hasRightType = types.some(type => {
135 if (!typeCheckers[type]) {
136 throw new _error.default(`Wrong type ${_kleur.default.bold(type)} specified for ${_kleur.default.bold(key)} in schema.`);
137 }
138
139 return typeCheckers[type](value);
140 });
141
142 if (!hasRightType) {
143 const exampleValue = props.example || props.default;
144 const example = {};
145
146 if (exampleValue) {
147 example[key] = exampleValue;
148 }
149
150 const exampleText = exampleValue ? `
151Example:
152
153${(0, _qI.stringify)(example)}` : '';
154 throw new _error.default(`${_kleur.default.bold(key)} config option should be ${typesList(types)}, received ${(0, _typeDetect.default)(value)}.\n${exampleText}`, key);
155 } // Absolutize paths
156
157
158 if ((0, _isString.default)(value) && (shouldBeFile(types) || shouldBeDirectory(types))) {
159 value = _path.default.resolve(rootDir, value); // Check for existence
160
161 if (shouldExist(types)) {
162 if (shouldBeFile(types) && !_fs.default.existsSync(value)) {
163 throw new _error.default(`A file specified in ${_kleur.default.bold(key)} config option does not exist:\n${value}`, key);
164 }
165
166 if (shouldBeDirectory(types) && !isDirectory(value)) {
167 throw new _error.default(`A directory specified in ${_kleur.default.bold(key)} config option does not exist:\n${value}`, key);
168 }
169 }
170 }
171 }
172
173 safeConfig[keyAny] = value;
174 });
175 return safeConfig;
176}
\No newline at end of file