Socket
Socket
Sign inDemoInstall

@percy/config

Package Overview
Dependencies
Maintainers
6
Versions
238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@percy/config - npm Package Compare versions

Comparing version 1.12.0 to 1.13.0

6

dist/defaults.js

@@ -9,5 +9,6 @@ import { merge } from './utils/index.js';

entries
} = Object; // Recursively walks a schema and collects defaults. When no schema is provided,
} = Object;
// Recursively walks a schema and collects defaults. When no schema is provided,
// the default config schema is used.
function getDefaultsFromSchema(schema) {

@@ -32,3 +33,2 @@ if (!schema || typeof schema.$ref === 'string') {

}
export function getDefaults(overrides = {}) {

@@ -35,0 +35,0 @@ return merge([getDefaultsFromSchema(), overrides], (path, prev, next) => {

@@ -5,6 +5,8 @@ import load, { search } from './load.js';

import { merge, normalize, stringify } from './utils/index.js';
import getDefaults from './defaults.js'; // public config API
import getDefaults from './defaults.js';
export { load, search, validate, addSchema, migrate, addMigration, getDefaults, merge, normalize, stringify }; // export the namespace by default
// public config API
export { load, search, validate, addSchema, migrate, addMigration, getDefaults, merge, normalize, stringify };
// export the namespace by default
export * as default from './index.js';

@@ -8,11 +8,14 @@ import fs from 'fs';

import getDefaults from './defaults.js';
import { inspect, normalize } from './utils/index.js'; // Loaded configuration file cache
import { inspect, normalize } from './utils/index.js';
export const cache = new Map(); // The cosmiconfig explorer used to load config files
// Loaded configuration file cache
export const cache = new Map();
// The cosmiconfig explorer used to load config files
export const explorer = cosmiconfigSync('percy', {
cache: false,
searchPlaces: ['package.json', '.percyrc', '.percy.json', '.percy.yaml', '.percy.yml', '.percy.js', '.percy.cjs', 'percy.config.js', 'percy.config.cjs']
}); // Searches within a provided directory, or loads the provided config path
});
// Searches within a provided directory, or loads the provided config path
export function search(path) {

@@ -25,3 +28,5 @@ try {

}
} // Finds and loads a config file using cosmiconfig, merges it with optional
}
// Finds and loads a config file using cosmiconfig, merges it with optional
// inputs, validates the combined config according to the schema, and returns

@@ -34,3 +39,2 @@ // the combined config. Loaded config files are cached and reused on next load,

// versions to the latest version while printing a warning.
export function load({

@@ -44,3 +48,2 @@ path,

var _Array$from;
// load cached config; when no path is specified, get the last config cached

@@ -50,12 +53,11 @@ let config = path ? cache.get(path) : (_Array$from = Array.from(cache)[cache.size - 1]) === null || _Array$from === void 0 ? void 0 : _Array$from[1];

let errorDebug = print ? 'error' : 'debug';
let log = logger('config'); // load config or reload cached config
let log = logger('config');
// load config or reload cached config
if (path !== false && (!config || reload)) {
try {
let result = search(path);
if (result !== null && result !== void 0 && result.config) {
log[infoDebug](`Found config file: ${relative('', result.filepath)}`);
let version = parseInt(result.config.version, 10);
if (Number.isNaN(version)) {

@@ -69,3 +71,2 @@ log.warn('Ignoring config file - missing or invalid version');

}
config = migrate(result.config);

@@ -82,5 +83,5 @@ cache.set(path, config);

}
} // normalize and merge with overrides then validate
}
// normalize and merge with overrides then validate
config = normalize(config, {

@@ -91,18 +92,14 @@ overrides,

let errors = config && validate(config);
if (errors) {
log.warn('Invalid config:');
for (let e of errors) log.warn(`- ${e.path}: ${e.message}`);
if (bail) return;
}
if (path !== false && config) {
log[infoDebug](`Using config:\n${inspect(config)}`);
} // merge with defaults
}
// merge with defaults
return getDefaults(config);
}
export default load;
import logger from '@percy/logger';
import { get, set, del, map, joinPropertyPath, normalize } from './utils/index.js'; // Global set of registered migrations
import { get, set, del, map, joinPropertyPath, normalize } from './utils/index.js';
const migrations = new Map(); // Register a migration function for the main config schema by default
// Global set of registered migrations
const migrations = new Map();
// Register a migration function for the main config schema by default
export function addMigration(migration, schema = '/config') {

@@ -12,15 +14,15 @@ if (Array.isArray(migration)) {

}
if (!migrations.has(schema)) migrations.set(schema, []);
migrations.get(schema).unshift(migration);
return migration;
} // Clear all migration functions
}
// Clear all migration functions
export function clearMigrations() {
migrations.clear();
addMigration(defaultMigration);
} // The default config migration
}
// The default config migration
addMigration(defaultMigration);
function defaultMigration(config, {

@@ -30,5 +32,5 @@ set

if (config.version !== 2) set('version', 2);
} // Migrate util for deprecated options
}
// Migrate util for deprecated options
function deprecate(config, log, path, options) {

@@ -48,6 +50,6 @@ if (get(config, path) == null) return;

return to ? map(config, path, to) : config;
} // Calls each registered migration function with a normalize provided config
}
// Calls each registered migration function with a normalize provided config
// and util functions for working with the config object
export function migrate(config, schema = '/config') {

@@ -57,3 +59,2 @@ config = normalize(config, {

}) ?? {};
if (migrations.has(schema)) {

@@ -68,8 +69,7 @@ let log = logger('config');

};
for (let migration of migrations.get(schema)) {
migration(config, util);
} // normalize again to adjust for migrations
}
// normalize again to adjust for migrations
config = normalize(config, {

@@ -79,5 +79,4 @@ schema

}
return config;
}
export default migrate;

@@ -9,19 +9,21 @@ const {

entries
} = Object; // Creates an empty object or array
} = Object;
// Creates an empty object or array
function create(array) {
return array ? [] : {};
} // Returns true or false if a subject has iterable keys or not
}
// Returns true or false if a subject has iterable keys or not
function hasKeys(subj) {
return isArray(subj) || Object.getPrototypeOf(subj) === Object.prototype;
} // Returns true if the provided key looks like an array key
}
// Returns true if the provided key looks like an array key
const ARRAY_PATH_KEY_REG = /^(\[\d+]|0|[1-9]\d*)$/;
export function isArrayKey(key) {
return isInteger(key) || ARRAY_PATH_KEY_REG.test(key);
} // Split a property path string by dot or array notation
}
// Split a property path string by dot or array notation
export function parsePropertyPath(path) {

@@ -34,21 +36,21 @@ return isArray(path) ? path : path.split('.').reduce((full, part) => {

}, []);
} // Join an array of path parts into a single path string
}
// Join an array of path parts into a single path string
export function joinPropertyPath(path) {
path = !Array.isArray(path) ? path : path.filter(Boolean).map(k => isArrayKey(k) ? `[${k}]` : `.${k}`).join('');
while ((_path = path) !== null && _path !== void 0 && _path.startsWith('.')) {
var _path;
path = path.substr(1);
}
return path;
} // Gets a value in the object at the path
}
// Gets a value in the object at the path
export function get(object, path, find) {
return parsePropertyPath(path).reduce((target, key) => target === null || target === void 0 ? void 0 : target[key], object);
} // Sets a value to the object at the path creating any necessary nested
}
// Sets a value to the object at the path creating any necessary nested
// objects or arrays along the way
export function set(object, path, value) {

@@ -64,4 +66,5 @@ return parsePropertyPath(path).reduce((target, key, index, path) => {

}, object);
} // Deletes properties from an object at the paths
}
// Deletes properties from an object at the paths
export function del(object, ...paths) {

@@ -78,16 +81,16 @@ return paths.reduce((object, path) => {

}, object);
} // Maps a value from one path to another, deleting the first path
}
// Maps a value from one path to another, deleting the first path
export function map(object, from, to, transform = v => v) {
return set(object, to, transform(parsePropertyPath(from).reduce((target, key, index, path) => {
let value = target === null || target === void 0 ? void 0 : target[key];
if (index === path.length - 1) {
target === null || target === void 0 ? true : delete target[key];
}
return value;
}, object)));
} // Steps through an object's properties calling the function with the path and value of each
}
// Steps through an object's properties calling the function with the path and value of each
function walk(object, fn, path = [], visited = new Set()) {

@@ -97,6 +100,4 @@ if (path.length && fn([...path], object) === false) return;

visited.add(object);
if (object != null && typeof object === 'object') {
let isArrayObject = isArray(object);
for (let [key, value] of entries(object)) {

@@ -107,5 +108,5 @@ if (isArrayObject) key = parseInt(key, 10);

}
} // Recursively mutate and filter empty values from arrays and objects
}
// Recursively mutate and filter empty values from arrays and objects
export function filterEmpty(subject) {

@@ -119,3 +120,2 @@ if (typeof subject === 'object') {

}
return subject.length > 0;

@@ -128,3 +128,2 @@ } else {

}
return entries(subject).length > 0;

@@ -135,6 +134,7 @@ }

}
} // Merges source values and returns a new merged value. The map function will be called with a
}
// Merges source values and returns a new merged value. The map function will be called with a
// property's path, previous value, and next value; it should return an array containing any
// replacement path and value; when a replacement value not defined, values will be merged.
export function merge(sources, map) {

@@ -145,32 +145,32 @@ return sources.reduce((target, source, i) => {

var _ctx;
let ctx = get(target, path.slice(0, -1));
let key = path[path.length - 1];
let prev = (_ctx = ctx) === null || _ctx === void 0 ? void 0 : _ctx[key]; // maybe map the property path and/or value
let prev = (_ctx = ctx) === null || _ctx === void 0 ? void 0 : _ctx[key];
let [mapped, next] = (map === null || map === void 0 ? void 0 : map(path, prev, value)) || []; // update the context and path if changed
// maybe map the property path and/or value
let [mapped, next] = (map === null || map === void 0 ? void 0 : map(path, prev, value)) || [];
// update the context and path if changed
if (mapped !== null && mapped !== void 0 && mapped.some((m, i) => m !== path[i])) {
ctx = get(target, mapped.slice(0, -1));
path = [...mapped];
} // adjust path to concat array values when necessary
}
// adjust path to concat array values when necessary
if (next !== null && (isArray(ctx) || isInteger(key))) {
var _ctx2;
path.splice(-1, 1, ((_ctx2 = ctx) === null || _ctx2 === void 0 ? void 0 : _ctx2.length) ?? 0);
} // delete prev values
}
// delete prev values
if (next === null || next == null && value === null) {
del(target, path);
} // set the next or default value if there is one
}
// set the next or default value if there is one
if (next != null || next !== null && value != null && !hasKeys(value)) {
set(target ?? (target = create(isSourceArray)), path, next ?? value);
} // do not recurse mapped objects
}
// do not recurse mapped objects
return next === undefined;

@@ -177,0 +177,0 @@ });

import merge from './merge.js';
import { getSchema } from '../validate.js'; // Edge case camelizations
import { getSchema } from '../validate.js';
const CAMELCASE_MAP = new Map([['css', 'CSS'], ['javascript', 'JavaScript']]); // Regular expression that matches words from boundaries or consecutive casing
// Edge case camelizations
const CAMELCASE_MAP = new Map([['css', 'CSS'], ['javascript', 'JavaScript']]);
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.
// Regular expression that matches words from boundaries or consecutive casing
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.
export function camelcase(str) {
if (typeof str !== 'string') return str;
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()), '');
} // Coverts camelCased and snake_cased strings to kebab-case.
}
// Coverts camelCased and snake_cased strings to kebab-case.
export function kebabcase(str) {
if (typeof str !== 'string') return str;
return Array.from(CAMELCASE_MAP).reduce((str, [word, camel]) => str.replace(camel, `-${word}`), str).match(WORD_REG).join('-').toLowerCase();
} // Coverts kebab-case and camelCased strings to snake_case.
}
// Coverts kebab-case and camelCased strings to snake_case.
export function snakecase(str) {
if (typeof str !== 'string') return str;
return Array.from(CAMELCASE_MAP).reduce((str, [word, camel]) => str.replace(camel, `_${word}`), str).match(WORD_REG).join('_').toLowerCase();
} // Removes undefined empty values and renames kebab-case properties to camelCase. Optionally
}
// Removes undefined empty values and renames kebab-case properties to camelCase. Optionally
// allows deep merging with options.overrides, converting keys to kebab-case with options.kebab,
// and normalizing against a schema with options.schema.
export function normalize(object, options) {
var _options, _options2, _options3;
if (typeof options === 'string') options = {

@@ -34,19 +39,16 @@ schema: options

var _options4, _schemas$shift;
let schemas = getSchema((_options4 = options) === null || _options4 === void 0 ? void 0 : _options4.schema, path.map(camelcase));
let skip = ((_schemas$shift = schemas.shift()) === null || _schemas$shift === void 0 ? void 0 : _schemas$shift.normalize) === false;
let mapped = []; // skip normalizing paths of class instances
let mapped = [];
// skip normalizing paths of class instances
if (!skip && typeof value === 'object' && value !== null && value !== void 0 && value.constructor) {
skip = Object.getPrototypeOf(value) !== Object.prototype;
}
for (let [i, k] of path.entries()) {
var _options5, _options5$skip, _schemas$i;
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)));
var _options5, _options5$skip, _options6, _schemas$i;
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)));
mapped.push(skip ? k : keycase(k));
skip || (skip = ((_schemas$i = schemas[i]) === null || _schemas$i === void 0 ? void 0 : _schemas$i.normalize) === false);
}
return [mapped];

@@ -53,0 +55,0 @@ });

import util from 'util';
import YAML from 'yaml';
import getDefaults from '../defaults.js'; // Provides native util.inspect with common options for printing configs.
import getDefaults from '../defaults.js';
// Provides native util.inspect with common options for printing configs.
export function inspect(config) {

@@ -10,5 +11,6 @@ return util.inspect(config, {

});
} // Converts a config to a yaml, json, or js string. When no config is provided,
}
// Converts a config to a yaml, json, or js string. When no config is provided,
// falls back to schema defaults.
export function stringify(format, config = getDefaults()) {

@@ -19,9 +21,6 @@ switch (format) {

return YAML.stringify(config);
case 'json':
return JSON.stringify(config, null, 2) + '\n';
case 'js':
return `module.exports = ${inspect(config)}\n`;
default:

@@ -28,0 +27,0 @@ throw new Error(`Unsupported format: ${format}`);

@@ -9,4 +9,5 @@ import AJV from 'ajv/dist/2019.js';

entries
} = Object; // AJV manages and validates schemas.
} = Object;
// AJV manages and validates schemas.
const ajv = new AJV({

@@ -47,3 +48,2 @@ strict: false,

} = cxt;
for (let prop of schema) {

@@ -59,4 +59,5 @@ gen.if(AJV._`${data}.${AJV._([prop])} !== undefined`, () => {

}]
}); // Returns a new default schema.
});
// Returns a new default schema.
function getDefaultSchema() {

@@ -74,24 +75,24 @@ return {

};
} // Gets the schema object from the AJV schema. If a path is provided, an array of schemas is
}
// Gets the schema object from the AJV schema. If a path is provided, an array of schemas is
// returned, with each index representing the schema of each part of the path (index zero is root).
export function getSchema(name, path, root) {
var _ajv$getSchema, _ref, _schema$properties;
// get the root schema if necessary, resolve it, and return it when there is no path
let schema = typeof name === 'string' ? (_ajv$getSchema = ajv.getSchema(name)) === null || _ajv$getSchema === void 0 ? void 0 : _ajv$getSchema.schema : name;
if (!schema || !path) return schema ?? [];
root ?? (root = schema); // parse and work with one key at a time
root ?? (root = schema);
let [key, ...rest] = path = parsePropertyPath(path); // if the desired schema is one of many, we need to find the best match
// parse and work with one key at a time
let [key, ...rest] = path = parsePropertyPath(path);
// if the desired schema is one of many, we need to find the best match
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) => {
var _a$;
return (// the best possible match will match most of the path or loosely match
return (
// the best possible match will match most of the path or loosely match
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)
);
})[0];
if (many !== null && many !== void 0 && many.length) {

@@ -116,7 +117,8 @@ return many;

}
} // Adds schemas to the config schema's properties. The config schema is removed, modified, and
}
// Adds schemas to the config schema's properties. The config schema is removed, modified, and
// replaced after the new schemas are added to clear any compiled caches. Existing schemas are
// removed and replaced as well. If a schema has an existing $id, the schema will not be added
// as config schema properties.
export function addSchema(schemas) {

@@ -126,3 +128,2 @@ if (isArray(schemas)) {

}
if (schemas.$id) {

@@ -135,6 +136,4 @@ let {

}
let config = getSchema('/config');
ajv.removeSchema('/config');
for (let [key, schema] of entries(schemas)) {

@@ -154,22 +153,21 @@ if (key === '$config') {

}
return ajv.addSchema(config, '/config');
} // Resets the schema by removing all schemas and inserting a new default schema.
}
// Resets the schema by removing all schemas and inserting a new default schema.
export function resetSchema() {
ajv.removeSchema();
ajv.addSchema(getDefaultSchema(), '/config');
} // Adds "a" or "an" to a word for readability.
}
// Adds "a" or "an" to a word for readability.
function a(word) {
if (word === 'undefined' || word === 'null') return word;
return `${'aeiou'.includes(word[0]) ? 'an' : 'a'} ${word}`;
} // Default errors anywhere within these keywords can be confusing
}
// Default errors anywhere within these keywords can be confusing
const HIDE_NESTED_KEYWORDS = ['oneOf', 'anyOf', 'allOf', 'if', 'then', 'else', 'not'];
function shouldHideError(key, path, error) {
var _parentSchema$errors;
let {

@@ -181,12 +179,10 @@ parentSchema,

return !(parentSchema.error || (_parentSchema$errors = parentSchema.errors) !== null && _parentSchema$errors !== void 0 && _parentSchema$errors[keyword]) && HIDE_NESTED_KEYWORDS.some(k => schemaPath.includes(`/${k}`));
} // Validates data according to the associated schema and returns a list of errors, if any.
}
// Validates data according to the associated schema and returns a list of errors, if any.
export function validate(data, key = '/config') {
if (!ajv.validate(key, data)) {
let errors = new Map();
for (let error of ajv.errors) {
var _parentSchema$errors2;
let {

@@ -200,4 +196,5 @@ instancePath,

let path = instancePath ? instancePath.substr(1).split('/') : [];
if (shouldHideError(key, path, error)) continue; // generate a custom error message
if (shouldHideError(key, path, error)) continue;
// generate a custom error message
if (parentSchema.error || (_parentSchema$errors2 = parentSchema.errors) !== null && _parentSchema$errors2 !== void 0 && _parentSchema$errors2[keyword]) {

@@ -213,5 +210,5 @@ let custom = parentSchema.error || parentSchema.errors[keyword];

message = 'unknown property';
} // fix paths
}
// fix paths
if (params.missingProperty) {

@@ -225,5 +222,5 @@ path.push(params.missingProperty);

path.push(params.disallowedProperty);
} // fix invalid data
}
// fix invalid data
if (keyword === 'minimum') {

@@ -237,7 +234,8 @@ set(data, path, Math.max(error.data, error.schema));

del(data, path);
} // joined for error messages
}
// joined for error messages
path = joinPropertyPath(path);
path = joinPropertyPath(path); // map one error per path
// map one error per path
errors.set(path, {

@@ -247,7 +245,8 @@ path,

});
} // filter empty values as a result of scrubbing
}
// filter empty values as a result of scrubbing
filterEmpty(data);
filterEmpty(data); // return an array of errors
// return an array of errors
return Array.from(errors.values());

@@ -254,0 +253,0 @@ }

{
"name": "@percy/config",
"version": "1.12.0",
"version": "1.13.0",
"license": "MIT",

@@ -37,3 +37,3 @@ "repository": {

"dependencies": {
"@percy/logger": "1.12.0",
"@percy/logger": "1.13.0",
"ajv": "^8.6.2",

@@ -46,3 +46,3 @@ "cosmiconfig": "^7.0.0",

},
"gitHead": "4303b74df91f60e36065141289d2ef2277d1d6fc"
"gitHead": "d2e812d14aa446fa580ffa75144a6280627b5a27"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc