@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 CHANGED
@@ -6,9 +6,10 @@ const {
6
6
  const {
7
7
  assign,
8
8
  entries
9
- } = Object; // Recursively walks a schema and collects defaults. When no schema is provided,
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'; // public config API
5
+ import getDefaults from './defaults.js';
6
6
 
7
- export { load, search, validate, addSchema, migrate, addMigration, getDefaults, merge, normalize, stringify }; // export the namespace by default
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'; // Loaded configuration file cache
8
+ import { inspect, normalize } from './utils/index.js';
9
9
 
10
- export const cache = new Map(); // The cosmiconfig explorer used to load config files
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
- }); // Searches within a provided directory, or loads the provided config path
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
- } // Finds and loads a config file using cosmiconfig, merges it with optional
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'); // load config or reload cached 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
- } // normalize and merge with overrides then validate
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
- } // merge with defaults
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'; // Global set of registered migrations
2
+ import { get, set, del, map, joinPropertyPath, normalize } from './utils/index.js';
3
3
 
4
- const migrations = new Map(); // Register a migration function for the main config schema by default
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
- } // Clear all migration functions
17
+ }
17
18
 
19
+ // Clear all migration functions
18
20
  export function clearMigrations() {
19
21
  migrations.clear();
20
22
  addMigration(defaultMigration);
21
- } // The default config migration
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
- } // Migrate util for deprecated options
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
- } // Calls each registered migration function with a normalize provided config
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
- } // normalize again to adjust for migrations
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;
@@ -6,23 +6,25 @@ const {
6
6
  } = Number;
7
7
  const {
8
8
  entries
9
- } = Object; // Creates an empty object or array
9
+ } = Object;
10
10
 
11
+ // Creates an empty object or array
11
12
  function create(array) {
12
13
  return array ? [] : {};
13
- } // Returns true or false if a subject has iterable keys or not
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
- } // Returns true if the provided key looks like an array key
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
- } // Split a property path string by dot or array notation
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
- } // Join an array of path parts into a single path string
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
- } // Gets a value in the object at the path
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
- } // Sets a value to the object at the path creating any necessary nested
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
- } // Deletes properties from an object at the paths
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
- } // Maps a value from one path to another, deleting the first path
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
- } // Steps through an object's properties calling the function with the path and value of each
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
- } // Recursively mutate and filter empty values from arrays and objects
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
- } // Merges source values and returns a new merged value. The map function will be called with a
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]; // maybe map the property path and/or value
138
+ let prev = (_ctx = ctx) === null || _ctx === void 0 ? void 0 : _ctx[key];
140
139
 
141
- let [mapped, next] = (map === null || map === void 0 ? void 0 : map(path, prev, value)) || []; // update the context and path if changed
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
- } // adjust path to concat array values when necessary
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
- } // delete prev values
154
-
153
+ }
155
154
 
155
+ // delete prev values
156
156
  if (next === null || next == null && value === null) {
157
157
  del(target, path);
158
- } // set the next or default value if there is one
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
- } // do not recurse mapped objects
164
-
163
+ }
165
164
 
165
+ // do not recurse mapped objects
166
166
  return next === undefined;
167
167
  });
168
168
  return target;
@@ -1,53 +1,55 @@
1
1
  import merge from './merge.js';
2
- import { getSchema } from '../validate.js'; // Edge case camelizations
2
+ import { getSchema } from '../validate.js';
3
3
 
4
- const CAMELCASE_MAP = new Map([['css', 'CSS'], ['javascript', 'JavaScript']]); // Regular expression that matches words from boundaries or consecutive casing
4
+ // Edge case camelizations
5
+ const CAMELCASE_MAP = new Map([['css', 'CSS'], ['javascript', 'JavaScript']]);
5
6
 
6
- const WORD_REG = /[a-z]{2,}|[A-Z]{2,}|[0-9]{2,}|[^-_\s]+?(?=[A-Z0-9-_\s]|$)/g; // Converts kebab-cased and snake_cased strings to camelCase.
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
- } // Coverts camelCased and snake_cased strings to kebab-case.
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
- } // Coverts kebab-case and camelCased strings to snake_case.
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
- } // Removes undefined empty values and renames kebab-case properties to camelCase. Optionally
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 = []; // skip normalizing paths of class instances
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
  }
@@ -1,27 +1,26 @@
1
1
  import util from 'util';
2
2
  import YAML from 'yaml';
3
- import getDefaults from '../defaults.js'; // Provides native util.inspect with common options for printing configs.
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
- } // Converts a config to a yaml, json, or js string. When no config is provided,
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; // AJV manages and validates schemas.
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
- }); // Returns a new default schema.
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
- } // Gets the schema object from the AJV schema. If a path is provided, an array of schemas is
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); // parse and work with one key at a time
81
+ root ?? (root = schema);
82
82
 
83
- let [key, ...rest] = path = parsePropertyPath(path); // if the desired schema is one of many, we need to find the best match
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
- return (// the best possible match will match most of the path or loosely match
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
- } // Adds schemas to the config schema's properties. The config schema is removed, modified, and
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
- } // Resets the schema by removing all schemas and inserting a new default schema.
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
- } // Adds "a" or "an" to a word for readability.
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
- } // Default errors anywhere within these keywords can be confusing
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
- } // Validates data according to the associated schema and returns a list of errors, if any.
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; // generate a custom error message
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
- } // fix paths
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
- } // fix invalid data
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
- } // joined for error messages
226
-
222
+ }
227
223
 
228
- path = joinPropertyPath(path); // map one error per path
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
- } // filter empty values as a result of scrubbing
235
-
232
+ }
236
233
 
237
- filterEmpty(data); // return an array of errors
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.12.0",
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.12.0",
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": "4303b74df91f60e36065141289d2ef2277d1d6fc"
45
+ "gitHead": "d2e812d14aa446fa580ffa75144a6280627b5a27"
46
46
  }