configorama 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +116 -0
- package/lib/main.js +419 -481
- package/lib/parsers/yaml.js +30 -18
- package/lib/parsers/yaml.test.js +169 -0
- package/lib/resolvers/valueFromEnv.js +10 -0
- package/lib/resolvers/valueFromGit.js +95 -1
- package/lib/resolvers/valueFromNumber.js +2 -2
- package/lib/utils/PromiseTracker.js +10 -8
- package/lib/utils/arrayToJsonPath.js +11 -0
- package/lib/utils/cloudformationSchema.js +10 -6
- package/lib/utils/find-project-root.js +25 -0
- package/lib/utils/formatFunctionArgs.js +2 -2
- package/lib/utils/handleSignalEvents.js +17 -14
- package/lib/utils/isValidValue.js +2 -2
- package/lib/utils/lodash.js +91 -0
- package/lib/utils/mergeByKeys.js +29 -0
- package/lib/utils/parse.js +62 -0
- package/lib/utils/replaceAll.js +16 -0
- package/lib/utils/splitByComma.js +7 -2
- package/lib/utils/splitCsv.js +29 -0
- package/lib/utils/textUtils.js +31 -0
- package/lib/utils/unknownValues.js +46 -0
- package/lib/utils/variableUtils.js +52 -0
- package/package.json +30 -19
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const isArray = require('lodash.isarray')
|
|
2
|
+
const isString = require('lodash.isstring')
|
|
3
|
+
const isNumber = require('lodash.isnumber')
|
|
4
|
+
const isObject = require('lodash.isobject')
|
|
5
|
+
const isDate = require('lodash.isdate')
|
|
6
|
+
const isRegExp = require('lodash.isregexp')
|
|
7
|
+
const isFunction = require('lodash.isfunction')
|
|
8
|
+
const isEmpty = require('lodash.isempty')
|
|
9
|
+
const camelCase = require('lodash.camelcase')
|
|
10
|
+
const kebabCase = require('lodash.kebabcase')
|
|
11
|
+
const capitalize = require('lodash.capitalize')
|
|
12
|
+
const split = require('lodash.split')
|
|
13
|
+
const map = require('lodash.map')
|
|
14
|
+
const mapValues = require('lodash.mapvalues')
|
|
15
|
+
const assign = require('lodash.assign')
|
|
16
|
+
const cloneDeep = require('lodash.clonedeep')
|
|
17
|
+
|
|
18
|
+
// Custom implementation of lodash.set
|
|
19
|
+
function set(object, path, value) {
|
|
20
|
+
if (object === null || typeof object !== 'object') {
|
|
21
|
+
return object;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const keys = Array.isArray(path) ? path : String(path)
|
|
25
|
+
.split('.')
|
|
26
|
+
.map(key => {
|
|
27
|
+
const numKey = Number(key);
|
|
28
|
+
return Number.isInteger(numKey) && numKey >= 0 ? numKey : key;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
let current = object;
|
|
32
|
+
const lastIndex = keys.length - 1;
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < lastIndex; i++) {
|
|
35
|
+
const key = keys[i];
|
|
36
|
+
|
|
37
|
+
if (current[key] === undefined) {
|
|
38
|
+
// Create appropriate container based on next key type
|
|
39
|
+
current[key] = Number.isInteger(keys[i + 1]) && keys[i + 1] >= 0 ? [] : {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
current = current[key];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
current[keys[lastIndex]] = value;
|
|
46
|
+
return object;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Custom implementation of lodash.trim
|
|
50
|
+
function trim(string, chars) {
|
|
51
|
+
if (string === null || string === undefined) {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
string = String(string);
|
|
56
|
+
|
|
57
|
+
if (!chars && String.prototype.trim) {
|
|
58
|
+
return string.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!chars) {
|
|
62
|
+
// Default characters to trim (whitespace)
|
|
63
|
+
chars = ' \t\n\r\f\v\u00a0\u1680\u2000\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create a regex pattern with the characters to trim
|
|
67
|
+
const pattern = new RegExp(`^[${chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}]+|[${chars.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')}]+$`, 'g');
|
|
68
|
+
|
|
69
|
+
return string.replace(pattern, '');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
isArray,
|
|
74
|
+
isString,
|
|
75
|
+
isNumber,
|
|
76
|
+
isObject,
|
|
77
|
+
isDate,
|
|
78
|
+
isRegExp,
|
|
79
|
+
isFunction,
|
|
80
|
+
isEmpty,
|
|
81
|
+
trim,
|
|
82
|
+
camelCase,
|
|
83
|
+
kebabCase,
|
|
84
|
+
capitalize,
|
|
85
|
+
split,
|
|
86
|
+
map,
|
|
87
|
+
mapValues,
|
|
88
|
+
assign,
|
|
89
|
+
set,
|
|
90
|
+
cloneDeep,
|
|
91
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge objects by specified keys
|
|
3
|
+
*/
|
|
4
|
+
function mergeByKeys(data, path, keysToMerge) {
|
|
5
|
+
if (!data) return {}
|
|
6
|
+
|
|
7
|
+
const items = path.split('.').reduce((obj, key) => obj?.[key], data)
|
|
8
|
+
if (!Array.isArray(items)) return {}
|
|
9
|
+
|
|
10
|
+
const result = {}
|
|
11
|
+
const mergeAll = !keysToMerge || !Array.isArray(keysToMerge) || keysToMerge.length === 0
|
|
12
|
+
|
|
13
|
+
for (const item of items) {
|
|
14
|
+
const key = Object.keys(item)[0]
|
|
15
|
+
|
|
16
|
+
if (mergeAll || keysToMerge.includes(key)) {
|
|
17
|
+
if (!result[key]) {
|
|
18
|
+
result[key] = Object.assign({}, item[key])
|
|
19
|
+
} else {
|
|
20
|
+
result[key] = Object.assign({}, result[key], item[key])
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
result[key] = item[key]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { mergeByKeys }
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const YAML = require('../parsers/yaml')
|
|
2
|
+
const TOML = require('../parsers/toml')
|
|
3
|
+
const cloudFormationSchema = require('./cloudformationSchema')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse file contents based on file extension
|
|
7
|
+
* @param {string} fileContents - Raw file contents to parse
|
|
8
|
+
* @param {string} fileType - File extension (.yml, .yaml, .json, etc)
|
|
9
|
+
* @param {string} filePath - Full file path (used for error messages)
|
|
10
|
+
* @param {RegExp} varRegex - Variable syntax regex
|
|
11
|
+
* @param {Object} opts - Additional options
|
|
12
|
+
* @returns {Object} Parsed configuration object
|
|
13
|
+
*/
|
|
14
|
+
function parseFileContents(fileContents, fileType, filePath, varRegex, opts = {}) {
|
|
15
|
+
let configObject
|
|
16
|
+
|
|
17
|
+
if (fileType.match(/\.(yml|yaml)/)) {
|
|
18
|
+
try {
|
|
19
|
+
const ymlText = YAML.preProcess(fileContents, varRegex)
|
|
20
|
+
configObject = YAML.parse(ymlText)
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// Attempt to fix cloudformation refs
|
|
23
|
+
if (err.message.match(/YAMLException/)) {
|
|
24
|
+
const ymlText = YAML.preProcess(fileContents, varRegex)
|
|
25
|
+
const result = YAML.load(ymlText, {
|
|
26
|
+
filename: filePath,
|
|
27
|
+
schema: cloudFormationSchema.schema,
|
|
28
|
+
})
|
|
29
|
+
if (result.error) {
|
|
30
|
+
throw result.error
|
|
31
|
+
}
|
|
32
|
+
configObject = result.data
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else if (fileType.match(/\.(toml)/)) {
|
|
36
|
+
configObject = TOML.parse(fileContents)
|
|
37
|
+
} else if (fileType.match(/\.(json)/)) {
|
|
38
|
+
configObject = JSON.parse(fileContents)
|
|
39
|
+
} else if (fileType.match(/\.(js)/)) {
|
|
40
|
+
let jsFile
|
|
41
|
+
try {
|
|
42
|
+
jsFile = require(filePath)
|
|
43
|
+
if (typeof jsFile !== 'function') {
|
|
44
|
+
configObject = jsFile
|
|
45
|
+
} else {
|
|
46
|
+
let jsArgs = opts.dynamicArgs || {}
|
|
47
|
+
if (jsArgs && typeof jsArgs === 'function') {
|
|
48
|
+
jsArgs = jsArgs()
|
|
49
|
+
}
|
|
50
|
+
configObject = jsFile(jsArgs)
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(err)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return configObject
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
parseFileContents
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const REPLACE_PATTERN = /([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|<>\-\&])/g
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Replace all occurrences of a string while handling regex special characters
|
|
5
|
+
* @param {string} replaceThis - String to replace
|
|
6
|
+
* @param {string} withThis - Replacement string
|
|
7
|
+
* @param {string} inThis - Source string
|
|
8
|
+
* @returns {string} String with all replacements made
|
|
9
|
+
*/
|
|
10
|
+
function replaceAll(replaceThis, withThis, inThis) {
|
|
11
|
+
withThis = withThis.replace(/\$/g, '$$$$')
|
|
12
|
+
const pat = new RegExp(replaceThis.replace(REPLACE_PATTERN, '\\$&'), 'g')
|
|
13
|
+
return inThis.replace(pat, withThis)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { replaceAll }
|
|
@@ -11,9 +11,10 @@ const stringRefSyntax = match
|
|
|
11
11
|
=> ["env:BAZ", "'defaultEnvValue'"]
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
// https://regex101.com/r/4uPmpt/1
|
|
14
15
|
const commasOutsideOfParens = /(?!<(?:\(|\[)[^)\]]+),(?![^(\[]+(?:\)|\]))/
|
|
15
16
|
// const commasOutOfParens = /(?!(?:\()[^)\]]+),(?![^(\[]+(?:\)))/g
|
|
16
|
-
|
|
17
|
+
function splitByComma(string) {
|
|
17
18
|
// If comma is not outside of parens, return early,
|
|
18
19
|
// for file(file.js, param, paramTwo) & function support
|
|
19
20
|
// TODO clean this up
|
|
@@ -40,7 +41,7 @@ module.exports = function splitByComma(string) {
|
|
|
40
41
|
while (match) {
|
|
41
42
|
const matchContained = contained(match)
|
|
42
43
|
const containedBy = stringMatches.find(matchContained)
|
|
43
|
-
if (!containedBy) { // if
|
|
44
|
+
if (!containedBy) { // if un-contained, this comma represents a splitting location
|
|
44
45
|
commaReplacements.push({
|
|
45
46
|
start: match.index,
|
|
46
47
|
end: overwriteSyntax.lastIndex,
|
|
@@ -64,3 +65,7 @@ module.exports = function splitByComma(string) {
|
|
|
64
65
|
results.push(input.slice(prior))
|
|
65
66
|
return results
|
|
66
67
|
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
splitByComma
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a string by comma while preserving quoted content
|
|
3
|
+
* @param {string} str - String to split
|
|
4
|
+
* @param {string} splitter - Optional custom splitter (defaults to ',')
|
|
5
|
+
* @returns {string[]} Array of split strings
|
|
6
|
+
*/
|
|
7
|
+
function splitCsv(str, splitter) {
|
|
8
|
+
const splitSyntax = splitter || ','
|
|
9
|
+
// Split at comma SPACE ", "
|
|
10
|
+
return str.split(splitSyntax).reduce(
|
|
11
|
+
(accum, curr) => {
|
|
12
|
+
if (accum.isConcatting) {
|
|
13
|
+
accum.soFar[accum.soFar.length - 1] += ',' + curr
|
|
14
|
+
} else {
|
|
15
|
+
accum.soFar.push(curr)
|
|
16
|
+
}
|
|
17
|
+
if (curr.split('"').length % 2 == 0) {
|
|
18
|
+
accum.isConcatting = !accum.isConcatting
|
|
19
|
+
}
|
|
20
|
+
return accum
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
soFar: [],
|
|
24
|
+
isConcatting: false,
|
|
25
|
+
},
|
|
26
|
+
).soFar
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { splitCsv }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get text after first occurrence of search string
|
|
3
|
+
* @param {string} str - Source string
|
|
4
|
+
* @param {string} search - String to search for
|
|
5
|
+
* @returns {string} Text after search string or empty string if not found
|
|
6
|
+
*/
|
|
7
|
+
function getTextAfterOccurance(str, search) {
|
|
8
|
+
const index = str.indexOf(search)
|
|
9
|
+
if (index === -1) return ''
|
|
10
|
+
return str.substring(index)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Find nested variable in split array that exists in original source
|
|
15
|
+
* @param {string[]} split - Array of potential variables
|
|
16
|
+
* @param {string} originalSource - Original source string
|
|
17
|
+
* @returns {string|undefined} Found nested variable or undefined
|
|
18
|
+
*/
|
|
19
|
+
function findNestedVariable(split, originalSource) {
|
|
20
|
+
return split.find((thing) => {
|
|
21
|
+
if (originalSource && typeof originalSource === 'string') {
|
|
22
|
+
return originalSource.indexOf(`\${${thing}}`) > -1
|
|
23
|
+
}
|
|
24
|
+
return false
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
getTextAfterOccurance,
|
|
30
|
+
findNestedVariable
|
|
31
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode unknown variable for passthrough
|
|
3
|
+
*/
|
|
4
|
+
function encodeUnknown(v) {
|
|
5
|
+
return `>passthrough[_[${Buffer.from(v).toString('base64')}]_]`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decode unknown variable from passthrough
|
|
10
|
+
*/
|
|
11
|
+
function decodeUnknown(rawValue) {
|
|
12
|
+
const x = findUnknownValues(rawValue)
|
|
13
|
+
let val = rawValue.replace(/>passthrough/g, '')
|
|
14
|
+
if (x.length) {
|
|
15
|
+
x.forEach(({ match, value }) => {
|
|
16
|
+
const decodedValue = Buffer.from(value, 'base64').toString('ascii')
|
|
17
|
+
val = val.replace(match, decodedValue)
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return val
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Find base64 encoded unknown values in text
|
|
25
|
+
*/
|
|
26
|
+
function findUnknownValues(text) {
|
|
27
|
+
const base64WrapperRegex = /\[_\[([A-Za-z0-9+/=\s]*)\]_\]/g
|
|
28
|
+
let matches
|
|
29
|
+
const links = []
|
|
30
|
+
while ((matches = base64WrapperRegex.exec(text)) !== null) {
|
|
31
|
+
if (matches.index === base64WrapperRegex.lastIndex) {
|
|
32
|
+
base64WrapperRegex.lastIndex++
|
|
33
|
+
}
|
|
34
|
+
links.push({
|
|
35
|
+
match: matches[0],
|
|
36
|
+
value: matches[1],
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
return links
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
encodeUnknown,
|
|
44
|
+
decodeUnknown,
|
|
45
|
+
findUnknownValues
|
|
46
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get fallback variable string
|
|
3
|
+
* @param {string[]} split - Array from split at comma
|
|
4
|
+
* @param {string} nestedVar - Fallback variable to reconstruct variable string from
|
|
5
|
+
* @returns {string} New ${variable, string}
|
|
6
|
+
*/
|
|
7
|
+
function getFallbackString(split, nestedVar) {
|
|
8
|
+
let isSet = false
|
|
9
|
+
const newVar = split
|
|
10
|
+
.reduce((acc, curr) => {
|
|
11
|
+
if (curr === nestedVar || isSet) {
|
|
12
|
+
acc = acc.concat(curr)
|
|
13
|
+
isSet = true
|
|
14
|
+
}
|
|
15
|
+
return acc
|
|
16
|
+
}, [])
|
|
17
|
+
.join(', ')
|
|
18
|
+
const cleanC = `\${${newVar.replace(/^\${/, '').replace(/}$/, '')}}`
|
|
19
|
+
return cleanC
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Verify if variable string is valid
|
|
24
|
+
*/
|
|
25
|
+
function verifyVariable(variableString, valueObject, variableTypes, config) {
|
|
26
|
+
const isRealVariable = variableTypes.some((r) => {
|
|
27
|
+
if (r.match instanceof RegExp && variableString.match(r.match)) {
|
|
28
|
+
return true
|
|
29
|
+
} else if (typeof r.match === 'function') {
|
|
30
|
+
if (r.match(variableString, config, valueObject)) {
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (!isRealVariable && variableString.match(/:/)) {
|
|
38
|
+
throw new Error(`
|
|
39
|
+
Variable \${${variableString}} is invalid variable syntax.
|
|
40
|
+
Value Path: ${valueObject.path ? valueObject.path.join('.') : 'na'}
|
|
41
|
+
Original Value: ${valueObject.originalSource}
|
|
42
|
+
|
|
43
|
+
Remove or update the \${${variableString}} to fix
|
|
44
|
+
`)
|
|
45
|
+
}
|
|
46
|
+
return isRealVariable
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
getFallbackString,
|
|
51
|
+
verifyVariable
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configorama",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Variable support for configuration files",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
7
|
+
"cli.js",
|
|
7
8
|
"lib",
|
|
8
9
|
"package.json",
|
|
9
10
|
"package-lock.json",
|
|
10
11
|
"README.md"
|
|
11
12
|
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"configorama": "./cli.js"
|
|
15
|
+
},
|
|
12
16
|
"scripts": {
|
|
13
17
|
"docs": "node ./scripts/docs.js",
|
|
14
|
-
"test": "
|
|
15
|
-
"test:api": "
|
|
16
|
-
"
|
|
18
|
+
"test": "npm run testlib && uvu tests \".*\\.test.js$\" ",
|
|
19
|
+
"test:api": "uvu tests/api api.test.js",
|
|
20
|
+
"testlib": "uvu lib \".*\\.test.js$\"",
|
|
21
|
+
"watch": "watchlist tests -- npm test",
|
|
17
22
|
"publish": "git push origin && git push origin --tags",
|
|
18
23
|
"release:patch": "npm version patch && npm publish",
|
|
19
24
|
"release:minor": "npm version minor && npm publish",
|
|
@@ -33,26 +38,32 @@
|
|
|
33
38
|
"git-url-parse": "^14.0.0",
|
|
34
39
|
"js-yaml": "^3.14.1",
|
|
35
40
|
"json5": "^2.2.3",
|
|
36
|
-
"lodash": "^4.
|
|
41
|
+
"lodash.isarray": "^4.0.0",
|
|
42
|
+
"lodash.isstring": "^4.0.1",
|
|
43
|
+
"lodash.isnumber": "^3.0.3",
|
|
44
|
+
"lodash.isobject": "^3.0.2",
|
|
45
|
+
"lodash.isdate": "^4.0.1",
|
|
46
|
+
"lodash.isregexp": "^4.0.1",
|
|
47
|
+
"lodash.isfunction": "^3.0.9",
|
|
48
|
+
"lodash.isempty": "^4.4.0",
|
|
49
|
+
"lodash.camelcase": "^4.3.0",
|
|
50
|
+
"lodash.kebabcase": "^4.1.1",
|
|
51
|
+
"lodash.capitalize": "^4.2.1",
|
|
52
|
+
"lodash.split": "^4.4.2",
|
|
53
|
+
"lodash.map": "^4.6.0",
|
|
54
|
+
"lodash.mapvalues": "^4.6.0",
|
|
55
|
+
"lodash.assign": "^4.2.0",
|
|
56
|
+
"lodash.clonedeep": "^4.5.0",
|
|
57
|
+
"lodash.includes": "^4.3.0",
|
|
58
|
+
"lodash.flatten": "^4.4.0",
|
|
37
59
|
"promise.prototype.finally": "^3.1.8",
|
|
38
|
-
"replaceall": "^0.1.6",
|
|
39
60
|
"sync-rpc": "^1.3.6",
|
|
40
61
|
"traverse": "^0.6.8"
|
|
41
62
|
},
|
|
42
63
|
"devDependencies": {
|
|
43
|
-
"ava": "^2.4.0",
|
|
44
64
|
"markdown-magic": "^2.6.1",
|
|
45
|
-
"minimist": "^1.2.8"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"files": [
|
|
49
|
-
"tests/**/*.test.js"
|
|
50
|
-
],
|
|
51
|
-
"sources": [
|
|
52
|
-
"**/*.{js,jsx}",
|
|
53
|
-
"*.yml",
|
|
54
|
-
"**/*.yml"
|
|
55
|
-
],
|
|
56
|
-
"verbose": true
|
|
65
|
+
"minimist": "^1.2.8",
|
|
66
|
+
"uvu": "^0.5.6",
|
|
67
|
+
"watchlist": "^0.3.1"
|
|
57
68
|
}
|
|
58
69
|
}
|