@percy/config 1.0.3 → 1.0.6

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.
@@ -86,15 +86,17 @@ export function map(object, from, to, transform = v => v) {
86
86
  }, object)));
87
87
  } // Steps through an object's properties calling the function with the path and value of each
88
88
 
89
- function walk(object, fn, path = []) {
89
+ function walk(object, fn, path = [], visited = new Set()) {
90
90
  if (path.length && fn([...path], object) === false) return;
91
+ if (visited.has(object)) return;
92
+ visited.add(object);
91
93
 
92
94
  if (object != null && typeof object === 'object') {
93
95
  let isArrayObject = isArray(object);
94
96
 
95
97
  for (let [key, value] of entries(object)) {
96
98
  if (isArrayObject) key = parseInt(key, 10);
97
- walk(value, fn, [...path, key]);
99
+ walk(value, fn, [...path, key], new Set(visited));
98
100
  }
99
101
  }
100
102
  } // Recursively mutate and filter empty values from arrays and objects
@@ -24,11 +24,16 @@ export function normalize(object, options) {
24
24
  schema: options
25
25
  };
26
26
  let keycase = (_options = options) !== null && _options !== void 0 && _options.kebab ? kebabcase : camelcase;
27
- return merge([object, (_options2 = options) === null || _options2 === void 0 ? void 0 : _options2.overrides], path => {
28
- var _options3, _schemas$shift;
27
+ return merge([object, (_options2 = options) === null || _options2 === void 0 ? void 0 : _options2.overrides], (path, value) => {
28
+ var _options3, _schemas$shift, _options4, _options4$skip;
29
29
 
30
30
  let schemas = getSchema((_options3 = options) === null || _options3 === void 0 ? void 0 : _options3.schema, path.map(camelcase));
31
- let skip = ((_schemas$shift = schemas.shift()) === null || _schemas$shift === void 0 ? void 0 : _schemas$shift.normalize) === false;
31
+ let skip = ((_schemas$shift = schemas.shift()) === null || _schemas$shift === void 0 ? void 0 : _schemas$shift.normalize) === false || ((_options4 = options) === null || _options4 === void 0 ? void 0 : (_options4$skip = _options4.skip) === null || _options4$skip === void 0 ? void 0 : _options4$skip.call(_options4, path, value)); // skip normalizing paths of class instances
32
+
33
+ if (!skip && typeof value === 'object' && value !== null && value !== void 0 && value.constructor) {
34
+ skip = Object.getPrototypeOf(value) !== Object.prototype;
35
+ }
36
+
32
37
  path = path.map((k, i) => {
33
38
  var _schemas$i;
34
39
 
package/dist/validate.js CHANGED
@@ -165,7 +165,7 @@ function shouldHideError(key, path, error) {
165
165
  keyword,
166
166
  schemaPath
167
167
  } = error;
168
- return !(parentSchema.error || (_parentSchema$errors = parentSchema.errors) !== null && _parentSchema$errors !== void 0 && _parentSchema$errors[keyword]) && (HIDE_NESTED_KEYWORDS.some(k => schemaPath.includes(`/${k}`)) || getSchema(key, path)[path.length] !== parentSchema);
168
+ return !(parentSchema.error || (_parentSchema$errors = parentSchema.errors) !== null && _parentSchema$errors !== void 0 && _parentSchema$errors[keyword]) && HIDE_NESTED_KEYWORDS.some(k => schemaPath.includes(`/${k}`));
169
169
  } // Validates data according to the associated schema and returns a list of errors, if any.
170
170
 
171
171
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/config",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,7 @@
16
16
  "files": [
17
17
  "dist",
18
18
  "types/index.d.ts",
19
- "tests/helpers.js"
19
+ "test/helpers.js"
20
20
  ],
21
21
  "main": "./dist/index.js",
22
22
  "types": "./types/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "test:types": "tsd"
35
35
  },
36
36
  "dependencies": {
37
- "@percy/logger": "1.0.3",
37
+ "@percy/logger": "1.0.6",
38
38
  "ajv": "^8.6.2",
39
39
  "cosmiconfig": "^7.0.0",
40
40
  "yaml": "^1.10.0"
@@ -42,5 +42,5 @@
42
42
  "devDependencies": {
43
43
  "json-schema-typed": "^7.0.3"
44
44
  },
45
- "gitHead": "a259d5cff0933711bced21a979c577e70765d318"
45
+ "gitHead": "f883f713ac513635245301622392870ba4018706"
46
46
  }
@@ -0,0 +1,118 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import url from 'url';
4
+ import path from 'path';
5
+ import Module from 'module';
6
+
7
+ // Reset various global @percy/config internals for testing
8
+ export async function resetPercyConfig(all) {
9
+ // aliased to src during tests
10
+ let { clearMigrations } = await import('../dist/migrate.js');
11
+ let { resetSchema } = await import('../dist/validate.js');
12
+ let { cache } = await import('../dist/load.js');
13
+ if (all) clearMigrations();
14
+ if (all) resetSchema();
15
+ cache.clear();
16
+ }
17
+
18
+ // When mocking fs, these classes should not be spied on
19
+ const FS_CLASSES = [
20
+ 'Stats', 'Dirent',
21
+ 'StatWatcher', 'FSWatcher',
22
+ 'ReadStream', 'WriteStream'
23
+ ];
24
+
25
+ // Used to bypass mocking internal package files
26
+ const INTERNAL_FILE_REG = new RegExp(
27
+ '(/|\\\\)(packages)\\1((?:(?!\\1).)+?)\\1' +
28
+ '(src|dist|test|package\\.json)(\\1|$)'
29
+ );
30
+
31
+ // Mock and spy on fs methods using an in-memory filesystem
32
+ export async function mockfs({
33
+ // set `true` to allow mocking files within `node_modules` (may cause dynamic import issues)
34
+ $modules = false,
35
+ // list of filepaths or function matchers to allow direct access to the real filesystem
36
+ $bypass = [],
37
+ // initial flat map of files and/or directories to create
38
+ ...initial
39
+ } = {}) {
40
+ let memfs = await import('memfs');
41
+ let vol = new memfs.Volume();
42
+
43
+ // automatically cleanup mock imports
44
+ global.__MOCK_IMPORTS__?.clear();
45
+
46
+ // when .js files are created, also mock the module for importing
47
+ spyOn(vol, 'writeFileSync').and.callFake((...args) => {
48
+ if (args[0].endsWith('.js')) mockFileModule(...args);
49
+ return vol.writeFileSync.and.originalFn.apply(vol, args);
50
+ });
51
+
52
+ // initial volume contents include the cwd and tmpdir
53
+ vol.fromJSON({
54
+ [process.cwd()]: null,
55
+ [os.tmpdir()]: null,
56
+ ...initial
57
+ });
58
+
59
+ let bypass = [
60
+ // bypass babel config for runtime registration
61
+ path.resolve(url.fileURLToPath(import.meta.url), '../../../../babel.config.cjs'),
62
+ // bypass descriptors that don't exist in the current volume
63
+ p => typeof p === 'number' && !vol.fds[p],
64
+ // bypass node_modules by default to avoid dynamic import issues
65
+ p => !$modules && p.includes?.('node_modules'),
66
+ // bypass internal package files to avoid dynamic import issues
67
+ p => p.match?.(INTERNAL_FILE_REG) && !vol.existsSync(p),
68
+ // additional bypass matches
69
+ ...$bypass
70
+ ];
71
+
72
+ // spies on fs methods and calls in-memory methods unless bypassed
73
+ let installFakes = (og, fake) => {
74
+ for (let k in og) {
75
+ if (k in fake && typeof og[k] === 'function' && !FS_CLASSES.includes(k)) {
76
+ spyOn(og, k).and.callFake((...args) => bypass.some(p => (
77
+ typeof p === 'function' ? p(...args) : (p === args[0])
78
+ )) ? og[k].and.originalFn(...args) : fake[k](...args));
79
+ }
80
+ }
81
+ };
82
+
83
+ // mock and install fs methods using the in-memory filesystem
84
+ let mock = memfs.createFsFromVolume(vol);
85
+ installFakes(fs.promises, mock.promises);
86
+ installFakes(fs, mock);
87
+
88
+ // allow tests access to the in-memory filesystem
89
+ fs.$bypass = bypass;
90
+ fs.$vol = vol;
91
+ return vol;
92
+ }
93
+
94
+ // Mock module loading to avoid node using internal C++ fs bindings
95
+ function mockFileModule(filepath, content = '') {
96
+ if (!jasmine.isSpy(Module._load)) {
97
+ spyOn(Module, '_load').and.callThrough();
98
+ spyOn(Module, '_resolveFilename').and.callThrough();
99
+ }
100
+
101
+ let mod = new Module();
102
+ let fp = mod.filename = path.resolve(filepath);
103
+ let any = { asymmetricMatch: () => true };
104
+
105
+ let matchFilepath = {
106
+ asymmetricMatch: f => path.resolve(f) === fp ||
107
+ fp.endsWith(path.join('node_modules', f))
108
+ };
109
+
110
+ Module._resolveFilename.withArgs(matchFilepath, any).and.returnValue(fp);
111
+ Module._load.withArgs(matchFilepath, any, any).and.callFake(() => {
112
+ mod.loaded = mod.loaded || (mod._compile(content, fp), true);
113
+ return mod.exports;
114
+ });
115
+ }
116
+
117
+ // export fs for convenience
118
+ export { fs };