From c414e47c206cdae393e097bd81cdd1cbf915693a Mon Sep 17 00:00:00 2001 From: DogmaDragon <103123951+DogmaDragon@users.noreply.github.com> Date: Fri, 21 Feb 2025 02:48:53 +0200 Subject: [PATCH] Add plugin schema validator workflow (#505) * Remove code formatting workflows and related configuration files * Remove formatting instructions from README * Add plugin validator * Add workflow * Limit workflow to plugins/themes directories --- .github/workflows/validate.yml | 24 +++ validate.js | 3 + validator/index.js | 163 ++++++++++++++++++ validator/package.json | 16 ++ validator/plugin.schema.json | 300 +++++++++++++++++++++++++++++++++ validator/yarn.lock | 210 +++++++++++++++++++++++ 6 files changed, 716 insertions(+) create mode 100644 .github/workflows/validate.yml create mode 100644 validate.js create mode 100644 validator/index.js create mode 100644 validator/package.json create mode 100644 validator/plugin.schema.json create mode 100644 validator/yarn.lock diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..cdc1f87 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,24 @@ +name: Validate Plugins + +on: + push: + branches: [main] + paths: + - 'plugins/**' + - 'themes/**' + pull_request: + branches: [main] + paths: + - 'plugins/**' + - 'themes/**' + +jobs: + validate: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + - run: cd ./validator && yarn install --frozen-lockfile + - run: node ./validate.js --ci \ No newline at end of file diff --git a/validate.js b/validate.js new file mode 100644 index 0000000..8ac557e --- /dev/null +++ b/validate.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +'use strict'; +require('./validator/index.js')(); \ No newline at end of file diff --git a/validator/index.js b/validator/index.js new file mode 100644 index 0000000..d3bc6c6 --- /dev/null +++ b/validator/index.js @@ -0,0 +1,163 @@ +#!/usr/bin/env node +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const safeRequire = (name) => { + try { + return require(name); + } catch (error) { + if (error && error.code === 'MODULE_NOT_FOUND') { + console.log(`Error: Cannot find module '${name}', have you installed the dependencies?`); + process.exit(1); + } + throw error; + } +}; + +const Ajv = safeRequire('ajv').default; +const betterAjvErrors = safeRequire('better-ajv-errors').default; +const chalk = safeRequire('chalk'); +const YAML = safeRequire('yaml'); +const addFormats = safeRequire('ajv-formats'); + +// https://www.peterbe.com/plog/nodejs-fs-walk-or-glob-or-fast-glob +function walk(directory, ext, filepaths = []) { + const files = fs.readdirSync(directory); + for (const filename of files) { + const filepath = path.join(directory, filename); + if (fs.statSync(filepath).isDirectory()) { + walk(filepath, ext, filepaths); + } else if (path.extname(filename) === ext && !filename.includes('config')) { + filepaths.push(filepath); + } + } + return filepaths; +} + +// https://stackoverflow.com/a/53833620 +const isSorted = arr => arr.every((v,i,a) => !i || a[i-1] <= v); + +class Validator { + constructor(flags) { + this.allowDeprecations = flags.includes('-d'); + this.stopOnError = !flags.includes('-a'); + this.sortedURLs = flags.includes('-s'); + this.verbose = flags.includes('-v'); + + const schemaPath = path.resolve(__dirname, './plugin.schema.json'); + this.schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + this.ajv = new Ajv({ + // allErrors: true, + allowUnionTypes: true, // Use allowUnionTypes instead of ignoreKeywordsWithRef + strict: true, + allowMatchingProperties: true, // Allow properties that match a pattern + }); + addFormats(this.ajv); + } + + run(files) { + let plugins; + + if (files && Array.isArray(files) && files.length > 0) { + plugins = files.map(file => path.resolve(file)); + } else { + const pluginsDir = path.resolve(__dirname, '../plugins'); + const themesDir = path.resolve(__dirname, '../themes'); + plugins = walk(pluginsDir, '.yml').concat(walk(themesDir, '.yml')); + } + + let result = true; + const validate = this.ajv.compile(this.schema); + + for (const file of plugins) { + const relPath = path.relative(process.cwd(), file); + let contents, data; + try { + contents = fs.readFileSync(file, 'utf8'); + data = YAML.parse(contents); + } catch (error) { + console.error(`${chalk.red(chalk.bold('ERROR'))} in: ${relPath}:`); + error.stack = null; + console.error(error); + result = result && false; + if (this.stopOnError) break; + else continue; + } + + let valid = validate(data); + + // Output validation errors + if (!valid) { + const output = betterAjvErrors(this.schema, data, validate.errors, { indent: 2 }); + console.log(output); + + // Detailed error checks + validate.errors.forEach(err => { + switch (err.keyword) { + case 'required': + console.error(`${chalk.red('Missing Required Property:')} ${err.params.missingProperty}`); + break; + case 'type': + console.error(`${chalk.red('Type Mismatch:')} ${err.dataPath} should be ${err.params.type}`); + break; + case 'pattern': + console.error(`${chalk.red('Pattern Mismatch:')} ${err.dataPath} should match pattern ${err.params.pattern}`); + break; + case 'enum': + console.error(`${chalk.red('Enum Violation:')} ${err.dataPath} should be one of ${err.params.allowedValues.join(', ')}`); + break; + case 'additionalProperties': + console.error(`${chalk.red('Additional Properties:')} ${err.params.additionalProperty} is not allowed`); + break; + case '$ref': + console.error(`${chalk.red('Invalid Reference:')} ${err.dataPath} ${err.message}`); + break; + case 'items': + console.error(`${chalk.red('Array Item Type Mismatch:')} ${err.dataPath} ${err.message}`); + break; + case 'format': + console.error(`${chalk.red('Invalid Format:')} ${err.dataPath} should match format ${err.params.format}`); + break; + default: + console.error(`${chalk.red('Validation Error:')} ${err.dataPath} ${err.message}`); + } + }); + } + + if (this.verbose || !valid) { + const validColor = valid ? chalk.green : chalk.red; + console.log(`${relPath} Valid: ${validColor(valid)}`); + } + + result = result && valid; + + if (!valid && this.stopOnError) break; + } + + if (!this.verbose && result) { + console.log(chalk.green('Validation passed!')); + } + + return result; + } +} + +function main(flags, files) { + const args = process.argv.slice(2) + flags = (flags === undefined) ? args.filter(arg => arg.startsWith('-')) : flags; + files = (files === undefined) ? args.filter(arg => !arg.startsWith('-')) : files; + const validator = new Validator(flags); + const result = validator.run(files); + if (flags.includes('--ci')) { + process.exit(result ? 0 : 1); + } +} + +if (require.main === module) { + main(); +} + +module.exports = main; +module.exports.Validator = Validator; \ No newline at end of file diff --git a/validator/package.json b/validator/package.json new file mode 100644 index 0000000..f0c4627 --- /dev/null +++ b/validator/package.json @@ -0,0 +1,16 @@ +{ + "name": "stash-script-validator", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "scripts": { + "main": "node index.js" + }, + "dependencies": { + "ajv": "7", + "ajv-formats": "^3.0.1", + "better-ajv-errors": "^1.2.0", + "chalk": "4", + "yaml": "^2.5.1" + } + } \ No newline at end of file diff --git a/validator/plugin.schema.json b/validator/plugin.schema.json new file mode 100644 index 0000000..9822fbf --- /dev/null +++ b/validator/plugin.schema.json @@ -0,0 +1,300 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "plugin", + "title": "Stash Plugin", + "description": "A stash plugin config", + "type": "object", + "additionalProperties": false, + "required": ["name"], + "properties": { + "name": { + "title": "Plugin name", + "description": "The name of the plugin.", + "type": "string" + }, + "description": { + "title": "Plugin description", + "description": "Short description of the plugin.", + "type": ["string", "null"] + }, + "version": { + "title": "Plugin version", + "description": "Format: x.y.z where x y and z are integers.", + "type": ["string", "integer", "number"], + "pattern": "^\\d+(\\.\\d+)?(\\.\\d+)?$" + }, + "url": { + "title": "Url", + "description": "Optional url", + "type": ["string", "null"] + }, + "ui": { + "title": "Plugin UI", + "description": "Optional files needed to render plugin specific UI", + "$ref": "#/definitions/UIConfig" + }, + "exec": { + "title": "Command to run the plugin", + "description": "For external plugin tasks, the exec field is a list with the first element being the binary that will be executed, and the subsequent elements are the arguments passed. The execution process will search the path for the binary, then will attempt to find the program in the same directory as the plugin configuration file. The exe extension is not necessary on Windows systems.\n\nFor embedded plugins, the exec field is a list with the first element being the path to the Javascript file that will be executed. It is expected that the path to the Javascript file is relative to the directory of the plugin configuration file.", + "type": "array", + "items": { + "type": "string" + } + }, + "interface": { + "title": "Plugin interface", + "description": "For external plugin tasks, the interface field must be set to one of the following values: rpc, raw\n\nFor embedded plugins, the interface field must be set to one of the following values: js\n\nThe interface field defaults to raw if not provided.", + "type": "string", + "enum": ["js", "raw", "rpc"], + "default": "raw" + }, + "errLog": { + "title": "Log level", + "description": "Tells stash what the default log level should be when the plugin outputs to stderr without encoding a log level. It defaults to the error level if no provided. This field is not necessary if the plugin outputs logging with the appropriate encoding.", + "type": "string", + "enum": ["trace", "debug", "info", "warning", "error"], + "default": "raw" + }, + "hooks": { + "title": "Hooks configuration", + "description": "Array of individual hook configurations.", + "type": "array", + "items": { + "$ref": "#/definitions/HookConfig" + } + }, + "tasks": { + "title": "Tasks configuration", + "description": "Array of individual tasks configurations.", + "type": "array", + "items": { + "$ref": "#/definitions/TaskConfig" + } + }, + "settings": { + "title": "Plugin settings", + "description": "The settings defined for this plugin.", + "$ref": "#/definitions/SettingConfig" + } + }, + "definitions": { + "UIConfig": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": ["css"] + }, + { + "required": ["javascript"] + } + ], + "properties": { + "css": { + "title": "CSS files", + "description": "Optional list of CSS files to include in the UI", + "items": { + "description": "Path to CSS file" + }, + "$ref": "#/definitions/StringList" + }, + "javascript": { + "title": "Javascript files", + "description": "Optional list of Javascript files to include in the UI", + "items": { + "description": "Path to Javascript file" + }, + "$ref": "#/definitions/StringList" + }, + "requires": { + "title": "Required plugins", + "description": "Optional list of plugin IDs to load prior to this plugin.", + "items": { + "description": "Required plugin ID" + }, + "$ref": "#/definitions/StringList" + }, + "assets": { + "title": "Assets", + "description": "Optional map of assets.", + "type": "object", + "patternProperties": { + "": { + "type": "string", + "title": "Asset location", + "description": "Asset location on the file system as pair of values urlPrefix: fsLocation." + } + } + }, + "csp": { + "title": "Content-security policy overrides", + "description": "Optional map of policy directives", + "type": "object", + "additionalProperties": true, + "properties": { + "script-src": { + "$ref": "#/definitions/CspDirective" + }, + "style-src": { + "$ref": "#/definitions/CspDirective" + }, + "connect-src": { + "$ref": "#/definitions/CspDirective" + } + }, + "patternProperties": { + "": { + "$ref": "#/definitions/CspDirective" + } + } + }, + "settings": { + "title": "UI plugin settings", + "description": "Map of setting names to be displayed in the plugins page in the UI.", + "$ref": "#/definitions/SettingConfig" + } + } + }, + "HookConfig": { + "type": "object", + "required": ["name", "triggeredBy"], + "properties": { + "name": { + "type": "string", + "description": "Hook name" + }, + "description": { + "type": ["string", "null"], + "description": "Optional description for this hook" + }, + "triggeredBy": { + "type": "array", + "items": { + "$ref": "#/definitions/TriggerType" + } + }, + "defaultArgs": { + "description": "Default arguments", + "type": "object", + "items": { + "type": "string" + } + } + } + }, + "TriggerType": { + "type": "string", + "pattern": "^(Scene|SceneMarker|Image|Gallery|GalleryChapter|Movie|Performer|Studio|Tag)\\.(Create|Update|Destroy|Merge)\\.Post$", + "enum": [ + "Scene.Create.Post", + "Scene.Update.Post", + "Scene.Destroy.Post", + "Scene.Merge.Post", + "SceneMarker.Create.Post", + "SceneMarker.Update.Post", + "SceneMarker.Destroy.Post", + "SceneMarker.Merge.Post", + "Image.Create.Post", + "Image.Update.Post", + "Image.Destroy.Post", + "Image.Merge.Post", + "Gallery.Create.Post", + "Gallery.Update.Post", + "Gallery.Destroy.Post", + "Gallery.Merge.Post", + "GalleryChapter.Create.Post", + "GalleryChapter.Update.Post", + "GalleryChapter.Destroy.Post", + "Movie.Create.Post", + "Movie.Update.Post", + "Movie.Destroy.Post", + "Movie.Merge.Post", + "Performer.Create.Post", + "Performer.Update.Post", + "Performer.Destroy.Post", + "Performer.Merge.Post", + "Studio.Create.Post", + "Studio.Update.Post", + "Studio.Destroy.Post", + "Studio.Merge.Post", + "Tag.Create.Post", + "Tag.Update.Post", + "Tag.Destroy.Post", + "Tag.Merge.Post" + ] + }, + "TaskConfig": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "Task name" + }, + "description": { + "type": "string", + "description": "Optional description for this task" + }, + "defaultArgs": { + "description": "Default arguments", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "execArgs": { + "description": "Default arguments", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "SettingConfig": { + "type": "object", + "patternProperties": { + "": { + "type": "object", + "description": "Internal name", + "required": ["displayName", "type"], + "properties": { + "displayName": { + "type": "string", + "description": "Name to display in the UI" + }, + "description": { + "type": ["string", "null"], + "description": "Optional description for this setting" + }, + "type": { + "type": "string", + "description": "Type of the attribute to show in the UI. It can be one of BOOLEAN, NUMBER, STRING", + "enum": ["BOOLEAN", "NUMBER", "STRING"] + } + } + } + } + }, + "StringList": { + "type": ["array", "null"], + "items": { + "type": "string" + } + }, + "CspDirective": { + "description": "Policy directive", + "type": ["array", "null"], + "items": { + "type": "string", + "description": "Allowed domain", + "examples": [ + "self", + "http://alloweddomain.com", + "example.com", + "*.example.com" + ] + } + } + } +} diff --git a/validator/yarn.lock b/validator/yarn.lock new file mode 100644 index 0000000..58970ef --- /dev/null +++ b/validator/yarn.lock @@ -0,0 +1,210 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.16.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@humanwhocodes/momoa@^2.0.2": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.4.tgz#8b9e7a629651d15009c3587d07a222deeb829385" + integrity sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA== + +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + +ajv@7: + version "7.2.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f" + integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ajv@^8.0.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +better-ajv-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz#6412d58fa4d460ff6ccbd9e65c5fef9781cc5286" + integrity sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA== + dependencies: + "@babel/code-frame" "^7.16.0" + "@humanwhocodes/momoa" "^2.0.2" + chalk "^4.1.2" + jsonpointer "^5.0.0" + leven "^3.1.0 < 4" + +chalk@4, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + +"leven@^3.1.0 < 4": + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +picocolors@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +yaml@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==