From faa01a291f60d61e93e9d5577b54b6f1b08989c6 Mon Sep 17 00:00:00 2001 From: Christopher Guindon <chris.guindon@eclipse-foundation.org> Date: Thu, 14 Oct 2021 14:02:51 -0400 Subject: [PATCH 01/11] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 82e7fbcb..5c11f32b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # git-eca-rest-api +--- +**NOTE** + +This project was migrated to [Eclipse Gitlab](https://gitlab.eclipse.org/eclipsefdn/it/api/git-eca-rest-api) on October 14, 2021. + +--- + This project uses Quarkus, the Supersonic Subatomic Java Framework. If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . -- GitLab From 6c9bee598601ab70aae4d8b10f355d939ab8ce04 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Wed, 26 Jan 2022 16:21:10 -0500 Subject: [PATCH 02/11] Implement EF commons lib, upgrade to Quarkus 2.6, use autovalue for pojo --- .gitignore | 3 + package-lock.json | 2087 +++++++++++++++++ package.json | 20 + pom.xml | 211 +- .../git/eca/api/AccountsAPI.java | 13 +- .../git/eca/config/CustomJacksonConfig.java | 33 - .../git/eca/config/SecretConfigSource.java | 92 - .../git/eca/model/Commit.java | 149 +- .../git/eca/model/CommitStatus.java | 192 +- .../git/eca/model/EclipseUser.java | 204 +- .../git/eca/model/GitUser.java | 61 +- .../git/eca/model/Project.java | 312 +-- .../git/eca/model/ValidationRequest.java | 90 +- .../git/eca/model/ValidationResponse.java | 189 +- .../git/eca/oauth/EclipseApi.java | 47 - .../git/eca/resource/ValidationResource.java | 361 +-- .../git/eca/service/CachingService.java | 74 - .../git/eca/service/OAuthService.java | 31 - .../eca/service/impl/DefaultOAuthService.java | 90 - .../eca/service/impl/GuavaCachingService.java | 137 -- src/main/js/openapi2schema.js | 54 + ...lipse.microprofile.config.spi.ConfigSource | 1 - src/main/resources/application.properties | 12 +- .../git/eca/api/MockAccountsAPI.java | 121 +- .../git/eca/api/MockProjectsAPI.java | 107 +- .../git/eca/helper/CommitHelperTest.java | 177 +- .../eca/resource/ValidationResourceTest.java | 1902 ++++++--------- .../eca/service/impl/MockOAuthService.java | 24 - src/test/resources/application.properties | 14 +- 29 files changed, 3903 insertions(+), 2905 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/config/CustomJacksonConfig.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/config/SecretConfigSource.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/CachingService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java delete mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/GuavaCachingService.java create mode 100644 src/main/js/openapi2schema.js delete mode 100644 src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource delete mode 100644 src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java diff --git a/.gitignore b/.gitignore index 3b04c571..6604264c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ nb-configuration.xml *.orig *.rej +# npm +node_modules + # Maven target/ pom.xml.tag diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..e116e830 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2087 @@ +{ + "name": "eclipsefdn-git-eca-rest-api-support", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "eclipsefdn-git-eca-rest-api-support", + "version": "1.0.0", + "devDependencies": { + "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1", + "@redocly/openapi-cli": "^1.0.0-beta.54", + "@stoplight/json-ref-resolver": "^3.1.2", + "decamelize": "^5.0.0", + "js-yaml": "^4.1.0", + "yargs": "^17.0.1" + } + }, + "node_modules/@openapi-contrib/openapi-schema-to-json-schema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.1.1.tgz", + "integrity": "sha512-FMvdhv9Jr9tULjJAQaQzhCmNYYj2vQFVnl7CGlLAImZvJal71oedXMGszpPaZTLftAk5TCHqjnirig+P6LZxug==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@redocly/ajv": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", + "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/openapi-cli": { + "version": "1.0.0-beta.80", + "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.80.tgz", + "integrity": "sha512-PZLustQdB0ZsKjM5vgGxEg6eFf8okaky/LmXiicnNeJhXmlv7CmvytSvm6zixCwDts4DpuFUNO1CcDf7+iJyDA==", + "dev": true, + "dependencies": { + "@redocly/openapi-core": "1.0.0-beta.80", + "@types/node": "^14.11.8", + "assert-node-version": "^1.0.3", + "chokidar": "^3.5.1", + "colorette": "^1.2.0", + "glob": "^7.1.6", + "glob-promise": "^3.4.0", + "handlebars": "^4.7.6", + "portfinder": "^1.0.26", + "simple-websocket": "^9.0.0", + "yargs": "17.0.1" + }, + "bin": { + "openapi": "bin/cli.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@redocly/openapi-cli/node_modules/yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@redocly/openapi-cli/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@redocly/openapi-core": { + "version": "1.0.0-beta.80", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.80.tgz", + "integrity": "sha512-IAQECLt/fDxjlfNdLGnJszt40BaiA6b78+zB6+7Rk8ums0HHLfwWFJPMTzh1bzJ5f+sZ4zDBi4gaIJ1n4XGCHg==", + "dev": true, + "dependencies": { + "@redocly/ajv": "^8.6.4", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^3.0.4", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@stoplight/json": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.2.tgz", + "integrity": "sha512-NwIVzanXRUy291J5BMkncCZRMG1Lx+aq+VidGQgfkJjgo8vh1Y/PSAz7fSU8gVGSZBCcqmOkMI7R4zw7DlfTwA==", + "dev": true, + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.2", + "@stoplight/types": "^12.3.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/@stoplight/json-ref-resolver": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz", + "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==", + "dev": true, + "dependencies": { + "@stoplight/json": "^3.17.0", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^12.3.0", + "@types/urijs": "^1.19.16", + "dependency-graph": "~0.11.0", + "fast-memoize": "^2.5.2", + "immer": "^9.0.6", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "tslib": "^2.3.1", + "urijs": "^1.19.6" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/@stoplight/ordered-object-literal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz", + "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/types": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.5.0.tgz", + "integrity": "sha512-dwqYcDrGmEyUv5TWrDam5TGOxU72ufyQ7hnOIIDdmW5ezOwZaBFoR5XQ9AsH49w7wgvOqB2Bmo799pJPWnpCbg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", + "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", + "dev": true + }, + "node_modules/@types/urijs": { + "version": "1.19.18", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.18.tgz", + "integrity": "sha512-tjftsOLuIWFLJxcpgFeehNnMhpMIv0ELJl0/i31jiV3au1GQpnd3/pTTDQg2zO5cSGJxtrDzMgebOH7+cqh3Vg==", + "dev": true + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assert-node-version": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz", + "integrity": "sha1-yupdG2pY285ZZhII3x4bnkxYD5E=", + "dev": true, + "dependencies": { + "expected-node-version": "^1.0.0", + "semver": "^5.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expected-node-version": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz", + "integrity": "sha1-uNIlub9nap6H4G29YVtS/J0eOGs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-promise": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", + "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", + "dev": true, + "dependencies": { + "@types/glob": "*" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "glob": "*" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/immer": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", + "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + } + }, + "node_modules/simple-websocket/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/simple-websocket/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", + "integrity": "sha512-qZukoSxOG0urUTvjc2ERMTcAy+BiFh3weWAkeurLwjrCba73poHmG3E36XEjd/JGukMzwTL7uCxZiAexj8ppvQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urijs": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", + "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", + "dev": true, + "engines": { + "node": ">=12" + } + } + }, + "dependencies": { + "@openapi-contrib/openapi-schema-to-json-schema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.1.1.tgz", + "integrity": "sha512-FMvdhv9Jr9tULjJAQaQzhCmNYYj2vQFVnl7CGlLAImZvJal71oedXMGszpPaZTLftAk5TCHqjnirig+P6LZxug==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "@redocly/ajv": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", + "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "@redocly/openapi-cli": { + "version": "1.0.0-beta.80", + "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.80.tgz", + "integrity": "sha512-PZLustQdB0ZsKjM5vgGxEg6eFf8okaky/LmXiicnNeJhXmlv7CmvytSvm6zixCwDts4DpuFUNO1CcDf7+iJyDA==", + "dev": true, + "requires": { + "@redocly/openapi-core": "1.0.0-beta.80", + "@types/node": "^14.11.8", + "assert-node-version": "^1.0.3", + "chokidar": "^3.5.1", + "colorette": "^1.2.0", + "glob": "^7.1.6", + "glob-promise": "^3.4.0", + "handlebars": "^4.7.6", + "portfinder": "^1.0.26", + "simple-websocket": "^9.0.0", + "yargs": "17.0.1" + }, + "dependencies": { + "yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } + }, + "@redocly/openapi-core": { + "version": "1.0.0-beta.80", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.80.tgz", + "integrity": "sha512-IAQECLt/fDxjlfNdLGnJszt40BaiA6b78+zB6+7Rk8ums0HHLfwWFJPMTzh1bzJ5f+sZ4zDBi4gaIJ1n4XGCHg==", + "dev": true, + "requires": { + "@redocly/ajv": "^8.6.4", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^3.0.4", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + } + }, + "@stoplight/json": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.2.tgz", + "integrity": "sha512-NwIVzanXRUy291J5BMkncCZRMG1Lx+aq+VidGQgfkJjgo8vh1Y/PSAz7fSU8gVGSZBCcqmOkMI7R4zw7DlfTwA==", + "dev": true, + "requires": { + "@stoplight/ordered-object-literal": "^1.0.2", + "@stoplight/types": "^12.3.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + } + }, + "@stoplight/json-ref-resolver": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz", + "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==", + "dev": true, + "requires": { + "@stoplight/json": "^3.17.0", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^12.3.0", + "@types/urijs": "^1.19.16", + "dependency-graph": "~0.11.0", + "fast-memoize": "^2.5.2", + "immer": "^9.0.6", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "tslib": "^2.3.1", + "urijs": "^1.19.6" + } + }, + "@stoplight/ordered-object-literal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz", + "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w==", + "dev": true + }, + "@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", + "dev": true + }, + "@stoplight/types": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.5.0.tgz", + "integrity": "sha512-dwqYcDrGmEyUv5TWrDam5TGOxU72ufyQ7hnOIIDdmW5ezOwZaBFoR5XQ9AsH49w7wgvOqB2Bmo799pJPWnpCbg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/node": { + "version": "14.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", + "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", + "dev": true + }, + "@types/urijs": { + "version": "1.19.18", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.18.tgz", + "integrity": "sha512-tjftsOLuIWFLJxcpgFeehNnMhpMIv0ELJl0/i31jiV3au1GQpnd3/pTTDQg2zO5cSGJxtrDzMgebOH7+cqh3Vg==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assert-node-version": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz", + "integrity": "sha1-yupdG2pY285ZZhII3x4bnkxYD5E=", + "dev": true, + "requires": { + "expected-node-version": "^1.0.0", + "semver": "^5.0.3" + } + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "expected-node-version": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz", + "integrity": "sha1-uNIlub9nap6H4G29YVtS/J0eOGs=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-promise": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", + "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "immer": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", + "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "dev": true, + "requires": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "uglify-js": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", + "integrity": "sha512-qZukoSxOG0urUTvjc2ERMTcAy+BiFh3weWAkeurLwjrCba73poHmG3E36XEjd/JGukMzwTL7uCxZiAexj8ppvQ==", + "dev": true, + "optional": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urijs": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", + "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true + }, + "yargs": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..558a65b5 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "eclipsefdn-git-eca-rest-api-support", + "version": "1.0.0", + "devDependencies": { + "@redocly/openapi-cli": "^1.0.0-beta.54", + "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1", + "@stoplight/json-ref-resolver": "^3.1.2", + "decamelize": "^5.0.0", + "js-yaml": "^4.1.0", + "yargs": "^17.0.1" + }, + "private": true, + "scripts": { + "start": "npm run generate-json-schema && npx @redocly/openapi-cli preview-docs spec/openapi.yaml -p 8093", + "test": "npm run generate-json-schema && npx @redocly/openapi-cli lint spec/openapi.yaml", + "generate-json-schema": "npm run clean && node src/main/js/openapi2schema.js -s spec/openapi.yaml -t src/test/resources", + "clean": "rm -rf src/test/resources/schemas/" + } + } + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1bf4edc1..c1cc7de9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,29 +1,35 @@ <?xml version="1.0"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>org.eclipsefoundation</groupId> <artifactId>git-eca</artifactId> <version>0.0.1</version> <properties> + <eclipse-api-version>0.6-SNAPSHOT</eclipse-api-version> <compiler-plugin.version>3.8.1</compiler-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <quarkus-plugin.version>1.3.1.Final</quarkus-plugin.version> <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id> - <quarkus.platform.version>1.3.0.Final</quarkus.platform.version> + <quarkus.platform.version>2.6.3.Final</quarkus.platform.version> <surefire-plugin.version>2.22.1</surefire-plugin.version> - <sonar.sources>src/main</sonar.sources> - <sonar.tests>src/test</sonar.tests> - <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> - <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> - <sonar.jacoco.reportPaths>${project.build.directory}/jacoco-report</sonar.jacoco.reportPaths> - <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath> + <auto-value.version>1.8.2</auto-value.version> </properties> + <repositories> + <repository> + <id>eclipsefdn</id> + <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> <dependencyManagement> <dependencies> <dependency> @@ -36,54 +42,99 @@ </dependencies> </dependencyManagement> <dependencies> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <artifactId>quarkus-core</artifactId> + <version>${eclipse-api-version}</version> + </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-junit5</artifactId> - <scope>test</scope> + <artifactId>quarkus-resteasy-jackson</artifactId> </dependency> <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>rest-assured</artifactId> - <scope>test</scope> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-smallrye-context-propagation</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy-jackson</artifactId> + <artifactId>quarkus-oidc</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-rest-client</artifactId> + <artifactId>quarkus-oidc-client-filter</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-smallrye-context-propagation</artifactId> + <artifactId>quarkus-rest-client</artifactId> + </dependency> + <!-- Annotation preprocessors - reduce all of the boiler plate --> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value-annotations</artifactId> </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </dependency> + + <!-- Third-party reqs --> <dependency> <groupId>com.github.scribejava</groupId> <artifactId>scribejava-apis</artifactId> <version>6.4.1</version> </dependency> - <!-- Caching --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> + + <!-- Test requirements --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>rest-assured</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>json-schema-validator</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit5-mockito</artifactId> + <scope>test</scope> + </dependency> </dependencies> + <build> <plugins> <plugin> - <groupId>io.quarkus</groupId> + <groupId>${quarkus.platform.group-id}</groupId> <artifactId>quarkus-maven-plugin</artifactId> - <version>${quarkus-plugin.version}</version> + <version>${quarkus.platform.version}</version> + <extensions>true</extensions> <executions> <execution> <goals> <goal>build</goal> + <goal>generate-code</goal> + <goal>generate-code-tests</goal> </goals> </execution> </executions> @@ -91,14 +142,75 @@ <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>${compiler-plugin.version}</version> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.3.2</version> + <executions> + <execution> + <id>npm install (initialize)</id> + <goals> + <goal>exec</goal> + </goals> + <phase>initialize</phase> + <configuration> + <executable>npm</executable> + <arguments> + <argument>install</argument> + <argument>-f</argument> + </arguments> + </configuration> + </execution> + <execution> + <id>npm clean</id> + <goals> + <goal>exec</goal> + </goals> + <phase>clean</phase> + <configuration> + <executable>npm</executable> + <arguments> + <argument>run</argument> + <argument>clean</argument> + </arguments> + </configuration> + </execution> + <execution> + <id>npm run pre-test</id> + <goals> + <goal>exec</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <executable>npm</executable> + <skip>${maven.test.skip}</skip> + <arguments> + <argument>run</argument> + <argument>generate-json-schema</argument> + </arguments> + </configuration> + </execution> + </executions> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire-plugin.version}</version> <configuration> - <systemProperties> + <skipTests>false</skipTests> + <systemPropertyVariables> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> - </systemProperties> + <maven.home>${maven.home}</maven.home> + </systemPropertyVariables> </configuration> </plugin> </plugins> @@ -123,9 +235,11 @@ <goal>verify</goal> </goals> <configuration> - <systemProperties> + <systemPropertyVariables> <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> - </systemProperties> + <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> + <maven.home>${maven.home}</maven.home> + </systemPropertyVariables> </configuration> </execution> </executions> @@ -136,50 +250,5 @@ <quarkus.package.type>native</quarkus.package.type> </properties> </profile> - <profile> - <id>sonar-dev</id> - <build> - <plugins> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire-plugin.version}</version> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.4</version> - <configuration> - <destFile>${sonar.jacoco.reportPaths}</destFile> - <append>true</append> - </configuration> - <executions> - <execution> - <goals> - <goal>prepare-agent</goal> - </goals> - </execution> - <execution> - <id>report</id> - <phase>test</phase> - <goals> - <goal>report</goal> - </goals> - <configuration> - <outputDirectory>${sonar.jacoco.reportPaths}</outputDirectory> - </configuration> - </execution> - </executions> - </plugin> - <plugin> - <groupId>org.sonarsource.scanner.maven</groupId> - <artifactId>sonar-maven-plugin</artifactId> - <version>3.6.0.1398</version> - </plugin> - </plugins> - </build> - <properties> - <sonar.host.url>https://sonarqube.dev.docker</sonar.host.url> - </properties> - </profile> </profiles> -</project> +</project> \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java index e52c14a4..1aadfe55 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java @@ -12,7 +12,6 @@ package org.eclipsefoundation.git.eca.api; import java.util.List; import javax.ws.rs.GET; -import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -21,6 +20,9 @@ import javax.ws.rs.QueryParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipsefoundation.git.eca.model.EclipseUser; +import io.quarkus.oidc.client.filter.OidcClientFilter; +import io.quarkus.security.Authenticated; + /** * Binding interface for the Eclipse Foundation user account API. Runtime * implementations are automatically generated by Quarkus at compile time. As @@ -30,7 +32,10 @@ import org.eclipsefoundation.git.eca.model.EclipseUser; * @author Martin Lowe * */ +@OidcClientFilter +@Authenticated @RegisterRestClient +@Produces("application/json") public interface AccountsAPI { /** @@ -43,8 +48,7 @@ public interface AccountsAPI { */ @GET @Path("/account/profile") - @Produces("application/json") - List<EclipseUser> getUsers(@HeaderParam("Authorization") String authBearer, @QueryParam("uid") String id, + List<EclipseUser> getUsers(@QueryParam("uid") String id, @QueryParam("name") String name, @QueryParam("mail") String mail); /** @@ -57,7 +61,6 @@ public interface AccountsAPI { */ @GET @Path("/github/profile/{uname}") - @Produces("application/json") - EclipseUser getUserByGithubUname(@HeaderParam("Authorization") String authBearer, @PathParam("uname") String uname); + EclipseUser getUserByGithubUname(@PathParam("uname") String uname); } diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/CustomJacksonConfig.java b/src/main/java/org/eclipsefoundation/git/eca/config/CustomJacksonConfig.java deleted file mode 100644 index 567946ac..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/config/CustomJacksonConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.config; - -import javax.inject.Singleton; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.quarkus.jackson.ObjectMapperCustomizer; - -/** - * Sets Jackson serializer to not fail when an unknown property is encountered. - * - * @author Martin Lowe - * - */ -@Singleton -public class CustomJacksonConfig implements ObjectMapperCustomizer { - - @Override - public void customize(ObjectMapper objectMapper) { - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } - -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/SecretConfigSource.java b/src/main/java/org/eclipsefoundation/git/eca/config/SecretConfigSource.java deleted file mode 100644 index 956db868..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/config/SecretConfigSource.java +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.config; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.ServiceLoader; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Reads in a set of secret configuration values from the secret.properties file - * in the resources folder. These values are only secret in that they are set to - * be ignored by Git and should be set on a per-server basis. - * - * ConfigSource implementation was used to enable the usage of the - * {@link ServiceLoader} to load the configurations. - * - * @author Martin Lowe - */ -public class SecretConfigSource implements ConfigSource { - private static final Logger LOGGER = LoggerFactory.getLogger(SecretConfigSource.class); - - private static final String DEFAULT_SECRET_LOCATION = "/run/secrets/secret.properties"; - - private Map<String, String> secrets; - - @Override - @SuppressWarnings({"rawtypes", "unchecked"}) - public Map<String, String> getProperties() { - if (secrets == null) { - this.secrets = new HashMap<>(); - String secretPath = System.getProperty("config.secret.path"); - // Fallback to checking env if not set in JVM - if (secretPath == null || "".equals(secretPath.trim())) { - secretPath = System.getenv("CONFIG_SECRET_PATH"); - } - if (secretPath == null || "".equals(secretPath.trim())) { - LOGGER.error( - "Configuration 'config.secret.path' and environment variable of 'CONFIG_SECRET_PATH' not set, using default value of " - + DEFAULT_SECRET_LOCATION); - secretPath = DEFAULT_SECRET_LOCATION; - } - // load the secrets file in - File f = new File(secretPath); - if (!f.exists() || !f.canRead()) { - LOGGER.error("File at path {} either does not exist or cannot be read", secretPath); - return this.secrets; - } - - // read each of the lines of secret config that should be added - try (BufferedReader br = new BufferedReader(new FileReader(f))) { - Properties p = new Properties(); - p.load(br); - secrets.putAll((Map) p); - - } catch (IOException e) { - LOGGER.error("Error while reading in secrets configuration file.", e); - } - LOGGER.debug("Found secret keys: {}", secrets.keySet()); - - // add priority ordinal to map if missing. 260 ordinal sets the priority between - // container and environment variable priority. - secrets.computeIfAbsent(ConfigSource.CONFIG_ORDINAL, key -> "260"); - } - return secrets; - } - - @Override - public String getValue(String propertyName) { - return getProperties().get(propertyName); - } - - @Override - public String getName() { - return "secret"; - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/Commit.java b/src/main/java/org/eclipsefoundation/git/eca/model/Commit.java index 506ea174..023dde77 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/Commit.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/Commit.java @@ -12,138 +12,63 @@ package org.eclipsefoundation.git.eca.model; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + /** * Represents a Git commit with basic data and metadata about the revision. * * @author Martin Lowe * */ -public class Commit { - private String hash; - private String subject; - private String body; - private List<String> parents; - private GitUser author; - private GitUser committer; - private boolean head; - - /** - * @return the hash - */ - public String getHash() { - return hash; - } +@AutoValue +@JsonDeserialize(builder = AutoValue_Commit.Builder.class) +public abstract class Commit { + @Nullable + public abstract String getHash(); - /** - * @param hash the hash to set - */ - public void setHash(String hash) { - this.hash = hash; - } + @Nullable + public abstract String getSubject(); - /** - * @return the subject - */ - public String getSubject() { - return subject; - } + @Nullable + public abstract String getBody(); - /** - * @param subject the subject to set - */ - public void setSubject(String subject) { - this.subject = subject; - } + @Nullable + public abstract List<String> getParents(); - /** - * @return the body - */ - public String getBody() { - return body; - } + @Nullable + public abstract GitUser getAuthor(); - /** - * @param body the body to set - */ - public void setBody(String body) { - this.body = body; - } + @Nullable + public abstract GitUser getCommitter(); - /** - * @return the parents - */ - public List<String> getParents() { - return new ArrayList<>(parents); - } + @Nullable + public abstract Boolean getHead(); - /** - * @param parents the parents to set - */ - public void setParents(List<String> parents) { - this.parents = new ArrayList<>(parents); + public static Builder builder() { + return new AutoValue_Commit.Builder().setParents(new ArrayList<>()); } - /** - * @return the author - */ - public GitUser getAuthor() { - return author; - } + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setHash(@Nullable String hash); - /** - * @param author the author to set - */ - public void setAuthor(GitUser author) { - this.author = author; - } + public abstract Builder setSubject(@Nullable String subject); - /** - * @return the commiter - */ - public GitUser getCommitter() { - return committer; - } + public abstract Builder setBody(@Nullable String body); - /** - * @param committer the committer to set - */ - public void setCommitter(GitUser committer) { - this.committer = committer; - } + public abstract Builder setParents(@Nullable List<String> parents); - /** - * @return the head - */ - public boolean isHead() { - return head; - } + public abstract Builder setAuthor(@Nullable GitUser author); - /** - * @param head the head to set - */ - public void setHead(boolean head) { - this.head = head; - } + public abstract Builder setCommitter(@Nullable GitUser committer); + + public abstract Builder setHead(@Nullable Boolean head); - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("Commit [hash="); - builder.append(hash); - builder.append(", subject="); - builder.append(subject); - builder.append(", body="); - builder.append(body); - builder.append(", parents="); - builder.append(parents); - builder.append(", author="); - builder.append(author); - builder.append(", committer="); - builder.append(committer); - builder.append(", head="); - builder.append(head); - builder.append("]"); - return builder.toString(); + public abstract Commit build(); } - } diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/CommitStatus.java b/src/main/java/org/eclipsefoundation/git/eca/model/CommitStatus.java index 7ddfd6ed..3fd94d51 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/CommitStatus.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/CommitStatus.java @@ -14,131 +14,79 @@ import java.util.List; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + /** - * Contains information generated about a commit that was submitted for - * validation to the API. + * Contains information generated about a commit that was submitted for validation to the API. * * @author Martin Lowe * */ -public class CommitStatus { - private List<CommitStatusMessage> messages; - private List<CommitStatusMessage> warnings; - private List<CommitStatusMessage> errors; - - public CommitStatus() { - this.messages = new ArrayList<>(); - this.warnings = new ArrayList<>(); - this.errors = new ArrayList<>(); - } - - /** - * @return the msgs - */ - public List<CommitStatusMessage> getMessages() { - return new ArrayList<>(messages); - } - - /** - * @param messages the msgs to set - */ - public void setMessages(List<CommitStatusMessage> messages) { - this.messages = new ArrayList<>(messages); - } - - /** - * @param message message to add to current commit status - * @param code the status code for the message - */ - public void addMessage(String message, APIStatusCode code) { - this.messages.add(new CommitStatusMessage(code, message)); - } - - /** - * @return the warnings - */ - public List<CommitStatusMessage> getWarnings() { - return new ArrayList<>(warnings); - } - - /** - * @param warnings the warnings to set - */ - public void setWarnings(List<CommitStatusMessage> warnings) { - this.warnings = new ArrayList<>(warnings); - } - - /** - * @param warning warning to add to current commit status - * @param code the status code for the message - */ - public void addWarning(String warning, APIStatusCode code) { - this.warnings.add(new CommitStatusMessage(code, warning)); - } - - /** - * @return the errs - */ - public List<CommitStatusMessage> getErrors() { - return new ArrayList<>(errors); - } - - /** - * @param errors the errors to set - */ - public void setErrors(List<CommitStatusMessage> errors) { - this.errors = new ArrayList<>(errors); - } - - /** - * @param error error message to add to current commit status - * @param code the error status for the current message - */ - public void addError(String error, APIStatusCode code) { - this.errors.add(new CommitStatusMessage(code, error)); - } - - /** - * Represents a message with an associated error or success status code. - * - * @author Martin Lowe - * - */ - public static class CommitStatusMessage { - private APIStatusCode code; - private String message; - - public CommitStatusMessage(APIStatusCode code, String message) { - this.code = code; - this.message = message; - } - - /** - * @return the code - */ - public APIStatusCode getCode() { - return code; - } - - /** - * @param code the code to set - */ - public void setCode(APIStatusCode code) { - this.code = code; - } - - /** - * @return the message - */ - public String getMessage() { - return message; - } - - /** - * @param message the message to set - */ - public void setMessage(String message) { - this.message = message; - } - } +@AutoValue +@JsonDeserialize(builder = AutoValue_CommitStatus.Builder.class) +public abstract class CommitStatus { + public abstract List<CommitStatusMessage> getMessages(); + + public abstract List<CommitStatusMessage> getWarnings(); + + public abstract List<CommitStatusMessage> getErrors(); + + public void addMessage(String message, APIStatusCode code) { + this.getMessages().add(CommitStatusMessage.builder().setCode(code).setMessage(message).build()); + } + + public void addWarning(String message, APIStatusCode code) { + this.getWarnings().add(CommitStatusMessage.builder().setCode(code).setMessage(message).build()); + } + + public void addError(String message, APIStatusCode code) { + this.getErrors().add(CommitStatusMessage.builder().setCode(code).setMessage(message).build()); + } + + public static Builder builder() { + return new AutoValue_CommitStatus.Builder().setErrors(new ArrayList<>()).setWarnings(new ArrayList<>()) + .setMessages(new ArrayList<>()); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setMessages(List<CommitStatusMessage> messages); + + public abstract Builder setWarnings(List<CommitStatusMessage> warnings); + + public abstract Builder setErrors(List<CommitStatusMessage> errors); + + public abstract CommitStatus build(); + } + + /** + * Represents a message with an associated error or success status code. + * + * @author Martin Lowe + * + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_CommitStatus_CommitStatusMessage.Builder.class) + public abstract static class CommitStatusMessage { + public abstract APIStatusCode getCode(); + + public abstract String getMessage(); + + public static Builder builder() { + return new AutoValue_CommitStatus_CommitStatusMessage.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setCode(APIStatusCode code); + + public abstract Builder setMessage(String message); + + public abstract CommitStatusMessage build(); + } + } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/EclipseUser.java b/src/main/java/org/eclipsefoundation/git/eca/model/EclipseUser.java index 966ebf23..3d1dae57 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/EclipseUser.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/EclipseUser.java @@ -8,154 +8,90 @@ */ package org.eclipsefoundation.git.eca.model; +import javax.annotation.Nullable; + import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; /** * Represents a users Eclipse Foundation account * * @author Martin Lowe */ -public class EclipseUser { - private int uid; - private String name; - private String mail; - private ECA eca; - private boolean isCommitter; - // this field is internal for tracking bot stubs - @JsonIgnore private boolean isBot; - - /** - * Create a bot user stub when there is no real Eclipse account for the bot. - * - * @param user the Git user that was detected to be a bot. - * @return a stubbed Eclipse user bot object. - */ - public static EclipseUser createBotStub(GitUser user) { - EclipseUser stub = new EclipseUser(); - stub.setName(user.getName()); - stub.setMail(user.getMail()); - stub.setEca(new ECA()); - stub.setBot(true); - return stub; - } - - /** @return the id */ - public int getId() { - return uid; - } - - /** @param id the id to set */ - public void setId(int uid) { - this.uid = uid; - } - - /** @return the name */ - public String getName() { - return name; - } - - /** @param name the name to set */ - public void setName(String name) { - this.name = name; - } - - /** @return the mail */ - public String getMail() { - return mail; - } - - /** @param mail the mail to set */ - public void setMail(String mail) { - this.mail = mail; - } - - /** @return the eca */ - public ECA getEca() { - return eca; - } - - /** @param eca the eca to set */ - public void setEca(ECA eca) { - this.eca = eca; - } - - /** @return the isCommitter */ - public boolean isCommitter() { - return isCommitter; - } - - /** @param isCommitter the isCommitter to set */ - public void setCommitter(boolean isCommitter) { - this.isCommitter = isCommitter; - } - - /** @return the isBot */ - public boolean isBot() { - return isBot; - } - - /** @param isBot the isBot to set */ - private void setBot(boolean isBot) { - this.isBot = isBot; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("EclipseUser [uid="); - builder.append(uid); - builder.append(", name="); - builder.append(name); - builder.append(", mail="); - builder.append(mail); - builder.append(", eca="); - builder.append(eca); - builder.append(", isCommitter="); - builder.append(isCommitter); - builder.append(", isBot="); - builder.append(isBot); - builder.append("]"); - return builder.toString(); - } - - /** - * ECA for Eclipse accounts, representing whether users have signed the Eclipse Committer - * Agreement to enable contribution. - */ - @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) - public static class ECA { - private boolean signed; - private boolean canContributeSpecProject; - - public ECA() { - this(false, false); - } +@AutoValue +@JsonDeserialize(builder = AutoValue_EclipseUser.Builder.class) +public abstract class EclipseUser { + public abstract int getUid(); - public ECA(boolean signed, boolean canContributeSpecProject) { - this.signed = signed; - this.canContributeSpecProject = canContributeSpecProject; - } + public abstract String getName(); + + public abstract String getMail(); + + public abstract ECA getECA(); - /** @return the signed */ - public boolean isSigned() { - return signed; + public abstract boolean getIsCommitter(); + + @Nullable + @JsonIgnore + public abstract Boolean getIsBot(); + + /** + * Create a bot user stub when there is no real Eclipse account for the bot. + * + * @param user the Git user that was detected to be a bot. + * @return a stubbed Eclipse user bot object. + */ + public static EclipseUser createBotStub(GitUser user) { + return EclipseUser.builder().setUid(0).setName(user.getName()).setMail(user.getMail()) + .setECA(ECA.builder().build()).setIsBot(true).build(); } - /** @param signed the signed to set */ - public void setSigned(boolean signed) { - this.signed = signed; + public static Builder builder() { + return new AutoValue_EclipseUser.Builder().setIsCommitter(false).setIsBot(false); } - /** @return the canContributeSpecProject */ - public boolean isCanContributeSpecProject() { - return canContributeSpecProject; + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setUid(int id); + + public abstract Builder setName(String name); + + public abstract Builder setMail(String mail); + + public abstract Builder setECA(ECA eca); + + public abstract Builder setIsCommitter(boolean isCommitter); + + @JsonIgnore + public abstract Builder setIsBot(@Nullable Boolean isBot); + + public abstract EclipseUser build(); } - /** @param canContributeSpecProject the canContributeSpecProject to set */ - public void setCanContributeSpecProject(boolean canContributeSpecProject) { - this.canContributeSpecProject = canContributeSpecProject; + @AutoValue + @JsonDeserialize(builder = AutoValue_EclipseUser_ECA.Builder.class) + public abstract static class ECA { + @Nullable + public abstract Boolean getSigned(); + + @Nullable + public abstract Boolean getCanContributeSpecProject(); + + public static Builder builder() { + return new AutoValue_EclipseUser_ECA.Builder().setCanContributeSpecProject(false).setSigned(false); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setSigned(@Nullable Boolean signed); + + public abstract Builder setCanContributeSpecProject(@Nullable Boolean canContributeSpecProject); + + public abstract ECA build(); + } } - } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/GitUser.java b/src/main/java/org/eclipsefoundation/git/eca/model/GitUser.java index b868ead5..9dd19392 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/GitUser.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/GitUser.java @@ -9,41 +9,38 @@ ******************************************************************************/ package org.eclipsefoundation.git.eca.model; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + /** * Basic object representing a Git users data required for verification. * * @author Martin Lowe * */ -public class GitUser { - private String name; - private String mail; - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the mail - */ - public String getMail() { - return mail; - } - - /** - * @param mail the mail to set - */ - public void setMail(String mail) { - this.mail = mail; - } -} +@AutoValue +@JsonDeserialize(builder = AutoValue_GitUser.Builder.class) +public abstract class GitUser { + @Nullable + public abstract String getName(); + + @Nullable + public abstract String getMail(); + + public static Builder builder() { + return new AutoValue_GitUser.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setName(@Nullable String name); + + public abstract Builder setMail(@Nullable String mail); + + public abstract GitUser build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java b/src/main/java/org/eclipsefoundation/git/eca/model/Project.java index b72d397d..2131223b 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/Project.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/Project.java @@ -13,212 +13,120 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; -import com.fasterxml.jackson.databind.annotation.JsonNaming; +import javax.annotation.Nullable; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; /** - * Represents a project in the Eclipse API, along with the users and repos that - * exist within the context of the project. + * Represents a project in the Eclipse API, along with the users and repos that exist within the context of the project. * * @author Martin Lowe * */ -@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) -public class Project { - private String projectId; - private String name; - private List<User> committers; - private List<Repo> repos; - private List<Repo> gitlabRepos; - private List<Repo> githubRepos; - private List<Repo> gerritRepos; - private String specWorkingGroup; - - public Project() { - this.committers = new ArrayList<>(); - this.repos = new ArrayList<>(); - this.gitlabRepos = new ArrayList<>(); - this.githubRepos = new ArrayList<>(); - this.gerritRepos = new ArrayList<>(); - } - - /** - * @return the projectId - */ - public String getProjectId() { - return projectId; - } - - /** - * @param projectId the projectId to set - */ - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the committers - */ - public List<User> getCommitters() { - return committers; - } - - /** - * @param committers the committers to set - */ - public void setCommitters(List<User> committers) { - this.committers = committers; - } - - /** - * @return the repos - */ - public List<Repo> getRepos() { - return new ArrayList<>(repos); - } - - /** - * @param repos the repos to set - */ - public void setRepos(List<Repo> repos) { - this.repos = new ArrayList<>(repos); - } - - /** - * @return the gitlabRepos - */ - public List<Repo> getGitlabRepos() { - return new ArrayList<>(gitlabRepos); - } - - /** - * @param githubRepos the githubRepos to set - */ - public void setGithubRepos(List<Repo> githubRepos) { - this.githubRepos = new ArrayList<>(githubRepos); - } - - /** - * @return the gitlabRepos - */ - public List<Repo> getGithubRepos() { - return new ArrayList<>(githubRepos); - } - - /** - * @param gitlabRepos the gitlabRepos to set - */ - public void setGitlabRepos(List<Repo> gitlabRepos) { - this.gitlabRepos = new ArrayList<>(gitlabRepos); - } - - /** - * @return the gerritRepos - */ - public List<Repo> getGerritRepos() { - return new ArrayList<>(gerritRepos); - } - - /** - * @param gerritRepos the gerritRepos to set - */ - public void setGerritRepos(List<Repo> gerritRepos) { - this.gerritRepos = new ArrayList<>(gerritRepos); - } - - /** - * @return the specWorkingGroup - */ - public String getSpecWorkingGroup() { - return specWorkingGroup; - } - - /** - * @param specWorkingGroup the specWorkingGroup to set - */ - public void setSpecWorkingGroup(String specWorkingGroup) { - this.specWorkingGroup = specWorkingGroup; - } - - /** - * @param specProjectWorkingGroup the value for the spec_project_working_group. - * When empty from API, represented by an empty - * array, and a map/object when set. - */ - @SuppressWarnings("unchecked") - @JsonProperty("spec_project_working_group") - private void unpackSpecProject(Object specProjectWorkingGroup) { - if (specProjectWorkingGroup instanceof Map) { - Object raw = ((Map<String, Object>) specProjectWorkingGroup).get("id"); - if (raw instanceof String) { - this.specWorkingGroup = (String) raw; - } - } - } - - public static class Repo { - private String url; - - /** - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * @param url the url to set - */ - public void setUrl(String url) { - this.url = url; - } - } - - public static class User { - private String username; - private String url; - - /** - * @return the username - */ - public String getUsername() { - return username; - } - - /** - * @param username the username to set - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * @return the url - */ - public String getUrl() { - return url; - } - - /** - * @param url the url to set - */ - public void setUrl(String url) { - this.url = url; - } - } +@AutoValue +@JsonDeserialize(builder = AutoValue_Project.Builder.class) +public abstract class Project { + public abstract String getProjectId(); + + public abstract String getName(); + + public abstract List<User> getCommitters(); + + @Nullable + public abstract List<Repo> getRepos(); + + public abstract List<Repo> getGitlabRepos(); + + public abstract List<Repo> getGithubRepos(); + + public abstract List<Repo> getGerritRepos(); + + public abstract Object getSpecProjectWorkingGroup(); + + @Nullable + @Memoized + public String getSpecWorkingGroup() { + // stored as map as empty returns an array instead of a map + Object specProjectWorkingGroup = getSpecProjectWorkingGroup(); + if (specProjectWorkingGroup instanceof Map) { + // we checked in line above that the map exists, so we can safely cast it + @SuppressWarnings("unchecked") + Object raw = ((Map<Object, Object>) specProjectWorkingGroup).get("id"); + if (raw instanceof String) { + return (String) raw; + } + } + return null; + } + + public static Builder builder() { + // adds empty lists as default values + return new AutoValue_Project.Builder().setRepos(new ArrayList<>()).setCommitters(new ArrayList<>()) + .setGithubRepos(new ArrayList<>()).setGitlabRepos(new ArrayList<>()).setGerritRepos(new ArrayList<>()); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setProjectId(String projectId); + + public abstract Builder setName(String name); + + public abstract Builder setCommitters(List<User> committers); + + public abstract Builder setRepos(@Nullable List<Repo> repos); + + public abstract Builder setGitlabRepos(List<Repo> gitlabRepos); + + public abstract Builder setGithubRepos(List<Repo> githubRepos); + + public abstract Builder setGerritRepos(List<Repo> gerritRepos); + + public abstract Builder setSpecProjectWorkingGroup(Object specProjectWorkingGroup); + + public abstract Project build(); + } + + @AutoValue + @JsonDeserialize(builder = AutoValue_Project_User.Builder.class) + public abstract static class User { + public abstract String getUsername(); + + public abstract String getUrl(); + + public static Builder builder() { + return new AutoValue_Project_User.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setUsername(String username); + + public abstract Builder setUrl(String url); + + public abstract User build(); + } + } + + /** + * Does not use autovalue as the value should be mutable. + * + * @author Martin Lowe + * + */ + public static class Repo { + private String url; + + public String getUrl() { + return this.url; + } + + public void setUrl(String url) { + this.url = url; + } + } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationRequest.java b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationRequest.java index 14f1b4e4..ca946c1f 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationRequest.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationRequest.java @@ -1,82 +1,66 @@ -/** - * ***************************************************************************** Copyright (C) 2020 +/** ***************************************************************************** Copyright (C) 2020 * Eclipse Foundation * * <p>This program and the accompanying materials are made available under the terms of the Eclipse * Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0/ * * <p>SPDX-License-Identifier: EPL-2.0 - * **************************************************************************** - */ + * *****************************************************************************/ package org.eclipsefoundation.git.eca.model; import java.net.URI; import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + import org.eclipsefoundation.git.eca.namespace.ProviderType; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.LowerCamelCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + /** * Represents a request to validate a list of commits. * * @author Martin Lowe */ -public class ValidationRequest { - private URI repoUrl; - private List<Commit> commits; - private ProviderType provider; - private boolean strictMode; +@AutoValue +@JsonNaming(LowerCamelCaseStrategy.class) +@JsonDeserialize(builder = AutoValue_ValidationRequest.Builder.class) +public abstract class ValidationRequest { + @Nullable + @JsonProperty("repoUrl") + public abstract URI getRepoUrl(); - /** @return the repoUrl */ - public URI getRepoUrl() { - return repoUrl; - } + public abstract List<Commit> getCommits(); - /** @param repoUrl the repoUrl to set */ - public void setRepoUrl(URI repoUrl) { - this.repoUrl = repoUrl; - } + public abstract ProviderType getProvider(); - /** @return the commits */ - public List<Commit> getCommits() { - return new ArrayList<>(commits); - } + @Nullable + @JsonProperty("strictMode") + public abstract Boolean getStrictMode(); - /** @param commits the commits to set */ - public void setCommits(List<Commit> commits) { - this.commits = new ArrayList<>(commits); - } + public static Builder builder() { + return new AutoValue_ValidationRequest.Builder().setStrictMode(false).setCommits(new ArrayList<>()); + } - /** @return the provider */ - public ProviderType getProvider() { - return provider; - } + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + @JsonProperty("repoUrl") + public abstract Builder setRepoUrl(@Nullable URI repoUrl); - /** @param provider the provider to set */ - public void setProvider(ProviderType provider) { - this.provider = provider; - } + public abstract Builder setCommits(List<Commit> commits); - /** @return the strictMode */ - public boolean isStrictMode() { - return strictMode; - } + public abstract Builder setProvider(ProviderType provider); - /** @param strictMode the strictMode to set */ - public void setStrictMode(boolean strictMode) { - this.strictMode = strictMode; - } + @JsonProperty("strictMode") + public abstract Builder setStrictMode(@Nullable Boolean strictMode); - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("ValidationRequest [repoUrl="); - builder.append(repoUrl); - builder.append(", commits="); - builder.append(commits); - builder.append(", provider="); - builder.append(provider); - builder.append("]"); - return builder.toString(); - } + public abstract ValidationRequest build(); + } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java index 6348452b..1071df85 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java +++ b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java @@ -10,7 +10,7 @@ */ package org.eclipsefoundation.git.eca.model; -import java.util.Date; +import java.time.ZonedDateTime; import java.util.HashMap; import java.util.Map; @@ -19,117 +19,90 @@ import javax.ws.rs.core.Response.Status; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.LowerCamelCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; + /** * Represents an internal response for a call to this API. * * @author Martin Lowe */ -public class ValidationResponse { - private boolean passed; - private int errorCount; - private Date time; - private Map<String, CommitStatus> commits; - private boolean trackedProject; - private boolean strictMode; - - public ValidationResponse() { - this.commits = new HashMap<>(); - this.time = new Date(); - this.strictMode = false; - } - - /** @return the passed */ - public boolean isPassed() { - return passed; - } - - /** @param passed the passed to set */ - public void setPassed(boolean passed) { - this.passed = passed; - } - - /** @return the errorCount */ - public int getErrorCount() { - this.errorCount = commits.values().stream().mapToInt(s -> s.getErrors().size()).sum(); - return errorCount; - } - - /** @param errorCount the errorCount to set */ - public void setErrorCount(int errorCount) { - this.errorCount = errorCount; - } - - /** @return the time */ - public Date getTime() { - return time; - } - - /** @param time the time to set */ - public void setTime(Date time) { - this.time = time; - } - - /** @return the commits */ - public Map<String, CommitStatus> getCommits() { - return commits; - } - - /** @param commits the commits to set */ - public void setCommits(Map<String, CommitStatus> commits) { - this.commits = commits; - } - - /** @param message message to add to the API response */ - public void addMessage(String hash, String message, APIStatusCode code) { - commits.computeIfAbsent(getHashKey(hash), k -> new CommitStatus()).addMessage(message, code); - } - - /** @param warning message to add to the API response */ - public void addWarning(String hash, String warning, APIStatusCode code) { - commits.computeIfAbsent(getHashKey(hash), k -> new CommitStatus()).addWarning(warning, code); - } - - /** @param error message to add to the API response */ - public void addError(String hash, String error, APIStatusCode code) { - commits.computeIfAbsent(getHashKey(hash), k -> new CommitStatus()).addError(error, code); - } - - /** @return the trackedProject */ - public boolean isTrackedProject() { - return trackedProject; - } - - /** @param trackedProject the trackedProject to set */ - public void setTrackedProject(boolean trackedProject) { - this.trackedProject = trackedProject; - } - - /** @return the strictMode */ - public boolean isStrictMode() { - return strictMode; - } - - /** @param strictMode the strictMode to set */ - public void setStrictMode(boolean strictMode) { - this.strictMode = strictMode; - } - - private String getHashKey(String hash) { - return hash == null ? "_nil" : hash; - } - - /** - * Converts the APIResponse to a web response with appropriate status. - * - * @return a web response with status {@link Status.OK} if the commits pass validation, {@link - * Status.FORBIDDEN} otherwise. - */ - public Response toResponse() { - // update error count before returning - if (passed) { - return Response.ok(this).build(); - } else { - return Response.status(Status.FORBIDDEN).entity(this).build(); +@AutoValue +@JsonNaming(LowerCamelCaseStrategy.class) +@JsonDeserialize(builder = AutoValue_ValidationResponse.Builder.class) +public abstract class ValidationResponse { + + public abstract ZonedDateTime getTime(); + + public abstract Map<String, CommitStatus> getCommits(); + + public abstract boolean getTrackedProject(); + + public abstract boolean getStrictMode(); + + public boolean getPassed() { + return getErrorCount() <= 0; + }; + + @Memoized + public int getErrorCount() { + return getCommits().values().stream().mapToInt(s -> s.getErrors().size()).sum(); + } + + /** @param message message to add to the API response */ + public void addMessage(String hash, String message, APIStatusCode code) { + getCommits().computeIfAbsent(getHashKey(hash), k -> CommitStatus.builder().build()).addMessage(message, code); + } + + /** @param warning message to add to the API response */ + public void addWarning(String hash, String warning, APIStatusCode code) { + getCommits().computeIfAbsent(getHashKey(hash), k -> CommitStatus.builder().build()).addWarning(warning, code); + } + + /** @param error message to add to the API response */ + public void addError(String hash, String error, APIStatusCode code) { + getCommits().computeIfAbsent(getHashKey(hash), k -> CommitStatus.builder().build()).addError(error, code); + } + + private String getHashKey(String hash) { + return hash == null ? "_nil" : hash; + } + + /** + * Converts the APIResponse to a web response with appropriate status. + * + * @return a web response with status {@link Status.OK} if the commits pass validation, {@link Status.FORBIDDEN} + * otherwise. + */ + public Response toResponse() { + // update error count before returning + if (getPassed()) { + return Response.ok(this).build(); + } else { + return Response.status(Status.FORBIDDEN).entity(this).build(); + } + } + + public static Builder builder() { + return new AutoValue_ValidationResponse.Builder().setStrictMode(false).setTrackedProject(false) + .setTime(ZonedDateTime.now()).setCommits(new HashMap<>()); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setTime(ZonedDateTime time); + + public abstract Builder setCommits(Map<String, CommitStatus> commits); + + public abstract Builder setTrackedProject(boolean trackedProject); + + public abstract Builder setStrictMode(boolean strictMode); + + public abstract ValidationResponse build(); } - } } diff --git a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java b/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java deleted file mode 100644 index 3e48dbb8..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.oauth; - -import com.github.scribejava.core.builder.api.DefaultApi20; -import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication; -import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme; - -/** - * Wrapper around the OAuth API for Scribejava. Enables OAuth2.0 binding to the - * Eclipse Foundation OAuth server. - * - * @author Martin Lowe - * - */ -public class EclipseApi extends DefaultApi20 { - - @Override - public String getAccessTokenEndpoint() { - return "https://accounts.eclipse.org/oauth2/token"; - } - - @Override - protected String getAuthorizationBaseUrl() { - return null; - } - - @Override - public ClientAuthentication getClientAuthentication() { - return RequestBodyAuthenticationScheme.instance(); - } - - private static class InstanceHolder { - private static final EclipseApi INSTANCE = new EclipseApi(); - } - - public static EclipseApi instance() { - return InstanceHolder.INSTANCE; - } -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java index aa669a93..8bae1e5c 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -9,6 +9,7 @@ package org.eclipsefoundation.git.eca.resource; import java.net.MalformedURLException; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -29,6 +30,7 @@ import javax.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.api.AccountsAPI; import org.eclipsefoundation.git.eca.api.BotsAPI; import org.eclipsefoundation.git.eca.helper.CommitHelper; @@ -40,25 +42,27 @@ import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; -import org.eclipsefoundation.git.eca.service.CachingService; -import org.eclipsefoundation.git.eca.service.OAuthService; import org.eclipsefoundation.git.eca.service.ProjectsService; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; /** - * ECA validation endpoint for Git commits. Will use information from the bots, projects, and - * accounts API to validate commits passed to this endpoint. Should be as system agnostic as - * possible to allow for any service to request validation with less reliance on services external + * ECA validation endpoint for Git commits. Will use information from the bots, + * projects, and + * accounts API to validate commits passed to this endpoint. Should be as system + * agnostic as + * possible to allow for any service to request validation with less reliance on + * services external * to the Eclipse foundation. * * @author Martin Lowe */ @Path("/eca") -@Consumes({MediaType.APPLICATION_JSON}) -@Produces({MediaType.APPLICATION_JSON}) +@Consumes({ MediaType.APPLICATION_JSON }) +@Produces({ MediaType.APPLICATION_JSON }) public class ValidationResource { private static final Logger LOGGER = LoggerFactory.getLogger(ValidationResource.class); @@ -70,13 +74,18 @@ public class ValidationResource { List<String> emailPatterns; // eclipse API rest client interfaces - @Inject @RestClient AccountsAPI accounts; - @Inject @RestClient BotsAPI bots; + @Inject + @RestClient + AccountsAPI accounts; + @Inject + @RestClient + BotsAPI bots; // external API/service harnesses - @Inject OAuthService oauth; - @Inject CachingService cache; - @Inject ProjectsService projects; + @Inject + CachingService cache; + @Inject + ProjectsService projects; // rendered list of regex values List<Pattern> patterns; @@ -88,40 +97,31 @@ public class ValidationResource { } /** - * Consuming a JSON request, this method will validate all passed commits, using the repo URL and - * the repository provider. These commits will be validated to ensure that all users are covered - * either by an ECA, or are committers on the project. In the case of ECA-only contributors, an + * Consuming a JSON request, this method will validate all passed commits, using + * the repo URL and + * the repository provider. These commits will be validated to ensure that all + * users are covered + * either by an ECA, or are committers on the project. In the case of ECA-only + * contributors, an * additional sign off footer is required in the body of the commit. * * @param req the request containing basic data plus the commits to be validated - * @return a web response indicating success or failure for each commit, along with standard - * messages that may be used to give users context on failure. + * @return a web response indicating success or failure for each commit, along + * with standard + * messages that may be used to give users context on failure. * @throws MalformedURLException */ @POST public Response validate(ValidationRequest req) { - ValidationResponse r = new ValidationResponse(); - r.setStrictMode(req.isStrictMode()); - // check that we have commits to validate - if (req.getCommits() == null || req.getCommits().isEmpty()) { - addError(r, "A commit is required to validate", null); - } - // check that we have a repo set - if (req.getRepoUrl() == null) { - addError(r, "A base repo URL needs to be set in order to validate", null); - } - // check that we have a type set - if (req.getProvider() == null) { - addError(r, "A provider needs to be set to validate a request", null); - } + List<String> messages = checkRequest(req); // only process if we have no errors - if (r.getErrorCount() == 0) { + if (messages.isEmpty()) { LOGGER.debug("Processing: {}", req); // filter the projects based on the repo URL. At least one repo in project must // match the repo URL to be valid List<Project> filteredProjects = retrieveProjectsForRequest(req); - // set whether this call has tracked projects - r.setTrackedProject(!filteredProjects.isEmpty()); + ValidationResponse r = ValidationResponse.builder().setStrictMode(req.getStrictMode() != null && req.getStrictMode() ? true : false) + .setTrackedProject(!filteredProjects.isEmpty()).build(); for (Commit c : req.getCommits()) { // process the request, capturing if we should continue processing boolean continueProcessing = processCommit(c, r, filteredProjects, req.getProvider()); @@ -130,21 +130,48 @@ public class ValidationResource { break; } } + return r.toResponse(); + } else { + // create a stubbed response with the errors + ValidationResponse out = ValidationResponse.builder().build(); + messages.forEach(m -> addError(out, m, null)); + return out.toResponse(); } - // depending on number of errors found, set response status - if (r.getErrorCount() == 0) { - r.setPassed(true); - } - return r.toResponse(); + } + + /** + * Check if there are any issues with the validation request, returning error messages if there are issues with the + * request. + * + * @param req the current validation request + * @return a list of error messages to report, or an empty list if there are no errors with the request. + */ + private List<String> checkRequest(ValidationRequest req) { + // check that we have commits to validate + List<String> messages = new ArrayList<>(); + if (req.getCommits() == null || req.getCommits().isEmpty()) { + messages.add("A commit is required to validate"); + } + // check that we have a repo set + if (req.getRepoUrl() == null) { + messages.add("A base repo URL needs to be set in order to validate"); + } + // check that we have a type set + if (req.getProvider() == null) { + messages.add("A provider needs to be set to validate a request"); + } + return messages; } /** - * Process the current request, validating that the passed commit is valid. The author and - * committers Eclipse Account is retrieved, which are then used to check if the current commit is + * Process the current request, validating that the passed commit is valid. The + * author and + * committers Eclipse Account is retrieved, which are then used to check if the + * current commit is * valid for the current project. * - * @param c the commit to process - * @param response the response container + * @param c the commit to process + * @param response the response container * @param filteredProjects tracked projects for the current request * @return true if we should continue processing, false otherwise. */ @@ -233,76 +260,92 @@ public class ValidationResource { return true; } - - /** - * Validates author access for the current commit. If there are errors, they are recorded in the - * response for the current request to be returned once all validation checks are completed. + * Validates author access for the current commit. If there are errors, they are + * recorded in the + * response for the current request to be returned once all validation checks + * are completed. * - * @param r the current response object for the request - * @param c the commit that is being validated - * @param eclipseUser the user to validate on a branch + * @param r the current response object for the request + * @param c the commit that is being validated + * @param eclipseUser the user to validate on a branch * @param filteredProjects tracked projects for the current request - * @param errorCode the error code to display if the user does not have access + * @param errorCode the error code to display if the user does not have + * access */ private void validateUserAccess( ValidationResponse r, Commit c, EclipseUser eclipseUser, List<Project> filteredProjects, APIStatusCode errorCode) { - // call isCommitter inline and pass to partial call + // call isCommitter inline and pass to partial call validateUserAccessPartial(r, c, eclipseUser, isCommitter(r, eclipseUser, c.getHash(), filteredProjects), errorCode); } /** - * Allows for isCommitter to be called external to this method. This was extracted to ensure that isCommitter isn't - * called twice for the same user when checking committer proxy push rules and committer general access. + * Allows for isCommitter to be called external to this method. This was + * extracted to ensure that isCommitter isn't + * called twice for the same user when checking committer proxy push rules and + * committer general access. * - * @param r the current response object for the request - * @param c the commit that is being validated + * @param r the current response object for the request + * @param c the commit that is being validated * @param eclipseUser the user to validate on a branch * @param isCommitter the results of the isCommitter call from this class. - * @param errorCode the error code to display if the user does not have access + * @param errorCode the error code to display if the user does not have access */ - private void validateUserAccessPartial(ValidationResponse r, Commit c, EclipseUser eclipseUser, - boolean isCommitter, APIStatusCode errorCode) { + private void validateUserAccessPartial(ValidationResponse r, Commit c, EclipseUser eclipseUser, + boolean isCommitter, APIStatusCode errorCode) { String userType = "author"; if (APIStatusCode.ERROR_COMMITTER.equals(errorCode)) { userType = "committer"; } if (isCommitter) { - addMessage(r, String.format("Eclipse user '%s'(%s) is a committer on the project.", eclipseUser.getName(), userType), c.getHash()); + addMessage(r, + String.format("Eclipse user '%s'(%s) is a committer on the project.", eclipseUser.getName(), userType), + c.getHash()); } else { - addMessage(r, String.format("Eclipse user '%s'(%s) is not a committer on the project.", eclipseUser.getName(), userType), c.getHash()); + addMessage(r, + String.format("Eclipse user '%s'(%s) is not a committer on the project.", eclipseUser.getName(), userType), + c.getHash()); // check if the author is signed off if not a committer - if (eclipseUser.getEca().isSigned()) { + if (eclipseUser.getECA().getSigned()) { addMessage( r, - String.format("Eclipse user '%s'(%s) has a current Eclipse Contributor Agreement (ECA) on file.", eclipseUser.getName(), userType), + String.format("Eclipse user '%s'(%s) has a current Eclipse Contributor Agreement (ECA) on file.", + eclipseUser.getName(), userType), c.getHash()); } else { addMessage( r, String.format("Eclipse user '%s'(%s) does not have a current Eclipse Contributor Agreement (ECA) on file.\n" - + "If there are multiple commits, please ensure that each author has a ECA.", eclipseUser.getName(), userType), + + "If there are multiple commits, please ensure that each author has a ECA.", eclipseUser.getName(), + userType), c.getHash()); - addError(r, String.format("An Eclipse Contributor Agreement is required for Eclipse user '%s'(%s).", eclipseUser.getName(), userType), + addError(r, + String.format("An Eclipse Contributor Agreement is required for Eclipse user '%s'(%s).", + eclipseUser.getName(), userType), c.getHash(), errorCode); } } } /** - * Checks whether the given user is a committer on the project. If they are and the project is - * also a specification for a working group, an additional access check is made against the user. + * Checks whether the given user is a committer on the project. If they are and + * the project is + * also a specification for a working group, an additional access check is made + * against the user. * - * <p>Additionally, a check is made to see if the user is a registered bot user for the given - * project. If they match for the given project, they are granted committer-like access to the + * <p> + * Additionally, a check is made to see if the user is a registered bot user for + * the given + * project. If they match for the given project, they are granted committer-like + * access to the * repository. * - * @param r the current response object for the request - * @param user the user to validate on a branch - * @param hash the hash of the commit that is being validated + * @param r the current response object for the request + * @param user the user to validate on a branch + * @param hash the hash of the commit that is being validated * @param filteredProjects tracked projects for the current request * @return true if user is considered a committer, false otherwise. */ @@ -318,7 +361,7 @@ public class ValidationResource { if (p.getCommitters().stream().anyMatch(u -> u.getUsername().equals(user.getName()))) { // check if the current project is a committer project, and if the user can // commit to specs - if (p.getSpecWorkingGroup() != null && !user.getEca().isCanContributeSpecProject()) { + if (p.getSpecWorkingGroup() != null && !user.getECA().getCanContributeSpecProject()) { // set error + update response status r.addError( hash, @@ -336,78 +379,85 @@ public class ValidationResource { } } } - // check if user is a bot, either through early detection or through on-demand check - if (user.isBot() || userIsABot(user.getMail(), filteredProjects)) { + // check if user is a bot, either through early detection or through on-demand + // check + if ((user.getIsBot() != null && user.getIsBot()) || userIsABot(user.getMail(), filteredProjects)) { LOGGER.debug("User '{} <{}>' was found to be a bot", user.getName(), user.getMail()); return true; } return false; } - private boolean userIsABot(String mail, List<Project> filteredProjects) { - if (mail == null || "".equals(mail.trim())) { - return false; - } - List<JsonNode> botObjs = getBots(); - // if there are no matching projects, then check against all bots, not just project bots - if (filteredProjects == null || filteredProjects.isEmpty()) { - return botObjs.stream().anyMatch(bot -> checkFieldsForMatchingMail(bot, mail)); - } - // for each of the matched projects, check the bot for the matching project ID - for (Project p : filteredProjects) { - LOGGER.debug("Checking project {} for matching bots", p.getProjectId()); - for (JsonNode bot : botObjs) { - // if the project ID match, and one of the email fields matches, then user is bot - if (p.getProjectId().equalsIgnoreCase(bot.get("projectId").asText()) - && checkFieldsForMatchingMail(bot, mail)) { - return true; - } - } + private boolean userIsABot(String mail, List<Project> filteredProjects) { + if (mail == null || "".equals(mail.trim())) { + return false; + } + List<JsonNode> botObjs = getBots(); + // if there are no matching projects, then check against all bots, not just + // project bots + if (filteredProjects == null || filteredProjects.isEmpty()) { + return botObjs.stream().anyMatch(bot -> checkFieldsForMatchingMail(bot, mail)); + } + // for each of the matched projects, check the bot for the matching project ID + for (Project p : filteredProjects) { + LOGGER.debug("Checking project {} for matching bots", p.getProjectId()); + for (JsonNode bot : botObjs) { + // if the project ID match, and one of the email fields matches, then user is + // bot + if (p.getProjectId().equalsIgnoreCase(bot.get("projectId").asText()) + && checkFieldsForMatchingMail(bot, mail)) { + return true; } - return false; + } } + return false; + } - /** - * Checks JSON node to look for email fields, both at the root, and nested email fields. - * - * @param bot the bots JSON object representation - * @param mail the email to match against - * @return true if the bot has a matching email value, otherwise false - */ - private boolean checkFieldsForMatchingMail(JsonNode bot, String mail) { - // check the root email for the bot for match - JsonNode botmail = bot.get("email"); - if (mail != null && botmail != null && mail.equalsIgnoreCase(botmail.asText(""))) { - LOGGER.debug("Found matching bot at root level for '{}'",mail); - return true; - } - Iterator<Entry<String, JsonNode>> i = bot.fields(); - while (i.hasNext()) { - Entry<String, JsonNode> e = i.next(); - // check that our field is an object with fields - JsonNode node = e.getValue(); - if (node.isObject()) { - LOGGER.debug("Checking {} for bot email", e.getKey()); - // if the mail matches (ignoring case) user is bot - JsonNode botAliasMail = node.get("email"); - if (mail != null && botAliasMail != null && mail.equalsIgnoreCase(botAliasMail.asText(""))) { - LOGGER.debug("Found match for bot email {}", mail); - return true; - } - } + /** + * Checks JSON node to look for email fields, both at the root, and nested email + * fields. + * + * @param bot the bots JSON object representation + * @param mail the email to match against + * @return true if the bot has a matching email value, otherwise false + */ + private boolean checkFieldsForMatchingMail(JsonNode bot, String mail) { + // check the root email for the bot for match + JsonNode botmail = bot.get("email"); + if (mail != null && botmail != null && mail.equalsIgnoreCase(botmail.asText(""))) { + LOGGER.debug("Found matching bot at root level for '{}'", mail); + return true; + } + Iterator<Entry<String, JsonNode>> i = bot.fields(); + while (i.hasNext()) { + Entry<String, JsonNode> e = i.next(); + // check that our field is an object with fields + JsonNode node = e.getValue(); + if (node.isObject()) { + LOGGER.debug("Checking {} for bot email", e.getKey()); + // if the mail matches (ignoring case) user is bot + JsonNode botAliasMail = node.get("email"); + if (mail != null && botAliasMail != null && mail.equalsIgnoreCase(botAliasMail.asText(""))) { + LOGGER.debug("Found match for bot email {}", mail); + return true; } - return false; + } } + return false; + } + private boolean isAllowedUser(String mail) { return allowListUsers.indexOf(mail) != -1; } /** - * Retrieves projects valid for the current request, or an empty list if no data or matching + * Retrieves projects valid for the current request, or an empty list if no data + * or matching * project repos could be found. * * @param req the current request - * @return list of matching projects for the current request, or an empty list if none found. + * @return list of matching projects for the current request, or an empty list + * if none found. */ private List<Project> retrieveProjectsForRequest(ValidationRequest req) { String repoUrl = req.getRepoUrl().getPath(); @@ -449,20 +499,22 @@ public class ValidationResource { } /** - * Retrieves an Eclipse Account user object given the Git users email address (at minimum). This - * is facilitated using the Eclipse Foundation accounts API, along short lived in-memory caching + * Retrieves an Eclipse Account user object given the Git users email address + * (at minimum). This + * is facilitated using the Eclipse Foundation accounts API, along short lived + * in-memory caching * for performance and some protection against duplicate requests. * * @param user the user to retrieve Eclipse Account information for - * @return the Eclipse Account user information if found, or null if there was an error or no user - * exists. + * @return the Eclipse Account user information if found, or null if there was + * an error or no user + * exists. */ private EclipseUser getIdentifiedUser(GitUser user) { // get the Eclipse account for the user try { // use cache to avoid asking for the same user repeatedly on repeated requests - Optional<EclipseUser> foundUser = - cache.get("user|" + user.getMail(), () -> retrieveUser(user), EclipseUser.class); + Optional<EclipseUser> foundUser = cache.get("user|" + user.getMail(), new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(user)); if (!foundUser.isPresent()) { LOGGER.warn("No users found for mail '{}'", user.getMail()); return null; @@ -480,7 +532,8 @@ public class ValidationResource { } /** - * Checks for standard and noreply email address matches for a Git user and converts to a + * Checks for standard and noreply email address matches for a Git user and + * converts to a * Eclipse Foundation account object. * * @param user the user to attempt account retrieval for. @@ -495,30 +548,34 @@ public class ValidationResource { // standard user check (returns best match) LOGGER.debug("Checking user with mail {}", user.getMail()); try { - List<EclipseUser> users = accounts.getUsers("Bearer " + oauth.getToken(), null, null, user.getMail()); - if (users != null) { + List<EclipseUser> users = accounts.getUsers(null, null, user.getMail()); + if (users != null && !users.isEmpty()) { return users.get(0); } - } catch(WebApplicationException e) { + } catch (WebApplicationException e) { LOGGER.warn("Could not find user account with mail '{}'", user.getMail()); } return null; } /** - * Checks git user for no-reply address, and attempts to ratify user through reverse lookup in API service. - * Currently, this service only recognizes Github no-reply addresses as they have a route to be mapped. + * Checks git user for no-reply address, and attempts to ratify user through + * reverse lookup in API service. + * Currently, this service only recognizes Github no-reply addresses as they + * have a route to be mapped. * * @param user the Git user account to check for no-reply mail address - * @return the Eclipse user if email address is detected no reply and one can be mapped, otherwise null + * @return the Eclipse user if email address is detected no reply and one can be + * mapped, otherwise null */ private EclipseUser checkForNoReplyUser(GitUser user) { LOGGER.debug("Checking user with mail {} for no-reply", user.getMail()); boolean isNoReply = patterns.stream().anyMatch(pattern -> pattern.matcher(user.getMail().trim()).find()); if (isNoReply) { - // get the username/ID string before the first @ symbol. + // get the username/ID string before the first @ symbol. String noReplyUser = user.getMail().substring(0, user.getMail().indexOf("@", 0)); - // split based on +, if more than one part, use second (contains user), otherwise, use whole string + // split based on +, if more than one part, use second (contains user), + // otherwise, use whole string String[] nameParts = noReplyUser.split("[\\+]"); String namePart; if (nameParts.length > 1 && nameParts[1] != null) { @@ -527,18 +584,18 @@ public class ValidationResource { namePart = nameParts[0]; } String uname = namePart.trim(); - LOGGER.debug("User with mail {} detected as noreply account, checking services for username match on '{}'", - user.getMail(), uname); + LOGGER.debug("User with mail {} detected as noreply account, checking services for username match on '{}'", + user.getMail(), uname); // check github for no-reply (only allowed noreply currently) if (user.getMail().endsWith("noreply.github.com")) { try { // check for Github no reply, return if set - EclipseUser eclipseUser = accounts.getUserByGithubUname("Bearer " + oauth.getToken(), uname); + EclipseUser eclipseUser = accounts.getUserByGithubUname(uname); if (eclipseUser != null) { return eclipseUser; } - } catch(WebApplicationException e) { + } catch (WebApplicationException e) { LOGGER.warn("No match for '{}' in Github", uname); } } @@ -546,14 +603,12 @@ public class ValidationResource { return null; } - @SuppressWarnings("unchecked") private List<JsonNode> getBots() { - Optional<List<JsonNode>> allBots = cache.get("allBots", () -> bots.getBots(), - (Class<List<JsonNode>>) (Object) List.class); - if (!allBots.isPresent()) { - return Collections.emptyList(); - } - return allBots.get(); + Optional<List<JsonNode>> allBots = cache.get("allBots", new MultivaluedMapImpl<>(), JsonNode.class, () -> bots.getBots()); + if (!allBots.isPresent()) { + return Collections.emptyList(); + } + return allBots.get(); } private void addMessage(ValidationResponse r, String message, String hash) { @@ -574,7 +629,7 @@ public class ValidationResource { private void addError(ValidationResponse r, String message, String hash, APIStatusCode code) { LOGGER.error(message); // only add as strict error for tracked projects - if (r.isTrackedProject() || r.isStrictMode()) { + if (r.getTrackedProject() || r.getStrictMode()) { r.addError(hash, message, code); } else { r.addWarning(hash, message, code); diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/CachingService.java b/src/main/java/org/eclipsefoundation/git/eca/service/CachingService.java deleted file mode 100644 index 9795719c..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/CachingService.java +++ /dev/null @@ -1,74 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -/* Copyright (c) 2019 Eclipse Foundation and others. - * This program and the accompanying materials are made available - * under the terms of the Eclipse Public License 2.0 - * which is available at http://www.eclipse.org/legal/epl-v20.html, - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipsefoundation.git.eca.service; - -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Callable; - -/** - * Interface defining the caching service to be used within the application. - * - * @author Martin Lowe - * @param <T> the type of object to be stored in the cache. - */ -public interface CachingService { - - /** - * Returns an Optional object of type T, returning a cached object if available, - * otherwise using the callable to generate a value to be stored in the cache - * and returned. - * - * @param cacheKey the cache key of the object to store in the cache - * @param callable a runnable that returns an object of type T - * @return the cached result - */ - <T> Optional<T> get(String cacheKey, Callable<? extends T> callable, Class<T> clazz); - - /** - * Returns the expiration date in millis since epoch. - * - * @param cacheKey the cache key to check for a value, and if set its - * expiration. - * @return an Optional expiration date for the current object if its set. If - * there is no underlying data, then empty would be returned - */ - Optional<Long> getExpiration(String cacheKey); - - /** - * @return the max age of cache entries - */ - long getMaxAge(); - - /** - * Retrieves a set of cache keys available to the current cache. - * - * @return unmodifiable set of cache entry keys. - */ - Set<String> getCacheKeys(); - - /** - * Removes cache entry for given cache entry key. - * - * @param key cache entry key - */ - void remove(String key); - - /** - * Removes all cache entries. - */ - void removeAll(); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java deleted file mode 100644 index 6b8b6a08..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.service; - -/** - * Used to generate OAuth tokens for use with internal services rather than - * bolted on introspection. This is required over the (now deprecated) Elytron - * plugin or the OIDC plugin as those plugins work with requests to validate - * incoming rather than outgoing requests. - * - * @author Martin Lowe - * - */ -public interface OAuthService { - - /** - * Retrieve an access token for the service from the Eclipse API for internal - * usage. - * - * @return current access token, or null if none could be retrieved for current - * API credentials/settings. - */ - String getToken(); -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java deleted file mode 100644 index a3e837f8..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.service.impl; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; -import javax.inject.Singleton; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipsefoundation.git.eca.oauth.EclipseApi; -import org.eclipsefoundation.git.eca.service.OAuthService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.github.scribejava.core.builder.ServiceBuilder; -import com.github.scribejava.core.model.OAuth2AccessToken; -import com.github.scribejava.core.oauth.OAuth20Service; - -/** - * Default implementation for requesting an OAuth request token. The reason that - * this class is implemented over the other implementations baked into Quarkus - * - * @author Martin Lowe - * - */ -@Singleton -public class DefaultOAuthService implements OAuthService { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOAuthService.class); - - @ConfigProperty(name = "oauth2.client-id") - String id; - @ConfigProperty(name = "oauth2.client-secret") - String secret; - @ConfigProperty(name = "oauth2.scope") - String scope; - - // service reference (as we only need one) - private OAuth20Service service; - - // token state vars - private long expirationTime; - private String accessToken; - - /** - * Create an OAuth service reference. - */ - @PostConstruct - void createServiceRef() { - this.service = new ServiceBuilder(id).apiSecret(secret).scope(scope).build(EclipseApi.instance()); - } - - @Override - public String getToken() { - // lock on the class instance to stop multiple threads from requesting new - // tokens at the same time - synchronized (this) { - if (accessToken == null || System.currentTimeMillis() >= expirationTime) { - // clear access token - this.accessToken = null; - try { - OAuth2AccessToken requestToken = service.getAccessTokenClientCredentialsGrant(); - if (requestToken != null) { - this.accessToken = requestToken.getAccessToken(); - this.expirationTime = System.currentTimeMillis() - + TimeUnit.SECONDS.toMillis(requestToken.getExpiresIn().longValue()); - } - } catch (IOException e) { - LOGGER.error("Issue communicating with OAuth server for authentication", e); - } catch (InterruptedException e) { - LOGGER.error("Authentication communication was interrupted before completion", e); - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - LOGGER.error("Error while retrieving access token for request", e); - } - } - } - return accessToken; - } - -} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/GuavaCachingService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/GuavaCachingService.java deleted file mode 100644 index 2dcf7b5c..00000000 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/GuavaCachingService.java +++ /dev/null @@ -1,137 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -/* Copyright (c) 2019 Eclipse Foundation and others. - * This program and the accompanying materials are made available - * under the terms of the Eclipse Public License 2.0 - * which is available at http://www.eclipse.org/legal/epl-v20.html, - * SPDX-License-Identifier: EPL-2.0 - */ -package org.eclipsefoundation.git.eca.service.impl; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipsefoundation.git.eca.service.CachingService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader.InvalidCacheLoadException; -import com.google.common.util.concurrent.UncheckedExecutionException; - -/** - * <p> - * Simple caching service for caching objects in an in-memory cache, implemented - * using the Google Guava cache mechanism. Cache size and time to live are - * configured within the MicroProfile configuration. - * </p> - * - * <p> - * Guava cache is inherently thread safe, so no synchronization needs to be done - * on access. - * </p> - * - * @author Martin Lowe - * @param <T> the type of object cached by this instance of the service - * - */ -@ApplicationScoped -public class GuavaCachingService implements CachingService { - private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCachingService.class); - - @ConfigProperty(name = "cache.max.size", defaultValue = "10000") - long maxSize; - @ConfigProperty(name = "cache.ttl.write.seconds", defaultValue = "900") - long ttlWrite; - - // actual cache object - private Map<Class<?>, Cache<String, ?>> caches; - private Map<String, Long> ttl; - - @PostConstruct - public void init() { - this.ttl = new HashMap<>(); - this.caches = new HashMap<>(); - } - - @Override - public <T> Optional<T> get(String cacheKey, Callable<? extends T> callable, Class<T> clazz) { - Objects.requireNonNull(cacheKey); - Objects.requireNonNull(callable); - try { - // multi cache support - @SuppressWarnings("unchecked") - Cache<String, T> c = (Cache<String, T>) caches.computeIfAbsent(clazz, key -> createCache()); - - // get entry, and enter a ttl as soon as it returns - T data = c.get(cacheKey, callable); - if (data != null) { - ttl.putIfAbsent(cacheKey, - System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(ttlWrite, TimeUnit.SECONDS)); - } - return Optional.of(c.get(cacheKey, callable)); - } catch (InvalidCacheLoadException | UncheckedExecutionException e) { - LOGGER.error("Error while retrieving fresh value for cachekey: {}", cacheKey, e); - } catch (Exception e) { - LOGGER.error("Error while retrieving value of callback", e); - } - return Optional.empty(); - } - - private <T> Cache<String, T> createCache() { - return CacheBuilder - .newBuilder() - .maximumSize(maxSize) - .expireAfterWrite(ttlWrite, TimeUnit.SECONDS) - .removalListener(not -> ttl.remove(not.getKey())) - .build(); - } - - @Override - public Optional<Long> getExpiration(String cacheKey) { - return Optional.ofNullable(ttl.get(cacheKey)); - } - - @Override - public Set<String> getCacheKeys() { - // create a set and return all keys - Set<String> out = new HashSet<>(); - caches.values().stream().forEach(c -> out.addAll(c.asMap().keySet())); - return out; - } - - @Override - public void remove(String key) { - // TODO we probably want to shift this to target a cache - caches.values().stream().forEach(c -> c.invalidate(key)); - } - - @Override - public void removeAll() { - // TODO we probably want to be able to target a given cache - caches.values().stream().forEach(Cache::invalidateAll); - } - - @Override - public long getMaxAge() { - return ttlWrite; - } -} diff --git a/src/main/js/openapi2schema.js b/src/main/js/openapi2schema.js new file mode 100644 index 00000000..e857cb33 --- /dev/null +++ b/src/main/js/openapi2schema.js @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2021 Eclipse + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe <martin.lowe@eclipsefoundation.org> + * + * SPDX-License-Identifier: EPL-2.0 + */ + const toJsonSchema = require('@openapi-contrib/openapi-schema-to-json-schema'); + const Resolver = require('@stoplight/json-ref-resolver'); + const yaml = require('js-yaml'); + const fs = require('fs'); + const decamelize = require('decamelize'); + const args = require('yargs') + .option('s', { + alias: 'src', + desc: 'The fully qualified path to the YAML spec.', + }) + .option('t', { + alias: 'target', + desc: 'The fully qualified path to write the JSON schema to', + }).argv; + if (!args.s || !args.t) { + process.exit(1); + } + + run(); + + /** + * Generates JSON schema files for consumption of the Java tests. + */ + async function run() { + try { + // load in the openapi yaml spec as an object + const doc = yaml.load(fs.readFileSync(args.s, 'utf8')); + // resolve $refs in openapi spec + let resolvedInp = await new Resolver.Resolver().resolve(doc); + const out = toJsonSchema(resolvedInp.result); + // if folder doesn't exist, create it + if (!fs.existsSync(`${args.t}/schemas`)) { + fs.mkdirSync(`${args.t}/schemas`); + } + // for each of the schemas, generate a JSON schema file + for (let schemaName in out.components.schemas) { + fs.writeFileSync(`${args.t}/schemas/${decamelize(schemaName, { separator: '-' })}-schema.json`, JSON.stringify(out.components.schemas[schemaName])); + } + } catch (e) { + console.log(e); + } + } + \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource deleted file mode 100644 index acd2ee19..00000000 --- a/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource +++ /dev/null @@ -1 +0,0 @@ -org.eclipsefoundation.git.eca.config.SecretConfigSource \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 94936fec..f41f5d75 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,11 +8,15 @@ eclipse.noreply.email-patterns=@users.noreply.github.com\$ quarkus.http.root-path=/git ## OAUTH CONFIG -oauth2.scope=eclipsefdn_view_all_profiles quarkus.http.port=8080 -## required to start when secret.properties isn't found/mounted -oauth2.client-id=placeholder -oauth2.client-secret=placeholder +quarkus.keycloak.devservices.enabled=false +quarkus.oauth2.enabled=false +quarkus.oidc.enabled=false +quarkus.oidc-client.auth-server-url=https://accounts.eclipse.org/oauth2 +quarkus.oidc-client.discovery-enabled=false +quarkus.oidc-client.token-path=/token +quarkus.oidc-client.grant.type=client +quarkus.oidc-client.scopes=eclipsefdn_view_all_profiles eclipse.mail.allowlist=noreply@github.com \ No newline at end of file diff --git a/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java index a9f53696..a96a9cd5 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java @@ -27,83 +27,52 @@ import io.quarkus.test.Mock; @ApplicationScoped public class MockAccountsAPI implements AccountsAPI { - private List<EclipseUser> src; - - @PostConstruct - public void build() { - this.src = new ArrayList<>(); - int id = 0; + private List<EclipseUser> src; - EclipseUser e1 = new EclipseUser(); - e1.setCommitter(false); - e1.setId(id++); - e1.setMail("newbie@important.co"); - e1.setName("newbieAnon"); - e1.setEca(new ECA()); - src.add(e1); - - EclipseUser e2 = new EclipseUser(); - e2.setCommitter(false); - e2.setId(id++); - e2.setMail("slom@eclipse-foundation.org"); - e2.setName("barshall_blathers"); - e2.setEca(new ECA(true, true)); - src.add(e2); - - EclipseUser e3 = new EclipseUser(); - e3.setCommitter(false); - e3.setId(id++); - e3.setMail("tester@eclipse-foundation.org"); - e3.setName("mctesterson"); - e3.setEca(new ECA(true, false)); - src.add(e3); - - EclipseUser e4 = new EclipseUser(); - e4.setCommitter(true); - e4.setId(id++); - e4.setMail("code.wiz@important.co"); - e4.setName("da_wizz"); - e4.setEca(new ECA(true, true)); - src.add(e4); - - EclipseUser e5 = new EclipseUser(); - e5.setCommitter(true); - e5.setId(id++); - e5.setMail("grunt@important.co"); - e5.setName("grunter"); - e5.setEca(new ECA(true, false)); - src.add(e5); - - EclipseUser e6 = new EclipseUser(); - e6.setCommitter(false); - e6.setId(id++); - e6.setMail("paper.pusher@important.co"); - e6.setName("sumAnalyst"); - e6.setEca(new ECA(true, false)); - src.add(e6); - } - - @Override - public List<EclipseUser> getUsers(String authBearer, String id, String name, String mail) { - return src.stream().filter(user -> { - boolean matches = true; - if (id != null && !Integer.toString(user.getId()).equals(id)) { - matches = false; - } - if (name != null && !user.getName().equals(name)) { - matches = false; - } - if (mail != null && !user.getMail().equalsIgnoreCase(mail)) { - matches = false; - } - return matches; - }).collect(Collectors.toList()); - } + @PostConstruct + public void build() { + this.src = new ArrayList<>(); + int id = 0; + src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("newbie@important.co") + .setName("newbieAnon").setECA(ECA.builder().build()).build()); + src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("slom@eclipse-foundation.org") + .setName("barshall_blathers") + .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()).build()); + src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("tester@eclipse-foundation.org") + .setName("mctesterson").setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .build()); + src.add(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("code.wiz@important.co") + .setName("da_wizz").setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()) + .build()); + src.add(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("grunt@important.co").setName("grunter") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); + src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("paper.pusher@important.co") + .setName("sumAnalyst").setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) + .build()); - @Override - public EclipseUser getUserByGithubUname(String authBearer, String uname) { - // assume GH username == Eclipse uname for simplicity of test - return src.stream().filter(user -> uname.equalsIgnoreCase(user.getName())).findFirst().orElse(null); - } + } + + @Override + public List<EclipseUser> getUsers(String id, String name, String mail) { + return src.stream().filter(user -> { + boolean matches = true; + if (id != null && !Integer.toString(user.getUid()).equals(id)) { + matches = false; + } + if (name != null && !user.getName().equals(name)) { + matches = false; + } + if (mail != null && !user.getMail().equalsIgnoreCase(mail)) { + matches = false; + } + return matches; + }).collect(Collectors.toList()); + } + + @Override + public EclipseUser getUserByGithubUname(String uname) { + // assume GH username == Eclipse uname for simplicity of test + return src.stream().filter(user -> uname.equalsIgnoreCase(user.getName())).findFirst().orElse(null); + } } diff --git a/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java index 64d90666..e4c103b4 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java @@ -12,7 +12,9 @@ package org.eclipsefoundation.git.eca.api; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; @@ -29,71 +31,56 @@ import io.quarkus.test.Mock; @ApplicationScoped public class MockProjectsAPI implements ProjectsAPI { - private List<Project> src; + private List<Project> src; - @PostConstruct - public void build() { - this.src = new ArrayList<>(); + @PostConstruct + public void build() { + this.src = new ArrayList<>(); - // sample repos - Repo r1 = new Repo(); - r1.setUrl("http://www.github.com/eclipsefdn/sample"); - Repo r2 = new Repo(); - r2.setUrl("http://www.github.com/eclipsefdn/test"); - Repo r3 = new Repo(); - r3.setUrl("http://www.github.com/eclipsefdn/prototype.git"); - Repo r4 = new Repo(); - r4.setUrl("http://www.github.com/eclipsefdn/tck-proto"); - Repo r5 = new Repo(); - r5.setUrl("/gitroot/sample/gerrit.project.git"); - Repo r6 = new Repo(); - r6.setUrl("/gitroot/sample/gerrit.other-project"); - Repo r7 = new Repo(); - r7.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git"); - Repo r8 = new Repo(); - r8.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test"); + // sample repos + Repo r1 = new Repo(); + r1.setUrl("http://www.github.com/eclipsefdn/sample"); + Repo r2 = new Repo(); + r2.setUrl("http://www.github.com/eclipsefdn/test"); + Repo r3 = new Repo(); + r3.setUrl("http://www.github.com/eclipsefdn/prototype.git"); + Repo r4 = new Repo(); + r4.setUrl("http://www.github.com/eclipsefdn/tck-proto"); + Repo r5 = new Repo(); + r5.setUrl("/gitroot/sample/gerrit.project.git"); + Repo r6 = new Repo(); + r6.setUrl("/gitroot/sample/gerrit.other-project"); + Repo r7 = new Repo(); + r7.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.git"); + Repo r8 = new Repo(); + r8.setUrl("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test"); - // sample users, correlates to users in Mock projects API - User u1 = new User(); - u1.setUrl(""); - u1.setUsername("da_wizz"); + // sample users, correlates to users in Mock projects API + User u1 = User.builder().setUrl("").setUsername("da_wizz").build(); + User u2 = User.builder().setUrl("").setUsername("grunter").build(); - User u2 = new User(); - u2.setUrl(""); - u2.setUsername("grunter"); + // projects + Project p1 = Project.builder().setName("Sample project").setProjectId("sample.proj") + .setSpecProjectWorkingGroup(Collections.emptyList()).setGithubRepos(Arrays.asList(r1, r2)) + .setGerritRepos(Arrays.asList(r5)).setCommitters(Arrays.asList(u1, u2)).build(); + src.add(p1); - // projects - Project p1 = new Project(); - p1.setName("Sample project"); - p1.setProjectId("sample.proj"); - p1.setSpecWorkingGroup(null); - p1.setGithubRepos(Arrays.asList(r1, r2)); - p1.setGerritRepos(Arrays.asList(r5)); - p1.setCommitters(Arrays.asList(u1, u2)); - src.add(p1); + Project p2 = Project.builder().setName("Prototype thing").setProjectId("sample.proto") + .setSpecProjectWorkingGroup(Collections.emptyList()).setGithubRepos(Arrays.asList(r3)) + .setGerritRepos(Arrays.asList(r6)).setGitlabRepos(Arrays.asList(r8)).setCommitters(Arrays.asList(u2)) + .build(); + src.add(p2); - Project p2 = new Project(); - p2.setName("Prototype thing"); - p2.setProjectId("sample.proto"); - p2.setSpecWorkingGroup(null); - p2.setGithubRepos(Arrays.asList(r3)); - p2.setGerritRepos(Arrays.asList(r6)); - p2.setGitlabRepos(Arrays.asList(r8)); - p2.setCommitters(Arrays.asList(u2)); - src.add(p2); + Map<String, String> map = new HashMap<>(); + map.put("id", "proj1"); + Project p3 = Project.builder().setName("Spec project").setProjectId("spec.proj").setSpecProjectWorkingGroup(map) + .setGithubRepos(Arrays.asList(r4)).setGitlabRepos(Arrays.asList(r7)) + .setCommitters(Arrays.asList(u1, u2)).build(); + src.add(p3); + } - Project p3 = new Project(); - p3.setName("Spec project"); - p3.setProjectId("spec.proj"); - p3.setSpecWorkingGroup("proj1"); - p3.setGithubRepos(Arrays.asList(r4)); - p3.setGitlabRepos(Arrays.asList(r7)); - p3.setCommitters(Arrays.asList(u1, u2)); - src.add(p3); - } - - @Override - public List<Project> getProject(int page, int pageSize) { - return page == 1 ? new ArrayList<>(src) : Collections.emptyList(); - } + @Override + public List<Project> getProject(int page, int pageSize) { + return page == 1 ? new ArrayList<>(src) : Collections.emptyList(); + } } diff --git a/src/test/java/org/eclipsefoundation/git/eca/helper/CommitHelperTest.java b/src/test/java/org/eclipsefoundation/git/eca/helper/CommitHelperTest.java index a4ce4684..15516403 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/helper/CommitHelperTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/helper/CommitHelperTest.java @@ -28,98 +28,87 @@ import io.quarkus.test.junit.QuarkusTest; @QuarkusTest class CommitHelperTest { - // represents a known good commit before the start of each test - GitUser testUser; - Commit baseCommit; - - @BeforeEach - void setup() { - // basic good user - testUser = new GitUser(); - testUser.setMail("test.user@eclipse-foundation.org"); - testUser.setName("Tester McTesterson"); - - // basic known good commit - baseCommit = new Commit(); - baseCommit.setBody( - String.format("Sample body content\n\nSigned-off-by: %s <%s>", testUser.getName(), testUser.getMail())); - baseCommit.setHash("abc123f"); - baseCommit.setHead(false); - baseCommit.setParents(new ArrayList<>()); - baseCommit.setSubject("Testing CommitHelper class #1337"); - baseCommit.setAuthor(testUser); - baseCommit.setCommitter(testUser); - } - - @Test - void validateCommitKnownGood() { - Assertions.assertTrue(CommitHelper.validateCommit(baseCommit), "Expected basic commit to pass validation"); - } - - @Test - void validateCommitNullCommit() { - Assertions.assertFalse(CommitHelper.validateCommit(null), "Expected null commit to fail validation"); - } - - @Test - void validateCommitNoAuthor() { - baseCommit.setAuthor(null); - Assertions.assertFalse(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to fail validation w/ no author"); - } - - @Test - void validateCommitNoAuthorMail() { - GitUser noMail = new GitUser(); - noMail.setName("Some Name"); - - baseCommit.setAuthor(noMail); - Assertions.assertFalse(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to fail validation w/ no author mail address"); - } - - @Test - void validateCommitNoCommitter() { - baseCommit.setCommitter(null); - Assertions.assertFalse(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to fail validation w/ no committer"); - } - - @Test - void validateCommitNoCommitterMail() { - GitUser noMail = new GitUser(); - noMail.setName("Some Name"); - - baseCommit.setCommitter(noMail); - Assertions.assertFalse(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to fail validation w/ no committer mail address"); - } - - @Test - void validateCommitNoHash() { - baseCommit.setHash(null); - Assertions.assertFalse(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to fail validation w/ no commit hash"); - } - - @Test - void validateCommitNoBody() { - baseCommit.setBody(null); - Assertions.assertTrue(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to pass validation w/ no body"); - } - - @Test - void validateCommitNoParents() { - baseCommit.setParents(new ArrayList<>()); - Assertions.assertTrue(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to pass validation w/ no parents"); - } - - @Test - void validateCommitNoSubject() { - baseCommit.setSubject(null); - Assertions.assertTrue(CommitHelper.validateCommit(baseCommit), - "Expected basic commit to pass validation w/ no subject"); - } + // represents a known good commit before the start of each test + GitUser testUser; + Commit.Builder baseCommit; + + @BeforeEach + void setup() { + // basic good user + testUser = GitUser.builder().setMail("test.user@eclipse-foundation.org").setName("Tester McTesterson").build(); + + // basic known good commit + baseCommit = Commit.builder() + .setBody(String.format("Sample body content\n\nSigned-off-by: %s <%s>", testUser.getName(), + testUser.getMail())) + .setHash("abc123f").setHead(false).setParents(new ArrayList<>()) + .setSubject("Testing CommitHelper class #1337").setAuthor(testUser).setCommitter(testUser); + } + + @Test + void validateCommitKnownGood() { + Assertions.assertTrue(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to pass validation"); + } + + @Test + void validateCommitNullCommit() { + Assertions.assertFalse(CommitHelper.validateCommit(null), "Expected null commit to fail validation"); + } + + @Test + void validateCommitNoAuthor() { + baseCommit.setAuthor(null); + Assertions.assertFalse(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to fail validation w/ no author"); + } + + @Test + void validateCommitNoAuthorMail() { + baseCommit.setAuthor(GitUser.builder().setName("Some Name").build()); + Assertions.assertFalse(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to fail validation w/ no author mail address"); + } + + @Test + void validateCommitNoCommitter() { + baseCommit.setCommitter(null); + Assertions.assertFalse(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to fail validation w/ no committer"); + } + + @Test + void validateCommitNoCommitterMail() { + baseCommit.setCommitter(GitUser.builder().setName("Some Name").build()); + Assertions.assertFalse(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to fail validation w/ no committer mail address"); + } + + @Test + void validateCommitNoHash() { + baseCommit.setHash(null); + Assertions.assertFalse(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to fail validation w/ no commit hash"); + } + + @Test + void validateCommitNoBody() { + baseCommit.setBody(null); + Assertions.assertTrue(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to pass validation w/ no body"); + } + + @Test + void validateCommitNoParents() { + baseCommit.setParents(new ArrayList<>()); + Assertions.assertTrue(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to pass validation w/ no parents"); + } + + @Test + void validateCommitNoSubject() { + baseCommit.setSubject(null); + Assertions.assertTrue(CommitHelper.validateCommit(baseCommit.build()), + "Expected basic commit to pass validation w/ no subject"); + } } diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index 0738f5ec..657ec486 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -20,22 +20,21 @@ import java.util.List; import javax.inject.Inject; +import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.model.Commit; import org.eclipsefoundation.git.eca.model.GitUser; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; -import org.eclipsefoundation.git.eca.service.CachingService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; -import io.restassured.response.ValidatableResponse; /** - * Tests for verifying end to end validation via the endpoint. Uses restassured to create pseudo - * requests, and Mock API endpoints to ensure that all data is kept internal for test checks. + * Tests for verifying end to end validation via the endpoint. Uses restassured to create pseudo requests, and Mock API + * endpoints to ensure that all data is kept internal for test checks. * * @author Martin Lowe */ @@ -44,1148 +43,765 @@ class ValidationResourceTest { @Inject CachingService cs; - - + @BeforeEach void cacheClear() { // if dev servers are run on the same machine, some values may live in the cache cs.removeAll(); } - - @Test - void validate() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("The Wizard"); - g1.setMail("code.wiz@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody("Signed-off-by: The Wizard <code.wiz@important.co>"); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - - // test output w/ assertions - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateMultipleCommits() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("The Wizard"); - g1.setMail("code.wiz@important.co"); - - GitUser g2 = new GitUser(); - g2.setName("Grunts McGee"); - g2.setMail("grunt@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody("Signed-off-by: The Wizard <code.wiz@important.co>"); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - Commit c2 = new Commit(); - c2.setAuthor(g2); - c2.setCommitter(g2); - c2.setBody("Signed-off-by: Grunts McGee<grunt@important.co>"); - c2.setHash("c044dca1847c94e709601651339f88a5c82e3cc7"); - c2.setSubject("Add in feature"); - c2.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c2); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - - // test output w/ assertions - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateMergeCommit() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Rando Calressian"); - g1.setMail("rando@nowhere.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents( - Arrays.asList( - "46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10", - "46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c11")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // No errors expected, should pass as only commit is a valid merge commit - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitNoSignOffCommitter() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Grunts McGee"); - g1.setMail("grunt@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody(""); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")); - vr.setCommits(commits); - - // test output w/ assertions - // Should be valid as Grunt is a committer on the prototype project - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitNoSignOffNonCommitter() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("The Wizard"); - g1.setMail("code.wiz@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody(""); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype.git")); - vr.setCommits(commits); - - // test output w/ assertions - // Should be valid as wizard has signed ECA - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitInvalidSignOff() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Barshall Blathers"); - g1.setMail("slom@eclipse-foundation.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), "barshallb@personal.co")); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype.git")); - vr.setCommits(commits); - - // test output w/ assertions - // Should be valid as signed off by footer is no longer checked - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitSignOffMultipleFooterLines_Last() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Barshall Blathers"); - g1.setMail("slom@eclipse-foundation.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody( - String.format( - "Change-Id: 0000000000000001\nSigned-off-by: %s <%s>", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")); - vr.setCommits(commits); - - // test output w/ assertions - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitSignOffMultipleFooterLines_First() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Barshall Blathers"); - g1.setMail("slom@eclipse-foundation.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody( - String.format( - "Signed-off-by: %s <%s>\nChange-Id: 0000000000000001", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")); - vr.setCommits(commits); - - // test output w/ assertions - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateCommitSignOffMultipleFooterLines_Multiple() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Barshall Blathers"); - g1.setMail("slom@eclipse-foundation.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody( - String.format( - "Change-Id: 0000000000000001\\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>", - g1.getName(), g1.getMail(), g1.getName(), "barshallb@personal.co")); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")); - vr.setCommits(commits); - - // test output w/ assertions - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - } - - @Test - void validateWorkingGroupSpecAccess() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("The Wizard"); - g1.setMail("code.wiz@important.co"); - - GitUser g2 = new GitUser(); - g2.setName("Grunts McGee"); - g2.setMail("grunt@important.co"); - - // CASE 1: WG Spec project write access valid - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody("Signed-off-by: The Wizard <code.wiz@important.co>"); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Collections.emptyList()); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/tck-proto")); - vr.setCommits(commits); - - // test output w/ assertions - // Should be valid as Wizard has spec project write access + is committer - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(200) - .body("passed", is(true), "errorCount", is(0)); - - // CASE 2: No WG Spec proj write access - commits = new ArrayList<>(); - // create sample commits - c1 = new Commit(); - c1.setAuthor(g2); - c1.setCommitter(g2); - c1.setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/tck-proto")); - vr.setCommits(commits); - - // test output w/ assertions - // Should be invalid as Grunt does not have spec project write access - // Should have 2 errors, as both users get validated - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body( - "passed", - is(false), - "errorCount", - is(2), - "commits.123456789abcdefghijklmnop.errors[0].code", - is(APIStatusCode.ERROR_SPEC_PROJECT.getValue())); - } - - @Test - void validateNoECA_author() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Newbie Anon"); - g1.setMail("newbie@important.co"); - - GitUser g2 = new GitUser(); - g2.setName("The Wizard"); - g2.setMail("code.wiz@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g2); - c1.setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Error should be singular + that there's no ECA on file - // Status 403 (forbidden) is the standard return for invalid requests - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body("passed", is(false), "errorCount", is(1)); - } - - @Test - void validateNoECA_committer() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Newbie Anon"); - g1.setMail("newbie@important.co"); - - GitUser g2 = new GitUser(); - g2.setName("The Wizard"); - g2.setMail("code.wiz@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g2); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Error count should be 1 for just the committer access - // Status 403 (forbidden) is the standard return for invalid requests - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body("passed", is(false), "errorCount", is(1)); - } - @Test - void validateNoECA_both() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Newbie Anon"); - g1.setMail("newbie@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Should have 2 errors, 1 for author entry and 1 for committer entry - // Status 403 (forbidden) is the standard return for invalid requests - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body("passed", is(false), "errorCount", is(2)); - } - - @Test - void validateAuthorNoEclipseAccount() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Rando Calressian"); - g1.setMail("rando@nowhere.co"); - - GitUser g2 = new GitUser(); - g2.setName("Grunts McGee"); - g2.setMail("grunt@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g2); - c1.setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Error should be singular + that there's no Eclipse Account on file for author - // Status 403 (forbidden) is the standard return for invalid requests - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body("passed", is(false), "errorCount", is(1)); - } - - @Test - void validateCommitterNoEclipseAccount() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Rando Calressian"); - g1.setMail("rando@nowhere.co"); - - GitUser g2 = new GitUser(); - g2.setName("Grunts McGee"); - g2.setMail("grunt@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g2); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Error should be singular + that there's no Eclipse Account on file for committer - // Status 403 (forbidden) is the standard return for invalid requests - given() - .body(vr) - .contentType(ContentType.JSON) - .when() - .post("/eca") - .then() - .statusCode(403) - .body("passed", is(false), "errorCount", is(1)); - } - - @Test - void validateProxyCommitUntrackedProject() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("Rando Calressian"); - g1.setMail("rando@nowhere.co"); - - GitUser g2 = new GitUser(); - g2.setName("Grunts McGee"); - g2.setMail("grunt@important.co"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g2); - c1.setCommitter(g1); - c1.setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-not-tracked")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as project is not tracked - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGithub() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("projbot"); - g1.setMail("1.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGithub_untracked() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("projbot"); - g1.setMail("1.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-untracked")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGithub_invalidBot() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("protobot-gh"); - g1.setMail("2.bot-github@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Should be invalid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateBotCommiterAccessGithub_wrongEmail() throws URISyntaxException { - // set up test users - uses Gerrit/LDAP email (wrong for case) - GitUser g1 = new GitUser(); - g1.setName("protobot"); - g1.setMail("2.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITHUB); - vr.setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")); - vr.setCommits(commits); - // test output w/ assertions - // Should be invalid as wrong email was used for bot (uses Gerrit bot email) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateBotCommiterAccessGitlab() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("protobot-gh"); - g1.setMail("2.bot-github@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITLAB); - vr.setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGitlab_untracked() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("protobot-gh"); - g1.setMail("2.bot-github@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITLAB); - vr.setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.untracked")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGitlab_invalidBot() throws URISyntaxException { - // set up test users (wrong bot for project) - GitUser g1 = new GitUser(); - g1.setName("specbot"); - g1.setMail("3.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITLAB); - vr.setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test")); - vr.setCommits(commits); - // test output w/ assertions - // Should be invalid as bots should only commit on their own projects - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateBotCommiterAccessGitlab_wrongEmail() throws URISyntaxException { - // set up test users - uses Gerrit/LDAP email (expects Gitlab email) - GitUser g1 = new GitUser(); - g1.setName("specbot"); - g1.setMail("3.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GITLAB); - vr.setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.git")); - vr.setCommits(commits); - // test output w/ assertions - // Should be valid as wrong email was used, but is still bot email alias (uses Gerrit bot email) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGerrit() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("protobot"); - g1.setMail("2.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGerrit_untracked() throws URISyntaxException { - // set up test users - GitUser g1 = new GitUser(); - g1.setName("protobot"); - g1.setMail("2.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/untracked.project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateBotCommiterAccessGerrit_invalidBot() throws URISyntaxException { - // set up test users - (wrong bot for project) - GitUser g1 = new GitUser(); - g1.setName("specbot"); - g1.setMail("3.bot@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be invalid as bots should only commit on their own projects (wrong project) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - - } - - @Test - void validateBotCommiterAccessGerrit_aliasEmail() throws URISyntaxException { - // set up test users - uses GH (instead of expected Gerrit/LDAP email) - GitUser g1 = new GitUser(); - g1.setName("protobot-gh"); - g1.setMail("2.bot-github@eclipse.org"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as wrong email was used, but is still bot email alias - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateNullEmailCheck() throws URISyntaxException { - // set up test users - uses GH (instead of expected Gerrit/LDAP email) - GitUser g1 = new GitUser(); - g1.setName("protobot-gh"); - g1.setMail("2.bot-github@eclipse.org"); - GitUser g2 = new GitUser(); - g2.setName("protobot-gh"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g2); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be invalid as there is no email (refuse commit, not server error) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateGithubNoReply_legacy() throws URISyntaxException { - GitUser g1 = new GitUser(); - g1.setName("grunter"); - g1.setMail("grunter@users.noreply.github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateGithubNoReply_success() throws URISyntaxException { - // sometimes the user ID and user name are reversed - GitUser g1 = new GitUser(); - g1.setName("grunter"); - g1.setMail("123456789+grunter@users.noreply.github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateGithubNoReply_nomatch() throws URISyntaxException { - GitUser g1 = new GitUser(); - g1.setName("some_guy"); - g1.setMail("123456789+some_guy@users.noreply.github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be invalid as no user exists with "Github" handle that matches some_guy - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateGithubNoReply_nomatch_legacy() throws URISyntaxException { - GitUser g1 = new GitUser(); - g1.setName("some_guy"); - g1.setMail("some_guy@users.noreply.github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be invalid as no user exists with "Github" handle that matches some_guy - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); - } - - @Test - void validateAllowListAuthor_success() throws URISyntaxException { - GitUser g1 = new GitUser(); - g1.setName("grunter"); - g1.setMail("grunter@users.noreply.github.com"); - GitUser g2 = new GitUser(); - g2.setName("grunter"); - g2.setMail("noreply@github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g2); - c1.setCommitter(g1); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } - - @Test - void validateAllowListCommitter_success() throws URISyntaxException { - GitUser g1 = new GitUser(); - g1.setName("grunter"); - g1.setMail("grunter@users.noreply.github.com"); - GitUser g2 = new GitUser(); - g2.setName("grunter"); - g2.setMail("noreply@github.com"); - - List<Commit> commits = new ArrayList<>(); - // create sample commits - Commit c1 = new Commit(); - c1.setAuthor(g1); - c1.setCommitter(g2); - c1.setHash("123456789abcdefghijklmnop"); - c1.setSubject("All of the things"); - c1.setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")); - commits.add(c1); - - ValidationRequest vr = new ValidationRequest(); - vr.setProvider(ProviderType.GERRIT); - vr.setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")); - vr.setCommits(commits); - vr.setStrictMode(true); - // test output w/ assertions - // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); - } + + @Test + void validate() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody("Signed-off-by: The Wizard <code.wiz@important.co>").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + + // test output w/ assertions + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateMultipleCommits() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + GitUser g2 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody("Signed-off-by: The Wizard <code.wiz@important.co>").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + commits.add(c1); + + Commit c2 = Commit.builder().setAuthor(g2).setCommitter(g2) + .setBody("Signed-off-by: Grunts McGee<grunt@important.co>") + .setHash("c044dca1847c94e709601651339f88a5c82e3cc7").setSubject("Add in feature") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c2); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + + // test output w/ assertions + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateMergeCommit() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Rando Calressian").setMail("rando@nowhere.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things").setParents(Arrays + .asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10", "46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c11")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // No errors expected, should pass as only commit is a valid merge commit + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitNoSignOffCommitter() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setBody("").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); + + // test output w/ assertions + // Should be valid as Grunt is a committer on the prototype project + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitNoSignOffNonCommitter() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setBody("").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype.git")).setCommits(commits).build(); + + // test output w/ assertions + // Should be valid as wizard has signed ECA + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitInvalidSignOff() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Barshall Blathers").setMail("slom@eclipse-foundation.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), "barshallb@personal.co")) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype.git")).setCommits(commits).build(); + + // test output w/ assertions + // Should be valid as signed off by footer is no longer checked + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitSignOffMultipleFooterLines_Last() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Barshall Blathers").setMail("slom@eclipse-foundation.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Change-Id: 0000000000000001\nSigned-off-by: %s <%s>", g1.getName(), + g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); + + // test output w/ assertions + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitSignOffMultipleFooterLines_First() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Barshall Blathers").setMail("slom@eclipse-foundation.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>\nChange-Id: 0000000000000001", g1.getName(), + g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); + + // test output w/ assertions + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateCommitSignOffMultipleFooterLines_Multiple() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Barshall Blathers").setMail("slom@eclipse-foundation.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Change-Id: 0000000000000001\\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>", + g1.getName(), g1.getMail(), g1.getName(), "barshallb@personal.co")) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); + + // test output w/ assertions + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + } + + @Test + void validateWorkingGroupSpecAccess() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + GitUser g2 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + // CASE 1: WG Spec project write access valid + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody("Signed-off-by: The Wizard <code.wiz@important.co>").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/tck-proto")).setCommits(commits).build(); + + // test output w/ assertions + // Should be valid as Wizard has spec project write access + is committer + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + is(true), "errorCount", is(0)); + + // CASE 2: No WG Spec proj write access + commits = new ArrayList<>(); + // create sample commits + c1 = Commit.builder().setAuthor(g2).setCommitter(g2) + .setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/tck-proto")).setCommits(commits).build(); + + // test output w/ assertions + // Should be invalid as Grunt does not have spec project write access + // Should have 2 errors, as both users get validated + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(2), "commits.123456789abcdefghijklmnop.errors[0].code", + is(APIStatusCode.ERROR_SPEC_PROJECT.getValue())); + } + + @Test + void validateNoECA_author() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Newbie Anon").setMail("newbie@important.co").build(); + + GitUser g2 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g2) + .setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Error should be singular + that there's no ECA on file + // Status 403 (forbidden) is the standard return for invalid requests + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(1)); + } + + @Test + void validateNoECA_committer() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Newbie Anon").setMail("newbie@important.co").build(); + + GitUser g2 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g2).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Error count should be 1 for just the committer access + // Status 403 (forbidden) is the standard return for invalid requests + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(1)); + } + + @Test + void validateNoECA_both() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Newbie Anon").setMail("newbie@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Should have 2 errors, 1 for author entry and 1 for committer entry + // Status 403 (forbidden) is the standard return for invalid requests + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(2)); + } + + @Test + void validateAuthorNoEclipseAccount() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Rando Calressian").setMail("rando@nowhere.co").build(); + + GitUser g2 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g2) + .setBody(String.format("Signed-off-by: %s <%s>", g1.getName(), g1.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Error should be singular + that there's no Eclipse Account on file for author + // Status 403 (forbidden) is the standard return for invalid requests + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(1)); + } + + @Test + void validateCommitterNoEclipseAccount() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Rando Calressian").setMail("rando@nowhere.co").build(); + + GitUser g2 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g2).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Error should be singular + that there's no Eclipse Account on file for committer + // Status 403 (forbidden) is the standard return for invalid requests + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + is(false), "errorCount", is(1)); + } + + @Test + void validateProxyCommitUntrackedProject() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("Rando Calressian").setMail("rando@nowhere.co").build(); + + GitUser g2 = GitUser.builder().setName("Grunts McGee").setMail("grunt@important.co").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g2).setCommitter(g1) + .setBody(String.format("Signed-off-by: %s <%s>", g2.getName(), g2.getMail())) + .setHash("123456789abcdefghijklmnop").setSubject("All of the things") + .setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")).build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-not-tracked")).setCommits(commits).build(); + // test output w/ assertions + // Should be valid as project is not tracked + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGithub() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("projbot").setMail("1.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Should be valid as bots should only commit on their own projects (including aliases) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGithub_untracked() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("projbot").setMail("1.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-untracked")).setCommits(commits).build(); + // test output w/ assertions + // Should be valid as bots can commit on any untracked project (legacy support) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGithub_invalidBot() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("protobot-gh").setMail("2.bot-github@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Should be invalid as bots should only commit on their own projects (including aliases) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateBotCommiterAccessGithub_wrongEmail() throws URISyntaxException { + // set up test users - uses Gerrit/LDAP email (wrong for case) + GitUser g1 = GitUser.builder().setName("protobot").setMail("2.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); + // test output w/ assertions + // Should be invalid as wrong email was used for bot (uses Gerrit bot email) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateBotCommiterAccessGitlab() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("protobot-gh").setMail("2.bot-github@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITLAB) + .setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test")).setCommits(commits) + .build(); + // test output w/ assertions + // Should be valid as bots should only commit on their own projects (including aliases) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGitlab_untracked() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("protobot-gh").setMail("2.bot-github@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITLAB) + .setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.untracked")) + .setCommits(commits).build(); + // test output w/ assertions + // Should be valid as bots can commit on any untracked project (legacy support) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGitlab_invalidBot() throws URISyntaxException { + // set up test users (wrong bot for project) + GitUser g1 = GitUser.builder().setName("specbot").setMail("3.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITLAB) + .setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.handbook.test")).setCommits(commits) + .build(); + // test output w/ assertions + // Should be invalid as bots should only commit on their own projects + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateBotCommiterAccessGitlab_wrongEmail() throws URISyntaxException { + // set up test users - uses Gerrit/LDAP email (expects Gitlab email) + GitUser g1 = GitUser.builder().setName("specbot").setMail("3.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITLAB) + .setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.git")).setCommits(commits).build(); + // test output w/ assertions + // Should be valid as wrong email was used, but is still bot email alias (uses Gerrit bot email) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGerrit() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("protobot").setMail("2.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as bots should only commit on their own projects (including aliases) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGerrit_untracked() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("protobot").setMail("2.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/untracked.project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as bots can commit on any untracked project (legacy support) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateBotCommiterAccessGerrit_invalidBot() throws URISyntaxException { + // set up test users - (wrong bot for project) + GitUser g1 = GitUser.builder().setName("specbot").setMail("3.bot@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be invalid as bots should only commit on their own projects (wrong project) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + + } + + @Test + void validateBotCommiterAccessGerrit_aliasEmail() throws URISyntaxException { + // set up test users - uses GH (instead of expected Gerrit/LDAP email) + GitUser g1 = GitUser.builder().setName("protobot-gh").setMail("2.bot-github@eclipse.org").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as wrong email was used, but is still bot email alias + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateNullEmailCheck() throws URISyntaxException { + // set up test users - uses GH (instead of expected Gerrit/LDAP email) + GitUser g1 = GitUser.builder().setName("protobot-gh").setMail("2.bot-github@eclipse.org").build(); + GitUser g2 = GitUser.builder().setName("protobot-gh").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g2).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be invalid as there is no email (refuse commit, not server error) + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateGithubNoReply_legacy() throws URISyntaxException { + GitUser g1 = GitUser.builder().setName("grunter").setMail("grunter@users.noreply.github.com").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as grunter used a no-reply Github account and has a matching GH handle + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateGithubNoReply_success() throws URISyntaxException { + // sometimes the user ID and user name are reversed + GitUser g1 = GitUser.builder().setName("grunter").setMail("123456789+grunter@users.noreply.github.com").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as grunter used a no-reply Github account and has a matching GH handle + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateGithubNoReply_nomatch() throws URISyntaxException { + GitUser g1 = GitUser.builder().setName("some_guy").setMail("123456789+some_guy@users.noreply.github.com") + .build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be invalid as no user exists with "Github" handle that matches some_guy + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateGithubNoReply_nomatch_legacy() throws URISyntaxException { + GitUser g1 = GitUser.builder().setName("some_guy").setMail("some_guy@users.noreply.github.com").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be invalid as no user exists with "Github" handle that matches some_guy + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + } + + @Test + void validateAllowListAuthor_success() throws URISyntaxException { + GitUser g1 = GitUser.builder().setName("grunter").setMail("grunter@users.noreply.github.com").build(); + GitUser g2 = GitUser.builder().setName("grunter").setMail("noreply@github.com").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g2).setCommitter(g1).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as grunter used a no-reply Github account and has a matching GH handle + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } + + @Test + void validateAllowListCommitter_success() throws URISyntaxException { + GitUser g1 = GitUser.builder().setName("grunter").setMail("grunter@users.noreply.github.com").build(); + GitUser g2 = GitUser.builder().setName("grunter").setMail("noreply@github.com").build(); + + List<Commit> commits = new ArrayList<>(); + // create sample commits + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g2).setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Arrays.asList("46bb69bf6aa4ed26b2bf8c322ae05bef0bcc5c10")) + .build(); + commits.add(c1); + + ValidationRequest vr = ValidationRequest.builder().setProvider(ProviderType.GERRIT) + .setRepoUrl(new URI("/gitroot/sample/gerrit.other-project")).setCommits(commits).setStrictMode(true) + .build(); + // test output w/ assertions + // Should be valid as grunter used a no-reply Github account and has a matching GH handle + given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + } } diff --git a/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java deleted file mode 100644 index 9d86b7c4..00000000 --- a/src/test/java/org/eclipsefoundation/git/eca/service/impl/MockOAuthService.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.eclipsefoundation.git.eca.service.impl; - -import org.eclipsefoundation.git.eca.service.OAuthService; - -import io.quarkus.test.Mock; - -/** - * Disable the OAuth service while in testing via a mock service. This will - * never authenticate, but since all external data is mocked, this does not - * impact testing. - * - * @author Martin Lowe - * - */ -@Mock -public class MockOAuthService implements OAuthService { - - @Override - public String getToken() { - // return an empty (invalid) token every time - return ""; - } - -} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 94936fec..e1cee2f0 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -7,12 +7,12 @@ eclipse.noreply.email-patterns=@users.noreply.github.com\$ ## Expect to be mounted to '/git' to match current URL spec quarkus.http.root-path=/git -## OAUTH CONFIG -oauth2.scope=eclipsefdn_view_all_profiles +## OIDC Connection/Authentication Info +quarkus.oauth2.enabled=false +quarkus.oidc.enabled=false +quarkus.keycloak.devservices.enabled=false +quarkus.oidc-client.enabled=false quarkus.http.port=8080 -## required to start when secret.properties isn't found/mounted -oauth2.client-id=placeholder -oauth2.client-secret=placeholder - -eclipse.mail.allowlist=noreply@github.com \ No newline at end of file +eclipse.mail.allowlist=noreply@github.com +#quarkus.log.level=DEBUG \ No newline at end of file -- GitLab From ec8965ca967cf01efcdf48a1d4b7ae1250688c58 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 27 Jan 2022 13:33:13 -0500 Subject: [PATCH 03/11] Update tests to ensure JSON format, some cleanup of package names --- .gitignore | 5 +- spec/openapi.yaml | 155 ++++++++++++++++++ .../eca/resource/ValidationResourceTest.java | 122 ++++++++++---- .../eca/{ => test}/api/MockAccountsAPI.java | 3 +- .../git/eca/{ => test}/api/MockBotsAPI.java | 3 +- .../eca/{ => test}/api/MockProjectsAPI.java | 3 +- .../namespaces/SchemaNamespaceHelper.java | 10 ++ 7 files changed, 261 insertions(+), 40 deletions(-) create mode 100644 spec/openapi.yaml rename src/test/java/org/eclipsefoundation/git/eca/{ => test}/api/MockAccountsAPI.java (97%) rename src/test/java/org/eclipsefoundation/git/eca/{ => test}/api/MockBotsAPI.java (96%) rename src/test/java/org/eclipsefoundation/git/eca/{ => test}/api/MockProjectsAPI.java (97%) create mode 100644 src/test/java/org/eclipsefoundation/git/eca/test/namespaces/SchemaNamespaceHelper.java diff --git a/.gitignore b/.gitignore index 6604264c..03d0e35a 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ release.properties # Secret store secret/ secrets/ -secret.properties \ No newline at end of file +secret.properties + +# Additional build resources +src/test/resources/schemas \ No newline at end of file diff --git a/spec/openapi.yaml b/spec/openapi.yaml new file mode 100644 index 00000000..6fb0172c --- /dev/null +++ b/spec/openapi.yaml @@ -0,0 +1,155 @@ +openapi: '3.1.0' +info: + version: 1.1.0 + title: Eclipse Foundation Git ECA API + license: + name: Eclipse Public License - 2.0 + url: https://www.eclipse.org/legal/epl-2.0/ +servers: +- url: https://api.eclipse.org/git + description: Production endpoint for the Git ECA validation API +tags: +- name: ECA Validation + description: Definitions in relation to the validation of Git commits through ECA signage +paths: + /eca: + post: + tags: + - ECA Validation + summary: ECA validation + description: Validates a list of commits for a merge request. + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationRequest' + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResponse' + 500: + description: Error while retrieving data +components: + schemas: + NullableString: + description: A nullable String type value + type: + - 'null' + - string + DateTime: + type: string + format: datetime + description: | + Date string in the RFC 3339 format. Example, `1990-12-31T15:59:60-08:00`. + + More on this standard can be read at https://tools.ietf.org/html/rfc3339. + ValidationRequest: + type: object + properties: + provider: + type: string + description: The provider for which the commit is being validated for + enum: + - github + - gitlab + - gerrit + repoUrl: + type: string + description: the outward facing URL of the repo the commit belongs to. + strictMode: + type: boolean + description: asd + commits: + type: array + minimum: 1 + items: + type: object + properties: + hash: + type: string + description: The hash of the commit. Used for messaging and logging. + body: + $ref: '#/components/schemas/NullableString' + description: The body message of the commit if available + subject: + $ref: '#/components/schemas/NullableString' + description: The subject of the commit + author: + $ref: '#/components/schemas/GitUser' + description: The author of the Git commit + committer: + $ref: '#/components/schemas/GitUser' + description: The committer of the Git commit + head: + type: + - boolean + - 'null' + description: True if the current commit is the head commit, false otherwise + parents: + type: array + items: + type: string + description: Parent commit hashes, multiple will be present for merge commits + GitUser: + type: object + properties: + name: + type: string + description: The name of the git user + mail: + type: string + description: the email address of the user + ValidationResponse: + type: object + properties: + time: + $ref: '#/components/schemas/DateTime' + description: Time of the request validation for logging/tracking purposes. + trackedProject: + type: boolean + description: Whether the project is tracked in PMI and is an Eclipse project. + strictMode: + type: boolean + description: Whether strict mode was enforced for the validation. + errorCount: + type: integer + description: The number of errors encountered while validating the request + passed: + type: boolean + description: Whether the current request is valid in relation to ECA signage. + commits: + type: object + propertyNames: + description: The commit hash of the commit that was validated + additionalProperties: + $ref: '#/components/schemas/Commit' + Commit: + type: object + properties: + messages: + type: array + description: List of informational messages about the validation of the current commit. + items: + $ref: '#/components/schemas/CommitMessage' + warnings: + type: array + description: List of non-fatal issues encountered in the validation of the current commit + items: + $ref: '#/components/schemas/CommitMessage' + errors: + type: array + description: List of errors encountered in the validation of the current commit + items: + $ref: '#/components/schemas/CommitMessage' + CommitMessage: + type: object + properties: + code: + type: integer + description: the internal status code for the message + message: + type: string + description: Information about the commit message. This can either be information about the validation process to report or the source of an error to be corrected. diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index 657ec486..886d1f6e 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -9,6 +9,7 @@ package org.eclipsefoundation.git.eca.resource; import static io.restassured.RestAssured.given; +import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath; import static org.hamcrest.CoreMatchers.is; import java.net.URI; @@ -26,9 +27,14 @@ import org.eclipsefoundation.git.eca.model.GitUser; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; +import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; @@ -40,9 +46,12 @@ import io.restassured.http.ContentType; */ @QuarkusTest class ValidationResourceTest { + public static final String ECA_BASE_URL = "/eca"; @Inject CachingService cs; + @Inject + ObjectMapper json; @BeforeEach void cacheClear() { @@ -66,10 +75,51 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } + @Test + void validate_success_format() throws URISyntaxException { + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody("Signed-off-by: The Wizard <code.wiz@important.co>").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(Arrays.asList(c1)).build(); + given().when().body(vr).contentType(ContentType.JSON).post(ECA_BASE_URL).then().assertThat() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.VALIDATION_RESPONSE_SCHEMA_PATH)); + } + + @Test + void validate_success_inputFormat() throws URISyntaxException { + // Check that the input matches what is specified in spec + // set up test users + GitUser g1 = GitUser.builder().setName("The Wizard").setMail("code.wiz@important.co").build(); + Commit c1 = Commit.builder().setAuthor(g1).setCommitter(g1) + .setBody("Signed-off-by: The Wizard <code.wiz@important.co>").setHash("123456789abcdefghijklmnop") + .setSubject("All of the things").setParents(Collections.emptyList()).build(); + + ValidationRequest vr = ValidationRequest.builder().setStrictMode(false).setProvider(ProviderType.GITHUB) + .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(Arrays.asList(c1)).build(); + // convert the object to JSON + String in; + try { + in = json.writeValueAsString(vr); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + System.out.println(in); + Assertions.assertTrue( + matchesJsonSchemaInClasspath(SchemaNamespaceHelper.VALIDATION_REQUEST_SCHEMA_PATH).matches(in)); + // known good request + given().contentType(ContentType.JSON).body(vr).when().post(ECA_BASE_URL).then() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.VALIDATION_RESPONSE_SCHEMA_PATH)) + .statusCode(200); + } + @Test void validateMultipleCommits() throws URISyntaxException { // set up test users @@ -94,7 +144,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -116,7 +166,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions // No errors expected, should pass as only commit is a valid merge commit - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -136,7 +186,7 @@ class ValidationResourceTest { // test output w/ assertions // Should be valid as Grunt is a committer on the prototype project - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -156,7 +206,7 @@ class ValidationResourceTest { // test output w/ assertions // Should be valid as wizard has signed ECA - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -178,7 +228,7 @@ class ValidationResourceTest { // test output w/ assertions // Should be valid as signed off by footer is no longer checked - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -200,7 +250,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); // test output w/ assertions - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -222,7 +272,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); // test output w/ assertions - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -244,7 +294,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/prototype")).setCommits(commits).build(); // test output w/ assertions - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); } @@ -268,7 +318,7 @@ class ValidationResourceTest { // test output w/ assertions // Should be valid as Wizard has spec project write access + is committer - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200).body("passed", is(true), "errorCount", is(0)); // CASE 2: No WG Spec proj write access @@ -285,7 +335,7 @@ class ValidationResourceTest { // test output w/ assertions // Should be invalid as Grunt does not have spec project write access // Should have 2 errors, as both users get validated - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(2), "commits.123456789abcdefghijklmnop.errors[0].code", is(APIStatusCode.ERROR_SPEC_PROJECT.getValue())); } @@ -310,7 +360,7 @@ class ValidationResourceTest { // test output w/ assertions // Error should be singular + that there's no ECA on file // Status 403 (forbidden) is the standard return for invalid requests - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(1)); } @@ -334,7 +384,7 @@ class ValidationResourceTest { // test output w/ assertions // Error count should be 1 for just the committer access // Status 403 (forbidden) is the standard return for invalid requests - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(1)); } @@ -356,7 +406,7 @@ class ValidationResourceTest { // test output w/ assertions // Should have 2 errors, 1 for author entry and 1 for committer entry // Status 403 (forbidden) is the standard return for invalid requests - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(2)); } @@ -380,7 +430,7 @@ class ValidationResourceTest { // test output w/ assertions // Error should be singular + that there's no Eclipse Account on file for author // Status 403 (forbidden) is the standard return for invalid requests - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(1)); } @@ -404,7 +454,7 @@ class ValidationResourceTest { // test output w/ assertions // Error should be singular + that there's no Eclipse Account on file for committer // Status 403 (forbidden) is the standard return for invalid requests - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403).body("passed", + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403).body("passed", is(false), "errorCount", is(1)); } @@ -427,7 +477,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-not-tracked")).setCommits(commits).build(); // test output w/ assertions // Should be valid as project is not tracked - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -446,7 +496,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -465,7 +515,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample-untracked")).setCommits(commits).build(); // test output w/ assertions // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -484,7 +534,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions // Should be invalid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -503,7 +553,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("http://www.github.com/eclipsefdn/sample")).setCommits(commits).build(); // test output w/ assertions // Should be invalid as wrong email was used for bot (uses Gerrit bot email) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -523,7 +573,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -543,7 +593,7 @@ class ValidationResourceTest { .setCommits(commits).build(); // test output w/ assertions // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -563,7 +613,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be invalid as bots should only commit on their own projects - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -582,7 +632,7 @@ class ValidationResourceTest { .setRepoUrl(new URI("https://gitlab.eclipse.org/eclipse/dash/dash.git")).setCommits(commits).build(); // test output w/ assertions // Should be valid as wrong email was used, but is still bot email alias (uses Gerrit bot email) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -602,7 +652,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as bots should only commit on their own projects (including aliases) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -622,7 +672,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as bots can commit on any untracked project (legacy support) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -642,7 +692,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be invalid as bots should only commit on their own projects (wrong project) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @@ -663,7 +713,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as wrong email was used, but is still bot email alias - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -684,7 +734,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be invalid as there is no email (refuse commit, not server error) - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -703,7 +753,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -723,7 +773,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -743,7 +793,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be invalid as no user exists with "Github" handle that matches some_guy - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -762,7 +812,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be invalid as no user exists with "Github" handle that matches some_guy - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(403); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(403); } @Test @@ -782,7 +832,7 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } @Test @@ -802,6 +852,6 @@ class ValidationResourceTest { .build(); // test output w/ assertions // Should be valid as grunter used a no-reply Github account and has a matching GH handle - given().body(vr).contentType(ContentType.JSON).when().post("/eca").then().statusCode(200); + given().body(vr).contentType(ContentType.JSON).when().post(ECA_BASE_URL).then().statusCode(200); } } diff --git a/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java similarity index 97% rename from src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java rename to src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java index a96a9cd5..1a93328c 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/api/MockAccountsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 ******************************************************************************/ -package org.eclipsefoundation.git.eca.api; +package org.eclipsefoundation.git.eca.test.api; import java.util.ArrayList; import java.util.List; @@ -17,6 +17,7 @@ import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.git.eca.api.AccountsAPI; import org.eclipsefoundation.git.eca.model.EclipseUser; import org.eclipsefoundation.git.eca.model.EclipseUser.ECA; diff --git a/src/test/java/org/eclipsefoundation/git/eca/api/MockBotsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockBotsAPI.java similarity index 96% rename from src/test/java/org/eclipsefoundation/git/eca/api/MockBotsAPI.java rename to src/test/java/org/eclipsefoundation/git/eca/test/api/MockBotsAPI.java index d356ffc2..52bc7a66 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/api/MockBotsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockBotsAPI.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 ******************************************************************************/ -package org.eclipsefoundation.git.eca.api; +package org.eclipsefoundation.git.eca.test.api; import java.util.ArrayList; import java.util.List; @@ -16,6 +16,7 @@ import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.git.eca.api.BotsAPI; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; diff --git a/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java similarity index 97% rename from src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java rename to src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java index e4c103b4..14737a9c 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/api/MockProjectsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockProjectsAPI.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 ******************************************************************************/ -package org.eclipsefoundation.git.eca.api; +package org.eclipsefoundation.git.eca.test.api; import java.util.ArrayList; import java.util.Arrays; @@ -20,6 +20,7 @@ import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.git.eca.api.ProjectsAPI; import org.eclipsefoundation.git.eca.model.Project; import org.eclipsefoundation.git.eca.model.Project.Repo; import org.eclipsefoundation.git.eca.model.Project.User; diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/namespaces/SchemaNamespaceHelper.java b/src/test/java/org/eclipsefoundation/git/eca/test/namespaces/SchemaNamespaceHelper.java new file mode 100644 index 00000000..bd99eadb --- /dev/null +++ b/src/test/java/org/eclipsefoundation/git/eca/test/namespaces/SchemaNamespaceHelper.java @@ -0,0 +1,10 @@ +package org.eclipsefoundation.git.eca.test.namespaces; + +public final class SchemaNamespaceHelper { + public static final String BASE_SCHEMAS_PATH = "schemas/"; + public static final String BASE_SCHEMAS_PATH_SUFFIX = "-schema.json"; + public static final String VALIDATION_REQUEST_SCHEMA_PATH = BASE_SCHEMAS_PATH + "validation-request" + + BASE_SCHEMAS_PATH_SUFFIX; + public static final String VALIDATION_RESPONSE_SCHEMA_PATH = BASE_SCHEMAS_PATH + "validation-response" + + BASE_SCHEMAS_PATH_SUFFIX; +} -- GitLab From 9fffe3e5faf4a383aca37d059d5a12900e10d752 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 21 Apr 2022 10:38:43 -0400 Subject: [PATCH 04/11] Fix Java version to be java 11 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c1cc7de9..6f964e01 100644 --- a/pom.xml +++ b/pom.xml @@ -8,8 +8,8 @@ <eclipse-api-version>0.6-SNAPSHOT</eclipse-api-version> <compiler-plugin.version>3.8.1</compiler-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> - <maven.compiler.source>1.8</maven.compiler.source> - <maven.compiler.target>1.8</maven.compiler.target> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id> -- GitLab From 71bedbc9916268752706cbad1322509e3120ab0d Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 21 Apr 2022 11:29:29 -0400 Subject: [PATCH 05/11] Update build to remove npm requirement and replace with yarn --- Jenkinsfile | 90 +- Makefile | 19 + package-lock.json | 2087 --------------------------------------------- pom.xml | 50 -- yarn.lock | 750 ++++++++++++++++ 5 files changed, 847 insertions(+), 2149 deletions(-) create mode 100644 Makefile delete mode 100644 package-lock.json create mode 100644 yarn.lock diff --git a/Jenkinsfile b/Jenkinsfile index 14fb8629..84f86b8f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,77 @@ @Library('common-shared') _ pipeline { - agent any + agent { + kubernetes { + label 'buildenv-agent' + yaml ''' + apiVersion: v1 + kind: Pod + spec: + containers: + - name: buildcontainer + image: eclipsefdn/stack-build-agent:latest + imagePullPolicy: Always + command: + - cat + tty: true + resources: + requests: + cpu: "1" + memory: "4Gi" + limits: + cpu: "2" + memory: "4Gi" + env: + - name: "HOME" + value: "/home/jenkins" + - name: "MAVEN_OPTS" + value: "-Duser.home=/home/jenkins" + volumeMounts: + - name: m2-repo + mountPath: /home/jenkins/.m2/repository + - name: m2-secret-dir + mountPath: /home/jenkins/.m2/settings.xml + subPath: settings.xml + readOnly: true + - mountPath: "/home/jenkins/.m2/settings-security.xml" + name: "m2-secret-dir" + readOnly: true + subPath: "settings-security.xml" + - mountPath: "/home/jenkins/.mavenrc" + name: "m2-dir" + readOnly: true + subPath: ".mavenrc" + - mountPath: "/home/jenkins/.m2/wrapper" + name: "m2-wrapper" + readOnly: false + - mountPath: "/home/jenkins/.cache" + name: "yarn-cache" + readOnly: false + - name: jnlp + resources: + requests: + memory: "1024Mi" + cpu: "500m" + limits: + memory: "1024Mi" + cpu: "1000m" + volumes: + - name: "m2-dir" + configMap: + name: "m2-dir" + - name: m2-secret-dir + secret: + secretName: m2-secret-dir + - name: m2-repo + emptyDir: {} + - name: m2-wrapper + emptyDir: {} + - name: yarn-cache + emptyDir: {} + ''' + } + } environment { APP_NAME = 'git-eca-rest-api' @@ -10,11 +80,7 @@ CONTAINER_NAME = 'app' ENVIRONMENT = sh( script: """ - if [ "${env.BRANCH_NAME}" = "master" ]; then - printf "production" - else - printf "${env.BRANCH_NAME}" - fi + printf "${env.BRANCH_NAME}" """, returnStdout: true ) @@ -43,12 +109,12 @@ stages { stage('Build Java code') { steps { - readTrusted 'mvnw' - readTrusted '.mvn/wrapper/MavenWrapperDownloader.java' - readTrusted 'pom.xml' - - sh './mvnw -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn --batch-mode package' - stash includes: 'target/', name: 'target' + container('buildcontainer') { + readTrusted 'Makefile' + readTrusted 'pom.xml' + sh 'make compile' + stash name: "target", includes: "target/**/*" + } } } diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..9dd78172 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +clean:; + mvn clean +compile-java: generate-spec; + mvn compile package +compile-java-quick: generate-spec; + mvn compile package -Dmaven.test.skip=true +compile: clean compile-java; +compile-quick: clean compile-java-quick; +install-yarn:; + yarn install --frozen-lockfile --audit +generate-spec: install-yarn validate-spec; + yarn run generate-json-schema +validate-spec: install-yarn; +compile-start: compile-quick; + docker-compose down + docker-compose build + docker-compose up +start-spec: validate-spec; + yarn run start \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e116e830..00000000 --- a/package-lock.json +++ /dev/null @@ -1,2087 +0,0 @@ -{ - "name": "eclipsefdn-git-eca-rest-api-support", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "eclipsefdn-git-eca-rest-api-support", - "version": "1.0.0", - "devDependencies": { - "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.1", - "@redocly/openapi-cli": "^1.0.0-beta.54", - "@stoplight/json-ref-resolver": "^3.1.2", - "decamelize": "^5.0.0", - "js-yaml": "^4.1.0", - "yargs": "^17.0.1" - } - }, - "node_modules/@openapi-contrib/openapi-schema-to-json-schema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.1.1.tgz", - "integrity": "sha512-FMvdhv9Jr9tULjJAQaQzhCmNYYj2vQFVnl7CGlLAImZvJal71oedXMGszpPaZTLftAk5TCHqjnirig+P6LZxug==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - } - }, - "node_modules/@redocly/ajv": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", - "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/openapi-cli": { - "version": "1.0.0-beta.80", - "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.80.tgz", - "integrity": "sha512-PZLustQdB0ZsKjM5vgGxEg6eFf8okaky/LmXiicnNeJhXmlv7CmvytSvm6zixCwDts4DpuFUNO1CcDf7+iJyDA==", - "dev": true, - "dependencies": { - "@redocly/openapi-core": "1.0.0-beta.80", - "@types/node": "^14.11.8", - "assert-node-version": "^1.0.3", - "chokidar": "^3.5.1", - "colorette": "^1.2.0", - "glob": "^7.1.6", - "glob-promise": "^3.4.0", - "handlebars": "^4.7.6", - "portfinder": "^1.0.26", - "simple-websocket": "^9.0.0", - "yargs": "17.0.1" - }, - "bin": { - "openapi": "bin/cli.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@redocly/openapi-cli/node_modules/yargs": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", - "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@redocly/openapi-cli/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@redocly/openapi-core": { - "version": "1.0.0-beta.80", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.80.tgz", - "integrity": "sha512-IAQECLt/fDxjlfNdLGnJszt40BaiA6b78+zB6+7Rk8ums0HHLfwWFJPMTzh1bzJ5f+sZ4zDBi4gaIJ1n4XGCHg==", - "dev": true, - "dependencies": { - "@redocly/ajv": "^8.6.4", - "@types/node": "^14.11.8", - "colorette": "^1.2.0", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "lodash.isequal": "^4.5.0", - "minimatch": "^3.0.4", - "node-fetch": "^2.6.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@stoplight/json": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.2.tgz", - "integrity": "sha512-NwIVzanXRUy291J5BMkncCZRMG1Lx+aq+VidGQgfkJjgo8vh1Y/PSAz7fSU8gVGSZBCcqmOkMI7R4zw7DlfTwA==", - "dev": true, - "dependencies": { - "@stoplight/ordered-object-literal": "^1.0.2", - "@stoplight/types": "^12.3.0", - "jsonc-parser": "~2.2.1", - "lodash": "^4.17.21", - "safe-stable-stringify": "^1.1" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/@stoplight/json-ref-resolver": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz", - "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==", - "dev": true, - "dependencies": { - "@stoplight/json": "^3.17.0", - "@stoplight/path": "^1.3.2", - "@stoplight/types": "^12.3.0", - "@types/urijs": "^1.19.16", - "dependency-graph": "~0.11.0", - "fast-memoize": "^2.5.2", - "immer": "^9.0.6", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "tslib": "^2.3.1", - "urijs": "^1.19.6" - }, - "engines": { - "node": ">=8.3.0" - } - }, - "node_modules/@stoplight/ordered-object-literal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz", - "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@stoplight/path": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", - "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@stoplight/types": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.5.0.tgz", - "integrity": "sha512-dwqYcDrGmEyUv5TWrDam5TGOxU72ufyQ7hnOIIDdmW5ezOwZaBFoR5XQ9AsH49w7wgvOqB2Bmo799pJPWnpCbg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.4", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "14.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", - "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", - "dev": true - }, - "node_modules/@types/urijs": { - "version": "1.19.18", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.18.tgz", - "integrity": "sha512-tjftsOLuIWFLJxcpgFeehNnMhpMIv0ELJl0/i31jiV3au1GQpnd3/pTTDQg2zO5cSGJxtrDzMgebOH7+cqh3Vg==", - "dev": true - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/assert-node-version": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz", - "integrity": "sha1-yupdG2pY285ZZhII3x4bnkxYD5E=", - "dev": true, - "dependencies": { - "expected-node-version": "^1.0.0", - "semver": "^5.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/expected-node-version": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz", - "integrity": "sha1-uNIlub9nap6H4G29YVtS/J0eOGs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-memoize": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", - "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", - "dev": true - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-promise": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", - "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", - "dev": true, - "dependencies": { - "@types/glob": "*" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "glob": "*" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/immer": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", - "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/jsonc-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", - "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", - "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", - "dev": true - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - } - }, - "node_modules/simple-websocket/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/simple-websocket/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, - "node_modules/uglify-js": { - "version": "3.14.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", - "integrity": "sha512-qZukoSxOG0urUTvjc2ERMTcAy+BiFh3weWAkeurLwjrCba73poHmG3E36XEjd/JGukMzwTL7uCxZiAexj8ppvQ==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yaml-ast-parser": { - "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", - "dev": true, - "engines": { - "node": ">=12" - } - } - }, - "dependencies": { - "@openapi-contrib/openapi-schema-to-json-schema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.1.1.tgz", - "integrity": "sha512-FMvdhv9Jr9tULjJAQaQzhCmNYYj2vQFVnl7CGlLAImZvJal71oedXMGszpPaZTLftAk5TCHqjnirig+P6LZxug==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "@redocly/ajv": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", - "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "@redocly/openapi-cli": { - "version": "1.0.0-beta.80", - "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.80.tgz", - "integrity": "sha512-PZLustQdB0ZsKjM5vgGxEg6eFf8okaky/LmXiicnNeJhXmlv7CmvytSvm6zixCwDts4DpuFUNO1CcDf7+iJyDA==", - "dev": true, - "requires": { - "@redocly/openapi-core": "1.0.0-beta.80", - "@types/node": "^14.11.8", - "assert-node-version": "^1.0.3", - "chokidar": "^3.5.1", - "colorette": "^1.2.0", - "glob": "^7.1.6", - "glob-promise": "^3.4.0", - "handlebars": "^4.7.6", - "portfinder": "^1.0.26", - "simple-websocket": "^9.0.0", - "yargs": "17.0.1" - }, - "dependencies": { - "yargs": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", - "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, - "@redocly/openapi-core": { - "version": "1.0.0-beta.80", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.80.tgz", - "integrity": "sha512-IAQECLt/fDxjlfNdLGnJszt40BaiA6b78+zB6+7Rk8ums0HHLfwWFJPMTzh1bzJ5f+sZ4zDBi4gaIJ1n4XGCHg==", - "dev": true, - "requires": { - "@redocly/ajv": "^8.6.4", - "@types/node": "^14.11.8", - "colorette": "^1.2.0", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "lodash.isequal": "^4.5.0", - "minimatch": "^3.0.4", - "node-fetch": "^2.6.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - } - }, - "@stoplight/json": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.17.2.tgz", - "integrity": "sha512-NwIVzanXRUy291J5BMkncCZRMG1Lx+aq+VidGQgfkJjgo8vh1Y/PSAz7fSU8gVGSZBCcqmOkMI7R4zw7DlfTwA==", - "dev": true, - "requires": { - "@stoplight/ordered-object-literal": "^1.0.2", - "@stoplight/types": "^12.3.0", - "jsonc-parser": "~2.2.1", - "lodash": "^4.17.21", - "safe-stable-stringify": "^1.1" - } - }, - "@stoplight/json-ref-resolver": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz", - "integrity": "sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A==", - "dev": true, - "requires": { - "@stoplight/json": "^3.17.0", - "@stoplight/path": "^1.3.2", - "@stoplight/types": "^12.3.0", - "@types/urijs": "^1.19.16", - "dependency-graph": "~0.11.0", - "fast-memoize": "^2.5.2", - "immer": "^9.0.6", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "tslib": "^2.3.1", - "urijs": "^1.19.6" - } - }, - "@stoplight/ordered-object-literal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz", - "integrity": "sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w==", - "dev": true - }, - "@stoplight/path": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", - "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", - "dev": true - }, - "@stoplight/types": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-12.5.0.tgz", - "integrity": "sha512-dwqYcDrGmEyUv5TWrDam5TGOxU72ufyQ7hnOIIDdmW5ezOwZaBFoR5XQ9AsH49w7wgvOqB2Bmo799pJPWnpCbg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "utility-types": "^3.10.0" - } - }, - "@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", - "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", - "dev": true - }, - "@types/node": { - "version": "14.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.9.tgz", - "integrity": "sha512-j11XSuRuAlft6vLDEX4RvhqC0KxNxx6QIyMXNb0vHHSNPXTPeiy3algESWmOOIzEtiEL0qiowPU3ewW9hHVa7Q==", - "dev": true - }, - "@types/urijs": { - "version": "1.19.18", - "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.18.tgz", - "integrity": "sha512-tjftsOLuIWFLJxcpgFeehNnMhpMIv0ELJl0/i31jiV3au1GQpnd3/pTTDQg2zO5cSGJxtrDzMgebOH7+cqh3Vg==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "assert-node-version": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz", - "integrity": "sha1-yupdG2pY285ZZhII3x4bnkxYD5E=", - "dev": true, - "requires": { - "expected-node-version": "^1.0.0", - "semver": "^5.0.3" - } - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", - "dev": true - }, - "dependency-graph": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "expected-node-version": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz", - "integrity": "sha1-uNIlub9nap6H4G29YVtS/J0eOGs=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-memoize": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", - "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "glob-promise": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", - "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", - "dev": true, - "requires": { - "@types/glob": "*" - } - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "immer": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", - "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "jsonc-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", - "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", - "dev": true - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true - }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safe-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", - "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "simple-websocket": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", - "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", - "dev": true, - "requires": { - "debug": "^4.3.1", - "queue-microtask": "^1.2.2", - "randombytes": "^2.1.0", - "readable-stream": "^3.6.0", - "ws": "^7.4.2" - }, - "dependencies": { - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - }, - "uglify-js": { - "version": "3.14.5", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", - "integrity": "sha512-qZukoSxOG0urUTvjc2ERMTcAy+BiFh3weWAkeurLwjrCba73poHmG3E36XEjd/JGukMzwTL7uCxZiAexj8ppvQ==", - "dev": true, - "optional": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urijs": { - "version": "1.19.7", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz", - "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yaml-ast-parser": { - "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", - "dev": true - }, - "yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", - "dev": true - } - } -} diff --git a/pom.xml b/pom.xml index 6f964e01..3a9eed7b 100644 --- a/pom.xml +++ b/pom.xml @@ -152,56 +152,6 @@ </annotationProcessorPaths> </configuration> </plugin> - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>exec-maven-plugin</artifactId> - <version>1.3.2</version> - <executions> - <execution> - <id>npm install (initialize)</id> - <goals> - <goal>exec</goal> - </goals> - <phase>initialize</phase> - <configuration> - <executable>npm</executable> - <arguments> - <argument>install</argument> - <argument>-f</argument> - </arguments> - </configuration> - </execution> - <execution> - <id>npm clean</id> - <goals> - <goal>exec</goal> - </goals> - <phase>clean</phase> - <configuration> - <executable>npm</executable> - <arguments> - <argument>run</argument> - <argument>clean</argument> - </arguments> - </configuration> - </execution> - <execution> - <id>npm run pre-test</id> - <goals> - <goal>exec</goal> - </goals> - <phase>generate-test-resources</phase> - <configuration> - <executable>npm</executable> - <skip>${maven.test.skip}</skip> - <arguments> - <argument>run</argument> - <argument>generate-json-schema</argument> - </arguments> - </configuration> - </execution> - </executions> - </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire-plugin.version}</version> diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..2076f7df --- /dev/null +++ b/yarn.lock @@ -0,0 +1,750 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@openapi-contrib/openapi-schema-to-json-schema@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-3.1.1.tgz#e43b09680e652bf1b9e135db3f8648e979b76c07" + integrity sha512-FMvdhv9Jr9tULjJAQaQzhCmNYYj2vQFVnl7CGlLAImZvJal71oedXMGszpPaZTLftAk5TCHqjnirig+P6LZxug== + dependencies: + fast-deep-equal "^3.1.3" + +"@redocly/ajv@^8.6.4": + version "8.6.4" + resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" + integrity sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +"@redocly/openapi-cli@^1.0.0-beta.54": + version "1.0.0-beta.94" + resolved "https://registry.yarnpkg.com/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.94.tgz#8bf84589864da941445bb21bd230e2c23c03c781" + integrity sha512-VHPVIP4K+KgYLDbQXIONS6GRMLsYz7tKa3QVVk83KS7X58fFC/N48hB1Ap2vnryj8HLSrG0yP9y6ZGH1t7kv7g== + dependencies: + "@redocly/openapi-core" "1.0.0-beta.94" + "@types/node" "^14.11.8" + assert-node-version "^1.0.3" + chokidar "^3.5.1" + colorette "^1.2.0" + glob "^7.1.6" + glob-promise "^3.4.0" + handlebars "^4.7.6" + portfinder "^1.0.26" + simple-websocket "^9.0.0" + yargs "17.0.1" + +"@redocly/openapi-core@1.0.0-beta.94": + version "1.0.0-beta.94" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.94.tgz#7fc3a34aea8b0ee12ae2de26bedf90a4cf717366" + integrity sha512-xTklcobv+51bQVkUOpUiNY0GztL+0u3yGsy2BtldaHpcnNGMu3lu/utsoOHkiNTpgVEGyEWVZzBtF6Sz5v/Fkg== + dependencies: + "@redocly/ajv" "^8.6.4" + "@types/node" "^14.11.8" + colorette "^1.2.0" + js-levenshtein "^1.1.6" + js-yaml "^4.1.0" + lodash.isequal "^4.5.0" + minimatch "^3.0.4" + node-fetch "^2.6.1" + pluralize "^8.0.0" + yaml-ast-parser "0.0.43" + +"@stoplight/json-ref-resolver@^3.1.2": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@stoplight/json-ref-resolver/-/json-ref-resolver-3.1.3.tgz#243bc8f6b8f7a5f8b141e2296fd11fb634bfc3ff" + integrity sha512-SgoKXwVnlpIZUyAFX4W79eeuTWvXmNlMfICZixL16GZXnkjcW+uZnfmAU0ZIjcnaTgaI4mjfxn8LAP2KR6Cr0A== + dependencies: + "@stoplight/json" "^3.17.0" + "@stoplight/path" "^1.3.2" + "@stoplight/types" "^12.3.0" + "@types/urijs" "^1.19.16" + dependency-graph "~0.11.0" + fast-memoize "^2.5.2" + immer "^9.0.6" + lodash.get "^4.4.2" + lodash.set "^4.3.2" + tslib "^2.3.1" + urijs "^1.19.6" + +"@stoplight/json@^3.17.0": + version "3.18.1" + resolved "https://registry.yarnpkg.com/@stoplight/json/-/json-3.18.1.tgz#725b34b24e8b0c0f73113362be54c7a4f294cdba" + integrity sha512-QmELAqBS8DC+8YuG7+OvDVP6RaUVi8bzN0KKW2UEcZg+0a1sqeeZgfW079AmJIZg8HEN7udAt4iozIB8Dm0t1Q== + dependencies: + "@stoplight/ordered-object-literal" "^1.0.2" + "@stoplight/types" "^13.0.0" + jsonc-parser "~2.2.1" + lodash "^4.17.21" + safe-stable-stringify "^1.1" + +"@stoplight/ordered-object-literal@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.2.tgz#2a88a5ebc8b68b54837ac9a9ae7b779cdd862062" + integrity sha512-0ZMS/9sNU3kVo/6RF3eAv7MK9DY8WLjiVJB/tVyfF2lhr2R4kqh534jZ0PlrFB9CRXrdndzn1DbX6ihKZXft2w== + +"@stoplight/path@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@stoplight/path/-/path-1.3.2.tgz#96e591496b72fde0f0cdae01a61d64f065bd9ede" + integrity sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ== + +"@stoplight/types@^12.3.0": + version "12.5.0" + resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-12.5.0.tgz#ebbeeb8c874de30e4cd9a1a2a6c8d6062c155da0" + integrity sha512-dwqYcDrGmEyUv5TWrDam5TGOxU72ufyQ7hnOIIDdmW5ezOwZaBFoR5XQ9AsH49w7wgvOqB2Bmo799pJPWnpCbg== + dependencies: + "@types/json-schema" "^7.0.4" + utility-types "^3.10.0" + +"@stoplight/types@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@stoplight/types/-/types-13.0.0.tgz#69a22d57d3a67f623a01929b10e25401c9635154" + integrity sha512-9OTVMiSUz2NlEW14OL6NKOuMTj3dtVVsugRwe3qbq0QnUpx/VLxOuO83n47rXZUTHvk69arOlFrDmRyZMw2DUg== + dependencies: + "@types/json-schema" "^7.0.4" + utility-types "^3.10.0" + +"@types/glob@*": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/json-schema@^7.0.4": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/node@*": + version "17.0.25" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448" + integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w== + +"@types/node@^14.11.8": + version "14.18.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.13.tgz#6ad4d9db59e6b3faf98dcfe4ca9d2aec84443277" + integrity sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA== + +"@types/urijs@^1.19.16": + version "1.19.19" + resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.19.tgz#2789369799907fc11e2bc6e3a00f6478c2281b95" + integrity sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.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" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +assert-node-version@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/assert-node-version/-/assert-node-version-1.0.3.tgz#caea5d1b6a58dbce59661208df1e1b9e4c580f91" + integrity sha1-yupdG2pY285ZZhII3x4bnkxYD5E= + dependencies: + expected-node-version "^1.0.0" + semver "^5.0.3" + +async@^2.6.2: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +chokidar@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +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.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== + +colorette@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decamelize@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" + integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== + +dependency-graph@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.11.0.tgz#ac0ce7ed68a54da22165a85e97a01d53f5eb2e27" + integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +expected-node-version@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/expected-node-version/-/expected-node-version-1.0.2.tgz#b8d225b9bf676a9e87e06dbd615b52fc9d1e386b" + integrity sha1-uNIlub9nap6H4G29YVtS/J0eOGs= + +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-memoize@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-promise@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" + integrity sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== + dependencies: + "@types/glob" "*" + +glob@^7.1.6: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +handlebars@^4.7.6: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +immer@^9.0.6: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +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== + +jsonc-parser@~2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.set@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= + +lodash@^4.17.14, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@^0.5.5: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-fetch@^2.6.1: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.5" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +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== + +safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" + integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== + +semver@^5.0.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +simple-websocket@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-9.1.0.tgz#91cbb39eafefbe7e66979da6c639109352786a7f" + integrity sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ== + dependencies: + debug "^4.3.1" + queue-microtask "^1.2.2" + randombytes "^2.1.0" + readable-stream "^3.6.0" + ws "^7.4.2" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +tslib@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +uglify-js@^3.1.4: + version "3.15.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.4.tgz#fa95c257e88f85614915b906204b9623d4fa340d" + integrity sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA== + +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" + +urijs@^1.19.6: + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utility-types@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.4.2: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaml-ast-parser@0.0.43: + version "0.0.43" + resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" + integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + +yargs@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" + integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.0.1: + version "17.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284" + integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" -- GitLab From 7365c231710ca267cadfc6a2c801d8fa5b93de61 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 21 Apr 2022 11:43:06 -0400 Subject: [PATCH 06/11] Fix dockerfile to use modern settings --- src/main/docker/Dockerfile.jvm | 37 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm index 0457e0bf..5492e55c 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/src/main/docker/Dockerfile.jvm @@ -14,21 +14,34 @@ # docker run -i --rm -p 8080:8080 eclipsefdn/git-eca-rest-api-jvm # ### -FROM fabric8/java-alpine-openjdk8-jre:1.6.5 -ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" -ENV AB_ENABLED=jmx_exporter +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 -# Be prepared for running in OpenShift too -RUN adduser -G root --no-create-home --disabled-password 1001 \ - && chown -R 1001 /deployments \ - && chmod -R "g+rwX" /deployments \ - && chown -R 1001:root /deployments +ARG JAVA_PACKAGE=java-11-openjdk-headless +ARG RUN_JAVA_VERSION=1.3.8 +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' +# Install java and the run-java script +# Also set up permissions for user `1001` +RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ + && microdnf update \ + && microdnf clean all \ + && mkdir /deployments \ + && chown 1001 /deployments \ + && chmod "g+rwX" /deployments \ + && chown 1001:root /deployments \ + && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ + && chown 1001 /deployments/run-java.sh \ + && chmod 540 /deployments/run-java.sh \ + && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security -COPY target/lib/* /deployments/lib/ -COPY target/*-runner.jar /deployments/app.jar -EXPOSE 8080 +# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=1001 target/quarkus-app/*.jar /deployments/ +COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ +COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ -# run with user 1001 +EXPOSE 8090 USER 1001 ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file -- GitLab From af1c870b69ea1925cd266868bc20d209caded19b Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Thu, 21 Apr 2022 13:42:58 -0400 Subject: [PATCH 07/11] Fix dockerignore to allow for updated packaging output --- .dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index b86c7ac3..94810d00 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ * !target/*-runner !target/*-runner.jar -!target/lib/* \ No newline at end of file +!target/lib/* +!target/quarkus-app/* \ No newline at end of file -- GitLab From 79ba36dd64e2d2c16e05f0f5c1c6cf80f5c69fbc Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 22 Apr 2022 13:40:31 -0400 Subject: [PATCH 08/11] Add user caching service, add previous oauth back in for performance Built-in oauth wasn't properly caching auth results, so performance tanked from having to fetch a fresh auth token on every request. --- docker-compose.yaml | 65 ++++++-- pom.xml | 10 +- .../git/eca/api/AccountsAPI.java | 57 +++---- .../git/eca/oauth/EclipseApi.java | 47 ++++++ .../git/eca/resource/ValidationResource.java | 98 +----------- .../git/eca/service/OAuthService.java | 28 ++++ .../git/eca/service/UserService.java | 10 ++ .../eca/service/impl/CachedUserService.java | 141 ++++++++++++++++++ .../eca/service/impl/DefaultOAuthService.java | 90 +++++++++++ src/main/k8s/staging.yml | 2 +- src/main/resources/application.properties | 21 +-- 11 files changed, 408 insertions(+), 161 deletions(-) create mode 100644 src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/UserService.java create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java create mode 100644 src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java diff --git a/docker-compose.yaml b/docker-compose.yaml index 5ff2a86e..81dd6f83 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,16 +1,49 @@ -web: - image: 'gitlab/gitlab-ce:latest' - restart: always - environment: - VIRTUAL_HOST: "gitlab.dev.docker" - VIRTUAL_PORT: 443 - VIRTUAL_PROTO: https - CERT_NAME: dev.docker - ports: - - 443 - - 80 - - 22 - volumes: - - '/localdocker/gitlab/config:/etc/gitlab' - - '/localdocker/gitlab/logs:/var/log/gitlab' - - '/localdocker/gitlab/data:/var/opt/gitlab' +version: '3' +services: + web: + container_name: gitlab + image: 'gitlab/gitlab-ce:latest' + restart: always + environment: + VIRTUAL_HOST: "gitlab.dev.docker" + VIRTUAL_PORT: 443 + VIRTUAL_PROTO: https + CERT_NAME: dev.docker + ports: + - 443:443 + - 80:80 + - 22:22 + volumes: + - '/localdocker/gitlab/config:/etc/gitlab' + - '/localdocker/gitlab/logs:/var/log/gitlab' + - '/localdocker/gitlab/data:/var/opt/gitlab' + postgres: + container_name: postgres + image: postgres:12.4 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + environment: + - POSTGRES_DB=${GIT_ECA_POSTGRES_DB} + - POSTGRES_USER=${GIT_ECA_POSTGRES_USER} + - POSTGRES_PASSWORD=${GIT_ECA_POSTGRES_PASSWORD} + ports: + - 5432 + keycloak: + container_name: keycloak + image: jboss/keycloak:11.0.1 + environment: + - VIRTUAL_HOST=keycloak + - VIRTUAL_PORT=8080 + - DB_VENDOR=POSTGRES + - DB_DATABASE=${GIT_ECA_POSTGRES_DB} + - DB_SCHEMA=public + - DB_ADDR=postgres + - DB_PORT=5432 + - DB_USER=${GIT_ECA_POSTGRES_USER} + - DB_PASSWORD=${GIT_ECA_POSTGRES_PASSWORD} + - KEYCLOAK_USER=${GIT_ECA_KEYCLOAK_USER} + - KEYCLOAK_PASSWORD=${GIT_ECA_KEYCLOAK_PASSWORD} + ports: + - '8080:8080' + depends_on: + - postgres diff --git a/pom.xml b/pom.xml index 3a9eed7b..20561aad 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <artifactId>git-eca</artifactId> <version>0.0.1</version> <properties> - <eclipse-api-version>0.6-SNAPSHOT</eclipse-api-version> + <eclipse-api-version>0.6.3-SNAPSHOT</eclipse-api-version> <compiler-plugin.version>3.8.1</compiler-plugin.version> <maven.compiler.parameters>true</maven.compiler.parameters> <maven.compiler.source>11</maven.compiler.source> @@ -59,14 +59,6 @@ <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-context-propagation</artifactId> </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-oidc</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-oidc-client-filter</artifactId> - </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-client</artifactId> diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java index 1aadfe55..5096632e 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java @@ -12,6 +12,7 @@ package org.eclipsefoundation.git.eca.api; import java.util.List; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @@ -20,47 +21,37 @@ import javax.ws.rs.QueryParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipsefoundation.git.eca.model.EclipseUser; -import io.quarkus.oidc.client.filter.OidcClientFilter; -import io.quarkus.security.Authenticated; - /** - * Binding interface for the Eclipse Foundation user account API. Runtime - * implementations are automatically generated by Quarkus at compile time. As - * the API deals with sensitive information, authentication is required to - * access this endpoint. + * Binding interface for the Eclipse Foundation user account API. Runtime implementations are automatically generated by + * Quarkus at compile time. As the API deals with sensitive information, authentication is required to access this + * endpoint. * * @author Martin Lowe * */ -@OidcClientFilter -@Authenticated @RegisterRestClient @Produces("application/json") public interface AccountsAPI { - /** - * Retrieves all user objects that match the given query parameters. - * - * @param id user ID of the Eclipse account to retrieve - * @param name the given name to match against for Eclipse accounts - * @param mail the email address to match against for Eclipse accounts - * @return all matching eclipse accounts - */ - @GET - @Path("/account/profile") - List<EclipseUser> getUsers(@QueryParam("uid") String id, - @QueryParam("name") String name, @QueryParam("mail") String mail); - - /** - * Retrieves user objects that matches the given Github username. - * - * @param authBearer authorization header value for validating call - * @param uname username of the Github account to retrieve Eclipse Account - * for - * @return the matching Eclipse account or null - */ - @GET - @Path("/github/profile/{uname}") - EclipseUser getUserByGithubUname(@PathParam("uname") String uname); + /** + * Retrieves all user objects that use the given mail address. + * + * @param mail the email address to match against for Eclipse accounts + * @return all matching eclipse accounts + */ + @GET + @Path("/account/profile") + List<EclipseUser> getUsers(@HeaderParam("Authorization") String token, @QueryParam("mail") String mail); + + /** + * Retrieves user objects that matches the given Github username. + * + * @param authBearer authorization header value for validating call + * @param uname username of the Github account to retrieve Eclipse Account for + * @return the matching Eclipse account or null + */ + @GET + @Path("/github/profile/{uname}") + EclipseUser getUserByGithubUname(@HeaderParam("Authorization") String token, @PathParam("uname") String uname); } diff --git a/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java b/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java new file mode 100644 index 00000000..226caf4e --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/oauth/EclipseApi.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (C) 2020 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************/ +package org.eclipsefoundation.git.eca.oauth; + +import com.github.scribejava.core.builder.api.DefaultApi20; +import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication; +import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme; + +/** + * Wrapper around the OAuth API for Scribejava. Enables OAuth2.0 binding to the + * Eclipse Foundation OAuth server. + * + * @author Martin Lowe + * + */ +public class EclipseApi extends DefaultApi20 { + + @Override + public String getAccessTokenEndpoint() { + return "https://accounts.eclipse.org/oauth2/token"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return null; + } + + @Override + public ClientAuthentication getClientAuthentication() { + return RequestBodyAuthenticationScheme.instance(); + } + + private static class InstanceHolder { + private static final EclipseApi INSTANCE = new EclipseApi(); + } + + public static EclipseApi instance() { + return InstanceHolder.INSTANCE; + } +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java index 8bae1e5c..2a14d535 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java +++ b/src/main/java/org/eclipsefoundation/git/eca/resource/ValidationResource.java @@ -15,10 +15,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Optional; -import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.POST; @@ -31,7 +29,6 @@ import javax.ws.rs.core.Response; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.core.service.CachingService; -import org.eclipsefoundation.git.eca.api.AccountsAPI; import org.eclipsefoundation.git.eca.api.BotsAPI; import org.eclipsefoundation.git.eca.helper.CommitHelper; import org.eclipsefoundation.git.eca.model.Commit; @@ -43,6 +40,7 @@ import org.eclipsefoundation.git.eca.model.ValidationResponse; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; import org.eclipsefoundation.git.eca.service.ProjectsService; +import org.eclipsefoundation.git.eca.service.UserService; import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,14 +67,10 @@ public class ValidationResource { @Inject @ConfigProperty(name = "eclipse.mail.allowlist") List<String> allowListUsers; - @Inject - @ConfigProperty(name = "eclipse.noreply.email-patterns") - List<String> emailPatterns; // eclipse API rest client interfaces @Inject - @RestClient - AccountsAPI accounts; + UserService users; @Inject @RestClient BotsAPI bots; @@ -87,15 +81,6 @@ public class ValidationResource { @Inject ProjectsService projects; - // rendered list of regex values - List<Pattern> patterns; - - @PostConstruct - void init() { - // compile the patterns once per object to save processing time - this.patterns = emailPatterns.stream().map(Pattern::compile).collect(Collectors.toList()); - } - /** * Consuming a JSON request, this method will validate all passed commits, using * the repo URL and @@ -514,12 +499,11 @@ public class ValidationResource { // get the Eclipse account for the user try { // use cache to avoid asking for the same user repeatedly on repeated requests - Optional<EclipseUser> foundUser = cache.get("user|" + user.getMail(), new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(user)); - if (!foundUser.isPresent()) { + EclipseUser foundUser = users.getUser(user.getMail()); + if (foundUser == null) { LOGGER.warn("No users found for mail '{}'", user.getMail()); - return null; } - return foundUser.get(); + return foundUser; } catch (WebApplicationException e) { Response r = e.getResponse(); if (r != null && r.getStatus() == 404) { @@ -531,78 +515,6 @@ public class ValidationResource { return null; } - /** - * Checks for standard and noreply email address matches for a Git user and - * converts to a - * Eclipse Foundation account object. - * - * @param user the user to attempt account retrieval for. - * @return the user account if found by mail, or null if none found. - */ - private EclipseUser retrieveUser(GitUser user) { - // check for noreply (no reply will never have user account, and fails fast) - EclipseUser eclipseUser = checkForNoReplyUser(user); - if (eclipseUser != null) { - return eclipseUser; - } - // standard user check (returns best match) - LOGGER.debug("Checking user with mail {}", user.getMail()); - try { - List<EclipseUser> users = accounts.getUsers(null, null, user.getMail()); - if (users != null && !users.isEmpty()) { - return users.get(0); - } - } catch (WebApplicationException e) { - LOGGER.warn("Could not find user account with mail '{}'", user.getMail()); - } - return null; - } - - /** - * Checks git user for no-reply address, and attempts to ratify user through - * reverse lookup in API service. - * Currently, this service only recognizes Github no-reply addresses as they - * have a route to be mapped. - * - * @param user the Git user account to check for no-reply mail address - * @return the Eclipse user if email address is detected no reply and one can be - * mapped, otherwise null - */ - private EclipseUser checkForNoReplyUser(GitUser user) { - LOGGER.debug("Checking user with mail {} for no-reply", user.getMail()); - boolean isNoReply = patterns.stream().anyMatch(pattern -> pattern.matcher(user.getMail().trim()).find()); - if (isNoReply) { - // get the username/ID string before the first @ symbol. - String noReplyUser = user.getMail().substring(0, user.getMail().indexOf("@", 0)); - // split based on +, if more than one part, use second (contains user), - // otherwise, use whole string - String[] nameParts = noReplyUser.split("[\\+]"); - String namePart; - if (nameParts.length > 1 && nameParts[1] != null) { - namePart = nameParts[1]; - } else { - namePart = nameParts[0]; - } - String uname = namePart.trim(); - LOGGER.debug("User with mail {} detected as noreply account, checking services for username match on '{}'", - user.getMail(), uname); - - // check github for no-reply (only allowed noreply currently) - if (user.getMail().endsWith("noreply.github.com")) { - try { - // check for Github no reply, return if set - EclipseUser eclipseUser = accounts.getUserByGithubUname(uname); - if (eclipseUser != null) { - return eclipseUser; - } - } catch (WebApplicationException e) { - LOGGER.warn("No match for '{}' in Github", uname); - } - } - } - return null; - } - private List<JsonNode> getBots() { Optional<List<JsonNode>> allBots = cache.get("allBots", new MultivaluedMapImpl<>(), JsonNode.class, () -> bots.getBots()); if (!allBots.isPresent()) { diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java new file mode 100644 index 00000000..eaa35324 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/service/OAuthService.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (C) 2020 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************/ +package org.eclipsefoundation.git.eca.service; + +/** + * Used to generate OAuth tokens for use with internal services rather than bolted on introspection. This is required + * over the (now deprecated) Elytron plugin or the OIDC plugin as those plugins work with requests to validate incoming + * rather than outgoing requests. + * + * @author Martin Lowe + * + */ +public interface OAuthService { + + /** + * Retrieve an access token for the service from the Eclipse API for internal usage. + * + * @return current access token, or null if none could be retrieved for current API credentials/settings. + */ + String getToken(); +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java new file mode 100644 index 00000000..377b9a0d --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/service/UserService.java @@ -0,0 +1,10 @@ +package org.eclipsefoundation.git.eca.service; + +import org.eclipsefoundation.git.eca.model.EclipseUser; + +public interface UserService { + + EclipseUser getUser(String mail); + + EclipseUser getUserByGithubUsername(String username); +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java new file mode 100644 index 00000000..a86db763 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -0,0 +1,141 @@ +package org.eclipsefoundation.git.eca.service.impl; + +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.ws.rs.WebApplicationException; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.service.CachingService; +import org.eclipsefoundation.git.eca.api.AccountsAPI; +import org.eclipsefoundation.git.eca.model.EclipseUser; +import org.eclipsefoundation.git.eca.service.OAuthService; +import org.eclipsefoundation.git.eca.service.UserService; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wrapped cached and authenticated access to user objects. + * + * @author Martin Lowe + * + */ +@Singleton +public class CachedUserService implements UserService { + private static final Logger LOGGER = LoggerFactory.getLogger(CachedUserService.class); + + @Inject + @ConfigProperty(name = "eclipse.noreply.email-patterns") + List<String> emailPatterns; + + // eclipse API rest client interfaces + @Inject + @RestClient + AccountsAPI accounts; + + @Inject + OAuthService oauth; + @Inject + CachingService cache; + + // rendered list of regex values + List<Pattern> patterns; + + @PostConstruct + void init() { + // compile the patterns once per object to save processing time + this.patterns = emailPatterns.stream().map(Pattern::compile).collect(Collectors.toList()); + } + + @Override + public EclipseUser getUser(String mail) { + return cache.get(mail, new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(mail)) + .orElseGet(() -> null); + } + + @Override + public EclipseUser getUserByGithubUsername(String username) { + return cache.get("gh:" + username, new MultivaluedMapImpl<>(), EclipseUser.class, + () -> accounts.getUserByGithubUname(getBearerToken(), username)).orElseGet(() -> null); + } + + /** + * Checks for standard and noreply email address matches for a Git user and converts to a Eclipse Foundation account + * object. + * + * @param user the user to attempt account retrieval for. + * @return the user account if found by mail, or null if none found. + */ + private EclipseUser retrieveUser(String mail) { + LOGGER.error("Getting fresh user for {}", mail); + // check for noreply (no reply will never have user account, and fails fast) + EclipseUser eclipseUser = checkForNoReplyUser(mail); + if (eclipseUser != null) { + return eclipseUser; + } + // standard user check (returns best match) + LOGGER.debug("Checking user with mail {}", mail); + try { + List<EclipseUser> matches = cache + .get(mail, null, EclipseUser.class, () -> accounts.getUsers(getBearerToken(), mail)) + .orElseGet(Collections::emptyList); + if (!matches.isEmpty()) { + return matches.get(0); + } + } catch (WebApplicationException e) { + LOGGER.warn("Could not find user account with mail '{}'", mail); + } + return null; + } + + /** + * Checks git user for no-reply address, and attempts to ratify user through reverse lookup in API service. + * Currently, this service only recognizes Github no-reply addresses as they have a route to be mapped. + * + * @param user the Git user account to check for no-reply mail address + * @return the Eclipse user if email address is detected no reply and one can be mapped, otherwise null + */ + private EclipseUser checkForNoReplyUser(String mail) { + LOGGER.debug("Checking user with mail {} for no-reply", mail); + boolean isNoReply = patterns.stream().anyMatch(pattern -> pattern.matcher(mail.trim()).find()); + if (isNoReply) { + // get the username/ID string before the first @ symbol. + String noReplyUser = mail.substring(0, mail.indexOf("@", 0)); + // split based on +, if more than one part, use second (contains user), + // otherwise, use whole string + String[] nameParts = noReplyUser.split("[\\+]"); + String namePart; + if (nameParts.length > 1 && nameParts[1] != null) { + namePart = nameParts[1]; + } else { + namePart = nameParts[0]; + } + String uname = namePart.trim(); + LOGGER.debug("User with mail {} detected as noreply account, checking services for username match on '{}'", + mail, uname); + + // check github for no-reply (only allowed noreply currently) + if (mail.endsWith("noreply.github.com")) { + try { + // check for Github no reply, return if set + return getUserByGithubUsername(uname); + } catch (WebApplicationException e) { + LOGGER.warn("No match for '{}' in Github", uname); + } + } + } + return null; + } + + private String getBearerToken() { + return "Bearer " + oauth.getToken(); + } + +} diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java new file mode 100644 index 00000000..9c8198ce --- /dev/null +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (C) 2020 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ******************************************************************************/ +package org.eclipsefoundation.git.eca.service.impl; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipsefoundation.git.eca.oauth.EclipseApi; +import org.eclipsefoundation.git.eca.service.OAuthService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.oauth.OAuth20Service; + +/** + * Default implementation for requesting an OAuth request token. The reason that this class is implemented over the + * other implementations baked into Quarkus is to better bind to the Drupal OAuth APIs. + * + * @author Martin Lowe + * + */ +@Singleton +public class DefaultOAuthService implements OAuthService { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOAuthService.class); + + @ConfigProperty(name = "oauth2.client-id") + String id; + @ConfigProperty(name = "oauth2.client-secret") + String secret; + @ConfigProperty(name = "oauth2.scope") + String scope; + + // service reference (as we only need one) + private OAuth20Service service; + + // token state vars + private long expirationTime; + private String accessToken; + + /** + * Create an OAuth service reference. + */ + @PostConstruct + void createServiceRef() { + this.service = new ServiceBuilder(id).apiSecret(secret).scope(scope).build(EclipseApi.instance()); + } + + @Override + public String getToken() { + // lock on the class instance to stop multiple threads from requesting new + // tokens at the same time + synchronized (this) { + if (accessToken == null || System.currentTimeMillis() >= expirationTime) { + // clear access token + this.accessToken = null; + try { + OAuth2AccessToken requestToken = service.getAccessTokenClientCredentialsGrant(); + if (requestToken != null) { + this.accessToken = requestToken.getAccessToken(); + this.expirationTime = System.currentTimeMillis() + + TimeUnit.SECONDS.toMillis(requestToken.getExpiresIn().longValue()); + } + } catch (IOException e) { + LOGGER.error("Issue communicating with OAuth server for authentication", e); + } catch (InterruptedException e) { + LOGGER.error("Authentication communication was interrupted before completion", e); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + LOGGER.error("Error while retrieving access token for request", e); + } + } + } + return accessToken; + } + +} diff --git a/src/main/k8s/staging.yml b/src/main/k8s/staging.yml index 1109179e..7bbee7a9 100644 --- a/src/main/k8s/staging.yml +++ b/src/main/k8s/staging.yml @@ -51,7 +51,7 @@ spec: volumes: - name: api-oauth-token secret: - secretName: git-eca-rest-api + secretName: git-eca-rest-api-staging --- apiVersion: "v1" kind: "Service" diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f41f5d75..16edcbc4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,17 +6,20 @@ eclipse.noreply.email-patterns=@users.noreply.github.com\$ ## Expect to be mounted to '/git' to match current URL spec quarkus.http.root-path=/git - -## OAUTH CONFIG quarkus.http.port=8080 -quarkus.keycloak.devservices.enabled=false +## OAUTH CONFIG quarkus.oauth2.enabled=false quarkus.oidc.enabled=false -quarkus.oidc-client.auth-server-url=https://accounts.eclipse.org/oauth2 -quarkus.oidc-client.discovery-enabled=false -quarkus.oidc-client.token-path=/token -quarkus.oidc-client.grant.type=client -quarkus.oidc-client.scopes=eclipsefdn_view_all_profiles +oauth2.scope=eclipsefdn_view_all_profiles +oauth2.client-id=placeholder +oauth2.client-secret=placeholder + +quarkus.cache.caffeine."default".initial-capacity=1000 +quarkus.cache.caffeine."default".expire-after-write=1H +quarkus.cache.caffeine."record".initial-capacity=${quarkus.cache.caffeine."default".initial-capacity} +quarkus.cache.caffeine."record".expire-after-write=${quarkus.cache.caffeine."default".expire-after-write} -eclipse.mail.allowlist=noreply@github.com \ No newline at end of file +eclipse.mail.allowlist=noreply@github.com +#%dev.quarkus.log.level=DEBUG +#quarkus.log.category."org.eclipsefoundation".level=DEBUG \ No newline at end of file -- GitLab From dea89047c0189de95ba5cd81bb4a51baecf2ea0a Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 22 Apr 2022 13:58:01 -0400 Subject: [PATCH 09/11] Fix broken tests around auth + changed accounts api signatures --- .../git/eca/test/api/MockAccountsAPI.java | 10 ++-------- .../test/service/impl/MockOAuthService.java | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java index 1a93328c..0bc5880a 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java +++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java @@ -54,15 +54,9 @@ public class MockAccountsAPI implements AccountsAPI { } @Override - public List<EclipseUser> getUsers(String id, String name, String mail) { + public List<EclipseUser> getUsers(String auth, String mail) { return src.stream().filter(user -> { boolean matches = true; - if (id != null && !Integer.toString(user.getUid()).equals(id)) { - matches = false; - } - if (name != null && !user.getName().equals(name)) { - matches = false; - } if (mail != null && !user.getMail().equalsIgnoreCase(mail)) { matches = false; } @@ -71,7 +65,7 @@ public class MockAccountsAPI implements AccountsAPI { } @Override - public EclipseUser getUserByGithubUname(String uname) { + public EclipseUser getUserByGithubUname(String auth, String uname) { // assume GH username == Eclipse uname for simplicity of test return src.stream().filter(user -> uname.equalsIgnoreCase(user.getName())).findFirst().orElse(null); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java new file mode 100644 index 00000000..21508dbf --- /dev/null +++ b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockOAuthService.java @@ -0,0 +1,18 @@ +package org.eclipsefoundation.git.eca.test.service.impl; + +import javax.inject.Singleton; + +import org.eclipsefoundation.git.eca.service.OAuthService; + +import io.quarkus.test.Mock; + +@Mock +@Singleton +public class MockOAuthService implements OAuthService { + + @Override + public String getToken() { + return ""; + } + +} -- GitLab From 380eea914b74b578a8953b740e736dea6fb68871 Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Fri, 22 Apr 2022 15:44:48 -0400 Subject: [PATCH 10/11] Fix scopes and tests that were broken from switch to userservice --- .../git/eca/api/AccountsAPI.java | 2 + .../git/eca/api/BotsAPI.java | 2 + .../git/eca/api/ProjectsAPI.java | 2 + .../eca/service/impl/CachedUserService.java | 4 +- .../eca/service/impl/DefaultOAuthService.java | 4 +- src/main/resources/application.properties | 3 +- .../eca/resource/ValidationResourceTest.java | 44 +++++++++++ .../git/eca/test/api/MockAccountsAPI.java | 73 ------------------- .../test/service/impl/MockUserService.java | 10 +++ src/test/resources/application.properties | 4 +- 10 files changed, 68 insertions(+), 80 deletions(-) delete mode 100644 src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java create mode 100644 src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockUserService.java diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java index 5096632e..171fa4fd 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/AccountsAPI.java @@ -11,6 +11,7 @@ package org.eclipsefoundation.git.eca.api; import java.util.List; +import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; @@ -29,6 +30,7 @@ import org.eclipsefoundation.git.eca.model.EclipseUser; * @author Martin Lowe * */ +@ApplicationScoped @RegisterRestClient @Produces("application/json") public interface AccountsAPI { diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/BotsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/BotsAPI.java index 961277fb..b0f60b0d 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/BotsAPI.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/BotsAPI.java @@ -11,6 +11,7 @@ package org.eclipsefoundation.git.eca.api; import java.util.List; +import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -25,6 +26,7 @@ import com.fasterxml.jackson.databind.JsonNode; * @author Martin Lowe * */ +@ApplicationScoped @Path("/bots") @RegisterRestClient public interface BotsAPI { diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java index 6c4db5ce..93a74582 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java +++ b/src/main/java/org/eclipsefoundation/git/eca/api/ProjectsAPI.java @@ -11,6 +11,7 @@ package org.eclipsefoundation.git.eca.api; import java.util.List; +import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -26,6 +27,7 @@ import org.eclipsefoundation.git.eca.model.Project; * @author Martin Lowe * */ +@ApplicationScoped @Path("/api/projects") @RegisterRestClient public interface ProjectsAPI { diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java index a86db763..ba0d037a 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -6,8 +6,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import javax.inject.Singleton; import javax.ws.rs.WebApplicationException; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; * @author Martin Lowe * */ -@Singleton +@ApplicationScoped public class CachedUserService implements UserService { private static final Logger LOGGER = LoggerFactory.getLogger(CachedUserService.class); diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java index 9c8198ce..72047985 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/DefaultOAuthService.java @@ -14,7 +14,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; -import javax.inject.Singleton; +import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipsefoundation.git.eca.oauth.EclipseApi; @@ -33,7 +33,7 @@ import com.github.scribejava.core.oauth.OAuth20Service; * @author Martin Lowe * */ -@Singleton +@ApplicationScoped public class DefaultOAuthService implements OAuthService { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOAuthService.class); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 16edcbc4..4ddc2d46 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,5 @@ -org.eclipsefoundation.git.eca.api.AccountsAPI/mp-rest/url=https://api.eclipse.org +quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".scope=javax.enterprise.context.ApplicationScoped +quarkus.rest-client."org.eclipsefoundation.git.eca.api.AccountsAPI".url=https://api.eclipse.org org.eclipsefoundation.git.eca.api.ProjectsAPI/mp-rest/url=https://projects.eclipse.org org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org diff --git a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java index 886d1f6e..2d81011a 100644 --- a/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java +++ b/src/test/java/org/eclipsefoundation/git/eca/resource/ValidationResourceTest.java @@ -23,19 +23,25 @@ import javax.inject.Inject; import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.git.eca.model.Commit; +import org.eclipsefoundation.git.eca.model.EclipseUser; +import org.eclipsefoundation.git.eca.model.EclipseUser.ECA; import org.eclipsefoundation.git.eca.model.GitUser; import org.eclipsefoundation.git.eca.model.ValidationRequest; import org.eclipsefoundation.git.eca.namespace.APIStatusCode; import org.eclipsefoundation.git.eca.namespace.ProviderType; +import org.eclipsefoundation.git.eca.service.impl.CachedUserService; import org.eclipsefoundation.git.eca.test.namespaces.SchemaNamespaceHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.mockito.InjectMock; import io.restassured.http.ContentType; /** @@ -53,8 +59,46 @@ class ValidationResourceTest { @Inject ObjectMapper json; + @InjectMock + CachedUserService accounts; + @BeforeEach void cacheClear() { + int id = 0; + // standard user fetches + Mockito.when(accounts.getUser(ArgumentMatchers.eq("newbie@important.co"))) + .thenReturn(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("newbie@important.co") + .setName("newbieAnon").setECA(ECA.builder().build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("slom@eclipse-foundation.org"))) + .thenReturn(EclipseUser.builder().setIsCommitter(false).setUid(id++) + .setMail("slom@eclipse-foundation.org").setName("barshall_blathers") + .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("tester@eclipse-foundation.org"))) + .thenReturn(EclipseUser.builder().setIsCommitter(false).setUid(id++) + .setMail("tester@eclipse-foundation.org").setName("mctesterson") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("code.wiz@important.co"))) + .thenReturn(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("code.wiz@important.co") + .setName("da_wizz") + .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("grunt@important.co"))) + .thenReturn(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("grunt@important.co") + .setName("grunter") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("paper.pusher@important.co"))) + .thenReturn(EclipseUser.builder().setIsCommitter(false).setUid(id++) + .setMail("paper.pusher@important.co").setName("sumAnalyst") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); + + // gh user fetches + Mockito.when(accounts.getUser(ArgumentMatchers.eq("grunter@users.noreply.github.com"))) + .thenReturn(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("grunt@important.co") + .setName("grunter") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); + Mockito.when(accounts.getUser(ArgumentMatchers.eq("123456789+grunter@users.noreply.github.com"))) + .thenReturn(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("grunt@important.co") + .setName("grunter") + .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); // if dev servers are run on the same machine, some values may live in the cache cs.removeAll(); } diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java deleted file mode 100644 index 0bc5880a..00000000 --- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockAccountsAPI.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2020 Eclipse Foundation - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - ******************************************************************************/ -package org.eclipsefoundation.git.eca.test.api; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.annotation.PostConstruct; -import javax.enterprise.context.ApplicationScoped; - -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipsefoundation.git.eca.api.AccountsAPI; -import org.eclipsefoundation.git.eca.model.EclipseUser; -import org.eclipsefoundation.git.eca.model.EclipseUser.ECA; - -import io.quarkus.test.Mock; - -@Mock -@RestClient -@ApplicationScoped -public class MockAccountsAPI implements AccountsAPI { - - private List<EclipseUser> src; - - @PostConstruct - public void build() { - this.src = new ArrayList<>(); - int id = 0; - src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("newbie@important.co") - .setName("newbieAnon").setECA(ECA.builder().build()).build()); - src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("slom@eclipse-foundation.org") - .setName("barshall_blathers") - .setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()).build()); - src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("tester@eclipse-foundation.org") - .setName("mctesterson").setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) - .build()); - src.add(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("code.wiz@important.co") - .setName("da_wizz").setECA(ECA.builder().setCanContributeSpecProject(true).setSigned(true).build()) - .build()); - src.add(EclipseUser.builder().setIsCommitter(true).setUid(id++).setMail("grunt@important.co").setName("grunter") - .setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()).build()); - src.add(EclipseUser.builder().setIsCommitter(false).setUid(id++).setMail("paper.pusher@important.co") - .setName("sumAnalyst").setECA(ECA.builder().setCanContributeSpecProject(false).setSigned(true).build()) - .build()); - - } - - @Override - public List<EclipseUser> getUsers(String auth, String mail) { - return src.stream().filter(user -> { - boolean matches = true; - if (mail != null && !user.getMail().equalsIgnoreCase(mail)) { - matches = false; - } - return matches; - }).collect(Collectors.toList()); - } - - @Override - public EclipseUser getUserByGithubUname(String auth, String uname) { - // assume GH username == Eclipse uname for simplicity of test - return src.stream().filter(user -> uname.equalsIgnoreCase(user.getName())).findFirst().orElse(null); - } - -} diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockUserService.java b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockUserService.java new file mode 100644 index 00000000..c799f363 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/git/eca/test/service/impl/MockUserService.java @@ -0,0 +1,10 @@ +package org.eclipsefoundation.git.eca.test.service.impl; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipsefoundation.git.eca.service.impl.CachedUserService; + +public class MockUserService extends CachedUserService { + + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index e1cee2f0..81d29deb 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,8 +1,9 @@ -org.eclipsefoundation.git.eca.api.AccountsAPI/mp-rest/url=https://api.eclipse.org + org.eclipsefoundation.git.eca.api.ProjectsAPI/mp-rest/url=https://projects.eclipse.org org.eclipsefoundation.git.eca.api.BotsAPI/mp-rest/url=https://api.eclipse.org eclipse.noreply.email-patterns=@users.noreply.github.com\$ +eclipse.mail.allowlist=noreply@github.com ## Expect to be mounted to '/git' to match current URL spec quarkus.http.root-path=/git @@ -14,5 +15,4 @@ quarkus.keycloak.devservices.enabled=false quarkus.oidc-client.enabled=false quarkus.http.port=8080 -eclipse.mail.allowlist=noreply@github.com #quarkus.log.level=DEBUG \ No newline at end of file -- GitLab From 2b417ae91ba65f696d20b2e6dfe6bd043e844cef Mon Sep 17 00:00:00 2001 From: Martin Lowe <martin.lowe@eclipse-foundation.org> Date: Mon, 25 Apr 2022 09:14:34 -0400 Subject: [PATCH 11/11] Fix issue w/ nested caching calls killing user service requests --- .../eca/service/impl/CachedUserService.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java index ba0d037a..8b8e523f 100644 --- a/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java +++ b/src/main/java/org/eclipsefoundation/git/eca/service/impl/CachedUserService.java @@ -1,7 +1,7 @@ package org.eclipsefoundation.git.eca.service.impl; -import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -56,14 +56,25 @@ public class CachedUserService implements UserService { @Override public EclipseUser getUser(String mail) { - return cache.get(mail, new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(mail)) - .orElseGet(() -> null); + Optional<EclipseUser> u =cache.get(mail, new MultivaluedMapImpl<>(), EclipseUser.class, () -> retrieveUser(mail)); + if (u.isPresent()) { + LOGGER.debug("Found user with email {}", mail); + return u.get(); + } + LOGGER.debug("Could not find user with email {}", mail); + return null; } @Override public EclipseUser getUserByGithubUsername(String username) { - return cache.get("gh:" + username, new MultivaluedMapImpl<>(), EclipseUser.class, - () -> accounts.getUserByGithubUname(getBearerToken(), username)).orElseGet(() -> null); + Optional<EclipseUser> u = cache.get("gh:" + username, new MultivaluedMapImpl<>(), EclipseUser.class, + () -> accounts.getUserByGithubUname(getBearerToken(), username)); + if (u.isPresent()) { + LOGGER.debug("Found user with name {}", username); + return u.get(); + } + LOGGER.debug("Could not find user with name {}", username); + return null; } /** @@ -83,10 +94,8 @@ public class CachedUserService implements UserService { // standard user check (returns best match) LOGGER.debug("Checking user with mail {}", mail); try { - List<EclipseUser> matches = cache - .get(mail, null, EclipseUser.class, () -> accounts.getUsers(getBearerToken(), mail)) - .orElseGet(Collections::emptyList); - if (!matches.isEmpty()) { + List<EclipseUser> matches = accounts.getUsers(getBearerToken(), mail); + if (matches != null && !matches.isEmpty()) { return matches.get(0); } } catch (WebApplicationException e) { -- GitLab