Socket
Socket
Sign inDemoInstall

schema-utils

Package Overview
Dependencies
7
Maintainers
5
Versions
39
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.5.0 to 2.6.0

declarations/index.d.ts

12

CHANGELOG.md

@@ -5,2 +5,14 @@ # Changelog

## [2.6.0](https://github.com/webpack/schema-utils/compare/v2.5.0...v2.6.0) (2019-11-27)
### Features
* support configuration via title ([#81](https://github.com/webpack/schema-utils/issues/81)) ([afddc10](https://github.com/webpack/schema-utils/commit/afddc109f6891cd37a9f1835d50862d119a072bf))
### Bug Fixes
* typescript definitions ([#70](https://github.com/webpack/schema-utils/issues/70)) ([f38158d](https://github.com/webpack/schema-utils/commit/f38158d6d040e2c701622778ae8122fb26a4f990))
## [2.5.0](https://github.com/webpack/schema-utils/compare/v2.4.1...v2.5.0) (2019-10-15)

@@ -7,0 +19,0 @@

82

dist/keywords/absolutePath.js

@@ -8,3 +8,14 @@ "use strict";

function errorMessage(schema, data, message) {
/** @typedef {import("ajv").Ajv} Ajv */
/** @typedef {import("../validate").SchemaUtilErrorObject} SchemaUtilErrorObject */
/**
*
* @param {string} data
* @param {object} schema
* @param {string} message
* @returns {object} // Todo `returns` should be `SchemaUtilErrorObject`
*/
function errorMessage(message, schema, data) {
return {

@@ -19,37 +30,60 @@ keyword: 'absolutePath',

}
/**
* @param {boolean} shouldBeAbsolute
* @param {object} schema
* @param {string} data
* @returns {object}
*/
function getErrorFor(shouldBeAbsolute, data, schema) {
function getErrorFor(shouldBeAbsolute, schema, data) {
const message = shouldBeAbsolute ? `The provided value ${JSON.stringify(data)} is not an absolute path!` : `A relative path is expected. However, the provided value ${JSON.stringify(data)} is an absolute path!`;
return errorMessage(schema, data, message);
return errorMessage(message, schema, data);
}
/**
*
* @param {Ajv} ajv
* @returns {Ajv}
*/
var _default = ajv => ajv.addKeyword('absolutePath', {
errors: true,
type: 'string',
compile(expected, schema) {
function callback(data) {
let passes = true;
const isExclamationMarkPresent = data.includes('!');
const isCorrectAbsoluteOrRelativePath = expected === /^(?:[A-Za-z]:\\|\/)/.test(data);
function addAbsolutePathKeyword(ajv) {
ajv.addKeyword('absolutePath', {
errors: true,
type: 'string',
if (isExclamationMarkPresent) {
callback.errors = [errorMessage(schema, data, `The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`)];
passes = false;
}
compile(schema, parentSchema) {
/**
* @param {any} data
* @returns {boolean}
*/
function callback(data) {
let passes = true;
const isExclamationMarkPresent = data.includes('!');
const isCorrectAbsoluteOrRelativePath = schema === /^(?:[A-Za-z]:\\|\/)/.test(data);
if (!isCorrectAbsoluteOrRelativePath) {
callback.errors = [getErrorFor(expected, data, schema)];
passes = false;
if (isExclamationMarkPresent) {
callback.errors = [errorMessage(`The provided value ${JSON.stringify(data)} contains exclamation mark (!) which is not allowed because it's reserved for loader syntax.`, parentSchema, data)];
passes = false;
}
if (!isCorrectAbsoluteOrRelativePath) {
callback.errors = [getErrorFor(schema, parentSchema, data)];
passes = false;
}
return passes;
}
/** @type {null | Array<SchemaUtilErrorObject>}*/
return passes;
callback.errors = [];
return callback;
}
callback.errors = [];
return callback;
}
});
return ajv;
}
});
var _default = addAbsolutePathKeyword;
exports.default = _default;
"use strict";
const left = Symbol('left');
const right = Symbol('right');
/**
* @typedef {[number, boolean]} RangeValue
*/
/**
* @callback RangeValueCallback
* @param {RangeValue} rangeValue
* @returns {boolean}
*/
class Range {

@@ -10,3 +16,3 @@ /**

* @param {boolean} exclusive
* @returns {">" | ">" | ">=" | "<="}
* @returns {">" | ">=" | "<" | "<="}
*/

@@ -68,5 +74,5 @@ static getOperator(side, exclusive) {

/**
* @param {[number, boolean][]} values
* @param {Array<RangeValue>} values
* @param {boolean} logic is not logic applied
* @return {[number, boolean]} computed value and it's exclusive flag
* @return {RangeValue} computed value and it's exclusive flag
*/

@@ -78,7 +84,11 @@

let j = -1;
const predicate = logic ? ([value]) => value <= minMax : ([value]) => value >= minMax;
const predicate = logic ?
/** @type {RangeValueCallback} */
([value]) => value <= minMax :
/** @type {RangeValueCallback} */
([value]) => value >= minMax;
for (let i = 0; i < values.length; i++) {
if (predicate(values[i])) {
minMax = values[i][0];
[minMax] = values[i];
j = i;

@@ -96,12 +106,25 @@ }

constructor() {
this[left] = [];
this[right] = [];
/** @type {Array<RangeValue>} */
this._left = [];
/** @type {Array<RangeValue>} */
this._right = [];
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
left(value, exclusive = false) {
this[left].push([value, exclusive]);
this._left.push([value, exclusive]);
}
/**
* @param {number} value
* @param {boolean=} exclusive
*/
right(value, exclusive = false) {
this[right].push([value, exclusive]);
this._right.push([value, exclusive]);
}

@@ -115,4 +138,4 @@ /**

format(logic = true) {
const [start, leftExclusive] = Range.getRangeValue(this[left], logic);
const [end, rightExclusive] = Range.getRangeValue(this[right], !logic);
const [start, leftExclusive] = Range.getRangeValue(this._left, logic);
const [end, rightExclusive] = Range.getRangeValue(this._right, !logic);

@@ -119,0 +142,0 @@ if (!Number.isFinite(start) && !Number.isFinite(end)) {

@@ -18,2 +18,27 @@ "use strict";

/** @typedef {import("json-schema").JSONSchema4} JSONSchema4 */
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
/** @typedef {import("ajv").ErrorObject} ErrorObject */
/** @typedef {(JSONSchema4 | JSONSchema6 | JSONSchema7)} Schema */
/** @typedef {ErrorObject & { children?: Array<ErrorObject>}} SchemaUtilErrorObject */
/**
* @callback PostFormatter
* @param {string} formattedError
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
/**
* @typedef {Object} ValidationErrorConfiguration
* @property {string=} name
* @property {string=} baseDataPath
* @property {PostFormatter=} postFormatter
*/
const ajv = new _ajv.default({

@@ -27,4 +52,10 @@ allErrors: true,

(0, _absolutePath.default)(ajv);
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @param {ValidationErrorConfiguration=} configuration
* @returns {void}
*/
function validate(schema, options, configuration = {}) {
function validate(schema, options, configuration) {
let errors = [];

@@ -35,3 +66,7 @@

errors.forEach((list, idx) => {
const applyPrefix = error => {
const applyPrefix =
/**
* @param {SchemaUtilErrorObject} error
*/
error => {
// eslint-disable-next-line no-param-reassign

@@ -55,19 +90,38 @@ error.dataPath = `[${idx}]${error.dataPath}`;

}
return errors;
}
/**
* @param {Schema} schema
* @param {Array<object> | object} options
* @returns {Array<SchemaUtilErrorObject>}
*/
function validateObject(schema, options) {
const compiledSchema = ajv.compile(schema);
const valid = compiledSchema(options);
if (!compiledSchema.errors) {
return [];
}
return valid ? [] : filterErrors(compiledSchema.errors);
}
/**
* @param {Array<ErrorObject>} errors
* @returns {Array<SchemaUtilErrorObject>}
*/
function filterErrors(errors) {
/** @type {Array<SchemaUtilErrorObject>} */
let newErrors = [];
for (const error of errors) {
for (const error of
/** @type {Array<SchemaUtilErrorObject>} */
errors) {
const {
dataPath
} = error;
/** @type {Array<SchemaUtilErrorObject>} */
let children = [];

@@ -74,0 +128,0 @@ newErrors = newErrors.filter(oldError => {

@@ -9,3 +9,17 @@ "use strict";

const Range = require('./util/Range');
/** @typedef {import("json-schema").JSONSchema6} JSONSchema6 */
/** @typedef {import("json-schema").JSONSchema7} JSONSchema7 */
/** @typedef {import("./validate").Schema} Schema */
/** @typedef {import("./validate").ValidationErrorConfiguration} ValidationErrorConfiguration */
/** @typedef {import("./validate").PostFormatter} PostFormatter */
/** @typedef {import("./validate").SchemaUtilErrorObject} SchemaUtilErrorObject */
/** @enum {number} */
const SPECIFICITY = {

@@ -45,2 +59,8 @@ type: 1,

};
/**
*
* @param {Array<SchemaUtilErrorObject>} array
* @param {(item: SchemaUtilErrorObject) => number} fn
* @returns {Array<SchemaUtilErrorObject>}
*/

@@ -51,7 +71,26 @@ function filterMax(array, fn) {

}
/**
*
* @param {Array<SchemaUtilErrorObject>} children
* @returns {Array<SchemaUtilErrorObject>}
*/
function filterChildren(children) {
let newChildren = children;
newChildren = filterMax(newChildren, error => error.dataPath ? error.dataPath.length : 0);
newChildren = filterMax(newChildren, error => SPECIFICITY[error.keyword] || 2);
newChildren = filterMax(newChildren,
/**
*
* @param {SchemaUtilErrorObject} error
* @returns {number}
*/
error => error.dataPath ? error.dataPath.length : 0);
newChildren = filterMax(newChildren,
/**
* @param {SchemaUtilErrorObject} error
* @returns {number}
*/
error => SPECIFICITY[
/** @type {keyof typeof SPECIFICITY} */
error.keyword] || 2);
return newChildren;

@@ -61,4 +100,4 @@ }

* Find all children errors
* @param children
* @param {string[]} schemaPaths
* @param {Array<SchemaUtilErrorObject>} children
* @param {Array<string>} schemaPaths
* @return {number} returns index of first child

@@ -71,3 +110,8 @@ */

const predicate = schemaPath => children[i].schemaPath.indexOf(schemaPath) !== 0;
const predicate =
/**
* @param {string} schemaPath
* @returns {boolean}
*/
schemaPath => children[i].schemaPath.indexOf(schemaPath) !== 0;

@@ -88,4 +132,4 @@ while (i > -1 && !schemaPaths.every(predicate)) {

* Extracts all refs from schema
* @param error
* @return {string[]}
* @param {SchemaUtilErrorObject} error
* @return {Array<string>}
*/

@@ -109,4 +153,4 @@

* Groups children by their first level parent (assuming that error is root)
* @param children
* @return {any[]}
* @param {Array<SchemaUtilErrorObject>} children
* @return {Array<SchemaUtilErrorObject>}
*/

@@ -147,39 +191,90 @@

}
/**
* @param {string} str
* @param {string} prefix
* @returns {string}
*/
function indent(str, prefix) {
return str.replace(/\n(?!$)/g, `\n${prefix}`);
}
/**
* @param {any} maybeObj
* @returns {boolean}
*/
function isObject(maybyObj) {
return typeof maybyObj === 'object' && maybyObj !== null;
function isObject(maybeObj) {
return typeof maybeObj === 'object' && maybeObj !== null;
}
/**
* @param {Schema} schema
* @returns {boolean}
*/
function likeNumber(schema) {
return schema.type === 'number' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined';
}
/**
* @param {Schema} schema
* @returns {boolean}
*/
function likeInteger(schema) {
return schema.type === 'integer' || typeof schema.minimum !== 'undefined' || typeof schema.exclusiveMinimum !== 'undefined' || typeof schema.maximum !== 'undefined' || typeof schema.exclusiveMaximum !== 'undefined' || typeof schema.multipleOf !== 'undefined';
}
/**
* @param {Schema & {formatMinimum?: string; formatMaximum?: string;}} schema
* @returns {boolean}
*/
function likeString(schema) {
return schema.type === 'string' || typeof schema.minLength !== 'undefined' || typeof schema.maxLength !== 'undefined' || typeof schema.pattern !== 'undefined' || typeof schema.format !== 'undefined' || typeof schema.formatMinimum !== 'undefined' || typeof schema.formatMaximum !== 'undefined';
}
/**
* @param {Schema} schema
* @returns {boolean}
*/
function likeBoolean(schema) {
return schema.type === 'boolean';
}
/**
* @param {Schema} schema
* @returns {boolean}
*/
function likeArray(schema) {
return schema.type === 'array' || typeof schema.minItems === 'number' || typeof schema.maxItems === 'number' || typeof schema.uniqueItems !== 'undefined' || typeof schema.items !== 'undefined' || typeof schema.additionalItems !== 'undefined' || typeof schema.contains !== 'undefined';
}
/**
* @param {Schema & {patternRequired?: Array<string>}} schema
* @returns {boolean}
*/
function likeObject(schema) {
return schema.type === 'object' || typeof schema.minProperties !== 'undefined' || typeof schema.maxProperties !== 'undefined' || typeof schema.required !== 'undefined' || typeof schema.properties !== 'undefined' || typeof schema.patternProperties !== 'undefined' || typeof schema.additionalProperties !== 'undefined' || typeof schema.dependencies !== 'undefined' || typeof schema.propertyNames !== 'undefined' || typeof schema.patternRequired !== 'undefined';
}
/**
* @param {Schema} schema
* @returns {boolean}
*/
function likeNull(schema) {
return schema.type === 'null';
}
/**
* @param {string} type
* @returns {string}
*/
function getArticle(type) {

@@ -192,4 +287,13 @@ if (/^[aeiou]/i.test(type)) {

}
/**
* @param {Schema=} schema
* @returns {string}
*/
function getSchemaNonTypes(schema) {
if (!schema) {
return '';
}
if (!schema.type) {

@@ -215,4 +319,13 @@ if (likeNumber(schema) || likeInteger(schema)) {

}
/**
* @param {Schema=} schema
* @returns {Array<string>}
*/
function numberHints(schema) {
if (!schema) {
return [];
}
const hints = [];

@@ -249,7 +362,17 @@ const range = new Range();

}
/**
* @param {Array<string>} hints
* @returns {string}
*/
function formatHints(hints) {
return hints.length > 0 ? `(${hints.join(', ')})` : '';
}
/**
* @param {Schema} schema
* @returns {string}
*/
function getHints(schema) {

@@ -264,15 +387,56 @@ if (likeNumber(schema) || likeInteger(schema)) {

class ValidationError extends Error {
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @param {Schema} schema
* @param {ValidationErrorConfiguration} configuration
*/
constructor(errors, schema, configuration = {}) {
super();
/** @type {string} */
this.name = 'ValidationError';
/** @type {Array<SchemaUtilErrorObject>} */
this.errors = errors;
/** @type {Schema} */
this.schema = schema;
this.headerName = configuration.name || 'Object';
this.baseDataPath = configuration.baseDataPath || 'configuration';
let headerNameFromSchema;
let baseDataPathFromSchema;
if (schema.title && (!configuration.name || !configuration.baseDataPath)) {
const splittedTitleFromSchema = schema.title.match(/^(.+) (.+)$/);
if (splittedTitleFromSchema) {
if (!configuration.name) {
[, headerNameFromSchema] = splittedTitleFromSchema;
}
if (!configuration.name) {
[,, baseDataPathFromSchema] = splittedTitleFromSchema;
}
}
}
/** @type {string} */
this.headerName = configuration.name || headerNameFromSchema || 'Object';
/** @type {string} */
this.baseDataPath = configuration.baseDataPath || baseDataPathFromSchema || 'configuration';
/** @type {PostFormatter | null} */
this.postFormatter = configuration.postFormatter || null;
const header = `Invalid ${this.baseDataPath} object. ${this.headerName} has been initialised using ${getArticle(this.baseDataPath)} ${this.baseDataPath} object that does not match the API schema.\n`;
/** @type {string} */
this.message = `${header}${this.formatValidationErrors(errors)}`;
Error.captureStackTrace(this, this.constructor);
}
/**
* @param {string} path
* @returns {Schema}
*/
getSchemaPart(path) {

@@ -283,3 +447,5 @@ const newPath = path.split('/');

for (let i = 1; i < newPath.length; i++) {
const inner = schemaPart[newPath[i]];
const inner = schemaPart[
/** @type {keyof Schema} */
newPath[i]];

@@ -295,5 +461,18 @@ if (!inner) {

}
/**
* @param {Schema} schema
* @param {Array<Object>} prevSchemas
* @returns {string}
*/
formatSchema(schema, prevSchemas = []) {
const formatInnerSchema = (innerSchema, addSelf) => {
const formatInnerSchema =
/**
*
* @param {Object} innerSchema
* @param {boolean=} addSelf
* @returns {string}
*/
(innerSchema, addSelf) => {
if (!addSelf) {

@@ -314,19 +493,24 @@ return this.formatSchema(innerSchema, prevSchemas);

if (schema.instanceof) {
if (Array.isArray(schema.instanceof)) {
return schema.instanceof.map(formatInnerSchema).join(' | ');
} // eslint-disable-next-line default-case
switch (schema.instanceof) {
case 'Function':
return 'function';
case 'RegExp':
return 'RegExp';
}
if (
/** @type {Schema & {instanceof: string | Array<string>}} */
schema.instanceof) {
const {
instanceof: value
} =
/** @type {Schema & {instanceof: string | Array<string>}} */
schema;
const values = !Array.isArray(value) ? [value] : value;
return values.map(
/**
* @param {string} item
* @returns {string}
*/
item => item === 'Function' ? 'function' : item).join(' | ');
}
if (schema.enum) {
return schema.enum.map(item => JSON.stringify(item)).join(' | ');
return (
/** @type {Array<any>} */
schema.enum.map(item => JSON.stringify(item)).join(' | ')
);
}

@@ -339,15 +523,33 @@

if (schema.oneOf) {
return schema.oneOf.map(formatInnerSchema).join(' | ');
return (
/** @type {Array<Schema>} */
schema.oneOf.map(item => formatInnerSchema(item, true)).join(' | ')
);
}
if (schema.anyOf) {
return schema.anyOf.map(formatInnerSchema).join(' | ');
return (
/** @type {Array<Schema>} */
schema.anyOf.map(item => formatInnerSchema(item, true)).join(' | ')
);
}
if (schema.allOf) {
return schema.allOf.map(formatInnerSchema).join(' & ');
return (
/** @type {Array<Schema>} */
schema.allOf.map(item => formatInnerSchema(item, true)).join(' & ')
);
}
if (schema.if) {
return `if ${formatInnerSchema(schema.if)} then ${formatInnerSchema(schema.then)}${schema.else ? ` else ${formatInnerSchema(schema.else)}` : ''}`;
if (
/** @type {JSONSchema7} */
schema.if) {
const {
if: ifValue,
then: thenValue,
else: elseValue
} =
/** @type {JSONSchema7} */
schema;
return `${ifValue ? `if ${formatInnerSchema(ifValue)}` : ''}${thenValue ? ` then ${formatInnerSchema(thenValue)}` : ''}${elseValue ? ` else ${formatInnerSchema(elseValue)}` : ''}`;
}

@@ -395,8 +597,24 @@

if (schema.formatMinimum) {
hints.push(`should be ${schema.formatExclusiveMinimum ? '>' : '>='} ${JSON.stringify(schema.formatMinimum)}`);
if (
/** @type {Schema & {formatMinimum?: string; formatExclusiveMinimum?: boolean;}} */
schema.formatMinimum) {
const {
formatExclusiveMinimum,
formatMinimum
} =
/** @type {Schema & {formatMinimum?: string; formatExclusiveMinimum?: boolean;}} */
schema;
hints.push(`should be ${formatExclusiveMinimum ? '>' : '>='} ${JSON.stringify(formatMinimum)}`);
}
if (schema.formatMaximum) {
hints.push(`should be ${schema.formatExclusiveMaximum ? '<' : '<='} ${JSON.stringify(schema.formatMaximum)}`);
if (
/** @type {Schema & {formatMaximum?: string; formatExclusiveMaximum?: boolean;}} */
schema.formatMaximum) {
const {
formatExclusiveMaximum,
formatMaximum
} =
/** @type {Schema & {formatMaximum?: string; formatExclusiveMaximum?: boolean;}} */
schema;
hints.push(`should be ${formatExclusiveMaximum ? '<' : '<='} ${JSON.stringify(formatMaximum)}`);
}

@@ -431,6 +649,8 @@

if (Array.isArray(schema.items) && schema.items.length > 0) {
items = `${schema.items.map(formatInnerSchema).join(', ')}`;
items = `${
/** @type {Array<Schema>} */
schema.items.map(item => formatInnerSchema(item)).join(', ')}`;
if (hasAdditionalItems) {
if (isObject(schema.additionalItems) && Object.keys(schema.additionalItems).length > 0) {
if (schema.additionalItems && isObject(schema.additionalItems) && Object.keys(schema.additionalItems).length > 0) {
hints.push(`additional items should be ${formatInnerSchema(schema.additionalItems)}`);

@@ -466,3 +686,3 @@ }

if (typeof schema.maxProperties === 'number') {
hints.push(`should not have more than ${schema.maxProperties} ${schema.minProperties > 1 ? 'properties' : 'property'}`);
hints.push(`should not have more than ${schema.maxProperties} ${schema.minProperties && schema.minProperties > 1 ? 'properties' : 'property'}`);
}

@@ -477,4 +697,5 @@

const required = schema.required ? schema.required : [];
const allProperties = [...new Set([].concat(required).concat(properties))];
const hasAdditionalProperties = typeof schema.additionalProperties === 'undefined' || Boolean(schema.additionalProperties);
const allProperties = [...new Set(
/** @type {Array<string>} */
[].concat(required).concat(properties))];
const objectStructure = allProperties.map(property => {

@@ -485,7 +706,14 @@ const isRequired = required.includes(property); // Some properties need quotes, maybe we should add check

return `${property}${isRequired ? '' : '?'}`;
}).concat(hasAdditionalProperties ? isObject(schema.additionalProperties) ? [`<key>: ${formatInnerSchema(schema.additionalProperties)}`] : ['…'] : []).join(', ');
}).concat(typeof schema.additionalProperties === 'undefined' || Boolean(schema.additionalProperties) ? schema.additionalProperties && isObject(schema.additionalProperties) ? [`<key>: ${formatInnerSchema(schema.additionalProperties)}`] : ['…'] : []).join(', ');
const {
dependencies,
propertyNames,
patternRequired
} =
/** @type {Schema & {patternRequired?: Array<string>;}} */
schema;
if (schema.dependencies) {
Object.keys(schema.dependencies).forEach(dependencyName => {
const dependency = schema.dependencies[dependencyName];
if (dependencies) {
Object.keys(dependencies).forEach(dependencyName => {
const dependency = dependencies[dependencyName];

@@ -500,8 +728,13 @@ if (Array.isArray(dependency)) {

if (schema.propertyNames && Object.keys(schema.propertyNames).length > 0) {
if (propertyNames && Object.keys(propertyNames).length > 0) {
hints.push(`each property name should match format ${JSON.stringify(schema.propertyNames.format)}`);
}
if (schema.patternRequired && schema.patternRequired.length > 0) {
hints.push(`should have property matching pattern ${schema.patternRequired.map(item => JSON.stringify(item))}`);
if (patternRequired && patternRequired.length > 0) {
hints.push(`should have property matching pattern ${patternRequired.map(
/**
* @param {string} item
* @returns {string}
*/
item => JSON.stringify(item))}`);
}

@@ -517,3 +750,3 @@

if (Array.isArray(schema.type)) {
return `${schema.type.map(item => item).join(' | ')}`;
return `${schema.type.join(' | ')}`;
} // Fallback for unknown keywords

@@ -526,7 +759,21 @@

}
/**
* @param {Schema=} schemaPart
* @param {(boolean | Array<string>)=} additionalPath
* @param {boolean=} needDot
* @returns {string}
*/
getSchemaPartText(schemaPart, additionalPath, needDot = false) {
if (additionalPath) {
if (!schemaPart) {
return '';
}
if (Array.isArray(additionalPath)) {
for (let i = 0; i < additionalPath.length; i++) {
const inner = schemaPart[additionalPath[i]];
/** @type {Schema | undefined} */
const inner = schemaPart[
/** @type {keyof Schema} */
additionalPath[i]];

@@ -555,4 +802,13 @@ if (inner) {

}
/**
* @param {Schema=} schemaPart
* @returns {string}
*/
getSchemaPartDescription(schemaPart) {
if (!schemaPart) {
return '';
}
while (schemaPart.$ref) {

@@ -569,47 +825,103 @@ // eslint-disable-next-line no-param-reassign

}
/**
* @param {SchemaUtilErrorObject} error
* @returns {string}
*/
formatValidationError(error) {
const dataPath = `${this.baseDataPath}${error.dataPath}`;
const {
keyword,
dataPath: errorDataPath
} = error;
const dataPath = `${this.baseDataPath}${errorDataPath}`;
switch (error.keyword) {
switch (keyword) {
case 'type':
// eslint-disable-next-line default-case
switch (error.params.type) {
case 'number':
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`;
{
const {
parentSchema,
params
} = error; // eslint-disable-next-line default-case
case 'integer':
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`;
switch (
/** @type {import("ajv").TypeParams} */
params.type) {
case 'number':
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
case 'string':
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`;
case 'integer':
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
case 'boolean':
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`;
case 'string':
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
case 'array':
return `${dataPath} should be an array:\n${this.getSchemaPartText(error.parentSchema)}`;
case 'boolean':
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
case 'object':
return `${dataPath} should be an object:\n${this.getSchemaPartText(error.parentSchema)}`;
case 'array':
return `${dataPath} should be an array:\n${this.getSchemaPartText(parentSchema)}`;
case 'null':
return `${dataPath} should be a ${this.getSchemaPartText(error.parentSchema, false, true)}`;
case 'object':
return `${dataPath} should be an object:\n${this.getSchemaPartText(parentSchema)}`;
default:
return `${dataPath} should be:\n${this.getSchemaPartText(error.parentSchema)}`;
case 'null':
return `${dataPath} should be a ${this.getSchemaPartText(parentSchema, false, true)}`;
default:
return `${dataPath} should be:\n${this.getSchemaPartText(parentSchema)}`;
}
}
case 'instanceof':
return `${dataPath} should be an instance of ${this.getSchemaPartText(error.parentSchema)}.`;
{
const {
parentSchema
} = error;
return `${dataPath} should be an instance of ${this.getSchemaPartText(parentSchema)}.`;
}
case 'pattern':
return `${dataPath} should match pattern ${JSON.stringify(error.params.pattern)}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
pattern
} =
/** @type {import("ajv").PatternParams} */
params;
return `${dataPath} should match pattern ${JSON.stringify(pattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'format':
return `${dataPath} should match format ${JSON.stringify(error.params.format)}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
format
} =
/** @type {import("ajv").FormatParams} */
params;
return `${dataPath} should match format ${JSON.stringify(format)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'formatMinimum':
case 'formatMaximum':
return `${dataPath} should be ${error.params.comparison} ${JSON.stringify(error.params.limit)}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
comparison,
limit
} =
/** @type {import("ajv").ComparisonParams} */
params;
return `${dataPath} should be ${comparison} ${JSON.stringify(limit)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}

@@ -621,25 +933,67 @@ case 'minimum':

{
const hints = numberHints(error.parentSchema);
const {
parentSchema,
params
} = error;
const {
comparison,
limit
} =
/** @type {import("ajv").ComparisonParams} */
params;
const hints = numberHints(parentSchema);
if (hints.length === 0) {
hints.push(`should be ${error.params.comparison} ${error.params.limit}`);
hints.push(`should be ${comparison} ${limit}`);
}
return `${dataPath} ${hints.join(' ')}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
return `${dataPath} ${hints.join(' ')}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'multipleOf':
return `${dataPath} should be multiple of ${error.params.multipleOf}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
multipleOf
} =
/** @type {import("ajv").MultipleOfParams} */
params;
return `${dataPath} should be multiple of ${multipleOf}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'patternRequired':
return `${dataPath} should have property matching pattern ${JSON.stringify(error.params.missingPattern)}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
missingPattern
} =
/** @type {import("ajv").PatternRequiredParams} */
params;
return `${dataPath} should have property matching pattern ${JSON.stringify(missingPattern)}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'minLength':
{
if (error.params.limit === 1) {
return `${dataPath} should be an non-empty string${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
if (limit === 1) {
return `${dataPath} should be an non-empty string${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
const length = error.params.limit - 1;
return `${dataPath} should be longer than ${length} character${length > 1 ? 's' : ''}${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const length = limit - 1;
return `${dataPath} should be longer than ${length} character${length > 1 ? 's' : ''}${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}

@@ -649,7 +1003,17 @@

{
if (error.params.limit === 1) {
return `${dataPath} should be an non-empty array${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
if (limit === 1) {
return `${dataPath} should be an non-empty array${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
return `${dataPath} should not have fewer than ${error.params.limit} items${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
return `${dataPath} should not have fewer than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}

@@ -659,41 +1023,147 @@

{
if (error.params.limit === 1) {
return `${dataPath} should be an non-empty object${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
if (limit === 1) {
return `${dataPath} should be an non-empty object${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
return `${dataPath} should not have fewer than ${error.params.limit} properties${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
return `${dataPath} should not have fewer than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'maxLength':
return `${dataPath} should be shorter than ${error.params.limit + 1} characters${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
return `${dataPath} should be shorter than ${limit + 1} characters${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'maxItems':
return `${dataPath} should not have more than ${error.params.limit} items${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'maxProperties':
return `${dataPath} should not have more than ${error.params.limit} properties${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
return `${dataPath} should not have more than ${limit} properties${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'uniqueItems':
return `${dataPath} should not contain the item '${error.data[error.params.i]}' twice${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
i
} =
/** @type {import("ajv").UniqueItemsParams} */
params;
return `${dataPath} should not contain the item '${error.data[i]}' twice${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}
case 'additionalItems':
return `${dataPath} should not have more than ${error.params.limit} items${getSchemaNonTypes(error.parentSchema)}. These items are valid:\n${this.getSchemaPartText(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
limit
} =
/** @type {import("ajv").LimitParams} */
params;
return `${dataPath} should not have more than ${limit} items${getSchemaNonTypes(parentSchema)}. These items are valid:\n${this.getSchemaPartText(parentSchema)}`;
}
case 'contains':
return `${dataPath} should contains at least one ${this.getSchemaPartText(error.parentSchema, ['contains'])} item${getSchemaNonTypes(error.parentSchema)}.`;
{
const {
parentSchema
} = error;
return `${dataPath} should contains at least one ${this.getSchemaPartText(parentSchema, ['contains'])} item${getSchemaNonTypes(parentSchema)}.`;
}
case 'required':
{
const missingProperty = error.params.missingProperty.replace(/^\./, '');
const hasProperty = Boolean(error.parentSchema.properties && error.parentSchema.properties[missingProperty]);
return `${dataPath} misses the property '${missingProperty}'${getSchemaNonTypes(error.parentSchema)}.${hasProperty ? ` Should be:\n${this.getSchemaPartText(error.parentSchema, ['properties', missingProperty])}` : this.getSchemaPartDescription(error.parentSchema)}`;
const {
parentSchema,
params
} = error;
const missingProperty =
/** @type {import("ajv").DependenciesParams} */
params.missingProperty.replace(/^\./, '');
const hasProperty = parentSchema && Boolean(
/** @type {Schema} */
parentSchema.properties &&
/** @type {Schema} */
parentSchema.properties[missingProperty]);
return `${dataPath} misses the property '${missingProperty}'${getSchemaNonTypes(parentSchema)}.${hasProperty ? ` Should be:\n${this.getSchemaPartText(parentSchema, ['properties', missingProperty])}` : this.getSchemaPartDescription(parentSchema)}`;
}
case 'additionalProperties':
return `${dataPath} has an unknown property '${error.params.additionalProperty}'${getSchemaNonTypes(error.parentSchema)}. These properties are valid:\n${this.getSchemaPartText(error.parentSchema)}`;
{
const {
params,
parentSchema
} = error;
const {
additionalProperty
} =
/** @type {import("ajv").AdditionalPropertiesParams} */
params;
return `${dataPath} has an unknown property '${additionalProperty}'${getSchemaNonTypes(parentSchema)}. These properties are valid:\n${this.getSchemaPartText(parentSchema)}`;
}
case 'dependencies':
{
const dependencies = error.params.deps.split(',').map(dep => `'${dep.trim()}'`).join(', ');
return `${dataPath} should have properties ${dependencies} when property '${error.params.property}' is present${getSchemaNonTypes(error.parentSchema)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const {
params,
parentSchema
} = error;
const {
property,
deps
} =
/** @type {import("ajv").DependenciesParams} */
params;
const dependencies = deps.split(',').map(
/**
* @param {string} dep
* @returns {string}
*/
dep => `'${dep.trim()}'`).join(', ');
return `${dataPath} should have properties ${dependencies} when property '${property}' is present${getSchemaNonTypes(parentSchema)}.${this.getSchemaPartDescription(parentSchema)}`;
}

@@ -703,3 +1173,13 @@

{
return `${dataPath} property name '${error.params.propertyName}' is invalid${getSchemaNonTypes(error.parentSchema)}. Property names should be match format ${JSON.stringify(error.schema.format)}.${this.getSchemaPartDescription(error.parentSchema)}`;
const {
params,
parentSchema,
schema
} = error;
const {
propertyName
} =
/** @type {import("ajv").PropertyNamesParams} */
params;
return `${dataPath} property name '${propertyName}' is invalid${getSchemaNonTypes(parentSchema)}. Property names should be match format ${JSON.stringify(schema.format)}.${this.getSchemaPartDescription(parentSchema)}`;
}

@@ -709,14 +1189,33 @@

{
if (error.parentSchema && error.parentSchema.enum && error.parentSchema.enum.length === 1) {
return `${dataPath} should be ${this.getSchemaPartText(error.parentSchema, false, true)}`;
const {
parentSchema
} = error;
if (parentSchema &&
/** @type {Schema} */
parentSchema.enum &&
/** @type {Schema} */
parentSchema.enum.length === 1) {
return `${dataPath} should be ${this.getSchemaPartText(parentSchema, false, true)}`;
}
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}`;
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`;
}
case 'const':
return `${dataPath} should be equal to constant ${this.getSchemaPartText(error.parentSchema)}`;
{
const {
parentSchema
} = error;
return `${dataPath} should be equal to constant ${this.getSchemaPartText(parentSchema)}`;
}
case 'not':
return `${dataPath} should not be ${this.getSchemaPartText(error.schema)}${likeObject(error.parentSchema) ? `\n${this.getSchemaPartText(error.parentSchema)}` : ''}`;
{
const {
schema,
parentSchema
} = error;
return `${dataPath} should not be ${this.getSchemaPartText(schema)}${parentSchema && likeObject(parentSchema) ? `\n${this.getSchemaPartText(parentSchema)}` : ''}`;
}

@@ -726,43 +1225,82 @@ case 'oneOf':

{
if (error.children && error.children.length > 0) {
const {
parentSchema,
children
} = error;
if (children && children.length > 0) {
if (error.schema.length === 1) {
const lastChild = error.children[error.children.length - 1];
const remainingChildren = error.children.slice(0, error.children.length - 1);
const lastChild = children[children.length - 1];
const remainingChildren = children.slice(0, children.length - 1);
return this.formatValidationError(Object.assign({}, lastChild, {
children: remainingChildren,
parentSchema: Object.assign({}, error.parentSchema, lastChild.parentSchema)
parentSchema: Object.assign({}, parentSchema, lastChild.parentSchema)
}));
}
let children = filterChildren(error.children);
let filteredChildren = filterChildren(children);
if (children.length === 1) {
return this.formatValidationError(children[0]);
if (filteredChildren.length === 1) {
return this.formatValidationError(filteredChildren[0]);
}
children = groupChildrenByFirstChild(children);
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}\nDetails:\n${children.map(nestedError => ` * ${indent(this.formatValidationError(nestedError), ' ')}`).join('\n')}`;
filteredChildren = groupChildrenByFirstChild(filteredChildren);
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}\nDetails:\n${filteredChildren.map(
/**
* @param {SchemaUtilErrorObject} nestedError
* @returns {string}
*/
nestedError => ` * ${indent(this.formatValidationError(nestedError), ' ')}`).join('\n')}`;
}
return `${dataPath} should be one of these:\n${this.getSchemaPartText(error.parentSchema)}`;
return `${dataPath} should be one of these:\n${this.getSchemaPartText(parentSchema)}`;
}
case 'if':
return `${dataPath} should match "${error.params.failingKeyword}" schema:\n${this.getSchemaPartText(error.parentSchema, [error.params.failingKeyword])}`;
{
const {
params,
parentSchema
} = error;
const {
failingKeyword
} =
/** @type {import("ajv").IfParams} */
params;
return `${dataPath} should match "${failingKeyword}" schema:\n${this.getSchemaPartText(parentSchema, [failingKeyword])}`;
}
case 'absolutePath':
return `${dataPath}: ${error.message}${this.getSchemaPartDescription(error.parentSchema)}`;
{
const {
message,
parentSchema
} = error;
return `${dataPath}: ${message}${this.getSchemaPartDescription(parentSchema)}`;
}
/* istanbul ignore next */
default:
// For `custom`, `false schema`, `$ref` keywords
// Fallback for unknown keywords
{
const {
message,
parentSchema
} = error;
const ErrorInJSON = JSON.stringify(error, null, 2); // For `custom`, `false schema`, `$ref` keywords
// Fallback for unknown keywords
/* istanbul ignore next */
return `${dataPath} ${error.message} (${JSON.stringify(error, null, 2)}).\n${this.getSchemaPartText(error.parentSchema)}`;
return `${dataPath} ${message} (${ErrorInJSON}).\n${this.getSchemaPartText(parentSchema, false)}`;
}
}
}
/**
* @param {Array<SchemaUtilErrorObject>} errors
* @returns {string}
*/
formatValidationErrors(errors) {
return errors.map(error => {
let formattedError = this.formatValidationError(error, this.schema);
let formattedError = this.formatValidationError(error);

@@ -769,0 +1307,0 @@ if (this.postFormatter) {

{
"name": "schema-utils",
"version": "2.5.0",
"version": "2.6.0",
"description": "webpack Validation Utils",

@@ -10,4 +10,8 @@ "license": "MIT",

"bugs": "https://github.com/webpack/schema-utils/issues",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"main": "dist/index.js",
"types": "index.d.ts",
"types": "declarations/index.d.ts",
"engines": {

@@ -18,17 +22,20 @@ "node": ">= 8.9.0"

"start": "npm run build -- -w",
"clean": "del-cli dist declarations",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production babel src -d dist --ignore \"src/**/*.test.js\" --copy-files",
"clean": "del-cli dist",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir declarations && prettier \"declarations/**/*.ts\" --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"commitlint": "commitlint --from=master",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css}\" --list-different",
"lint:js": "eslint --cache src test",
"security": "npm audit",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css,ts}\" --list-different",
"lint:js": "eslint --cache .",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
"test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "npm run test:coverage",
"prepare": "npm run build",
"release": "standard-version",
"security": "npm audit",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "cross-env NODE_ENV=test jest --watch",
"test:coverage": "cross-env NODE_ENV=test jest --collectCoverageFrom=\"src/**/*.js\" --coverage",
"pretest": "npm run lint",
"test": "cross-env NODE_ENV=test npm run test:coverage",
"defaults": "webpack-defaults"

@@ -38,3 +45,3 @@ },

"dist",
"index.d.ts"
"declarations"
],

@@ -46,25 +53,26 @@ "dependencies": {

"devDependencies": {
"@babel/cli": "^7.6.2",
"@babel/core": "^7.6.2",
"@babel/preset-env": "^7.6.2",
"@babel/cli": "^7.7.4",
"@babel/core": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@types/json-schema": "^7.0.3",
"@webpack-contrib/defaults": "^5.0.2",
"@webpack-contrib/defaults": "^6.2.0",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^24.9.0",
"commitlint-azure-pipelines-cli": "^1.0.2",
"cross-env": "^6.0.0",
"cross-env": "^6.0.3",
"del": "^5.1.0",
"del-cli": "^3.0.0",
"eslint": "^6.4.0",
"eslint-config-prettier": "^6.3.0",
"eslint": "^6.7.1",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-import": "^2.18.2",
"husky": "^3.0.5",
"husky": "^3.1.0",
"jest": "^24.9.0",
"jest-junit": "^8.0.0",
"lint-staged": "^9.4.0",
"jest-junit": "^9.0.0",
"lint-staged": "^9.5.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"standard-version": "^7.0.0"
"prettier": "^1.19.1",
"standard-version": "^7.0.1",
"typescript": "^3.7.2"
},

@@ -71,0 +79,0 @@ "keywords": [

@@ -99,2 +99,22 @@ <div align="center">

There is an alternative method to configure the `name` and`baseDataPath` options via the `title` property in the schema.
For example:
```json
{
"title": "My Loader options",
"type": "object",
"properties": {
"name": {
"description": "This is description of option.",
"type": "string"
}
},
"additionalProperties": false
}
```
The last word used for the `baseDataPath` option, other words used for the `name` option.
Based on the example above the `name` option equals `My Loader`, the `baseDataPath` option equals `options`.
#### `name`

@@ -101,0 +121,0 @@

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc