@percy/config 1.12.0 → 1.13.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/dist/defaults.js +3 -3
- package/dist/index.js +4 -2
- package/dist/load.js +15 -18
- package/dist/migrate.js +15 -16
- package/dist/utils/merge.js +38 -38
- package/dist/utils/normalize.js +17 -15
- package/dist/utils/stringify.js +5 -6
- package/dist/validate.js +38 -39
- package/package.json +3 -3
package/dist/defaults.js
CHANGED
|
@@ -6,9 +6,10 @@ const {
|
|
|
6
6
|
const {
|
|
7
7
|
assign,
|
|
8
8
|
entries
|
|
9
|
-
} = Object;
|
|
10
|
-
// the default config schema is used.
|
|
9
|
+
} = Object;
|
|
11
10
|
|
|
11
|
+
// Recursively walks a schema and collects defaults. When no schema is provided,
|
|
12
|
+
// the default config schema is used.
|
|
12
13
|
function getDefaultsFromSchema(schema) {
|
|
13
14
|
if (!schema || typeof schema.$ref === 'string') {
|
|
14
15
|
// get the schema from ajv
|
|
@@ -28,7 +29,6 @@ function getDefaultsFromSchema(schema) {
|
|
|
28
29
|
return undefined;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
|
|
32
32
|
export function getDefaults(overrides = {}) {
|
|
33
33
|
return merge([getDefaultsFromSchema(), overrides], (path, prev, next) => {
|
|
34
34
|
// override default array instead of merging
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,10 @@ import load, { search } from './load.js';
|
|
|
2
2
|
import validate, { addSchema } from './validate.js';
|
|
3
3
|
import migrate, { addMigration } from './migrate.js';
|
|
4
4
|
import { merge, normalize, stringify } from './utils/index.js';
|
|
5
|
-
import getDefaults from './defaults.js';
|
|
5
|
+
import getDefaults from './defaults.js';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// public config API
|
|
8
|
+
export { load, search, validate, addSchema, migrate, addMigration, getDefaults, merge, normalize, stringify };
|
|
8
9
|
|
|
10
|
+
// export the namespace by default
|
|
9
11
|
export * as default from './index.js';
|
package/dist/load.js
CHANGED
|
@@ -5,15 +5,18 @@ import logger from '@percy/logger';
|
|
|
5
5
|
import migrate from './migrate.js';
|
|
6
6
|
import validate from './validate.js';
|
|
7
7
|
import getDefaults from './defaults.js';
|
|
8
|
-
import { inspect, normalize } from './utils/index.js';
|
|
8
|
+
import { inspect, normalize } from './utils/index.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Loaded configuration file cache
|
|
11
|
+
export const cache = new Map();
|
|
11
12
|
|
|
13
|
+
// The cosmiconfig explorer used to load config files
|
|
12
14
|
export const explorer = cosmiconfigSync('percy', {
|
|
13
15
|
cache: false,
|
|
14
16
|
searchPlaces: ['package.json', '.percyrc', '.percy.json', '.percy.yaml', '.percy.yml', '.percy.js', '.percy.cjs', 'percy.config.js', 'percy.config.cjs']
|
|
15
|
-
});
|
|
17
|
+
});
|
|
16
18
|
|
|
19
|
+
// Searches within a provided directory, or loads the provided config path
|
|
17
20
|
export function search(path) {
|
|
18
21
|
try {
|
|
19
22
|
let result = path && !fs.statSync(path).isDirectory() ? explorer.load(path) : explorer.search(path);
|
|
@@ -21,7 +24,9 @@ export function search(path) {
|
|
|
21
24
|
} catch (error) {
|
|
22
25
|
if (error.code === 'ENOENT') return {};else throw error;
|
|
23
26
|
}
|
|
24
|
-
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Finds and loads a config file using cosmiconfig, merges it with optional
|
|
25
30
|
// inputs, validates the combined config according to the schema, and returns
|
|
26
31
|
// the combined config. Loaded config files are cached and reused on next load,
|
|
27
32
|
// unless `reload` is true in which the file will be reloaded and the cache
|
|
@@ -29,7 +34,6 @@ export function search(path) {
|
|
|
29
34
|
// unless `bail` is true. Supports kebab-case and camelCase config options and
|
|
30
35
|
// always returns camelCase options. Will automatically convert older config
|
|
31
36
|
// versions to the latest version while printing a warning.
|
|
32
|
-
|
|
33
37
|
export function load({
|
|
34
38
|
path,
|
|
35
39
|
overrides = {},
|
|
@@ -38,21 +42,19 @@ export function load({
|
|
|
38
42
|
print = false
|
|
39
43
|
} = {}) {
|
|
40
44
|
var _Array$from;
|
|
41
|
-
|
|
42
45
|
// load cached config; when no path is specified, get the last config cached
|
|
43
46
|
let config = path ? cache.get(path) : (_Array$from = Array.from(cache)[cache.size - 1]) === null || _Array$from === void 0 ? void 0 : _Array$from[1];
|
|
44
47
|
let infoDebug = print ? 'info' : 'debug';
|
|
45
48
|
let errorDebug = print ? 'error' : 'debug';
|
|
46
|
-
let log = logger('config');
|
|
49
|
+
let log = logger('config');
|
|
47
50
|
|
|
51
|
+
// load config or reload cached config
|
|
48
52
|
if (path !== false && (!config || reload)) {
|
|
49
53
|
try {
|
|
50
54
|
let result = search(path);
|
|
51
|
-
|
|
52
55
|
if (result !== null && result !== void 0 && result.config) {
|
|
53
56
|
log[infoDebug](`Found config file: ${relative('', result.filepath)}`);
|
|
54
57
|
let version = parseInt(result.config.version, 10);
|
|
55
|
-
|
|
56
58
|
if (Number.isNaN(version)) {
|
|
57
59
|
log.warn('Ignoring config file - missing or invalid version');
|
|
58
60
|
} else if (version > 2) {
|
|
@@ -61,7 +63,6 @@ export function load({
|
|
|
61
63
|
if (version < 2) {
|
|
62
64
|
log.warn('Found older config file version, please run ' + '`percy config:migrate` to update to the latest version');
|
|
63
65
|
}
|
|
64
|
-
|
|
65
66
|
config = migrate(result.config);
|
|
66
67
|
cache.set(path, config);
|
|
67
68
|
}
|
|
@@ -73,28 +74,24 @@ export function load({
|
|
|
73
74
|
log[errorDebug](error);
|
|
74
75
|
if (bail) return;
|
|
75
76
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
77
|
+
}
|
|
78
78
|
|
|
79
|
+
// normalize and merge with overrides then validate
|
|
79
80
|
config = normalize(config, {
|
|
80
81
|
overrides,
|
|
81
82
|
schema: '/config'
|
|
82
83
|
});
|
|
83
84
|
let errors = config && validate(config);
|
|
84
|
-
|
|
85
85
|
if (errors) {
|
|
86
86
|
log.warn('Invalid config:');
|
|
87
|
-
|
|
88
87
|
for (let e of errors) log.warn(`- ${e.path}: ${e.message}`);
|
|
89
|
-
|
|
90
88
|
if (bail) return;
|
|
91
89
|
}
|
|
92
|
-
|
|
93
90
|
if (path !== false && config) {
|
|
94
91
|
log[infoDebug](`Using config:\n${inspect(config)}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
92
|
+
}
|
|
97
93
|
|
|
94
|
+
// merge with defaults
|
|
98
95
|
return getDefaults(config);
|
|
99
96
|
}
|
|
100
97
|
export default load;
|
package/dist/migrate.js
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
import logger from '@percy/logger';
|
|
2
|
-
import { get, set, del, map, joinPropertyPath, normalize } from './utils/index.js';
|
|
2
|
+
import { get, set, del, map, joinPropertyPath, normalize } from './utils/index.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Global set of registered migrations
|
|
5
|
+
const migrations = new Map();
|
|
5
6
|
|
|
7
|
+
// Register a migration function for the main config schema by default
|
|
6
8
|
export function addMigration(migration, schema = '/config') {
|
|
7
9
|
if (Array.isArray(migration)) {
|
|
8
10
|
return migration.map(m => addMigration(m, schema));
|
|
9
11
|
} else if (typeof migration !== 'function') {
|
|
10
12
|
return Object.entries(migration).map(([s, m]) => addMigration(m, s));
|
|
11
13
|
}
|
|
12
|
-
|
|
13
14
|
if (!migrations.has(schema)) migrations.set(schema, []);
|
|
14
15
|
migrations.get(schema).unshift(migration);
|
|
15
16
|
return migration;
|
|
16
|
-
}
|
|
17
|
+
}
|
|
17
18
|
|
|
19
|
+
// Clear all migration functions
|
|
18
20
|
export function clearMigrations() {
|
|
19
21
|
migrations.clear();
|
|
20
22
|
addMigration(defaultMigration);
|
|
21
|
-
}
|
|
23
|
+
}
|
|
22
24
|
|
|
25
|
+
// The default config migration
|
|
23
26
|
addMigration(defaultMigration);
|
|
24
|
-
|
|
25
27
|
function defaultMigration(config, {
|
|
26
28
|
set
|
|
27
29
|
}) {
|
|
28
30
|
if (config.version !== 2) set('version', 2);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
+
}
|
|
31
32
|
|
|
33
|
+
// Migrate util for deprecated options
|
|
32
34
|
function deprecate(config, log, path, options) {
|
|
33
35
|
if (get(config, path) == null) return;
|
|
34
36
|
let {
|
|
@@ -43,15 +45,14 @@ function deprecate(config, log, path, options) {
|
|
|
43
45
|
let message = 'The ' + [type ? `${type} option \`${name}\`` : `\`${name}\` option`, `will be removed in ${ver || 'a future release'}.`, to ? `Use \`${to}\` instead.` : alt || ''].join(' ').trim();
|
|
44
46
|
if (warn) log.warn(`Warning: ${message}`);else log.deprecated(message);
|
|
45
47
|
return to ? map(config, path, to) : config;
|
|
46
|
-
}
|
|
47
|
-
// and util functions for working with the config object
|
|
48
|
-
|
|
48
|
+
}
|
|
49
49
|
|
|
50
|
+
// Calls each registered migration function with a normalize provided config
|
|
51
|
+
// and util functions for working with the config object
|
|
50
52
|
export function migrate(config, schema = '/config') {
|
|
51
53
|
config = normalize(config, {
|
|
52
54
|
schema
|
|
53
55
|
}) ?? {};
|
|
54
|
-
|
|
55
56
|
if (migrations.has(schema)) {
|
|
56
57
|
let log = logger('config');
|
|
57
58
|
let util = {
|
|
@@ -61,17 +62,15 @@ export function migrate(config, schema = '/config') {
|
|
|
61
62
|
del: del.bind(null, config),
|
|
62
63
|
log
|
|
63
64
|
};
|
|
64
|
-
|
|
65
65
|
for (let migration of migrations.get(schema)) {
|
|
66
66
|
migration(config, util);
|
|
67
|
-
}
|
|
68
|
-
|
|
67
|
+
}
|
|
69
68
|
|
|
69
|
+
// normalize again to adjust for migrations
|
|
70
70
|
config = normalize(config, {
|
|
71
71
|
schema
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
|
|
75
74
|
return config;
|
|
76
75
|
}
|
|
77
76
|
export default migrate;
|
package/dist/utils/merge.js
CHANGED
|
@@ -6,23 +6,25 @@ const {
|
|
|
6
6
|
} = Number;
|
|
7
7
|
const {
|
|
8
8
|
entries
|
|
9
|
-
} = Object;
|
|
9
|
+
} = Object;
|
|
10
10
|
|
|
11
|
+
// Creates an empty object or array
|
|
11
12
|
function create(array) {
|
|
12
13
|
return array ? [] : {};
|
|
13
|
-
}
|
|
14
|
-
|
|
14
|
+
}
|
|
15
15
|
|
|
16
|
+
// Returns true or false if a subject has iterable keys or not
|
|
16
17
|
function hasKeys(subj) {
|
|
17
18
|
return isArray(subj) || Object.getPrototypeOf(subj) === Object.prototype;
|
|
18
|
-
}
|
|
19
|
-
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
+
// Returns true if the provided key looks like an array key
|
|
21
22
|
const ARRAY_PATH_KEY_REG = /^(\[\d+]|0|[1-9]\d*)$/;
|
|
22
23
|
export function isArrayKey(key) {
|
|
23
24
|
return isInteger(key) || ARRAY_PATH_KEY_REG.test(key);
|
|
24
|
-
}
|
|
25
|
+
}
|
|
25
26
|
|
|
27
|
+
// Split a property path string by dot or array notation
|
|
26
28
|
export function parsePropertyPath(path) {
|
|
27
29
|
return isArray(path) ? path : path.split('.').reduce((full, part) => {
|
|
28
30
|
return full.concat(part.split('[').reduce((f, p) => {
|
|
@@ -30,25 +32,25 @@ export function parsePropertyPath(path) {
|
|
|
30
32
|
return f.concat(isArrayKey(p) ? parseInt(p, 10) : p || []);
|
|
31
33
|
}, []));
|
|
32
34
|
}, []);
|
|
33
|
-
}
|
|
35
|
+
}
|
|
34
36
|
|
|
37
|
+
// Join an array of path parts into a single path string
|
|
35
38
|
export function joinPropertyPath(path) {
|
|
36
39
|
path = !Array.isArray(path) ? path : path.filter(Boolean).map(k => isArrayKey(k) ? `[${k}]` : `.${k}`).join('');
|
|
37
|
-
|
|
38
40
|
while ((_path = path) !== null && _path !== void 0 && _path.startsWith('.')) {
|
|
39
41
|
var _path;
|
|
40
|
-
|
|
41
42
|
path = path.substr(1);
|
|
42
43
|
}
|
|
43
|
-
|
|
44
44
|
return path;
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
+
// Gets a value in the object at the path
|
|
47
48
|
export function get(object, path, find) {
|
|
48
49
|
return parsePropertyPath(path).reduce((target, key) => target === null || target === void 0 ? void 0 : target[key], object);
|
|
49
|
-
}
|
|
50
|
-
// objects or arrays along the way
|
|
50
|
+
}
|
|
51
51
|
|
|
52
|
+
// Sets a value to the object at the path creating any necessary nested
|
|
53
|
+
// objects or arrays along the way
|
|
52
54
|
export function set(object, path, value) {
|
|
53
55
|
return parsePropertyPath(path).reduce((target, key, index, path) => {
|
|
54
56
|
if (index < path.length - 1) {
|
|
@@ -59,8 +61,9 @@ export function set(object, path, value) {
|
|
|
59
61
|
return object;
|
|
60
62
|
}
|
|
61
63
|
}, object);
|
|
62
|
-
}
|
|
64
|
+
}
|
|
63
65
|
|
|
66
|
+
// Deletes properties from an object at the paths
|
|
64
67
|
export function del(object, ...paths) {
|
|
65
68
|
return paths.reduce((object, path) => {
|
|
66
69
|
return parsePropertyPath(path).reduce((target, key, index, path) => {
|
|
@@ -72,36 +75,34 @@ export function del(object, ...paths) {
|
|
|
72
75
|
}
|
|
73
76
|
}, object);
|
|
74
77
|
}, object);
|
|
75
|
-
}
|
|
78
|
+
}
|
|
76
79
|
|
|
80
|
+
// Maps a value from one path to another, deleting the first path
|
|
77
81
|
export function map(object, from, to, transform = v => v) {
|
|
78
82
|
return set(object, to, transform(parsePropertyPath(from).reduce((target, key, index, path) => {
|
|
79
83
|
let value = target === null || target === void 0 ? void 0 : target[key];
|
|
80
|
-
|
|
81
84
|
if (index === path.length - 1) {
|
|
82
85
|
target === null || target === void 0 ? true : delete target[key];
|
|
83
86
|
}
|
|
84
|
-
|
|
85
87
|
return value;
|
|
86
88
|
}, object)));
|
|
87
|
-
}
|
|
89
|
+
}
|
|
88
90
|
|
|
91
|
+
// Steps through an object's properties calling the function with the path and value of each
|
|
89
92
|
function walk(object, fn, path = [], visited = new Set()) {
|
|
90
93
|
if (path.length && fn([...path], object) === false) return;
|
|
91
94
|
if (visited.has(object)) return;
|
|
92
95
|
visited.add(object);
|
|
93
|
-
|
|
94
96
|
if (object != null && typeof object === 'object') {
|
|
95
97
|
let isArrayObject = isArray(object);
|
|
96
|
-
|
|
97
98
|
for (let [key, value] of entries(object)) {
|
|
98
99
|
if (isArrayObject) key = parseInt(key, 10);
|
|
99
100
|
walk(value, fn, [...path, key], new Set(visited));
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
103
|
+
}
|
|
104
104
|
|
|
105
|
+
// Recursively mutate and filter empty values from arrays and objects
|
|
105
106
|
export function filterEmpty(subject) {
|
|
106
107
|
if (typeof subject === 'object') {
|
|
107
108
|
if (isArray(subject)) {
|
|
@@ -110,7 +111,6 @@ export function filterEmpty(subject) {
|
|
|
110
111
|
subject.splice(i--, 1);
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
|
-
|
|
114
114
|
return subject.length > 0;
|
|
115
115
|
} else {
|
|
116
116
|
for (let k in subject) {
|
|
@@ -118,51 +118,51 @@ export function filterEmpty(subject) {
|
|
|
118
118
|
delete subject[k];
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
-
|
|
122
121
|
return entries(subject).length > 0;
|
|
123
122
|
}
|
|
124
123
|
} else {
|
|
125
124
|
return subject != null;
|
|
126
125
|
}
|
|
127
|
-
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Merges source values and returns a new merged value. The map function will be called with a
|
|
128
129
|
// property's path, previous value, and next value; it should return an array containing any
|
|
129
130
|
// replacement path and value; when a replacement value not defined, values will be merged.
|
|
130
|
-
|
|
131
131
|
export function merge(sources, map) {
|
|
132
132
|
return sources.reduce((target, source, i) => {
|
|
133
133
|
let isSourceArray = isArray(source);
|
|
134
134
|
walk(source, (path, value) => {
|
|
135
135
|
var _ctx;
|
|
136
|
-
|
|
137
136
|
let ctx = get(target, path.slice(0, -1));
|
|
138
137
|
let key = path[path.length - 1];
|
|
139
|
-
let prev = (_ctx = ctx) === null || _ctx === void 0 ? void 0 : _ctx[key];
|
|
138
|
+
let prev = (_ctx = ctx) === null || _ctx === void 0 ? void 0 : _ctx[key];
|
|
140
139
|
|
|
141
|
-
|
|
140
|
+
// maybe map the property path and/or value
|
|
141
|
+
let [mapped, next] = (map === null || map === void 0 ? void 0 : map(path, prev, value)) || [];
|
|
142
142
|
|
|
143
|
+
// update the context and path if changed
|
|
143
144
|
if (mapped !== null && mapped !== void 0 && mapped.some((m, i) => m !== path[i])) {
|
|
144
145
|
ctx = get(target, mapped.slice(0, -1));
|
|
145
146
|
path = [...mapped];
|
|
146
|
-
}
|
|
147
|
-
|
|
147
|
+
}
|
|
148
148
|
|
|
149
|
+
// adjust path to concat array values when necessary
|
|
149
150
|
if (next !== null && (isArray(ctx) || isInteger(key))) {
|
|
150
151
|
var _ctx2;
|
|
151
|
-
|
|
152
152
|
path.splice(-1, 1, ((_ctx2 = ctx) === null || _ctx2 === void 0 ? void 0 : _ctx2.length) ?? 0);
|
|
153
|
-
}
|
|
154
|
-
|
|
153
|
+
}
|
|
155
154
|
|
|
155
|
+
// delete prev values
|
|
156
156
|
if (next === null || next == null && value === null) {
|
|
157
157
|
del(target, path);
|
|
158
|
-
}
|
|
159
|
-
|
|
158
|
+
}
|
|
160
159
|
|
|
160
|
+
// set the next or default value if there is one
|
|
161
161
|
if (next != null || next !== null && value != null && !hasKeys(value)) {
|
|
162
162
|
set(target ?? (target = create(isSourceArray)), path, next ?? value);
|
|
163
|
-
}
|
|
164
|
-
|
|
163
|
+
}
|
|
165
164
|
|
|
165
|
+
// do not recurse mapped objects
|
|
166
166
|
return next === undefined;
|
|
167
167
|
});
|
|
168
168
|
return target;
|
package/dist/utils/normalize.js
CHANGED
|
@@ -1,53 +1,55 @@
|
|
|
1
1
|
import merge from './merge.js';
|
|
2
|
-
import { getSchema } from '../validate.js';
|
|
2
|
+
import { getSchema } from '../validate.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Edge case camelizations
|
|
5
|
+
const CAMELCASE_MAP = new Map([['css', 'CSS'], ['javascript', 'JavaScript']]);
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// Regular expression that matches words from boundaries or consecutive casing
|
|
8
|
+
const WORD_REG = /[a-z]{2,}|[A-Z]{2,}|[0-9]{2,}|[^-_\s]+?(?=[A-Z0-9-_\s]|$)/g;
|
|
7
9
|
|
|
10
|
+
// Converts kebab-cased and snake_cased strings to camelCase.
|
|
8
11
|
export function camelcase(str) {
|
|
9
12
|
if (typeof str !== 'string') return str;
|
|
10
13
|
return str.match(WORD_REG).reduce((s, w, i) => s + (i ? CAMELCASE_MAP.get(w.toLowerCase()) || w[0].toUpperCase() + w.slice(1).toLowerCase() : w.toLowerCase()), '');
|
|
11
|
-
}
|
|
14
|
+
}
|
|
12
15
|
|
|
16
|
+
// Coverts camelCased and snake_cased strings to kebab-case.
|
|
13
17
|
export function kebabcase(str) {
|
|
14
18
|
if (typeof str !== 'string') return str;
|
|
15
19
|
return Array.from(CAMELCASE_MAP).reduce((str, [word, camel]) => str.replace(camel, `-${word}`), str).match(WORD_REG).join('-').toLowerCase();
|
|
16
|
-
}
|
|
20
|
+
}
|
|
17
21
|
|
|
22
|
+
// Coverts kebab-case and camelCased strings to snake_case.
|
|
18
23
|
export function snakecase(str) {
|
|
19
24
|
if (typeof str !== 'string') return str;
|
|
20
25
|
return Array.from(CAMELCASE_MAP).reduce((str, [word, camel]) => str.replace(camel, `_${word}`), str).match(WORD_REG).join('_').toLowerCase();
|
|
21
|
-
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Removes undefined empty values and renames kebab-case properties to camelCase. Optionally
|
|
22
29
|
// allows deep merging with options.overrides, converting keys to kebab-case with options.kebab,
|
|
23
30
|
// and normalizing against a schema with options.schema.
|
|
24
|
-
|
|
25
31
|
export function normalize(object, options) {
|
|
26
32
|
var _options, _options2, _options3;
|
|
27
|
-
|
|
28
33
|
if (typeof options === 'string') options = {
|
|
29
34
|
schema: options
|
|
30
35
|
};
|
|
31
36
|
let keycase = (_options = options) !== null && _options !== void 0 && _options.kebab ? kebabcase : (_options2 = options) !== null && _options2 !== void 0 && _options2.snake ? snakecase : camelcase;
|
|
32
37
|
return merge([object, (_options3 = options) === null || _options3 === void 0 ? void 0 : _options3.overrides], (path, value) => {
|
|
33
38
|
var _options4, _schemas$shift;
|
|
34
|
-
|
|
35
39
|
let schemas = getSchema((_options4 = options) === null || _options4 === void 0 ? void 0 : _options4.schema, path.map(camelcase));
|
|
36
40
|
let skip = ((_schemas$shift = schemas.shift()) === null || _schemas$shift === void 0 ? void 0 : _schemas$shift.normalize) === false;
|
|
37
|
-
let mapped = [];
|
|
41
|
+
let mapped = [];
|
|
38
42
|
|
|
43
|
+
// skip normalizing paths of class instances
|
|
39
44
|
if (!skip && typeof value === 'object' && value !== null && value !== void 0 && value.constructor) {
|
|
40
45
|
skip = Object.getPrototypeOf(value) !== Object.prototype;
|
|
41
46
|
}
|
|
42
|
-
|
|
43
47
|
for (let [i, k] of path.entries()) {
|
|
44
|
-
var _options5, _options5$skip, _schemas$i;
|
|
45
|
-
|
|
46
|
-
skip || (skip = (_options5 = options) === null || _options5 === void 0 ? void 0 : (_options5$skip = _options5.skip) === null || _options5$skip === void 0 ? void 0 : _options5$skip.call(_options5, mapped.concat(k)));
|
|
48
|
+
var _options5, _options5$skip, _options6, _schemas$i;
|
|
49
|
+
skip || (skip = (_options5 = options) === null || _options5 === void 0 ? void 0 : (_options5$skip = (_options6 = _options5).skip) === null || _options5$skip === void 0 ? void 0 : _options5$skip.call(_options6, mapped.concat(k)));
|
|
47
50
|
mapped.push(skip ? k : keycase(k));
|
|
48
51
|
skip || (skip = ((_schemas$i = schemas[i]) === null || _schemas$i === void 0 ? void 0 : _schemas$i.normalize) === false);
|
|
49
52
|
}
|
|
50
|
-
|
|
51
53
|
return [mapped];
|
|
52
54
|
});
|
|
53
55
|
}
|
package/dist/utils/stringify.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import util from 'util';
|
|
2
2
|
import YAML from 'yaml';
|
|
3
|
-
import getDefaults from '../defaults.js';
|
|
3
|
+
import getDefaults from '../defaults.js';
|
|
4
4
|
|
|
5
|
+
// Provides native util.inspect with common options for printing configs.
|
|
5
6
|
export function inspect(config) {
|
|
6
7
|
return util.inspect(config, {
|
|
7
8
|
depth: null,
|
|
8
9
|
compact: false
|
|
9
10
|
});
|
|
10
|
-
}
|
|
11
|
-
// falls back to schema defaults.
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
+
// Converts a config to a yaml, json, or js string. When no config is provided,
|
|
14
|
+
// falls back to schema defaults.
|
|
13
15
|
export function stringify(format, config = getDefaults()) {
|
|
14
16
|
switch (format) {
|
|
15
17
|
case 'yml':
|
|
16
18
|
case 'yaml':
|
|
17
19
|
return YAML.stringify(config);
|
|
18
|
-
|
|
19
20
|
case 'json':
|
|
20
21
|
return JSON.stringify(config, null, 2) + '\n';
|
|
21
|
-
|
|
22
22
|
case 'js':
|
|
23
23
|
return `module.exports = ${inspect(config)}\n`;
|
|
24
|
-
|
|
25
24
|
default:
|
|
26
25
|
throw new Error(`Unsupported format: ${format}`);
|
|
27
26
|
}
|
package/dist/validate.js
CHANGED
|
@@ -6,8 +6,9 @@ const {
|
|
|
6
6
|
const {
|
|
7
7
|
assign,
|
|
8
8
|
entries
|
|
9
|
-
} = Object;
|
|
9
|
+
} = Object;
|
|
10
10
|
|
|
11
|
+
// AJV manages and validates schemas.
|
|
11
12
|
const ajv = new AJV({
|
|
12
13
|
strict: false,
|
|
13
14
|
verbose: true,
|
|
@@ -43,7 +44,6 @@ const ajv = new AJV({
|
|
|
43
44
|
gen,
|
|
44
45
|
schema
|
|
45
46
|
} = cxt;
|
|
46
|
-
|
|
47
47
|
for (let prop of schema) {
|
|
48
48
|
gen.if(AJV._`${data}.${AJV._([prop])} !== undefined`, () => {
|
|
49
49
|
cxt.setParams({
|
|
@@ -54,8 +54,9 @@ const ajv = new AJV({
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}]
|
|
57
|
-
});
|
|
57
|
+
});
|
|
58
58
|
|
|
59
|
+
// Returns a new default schema.
|
|
59
60
|
function getDefaultSchema() {
|
|
60
61
|
return {
|
|
61
62
|
$id: '/config',
|
|
@@ -68,28 +69,28 @@ function getDefaultSchema() {
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
|
-
}
|
|
72
|
-
// returned, with each index representing the schema of each part of the path (index zero is root).
|
|
73
|
-
|
|
72
|
+
}
|
|
74
73
|
|
|
74
|
+
// Gets the schema object from the AJV schema. If a path is provided, an array of schemas is
|
|
75
|
+
// returned, with each index representing the schema of each part of the path (index zero is root).
|
|
75
76
|
export function getSchema(name, path, root) {
|
|
76
77
|
var _ajv$getSchema, _ref, _schema$properties;
|
|
77
|
-
|
|
78
78
|
// get the root schema if necessary, resolve it, and return it when there is no path
|
|
79
79
|
let schema = typeof name === 'string' ? (_ajv$getSchema = ajv.getSchema(name)) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema : name;
|
|
80
80
|
if (!schema || !path) return schema ?? [];
|
|
81
|
-
root ?? (root = schema);
|
|
81
|
+
root ?? (root = schema);
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
// parse and work with one key at a time
|
|
84
|
+
let [key, ...rest] = path = parsePropertyPath(path);
|
|
84
85
|
|
|
86
|
+
// if the desired schema is one of many, we need to find the best match
|
|
85
87
|
let many = (_ref = isArray(schema) ? schema : schema === null || schema === void 0 ? void 0 : schema[['anyOf', 'oneOf', 'allOf'].find(p => schema[p])]) === null || _ref === void 0 ? void 0 : _ref.map(p => getSchema(p, path, root)).sort((a, b) => {
|
|
86
88
|
var _a$;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
return (
|
|
90
|
+
// the best possible match will match most of the path or loosely match
|
|
89
91
|
b.length - a.length || (((_a$ = a[0]) === null || _a$ === void 0 ? void 0 : _a$.type) === 'object' && (a[0].additionalProperties !== false || a[0].unevaluatedProperties !== false) ? -1 : 1)
|
|
90
92
|
);
|
|
91
93
|
})[0];
|
|
92
|
-
|
|
93
94
|
if (many !== null && many !== void 0 && many.length) {
|
|
94
95
|
return many;
|
|
95
96
|
} else if ((schema === null || schema === void 0 ? void 0 : schema.type) === 'array' && isArrayKey(key)) {
|
|
@@ -109,16 +110,16 @@ export function getSchema(name, path, root) {
|
|
|
109
110
|
// no match
|
|
110
111
|
return [];
|
|
111
112
|
}
|
|
112
|
-
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Adds schemas to the config schema's properties. The config schema is removed, modified, and
|
|
113
116
|
// replaced after the new schemas are added to clear any compiled caches. Existing schemas are
|
|
114
117
|
// removed and replaced as well. If a schema has an existing $id, the schema will not be added
|
|
115
118
|
// as config schema properties.
|
|
116
|
-
|
|
117
119
|
export function addSchema(schemas) {
|
|
118
120
|
if (isArray(schemas)) {
|
|
119
121
|
return schemas.map(addSchema);
|
|
120
122
|
}
|
|
121
|
-
|
|
122
123
|
if (schemas.$id) {
|
|
123
124
|
let {
|
|
124
125
|
$id
|
|
@@ -126,10 +127,8 @@ export function addSchema(schemas) {
|
|
|
126
127
|
if (ajv.getSchema($id)) ajv.removeSchema($id);
|
|
127
128
|
return ajv.addSchema(schemas);
|
|
128
129
|
}
|
|
129
|
-
|
|
130
130
|
let config = getSchema('/config');
|
|
131
131
|
ajv.removeSchema('/config');
|
|
132
|
-
|
|
133
132
|
for (let [key, schema] of entries(schemas)) {
|
|
134
133
|
if (key === '$config') {
|
|
135
134
|
assign(config, typeof schema === 'function' ? schema(config) : schema);
|
|
@@ -144,42 +143,39 @@ export function addSchema(schemas) {
|
|
|
144
143
|
ajv.addSchema(schema, $id);
|
|
145
144
|
}
|
|
146
145
|
}
|
|
147
|
-
|
|
148
146
|
return ajv.addSchema(config, '/config');
|
|
149
|
-
}
|
|
147
|
+
}
|
|
150
148
|
|
|
149
|
+
// Resets the schema by removing all schemas and inserting a new default schema.
|
|
151
150
|
export function resetSchema() {
|
|
152
151
|
ajv.removeSchema();
|
|
153
152
|
ajv.addSchema(getDefaultSchema(), '/config');
|
|
154
|
-
}
|
|
153
|
+
}
|
|
155
154
|
|
|
155
|
+
// Adds "a" or "an" to a word for readability.
|
|
156
156
|
function a(word) {
|
|
157
157
|
if (word === 'undefined' || word === 'null') return word;
|
|
158
158
|
return `${'aeiou'.includes(word[0]) ? 'an' : 'a'} ${word}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
159
|
+
}
|
|
161
160
|
|
|
161
|
+
// Default errors anywhere within these keywords can be confusing
|
|
162
162
|
const HIDE_NESTED_KEYWORDS = ['oneOf', 'anyOf', 'allOf', 'if', 'then', 'else', 'not'];
|
|
163
|
-
|
|
164
163
|
function shouldHideError(key, path, error) {
|
|
165
164
|
var _parentSchema$errors;
|
|
166
|
-
|
|
167
165
|
let {
|
|
168
166
|
parentSchema,
|
|
169
167
|
keyword,
|
|
170
168
|
schemaPath
|
|
171
169
|
} = error;
|
|
172
170
|
return !(parentSchema.error || (_parentSchema$errors = parentSchema.errors) !== null && _parentSchema$errors !== void 0 && _parentSchema$errors[keyword]) && HIDE_NESTED_KEYWORDS.some(k => schemaPath.includes(`/${k}`));
|
|
173
|
-
}
|
|
174
|
-
|
|
171
|
+
}
|
|
175
172
|
|
|
173
|
+
// Validates data according to the associated schema and returns a list of errors, if any.
|
|
176
174
|
export function validate(data, key = '/config') {
|
|
177
175
|
if (!ajv.validate(key, data)) {
|
|
178
176
|
let errors = new Map();
|
|
179
|
-
|
|
180
177
|
for (let error of ajv.errors) {
|
|
181
178
|
var _parentSchema$errors2;
|
|
182
|
-
|
|
183
179
|
let {
|
|
184
180
|
instancePath,
|
|
185
181
|
parentSchema,
|
|
@@ -188,8 +184,9 @@ export function validate(data, key = '/config') {
|
|
|
188
184
|
params
|
|
189
185
|
} = error;
|
|
190
186
|
let path = instancePath ? instancePath.substr(1).split('/') : [];
|
|
191
|
-
if (shouldHideError(key, path, error)) continue;
|
|
187
|
+
if (shouldHideError(key, path, error)) continue;
|
|
192
188
|
|
|
189
|
+
// generate a custom error message
|
|
193
190
|
if (parentSchema.error || (_parentSchema$errors2 = parentSchema.errors) !== null && _parentSchema$errors2 !== void 0 && _parentSchema$errors2[keyword]) {
|
|
194
191
|
let custom = parentSchema.error || parentSchema.errors[keyword];
|
|
195
192
|
message = typeof custom === 'function' ? custom(error) : custom;
|
|
@@ -200,9 +197,9 @@ export function validate(data, key = '/config') {
|
|
|
200
197
|
message = 'missing required property';
|
|
201
198
|
} else if (keyword === 'additionalProperties' || keyword === 'unevaluatedProperties') {
|
|
202
199
|
message = 'unknown property';
|
|
203
|
-
}
|
|
204
|
-
|
|
200
|
+
}
|
|
205
201
|
|
|
202
|
+
// fix paths
|
|
206
203
|
if (params.missingProperty) {
|
|
207
204
|
path.push(params.missingProperty);
|
|
208
205
|
} else if (params.additionalProperty) {
|
|
@@ -211,9 +208,9 @@ export function validate(data, key = '/config') {
|
|
|
211
208
|
path.push(params.unevaluatedProperty);
|
|
212
209
|
} else if (params.disallowedProperty) {
|
|
213
210
|
path.push(params.disallowedProperty);
|
|
214
|
-
}
|
|
215
|
-
|
|
211
|
+
}
|
|
216
212
|
|
|
213
|
+
// fix invalid data
|
|
217
214
|
if (keyword === 'minimum') {
|
|
218
215
|
set(data, path, Math.max(error.data, error.schema));
|
|
219
216
|
} else if (keyword === 'maximum') {
|
|
@@ -222,20 +219,22 @@ export function validate(data, key = '/config') {
|
|
|
222
219
|
del(data, path.slice(0, -1));
|
|
223
220
|
} else if (!params.passingSchemas) {
|
|
224
221
|
del(data, path);
|
|
225
|
-
}
|
|
226
|
-
|
|
222
|
+
}
|
|
227
223
|
|
|
228
|
-
|
|
224
|
+
// joined for error messages
|
|
225
|
+
path = joinPropertyPath(path);
|
|
229
226
|
|
|
227
|
+
// map one error per path
|
|
230
228
|
errors.set(path, {
|
|
231
229
|
path,
|
|
232
230
|
message
|
|
233
231
|
});
|
|
234
|
-
}
|
|
235
|
-
|
|
232
|
+
}
|
|
236
233
|
|
|
237
|
-
|
|
234
|
+
// filter empty values as a result of scrubbing
|
|
235
|
+
filterEmpty(data);
|
|
238
236
|
|
|
237
|
+
// return an array of errors
|
|
239
238
|
return Array.from(errors.values());
|
|
240
239
|
}
|
|
241
240
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test:types": "tsd"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@percy/logger": "1.
|
|
37
|
+
"@percy/logger": "1.13.0",
|
|
38
38
|
"ajv": "^8.6.2",
|
|
39
39
|
"cosmiconfig": "^7.0.0",
|
|
40
40
|
"yaml": "^2.0.0"
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"json-schema-typed": "^7.0.3"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "d2e812d14aa446fa580ffa75144a6280627b5a27"
|
|
46
46
|
}
|