From 96afc41a61d6666518ce83be4e9300243f3f8d07 Mon Sep 17 00:00:00 2001 From: Vojtech Szocs Date: Thu, 12 Oct 2023 18:01:07 +0200 Subject: [PATCH] Phase 1 of using OpenShift Dynamic Plugin SDK --- dynamic-demo-plugin/package.json | 4 +- dynamic-demo-plugin/yarn.lock | 254 +++++++++------- frontend/package.json | 2 +- .../ConsoleOperatorConfig.tsx | 4 +- .../console-dynamic-plugin-sdk/README.md | 2 - .../console-dynamic-plugin-sdk/package.json | 3 +- .../scripts/generate-schema.ts | 5 - .../scripts/package-definitions.ts | 6 +- .../src/__tests__/shared-modules-init.spec.ts | 14 - .../src/build-types.ts | 53 ++++ .../src/lib-webpack.ts | 5 +- .../__tests__/plugin-dependencies.spec.ts | 10 +- .../runtime/__tests__/plugin-loader.spec.ts | 178 ++++++++---- .../src/runtime/plugin-dependencies.ts | 4 +- .../src/runtime/plugin-loader.ts | 128 +++++--- .../src/runtime/plugin-manifest.ts | 10 +- .../src/runtime/plugin-utils.ts | 4 +- .../src/schema/plugin-manifest.ts | 8 +- .../src/schema/plugin-package.ts | 41 --- .../src/shared-modules-init.ts | 6 - .../console-dynamic-plugin-sdk/src/types.ts | 7 - .../src/utils/schema.ts | 14 + .../src/utils/test-utils.ts | 13 +- .../src/validation/BaseValidator.ts | 11 + .../src/validation/ExtensionValidator.ts | 21 +- .../src/validation/SchemaValidator.ts | 12 +- .../src/validation/ValidationAssertions.ts | 36 --- .../src/validation/ValidationResult.ts | 37 +++ .../__tests__/ExtensionValidator.spec.ts | 7 +- .../src/webpack/ConsoleAssetPlugin.ts | 124 -------- .../src/webpack/ConsoleRemotePlugin.ts | 274 +++++++++--------- .../src/__tests__/store.spec.ts | 8 +- .../codegen/__tests__/active-plugins.spec.ts | 33 ++- .../src/codegen/active-plugins.ts | 4 +- .../packages/console-plugin-sdk/src/store.ts | 15 +- frontend/webpack.config.ts | 3 + frontend/yarn.lock | 124 +++++--- 37 files changed, 794 insertions(+), 690 deletions(-) create mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts delete mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-package.ts create mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/utils/schema.ts create mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/validation/BaseValidator.ts delete mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationAssertions.ts delete mode 100644 frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin.ts diff --git a/dynamic-demo-plugin/package.json b/dynamic-demo-plugin/package.json index 0eb3905f664a..b388580959a4 100644 --- a/dynamic-demo-plugin/package.json +++ b/dynamic-demo-plugin/package.json @@ -38,8 +38,8 @@ "ts-loader": "9.x", "ts-node": "5.0.1", "typescript": "4.x", - "webpack": "^5.73.0", - "webpack-cli": "4.9.x" + "webpack": "5.75.0", + "webpack-cli": "5.0.x" }, "consolePlugin": { "name": "console-demo-plugin", diff --git a/dynamic-demo-plugin/yarn.lock b/dynamic-demo-plugin/yarn.lock index 612418b59288..2e7e85981d8a 100644 --- a/dynamic-demo-plugin/yarn.lock +++ b/dynamic-demo-plugin/yarn.lock @@ -33,6 +33,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.15.4": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.7.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580" @@ -83,6 +90,7 @@ "@openshift-console/dynamic-plugin-sdk-webpack@file:../frontend/packages/console-dynamic-plugin-sdk/dist/webpack": version "0.0.0-fixed" dependencies: + "@openshift/dynamic-plugin-sdk-webpack" "file:../../dynamic-plugin-sdk/packages/lib-webpack" ajv "^6.12.3" chalk "2.4.x" comment-json "4.x" @@ -90,7 +98,7 @@ lodash "^4.17.21" read-pkg "5.x" semver "6.x" - webpack "^5.73.0" + webpack "5.75.0" "@openshift-console/dynamic-plugin-sdk@file:../frontend/packages/console-dynamic-plugin-sdk/dist/core": version "0.0.0-fixed" @@ -122,6 +130,13 @@ sanitize-html "^2.3.2" showdown "1.8.6" +"@openshift/dynamic-plugin-sdk-webpack@file:../../dynamic-plugin-sdk/packages/lib-webpack": + version "3.0.1" + dependencies: + lodash "^4.17.21" + semver "^7.3.7" + yup "^0.32.11" + "@patternfly/patternfly@^4.224.2": version "4.224.2" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.224.2.tgz#9d1b000c3c43457ae47c3556a334412d164fad90" @@ -245,6 +260,11 @@ version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" +"@types/lodash@^4.14.175": + version "4.14.198" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" + integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -437,22 +457,20 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" -"@webpack-cli/configtest@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.0.tgz#8342bef0badfb7dfd3b576f2574ab80c725be043" - integrity sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg== +"@webpack-cli/configtest@^2.0.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== -"@webpack-cli/info@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.0.tgz#b9179c3227ab09cbbb149aa733475fcf99430223" - integrity sha512-F6b+Man0rwE4n0409FyAJHStYA5OIZERxmnUfLVwv0mc0V1wLad3V7jqRlMkgKBeAq07jUvglacNaa6g9lOpuw== - dependencies: - envinfo "^7.7.3" +"@webpack-cli/info@^2.0.1": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== -"@webpack-cli/serve@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.0.tgz#2c275aa05c895eccebbfc34cfb223c6e8bd591a2" - integrity sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA== +"@webpack-cli/serve@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -467,10 +485,10 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.7.1: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== aggregate-error@^3.0.0: version "3.0.1" @@ -909,14 +927,15 @@ colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" -commander@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" - commander@~7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -1256,10 +1275,10 @@ enhanced-resolve@^5.0.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enhanced-resolve@^5.9.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" - integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== +enhanced-resolve@^5.10.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -1366,20 +1385,6 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1556,10 +1561,6 @@ get-stream@^3.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== -get-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" - glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -1774,10 +1775,6 @@ http-server@0.12.x: secure-compare "3.0.1" union "~0.5.0" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - i18next-parser@^3.3.0: version "3.11.0" resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-3.11.0.tgz#62ead424f63c6e5e40da26bca258a5d6fb08538e" @@ -1858,9 +1855,10 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== invert-kv@^1.0.0: version "1.0.0" @@ -1886,6 +1884,13 @@ is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -1954,10 +1959,6 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" @@ -2249,10 +2250,6 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -2316,6 +2313,11 @@ ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -2376,12 +2378,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - dependencies: - path-key "^3.0.0" - nth-check@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" @@ -2420,12 +2416,6 @@ once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - dependencies: - mimic-fn "^2.1.0" - opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -2544,7 +2534,7 @@ path-key@^2.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -2552,6 +2542,11 @@ path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + path-posix@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" @@ -2884,6 +2879,11 @@ prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== + pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -3086,11 +3086,12 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -rechoir@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== dependencies: - resolve "^1.9.0" + resolve "^1.20.0" reduce-css-calc@^1.2.6: version "1.3.0" @@ -3128,6 +3129,11 @@ regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + remove-bom-buffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" @@ -3194,13 +3200,22 @@ resolve-pathname@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" -resolve@^1.10.0, resolve@^1.9.0: +resolve@^1.10.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" dependencies: is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.20.0: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3305,6 +3320,13 @@ semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + serialize-javascript@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" @@ -3363,10 +3385,6 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3506,10 +3524,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - style-loader@0.23.1: version "0.23.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" @@ -3547,6 +3561,11 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svgo@^0.7.0: version "0.7.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" @@ -3664,6 +3683,11 @@ to-through@^2.0.0: dependencies: through2 "^2.0.3" +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + ts-loader@9.x: version "9.2.6" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74" @@ -3875,30 +3899,31 @@ warning@^4.0.0: dependencies: loose-envify "^1.0.0" -watchpack@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -webpack-cli@4.9.x: - version "4.9.1" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.1.tgz#b64be825e2d1b130f285c314caa3b1ba9a4632b3" - integrity sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ== +webpack-cli@5.0.x: + version "5.0.2" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.2.tgz#2954c10ecb61c5d4dad6f68ee2d77f051741946c" + integrity sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ== dependencies: "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.1.0" - "@webpack-cli/info" "^1.4.0" - "@webpack-cli/serve" "^1.6.0" + "@webpack-cli/configtest" "^2.0.1" + "@webpack-cli/info" "^2.0.1" + "@webpack-cli/serve" "^2.0.2" colorette "^2.0.14" - commander "^7.0.0" - execa "^5.0.0" + commander "^10.0.1" + cross-spawn "^7.0.3" + envinfo "^7.7.3" fastest-levenshtein "^1.0.12" import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" + interpret "^3.1.1" + rechoir "^0.8.0" webpack-merge "^5.7.3" webpack-merge@^5.7.3: @@ -3920,21 +3945,21 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.73.0: - version "5.73.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" - integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== +webpack@5.75.0: + version "5.75.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" + integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" + acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.3" + enhanced-resolve "^5.10.0" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" @@ -3947,7 +3972,7 @@ webpack@^5.73.0: schema-utils "^3.1.0" tapable "^2.1.1" terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" + watchpack "^2.4.0" webpack-sources "^3.2.3" whatwg-fetch@2.x: @@ -4043,3 +4068,16 @@ yn@^2.0.0: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + +yup@^0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" diff --git a/frontend/package.json b/frontend/package.json index 022a8486f845..f5182f073fcb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "integration-tests" ], "scripts": { - "postinstall": "yarn generate", + "postinstall": "# TODO yarn generate", "clean": "rm -rf ./public/dist", "dev": "yarn clean && yarn generate && REACT_REFRESH=true NODE_OPTIONS=--max-old-space-size=4096 yarn ts-node ./node_modules/.bin/webpack serve --mode=development --progress", "dev-once": "yarn clean && yarn generate && NODE_OPTIONS=--max-old-space-size=4096 yarn ts-node ./node_modules/.bin/webpack --mode=development", diff --git a/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx index 57f6e89ae8d6..f7f72cd07253 100644 --- a/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx +++ b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx @@ -105,7 +105,7 @@ const ConsolePluginsList: React.FC = ({ obj }) => { return { name: plugin.metadata.name, version: plugin.metadata.version, - description: plugin.metadata?.description || placeholder, + description: plugin.metadata?.customProperties?.console?.description || placeholder, enabled: plugin.enabled, status: plugin.status, }; @@ -148,7 +148,7 @@ const ConsolePluginsList: React.FC = ({ obj }) => { return { name: plugin?.metadata?.name, version: loadedPluginInfo?.metadata?.version, - description: loadedPluginInfo?.metadata?.description, + description: loadedPluginInfo?.metadata?.customProperties?.console?.description, enabled, status: loadedPluginInfo?.status, }; diff --git a/frontend/packages/console-dynamic-plugin-sdk/README.md b/frontend/packages/console-dynamic-plugin-sdk/README.md index 245ec0dd1545..a6d371c744da 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -100,8 +100,6 @@ a [semver pre-release](https://semver.org/#spec-item-9) identifier, adapt your s to include the relevant pre-release prefix, e.g. use `~4.11.0-0.ci` when targeting pre-release versions like `4.11.0-0.ci-1234`. -See `ConsolePluginMetadata` type for details on the `consolePlugin` object and its schema. - ## `console-extensions.json` Declares all extensions contributed by the plugin. diff --git a/frontend/packages/console-dynamic-plugin-sdk/package.json b/frontend/packages/console-dynamic-plugin-sdk/package.json index 0068c293328b..63f587c0c807 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/package.json +++ b/frontend/packages/console-dynamic-plugin-sdk/package.json @@ -17,12 +17,13 @@ }, "devDependencies": { "@microsoft/tsdoc": "0.14.2", + "@openshift/dynamic-plugin-sdk-webpack": "file:/home/vszocs/work/openshift/dynamic-plugin-sdk/packages/lib-webpack", "@types/ejs": "3.x", "@types/fs-extra": "9.x", "ejs": "3.x", "fs-extra": "9.x", "ts-json-schema-generator": "0.98.0", "tsutils": "3.21.0", - "webpack": "^5.73.0" + "webpack": "5.75.0" } } diff --git a/frontend/packages/console-dynamic-plugin-sdk/scripts/generate-schema.ts b/frontend/packages/console-dynamic-plugin-sdk/scripts/generate-schema.ts index cf31b8030604..43eb6d712712 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/scripts/generate-schema.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/scripts/generate-schema.ts @@ -17,10 +17,6 @@ type SchemaTypeConfig = { }; const typeConfigs: SchemaTypeConfig[] = [ - { - srcFile: 'src/schema/plugin-package.ts', - typeName: 'ConsolePluginMetadata', - }, { srcFile: 'src/schema/console-extensions.ts', typeName: 'ConsoleExtensionsJSON', @@ -29,7 +25,6 @@ const typeConfigs: SchemaTypeConfig[] = [ { srcFile: 'src/schema/plugin-manifest.ts', typeName: 'ConsolePluginManifestJSON', - handleConsoleExtensions: true, }, ]; diff --git a/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts b/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts index 388ae2fbd664..d0de13f0a520 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts @@ -149,7 +149,11 @@ export const getWebpackPackage: GetPackageDefinition = ( main: 'lib/lib-webpack.js', ...commonManifestFields, dependencies: { - ...parseDeps(sdkPackage, ['webpack'], missingDepCallback), + ...parseDeps( + sdkPackage, + ['@openshift/dynamic-plugin-sdk-webpack', 'webpack'], + missingDepCallback, + ), ...parseDeps( rootPackage, ['ajv', 'chalk', 'comment-json', 'find-up', 'read-pkg', 'semver'], diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/__tests__/shared-modules-init.spec.ts b/frontend/packages/console-dynamic-plugin-sdk/src/__tests__/shared-modules-init.spec.ts index 0a8780fefaaf..803788aff573 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/__tests__/shared-modules-init.spec.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/__tests__/shared-modules-init.spec.ts @@ -14,18 +14,4 @@ describe('initSharedPluginModules', () => { new Set(sharedPluginModules), ); }); - - it('supports plugins built with an older version of plugin SDK', () => { - const [, entryModule] = getEntryModuleMocks({}); - entryModule.override = jest.fn(); - - initSharedPluginModules(entryModule); - - expect(entryModule.override).toHaveBeenCalledTimes(1); - expect(entryModule.init).not.toHaveBeenCalled(); - - expect(new Set(Object.keys(entryModule.override.mock.calls[0][0]))).toEqual( - new Set(sharedPluginModules), - ); - }); }); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts new file mode 100644 index 000000000000..d31a4ad35de6 --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts @@ -0,0 +1,53 @@ +import { PluginBuildMetadata, PluginManifest } from '@openshift/dynamic-plugin-sdk-webpack'; + +/** + * Additional plugin metadata supported by the Console application. + */ +export type ConsoleSupportedCustomProperties = Partial<{ + /** User-friendly plugin name. */ + displayName: string; + + /** User-friendly plugin description. */ + description: string; + + /** Disable the given static plugins when this plugin gets loaded. */ + disableStaticPlugins: string[]; +}>; + +/** + * Build-time Console dynamic plugin metadata. + */ +export type ConsolePluginBuildMetadata = PluginBuildMetadata & ConsoleSupportedCustomProperties; + +/** + * Standard Console dynamic plugin manifest format. + */ +export type StandardConsolePluginManifest = { + customProperties?: { + console?: ConsoleSupportedCustomProperties; + [customNamespace: string]: unknown; + }; +} & PluginManifest; + +/** + * Legacy Console dynamic plugin manifest format. + */ +export type LegacyConsolePluginManifest = Pick< + PluginManifest, + 'name' | 'version' | 'dependencies' | 'extensions' +> & + ConsoleSupportedCustomProperties; + +/** + * This type supports both standard and legacy Console dynamic plugin manifest formats. + * + * Console application automatically adapts the manifest to standard format when loading + * the given plugin. + */ +export type AnyConsolePluginManifest = StandardConsolePluginManifest | LegacyConsolePluginManifest; + +export const isStandardPluginManifest = ( + m: AnyConsolePluginManifest, +): m is StandardConsolePluginManifest => + // Standard plugin manifests must have a string valued baseURL property + m['baseURL'] && typeof m['baseURL'] === 'string'; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/lib-webpack.ts b/frontend/packages/console-dynamic-plugin-sdk/src/lib-webpack.ts index d5ea58f246e5..93395d76388f 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/lib-webpack.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/lib-webpack.ts @@ -1,7 +1,8 @@ /** * @file Entrypoint for the `@openshift-console/dynamic-plugin-sdk-webpack` package published to npmjs. * - * Provides webpack plugin `ConsoleRemotePlugin` used to build all dynamic plugin assets. + * Provides webpack plugin `ConsoleRemotePlugin` used to build all Console dynamic plugin assets. */ -export { ConsoleRemotePlugin } from './webpack/ConsoleRemotePlugin'; +export { ConsoleRemotePlugin, ConsoleRemotePluginOptions } from './webpack/ConsoleRemotePlugin'; +export { ConsolePluginBuildMetadata } from './build-types'; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-dependencies.spec.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-dependencies.spec.ts index 4a6c7f2e59ce..9133afc1ac21 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-dependencies.spec.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-dependencies.spec.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; import * as pluginSubscriptionServiceModule from '@console/plugin-sdk/src/api/pluginSubscriptionService'; import { LoadedDynamicPluginInfo, NotLoadedDynamicPluginInfo } from '@console/plugin-sdk/src/store'; -import { ConsolePluginManifestJSON } from '../../schema/plugin-manifest'; +import { StandardConsolePluginManifest } from '../../build-types'; import { getPluginManifest } from '../../utils/test-utils'; import { resolvePluginDependencies, @@ -26,23 +26,23 @@ beforeEach(() => { describe('resolvePluginDependencies', () => { const getLoadedDynamicPluginInfo = ( - manifest: ConsolePluginManifestJSON, + manifest: StandardConsolePluginManifest, ): LoadedDynamicPluginInfo => ({ status: 'Loaded', pluginID: getPluginID(manifest), - metadata: _.pick(manifest, 'name', 'version', 'dependencies'), + metadata: _.omit(manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: true, }); const getPendingDynamicPluginInfo = ( - manifest: ConsolePluginManifestJSON, + manifest: StandardConsolePluginManifest, ): NotLoadedDynamicPluginInfo => ({ status: 'Pending', pluginName: manifest.name, }); const getFailedDynamicPluginInfo = ( - manifest: ConsolePluginManifestJSON, + manifest: StandardConsolePluginManifest, ): NotLoadedDynamicPluginInfo => ({ status: 'Failed', pluginName: manifest.name, diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-loader.spec.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-loader.spec.ts index f58f26a563ad..f06a47255db3 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-loader.spec.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/__tests__/plugin-loader.spec.ts @@ -2,7 +2,7 @@ import { act } from '@testing-library/react'; import { Simulate } from 'react-dom/test-utils'; import { PluginStore } from '@console/plugin-sdk/src/store'; import * as utilsModule from '@console/shared/src/utils/utils'; -import { ConsolePluginManifestJSON } from '../../schema/plugin-manifest'; +import { StandardConsolePluginManifest, LegacyConsolePluginManifest } from '../../build-types'; import { Extension } from '../../types'; import { getPluginManifest, @@ -15,11 +15,11 @@ import * as pluginManifestModule from '../plugin-manifest'; import { getPluginID } from '../plugin-utils'; const { - scriptIDPrefix, getScriptElementID, loadDynamicPlugin, getPluginEntryCallback, registerPluginEntryCallback, + adaptPluginManifest, loadAndEnablePlugin, getStateForTestPurposes, resetStateAndEnvForTestPurposes, @@ -30,34 +30,26 @@ const resolvePluginDependencies = jest.spyOn(pluginDependenciesModule, 'resolveP const loadDynamicPluginMock = jest.spyOn(pluginLoaderModule, 'loadDynamicPlugin'); const getRandomCharsMock = jest.spyOn(utilsModule, 'getRandomChars'); -const originalConsole = { ...console }; const originalServerFlags = window.SERVER_FLAGS; -const consoleMock = jest.fn(); beforeEach(() => { jest.resetAllMocks(); resetStateAndEnvForTestPurposes(); - // eslint-disable-next-line no-console - ['log', 'info', 'warn', 'error'].forEach((key) => (console[key] = consoleMock)); -}); - -afterEach(() => { - // eslint-disable-next-line no-console - ['log', 'info', 'warn', 'error'].forEach((key) => (console[key] = originalConsole[key])); }); describe('getScriptElementID', () => { - it('returns a string formatted as {prefix}@{name}', () => { - expect(getScriptElementID(getPluginManifest('Test', '1.2.3'))).toBe('console-plugin-Test'); + it('returns a string formatted as {pluginName}/{scriptName}', () => { + expect(getScriptElementID('Test', 'plugin-entry.js')).toBe('Test/plugin-entry.js'); }); }); describe('loadDynamicPlugin', () => { - const getScriptElement = (manifest: ConsolePluginManifestJSON) => - document.querySelector(`[id="${getScriptElementID(manifest)}"]`); + const getAllScripts = () => Array.from(document.scripts); - const getAllScriptElements = () => - document.querySelectorAll(`[id^="${scriptIDPrefix}"]`); + const getFirstPluginScript = (manifest: StandardConsolePluginManifest) => + getAllScripts().find( + (element) => element.id === getScriptElementID(manifest.name, manifest.loadScripts[0]), + ); beforeEach(() => { getRandomCharsMock.mockImplementation(() => 'r4nd0m'); @@ -65,7 +57,7 @@ describe('loadDynamicPlugin', () => { it('updates pluginMap and adds a script element to document head', () => { const manifest = getPluginManifest('Test', '1.2.3'); - loadDynamicPlugin('http://example.com/test/', manifest); + loadDynamicPlugin(manifest); const { pluginMap } = getStateForTestPurposes(); expect(pluginMap.size).toBe(1); @@ -73,11 +65,12 @@ describe('loadDynamicPlugin', () => { expect(pluginMap.get('Test@1.2.3').manifest).toBe(manifest); expect(pluginMap.get('Test@1.2.3').entryCallbackFired).toBe(false); - const script = getScriptElement(manifest); + const script = getFirstPluginScript(manifest); expect(script instanceof HTMLScriptElement).toBe(true); expect(script.parentElement).toBe(document.head); - expect(script.id).toBe(getScriptElementID(manifest)); - expect(script.src).toBe('http://example.com/test/plugin-entry.js?cacheBuster=r4nd0m'); + expect(script.id).toBe('Test/plugin-entry.js'); + expect(script.src).toBe('http://example.com/Test/plugin-entry.js?cacheBuster=r4nd0m'); + expect(getAllScripts().length).toBe(1); expect(getRandomCharsMock).toHaveBeenCalledTimes(1); }); @@ -85,11 +78,11 @@ describe('loadDynamicPlugin', () => { it('throws an error if a plugin with the same name is already registered', async () => { const manifest1 = getPluginManifest('Test', '1.2.3'); const manifest2 = getPluginManifest('Test', '2.3.4'); - loadDynamicPlugin('http://example.com/test1/', manifest1); + loadDynamicPlugin(manifest1); try { - await loadDynamicPlugin('http://example.com/test2/', manifest2); - fail('Expected that loadDynamicPlugin fails and throw an error'); + await loadDynamicPlugin(manifest2); + fail('Expected that loadDynamicPlugin fails and throws an error'); } catch (error) { expect(error).toEqual(new Error('Attempt to reload plugin Test@1.2.3 with Test@2.3.4')); @@ -98,27 +91,41 @@ describe('loadDynamicPlugin', () => { expect(pluginMap.get('Test@1.2.3').manifest).toBe(manifest1); expect(pluginMap.get('Test@1.2.3').entryCallbackFired).toBe(false); - const allScripts = getAllScriptElements(); - expect(allScripts.length).toBe(1); - expect(allScripts[0].id).toBe(getScriptElementID(manifest1)); - expect(allScripts[0].src).toBe('http://example.com/test1/plugin-entry.js?cacheBuster=r4nd0m'); + const script = getFirstPluginScript(manifest1); + expect(script.id).toBe('Test/plugin-entry.js'); + expect(script.src).toBe('http://example.com/Test/plugin-entry.js?cacheBuster=r4nd0m'); + expect(getAllScripts().length).toBe(1); + } + }); + + it('throws an error if a plugin does not use callback registration method', async () => { + const manifest = getPluginManifest('Test', '1.2.3'); + manifest.registrationMethod = 'custom'; + + try { + await loadDynamicPlugin(manifest); + fail('Expected that loadDynamicPlugin fails and throws an error'); + } catch (error) { + expect(error).toEqual( + new Error('Plugin Test@1.2.3 does not use callback registration method'), + ); - expect(getRandomCharsMock).toHaveBeenCalledTimes(1); + const { pluginMap } = getStateForTestPurposes(); + expect(pluginMap.size).toBe(0); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenCalledWith('Loading entry script for plugin Test@1.2.3'); + expect(getAllScripts().length).toBe(0); } }); it('returns plugin ID if the script was loaded successfully and the entry callback was fired', async () => { const manifest = getPluginManifest('Test', '1.2.3'); - const promise = loadDynamicPlugin('http://example.com/test/', manifest); + const promise = loadDynamicPlugin(manifest); const { pluginMap } = getStateForTestPurposes(); pluginMap.get('Test@1.2.3').entryCallbackFired = true; act(() => { - Simulate.load(getScriptElement(manifest)); + Simulate.load(getFirstPluginScript(manifest)); }); expect(await promise).toBe('Test@1.2.3'); @@ -126,41 +133,37 @@ describe('loadDynamicPlugin', () => { it('throws an error if the script was loaded successfully but the entry callback was not fired', async () => { const manifest = getPluginManifest('Test', '1.2.3'); - const promise = loadDynamicPlugin('http://example.com/test/', manifest); + const promise = loadDynamicPlugin(manifest); + const script = getFirstPluginScript(manifest); act(() => { - Simulate.load(getScriptElement(manifest)); + Simulate.load(script); }); try { await promise; - fail('Expected that loadDynamicPlugin fails and throw an error'); + fail('Expected that loadDynamicPlugin fails and throws an error'); } catch (error) { expect(error).toEqual( - new Error('Entry script for plugin Test@1.2.3 loaded without callback'), + new Error('Scripts of plugin Test@1.2.3 loaded without entry callback'), ); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenCalledWith('Loading entry script for plugin Test@1.2.3'); } }); it('throws an error if the script was not loaded successfully', async () => { const manifest = getPluginManifest('Test', '1.2.3'); - const promise = loadDynamicPlugin('http://example.com/test/', manifest); + const promise = loadDynamicPlugin(manifest); + const script = getFirstPluginScript(manifest); - act(() => { - Simulate.error(getScriptElement(manifest)); - }); + // Invoke script.onerror DOM event handler directly, since the Simulate.error() function + // does not seem to work as expected (it invokes script.onload DOM event handler instead) + script.onerror('Test Error Event'); try { await promise; - fail('Expected that loadDynamicPlugin fails and throw an error'); + fail('Expected that loadDynamicPlugin fails and throws an error'); } catch (error) { - expect(error).toEqual( - new Error('Entry script for plugin Test@1.2.3 loaded without callback'), - ); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenCalledWith('Loading entry script for plugin Test@1.2.3'); + expect(error).toEqual(new Error('Detected errors while loading plugin entry scripts')); } }); }); @@ -308,18 +311,49 @@ describe('window.loadPluginEntry', () => { expect(initSharedPluginModules).toHaveBeenCalledWith(entryModule); expect(resolveEncodedCodeRefs).not.toHaveBeenCalled(); expect(addDynamicPlugin).not.toHaveBeenCalled(); + }); +}); - expect(consoleMock).toHaveBeenCalledTimes(1); - expect(consoleMock).toHaveBeenCalledWith( - 'Failed to initialize shared modules for plugin Test@1.2.3', - new Error('boom'), +describe('adaptPluginManifest', () => { + it('returns the same manifest if it already meets the standard format', () => { + const manifest = getPluginManifest('Test', '1.2.3'); + const adaptedManifest = adaptPluginManifest(manifest, 'http://example.com/Test/'); + + expect(adaptedManifest).toBe(manifest); + }); + + it('adapts the legacy manifest to the standard format', () => { + const manifest: LegacyConsolePluginManifest = { + name: 'Test', + version: '1.2.3', + extensions: [], + displayName: 'Test Plugin', + description: 'Test Plugin Description', + disableStaticPlugins: ['StaticTest'], + dependencies: { RequiredTest: '*' }, + }; + + const adaptedManifest = adaptPluginManifest(manifest, 'http://example.com/Test/'); + + expect(adaptedManifest).not.toBe(manifest); + + expect(adaptedManifest.name).toBe(manifest.name); + expect(adaptedManifest.version).toBe(manifest.version); + expect(adaptedManifest.extensions).toBe(manifest.extensions); + expect(adaptedManifest.dependencies).toBe(manifest.dependencies); + expect(adaptedManifest.baseURL).toBe('http://example.com/Test/'); + expect(adaptedManifest.loadScripts).toEqual(['plugin-entry.js']); + expect(adaptedManifest.registrationMethod).toBe('callback'); + + expect(adaptedManifest.customProperties.console.displayName).toBe(manifest.displayName); + expect(adaptedManifest.customProperties.console.description).toBe(manifest.description); + expect(adaptedManifest.customProperties.console.disableStaticPlugins).toBe( + manifest.disableStaticPlugins, ); }); }); describe('loadAndEnablePlugin', () => { - const manifest = getPluginManifest('Test', '1.2.3'); - let pluginStore: PluginStore; let setDynamicPluginEnabled: jest.SpyInstance; @@ -334,18 +368,20 @@ describe('loadAndEnablePlugin', () => { }); it('loads the plugin from URL {basePath}api/plugins/{pluginName}/', async () => { + const manifest = getPluginManifest('Test', '1.2.3'); + fetchPluginManifest.mockImplementation(() => Promise.resolve(manifest)); resolvePluginDependencies.mockImplementation(() => Promise.resolve()); loadDynamicPluginMock.mockImplementation(() => Promise.resolve('Test@1.2.3')); window.SERVER_FLAGS.basePath = '/test/'; - window.SERVER_FLAGS.releaseVersion = '4.11.1-test.2'; + window.SERVER_FLAGS.releaseVersion = '4.11.1-test.2'; // semver compliant await loadAndEnablePlugin('Test', pluginStore); expect(fetchPluginManifest).toHaveBeenLastCalledWith('/test/api/plugins/Test/'); expect(resolvePluginDependencies).toHaveBeenLastCalledWith(manifest, '4.11.1-test.2', ['Test']); - expect(loadDynamicPluginMock).toHaveBeenLastCalledWith('/test/api/plugins/Test/', manifest); + expect(loadDynamicPluginMock).toHaveBeenLastCalledWith(manifest); [fetchPluginManifest, resolvePluginDependencies, loadDynamicPluginMock].forEach((mock) => { expect(mock).toHaveBeenCalledTimes(1); @@ -357,14 +393,39 @@ describe('loadAndEnablePlugin', () => { expect(fetchPluginManifest).toHaveBeenLastCalledWith('/test/api/plugins/Test/'); expect(resolvePluginDependencies).toHaveBeenLastCalledWith(manifest, null, ['Test']); - expect(loadDynamicPluginMock).toHaveBeenLastCalledWith('/test/api/plugins/Test/', manifest); + expect(loadDynamicPluginMock).toHaveBeenLastCalledWith(manifest); [fetchPluginManifest, resolvePluginDependencies, loadDynamicPluginMock].forEach((mock) => { expect(mock).toHaveBeenCalledTimes(2); }); }); + it('ensures that the plugin manifest is adapted to the standard format', async () => { + const manifest: LegacyConsolePluginManifest = { + name: 'Test', + version: '1.2.3', + extensions: [], + displayName: 'Test Plugin', + description: 'Test Plugin Description', + disableStaticPlugins: ['StaticTest'], + dependencies: { RequiredTest: '*' }, + }; + + fetchPluginManifest.mockImplementation(() => Promise.resolve(manifest)); + resolvePluginDependencies.mockImplementation(() => Promise.resolve()); + loadDynamicPluginMock.mockImplementation(() => Promise.resolve('Test@1.2.3')); + + window.SERVER_FLAGS.basePath = '/'; + + await loadAndEnablePlugin('Test', pluginStore); + + expect(loadDynamicPluginMock).toHaveBeenLastCalledWith( + adaptPluginManifest(manifest, '/api/plugins/Test/'), + ); + }); + it('enables the plugin if it was loaded successfully', async () => { + const manifest = getPluginManifest('Test', '1.2.3'); const onError = jest.fn(); fetchPluginManifest.mockImplementation(() => Promise.resolve(manifest)); @@ -378,6 +439,7 @@ describe('loadAndEnablePlugin', () => { }); it('calls the provided error handler when the plugin fails to load properly', async () => { + const manifest = getPluginManifest('Test', '1.2.3'); const onError = jest.fn(); fetchPluginManifest.mockImplementation(() => Promise.reject(new Error('boom1'))); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-dependencies.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-dependencies.ts index 97e03bda29a5..f96d1f1a94fd 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-dependencies.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-dependencies.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; import * as semver from 'semver'; import { subscribeToDynamicPlugins } from '@console/plugin-sdk/src/api/pluginSubscriptionService'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; +import { StandardConsolePluginManifest } from '../build-types'; import { CustomError } from '../utils/error/custom-error'; import { getPluginID } from './plugin-utils'; @@ -33,7 +33,7 @@ const formatUnmetDependency = (depName: string, requiredRange: string, currentVe * the semver version properly. In that case, the corresponding dependency check will be skipped. */ export const resolvePluginDependencies = ( - manifest: ConsolePluginManifestJSON, + manifest: StandardConsolePluginManifest, consolePluginAPIVersion: string, allowedPluginNames: string[], ) => { diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-loader.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-loader.ts index 032b7829a208..b37c82af533a 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-loader.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-loader.ts @@ -6,7 +6,11 @@ import { PluginStore } from '@console/plugin-sdk/src/store'; import { getRandomChars } from '@console/shared/src/utils/utils'; import { resolveEncodedCodeRefs } from '../coderefs/coderef-resolver'; import { remoteEntryFile } from '../constants'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; +import { + AnyConsolePluginManifest, + StandardConsolePluginManifest, + isStandardPluginManifest, +} from '../build-types'; import { initSharedPluginModules } from '../shared-modules-init'; import { RemoteEntryModule } from '../types'; import { ErrorWithCause } from '../utils/error/custom-error'; @@ -14,21 +18,40 @@ import { resolveURL } from '../utils/url'; import { resolvePluginDependencies } from './plugin-dependencies'; import { fetchPluginManifest } from './plugin-manifest'; import { getPluginID } from './plugin-utils'; +import { settleAllPromises } from '../utils/promise'; type ConsolePluginData = { /** The manifest containing plugin metadata and extension declarations. */ - manifest: ConsolePluginManifestJSON; + manifest: StandardConsolePluginManifest; /** Indicates if `window.loadPluginEntry` callback has been fired for this plugin. */ entryCallbackFired: boolean; }; const pluginMap = new Map(); -export const scriptIDPrefix = 'console-plugin'; +export const getScriptElementID = (pluginName: string, scriptName: string) => + `${pluginName}/${scriptName}`; -export const getScriptElementID = (m: ConsolePluginManifestJSON) => `${scriptIDPrefix}-${m.name}`; +const injectScriptElement = (url: string, id: string) => + new Promise((resolve, reject) => { + const script = document.createElement('script'); + + script.async = true; + script.src = url; + script.id = id; + + script.onload = () => { + resolve(); + }; + + script.onerror = (event) => { + reject(event); + }; + + document.head.appendChild(script); + }); -export const loadDynamicPlugin = (baseURL: string, manifest: ConsolePluginManifestJSON) => +export const loadDynamicPlugin = (manifest: StandardConsolePluginManifest) => new Promise((resolve, reject) => { const pluginID = getPluginID(manifest); @@ -42,37 +65,44 @@ export const loadDynamicPlugin = (baseURL: string, manifest: ConsolePluginManife return; } + if (manifest.registrationMethod !== 'callback') { + reject(new Error(`Plugin ${pluginID} does not use callback registration method`)); + return; + } + pluginMap.set(pluginID, { manifest, entryCallbackFired: false, }); - const scriptURL = resolveURL(baseURL, remoteEntryFile, (url) => { - url.search = `?cacheBuster=${getRandomChars()}`; - return url; - }); - - const script = document.createElement('script'); - script.id = getScriptElementID(manifest); - script.src = scriptURL; - script.async = true; - - script.onload = () => { - if (pluginMap.get(pluginID).entryCallbackFired) { - resolve(pluginID); - } else { - reject(new Error(`Entry script for plugin ${pluginID} loaded without callback`)); + console.info(`Loading scripts of plugin ${pluginID}`); + + settleAllPromises( + manifest.loadScripts.map((scriptName) => { + const scriptID = getScriptElementID(manifest.name, scriptName); + + const scriptURL = resolveURL(manifest.baseURL, scriptName, (url) => { + url.search = `?cacheBuster=${getRandomChars()}`; + return url; + }); + + return injectScriptElement(scriptURL, scriptID); + }), + ).then(([, rejectedReasons]) => { + if (rejectedReasons.length > 0) { + reject( + new ErrorWithCause(`Detected errors while loading plugin entry scripts`, rejectedReasons), + ); + return; } - }; - script.onerror = (event) => { - reject( - new ErrorWithCause(`Error while loading plugin entry script from ${scriptURL}`, event), - ); - }; + if (!pluginMap.get(pluginID).entryCallbackFired) { + reject(new Error(`Scripts of plugin ${pluginID} loaded without entry callback`)); + return; + } - console.info(`Loading entry script for plugin ${pluginID}`); - document.head.appendChild(script); + resolve(pluginID); + }); }); export const getPluginEntryCallback = ( @@ -121,18 +151,48 @@ export const registerPluginEntryCallback = (pluginStore: PluginStore) => { ); }; +export const adaptPluginManifest = ( + manifest: AnyConsolePluginManifest, + baseURL: string, +): StandardConsolePluginManifest => { + if (isStandardPluginManifest(manifest)) { + return manifest; + } + + const { + name, + version, + extensions, + dependencies, + displayName, + description, + disableStaticPlugins, + } = manifest; + + return { + name, + version, + extensions, + dependencies, + customProperties: { console: { displayName, description, disableStaticPlugins } }, + baseURL, + loadScripts: [remoteEntryFile], + registrationMethod: 'callback', + }; +}; + export const loadAndEnablePlugin = async ( pluginName: string, pluginStore: PluginStore, onError: (errorMessage: string, errorCause?: unknown) => void = _.noop, ) => { - const url = `${window.SERVER_FLAGS.basePath}api/plugins/${pluginName}/`; - let manifest: ConsolePluginManifestJSON; + const baseURL = `${window.SERVER_FLAGS.basePath}api/plugins/${pluginName}/`; + let manifest: StandardConsolePluginManifest; try { - manifest = await fetchPluginManifest(url); + manifest = adaptPluginManifest(await fetchPluginManifest(baseURL), baseURL); } catch (e) { - onError(`Failed to get a valid plugin manifest from ${url}`, e); + onError(`Failed to get a valid plugin manifest from ${baseURL}`, e); return; } @@ -148,7 +208,7 @@ export const loadAndEnablePlugin = async ( } try { - await loadDynamicPlugin(url, manifest); + await loadDynamicPlugin(manifest); } catch (e) { onError(`Failed to load entry script of plugin ${pluginName}`, e); return; @@ -164,7 +224,7 @@ export const getStateForTestPurposes = () => ({ export const resetStateAndEnvForTestPurposes = () => { pluginMap.clear(); - document.querySelectorAll(`[id^="${scriptIDPrefix}"]`).forEach((element) => { + Array.from(document.scripts).forEach((element) => { element.remove(); }); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-manifest.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-manifest.ts index 9ae6b96e2267..e7ccbbae7918 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-manifest.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-manifest.ts @@ -19,12 +19,15 @@ export const validatePluginManifestSchema = async ( const validator = new SchemaValidator(manifestURL); validator.validate(schema, manifest, 'manifest'); - validator.assert.validDNSSubdomainName(manifest.name, 'manifest.name'); - validator.assert.validSemverString(manifest.version, 'manifest.version'); + validator.result.assertions.validDNSSubdomainName(manifest.name, 'manifest.name'); + validator.result.assertions.validSemverString(manifest.version, 'manifest.version'); if (_.isPlainObject(manifest.dependencies)) { Object.entries(manifest.dependencies).forEach(([depName, versionRange]) => { - validator.assert.validSemverRangeString(versionRange, `manifest.dependencies['${depName}']`); + validator.result.assertions.validSemverRangeString( + versionRange, + `manifest.dependencies['${depName}']`, + ); }); } @@ -40,5 +43,6 @@ export const fetchPluginManifest = async (baseURL: string) => { const manifest = (await response.json()) as ConsolePluginManifestJSON; (await validatePluginManifestSchema(manifest, url)).report(); + return manifest; }; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-utils.ts b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-utils.ts index e7150745dd7c..209b249dfc2a 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-utils.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/runtime/plugin-utils.ts @@ -1,3 +1,3 @@ -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; +import { StandardConsolePluginManifest } from '../build-types'; -export const getPluginID = (m: ConsolePluginManifestJSON) => `${m.name}@${m.version}`; +export const getPluginID = (m: StandardConsolePluginManifest) => `${m.name}@${m.version}`; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-manifest.ts b/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-manifest.ts index 51332de7bf56..e65ff2d2932d 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-manifest.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-manifest.ts @@ -1,10 +1,6 @@ -import { ConsoleExtensionsJSON } from './console-extensions'; -import { ConsolePluginMetadata } from './plugin-package'; +import { AnyConsolePluginManifest } from '../build-types'; /** * Schema of Console plugin's `plugin-manifest.json` file. */ -export type ConsolePluginManifestJSON = Omit & { - /** List of extensions contributed by the plugin. */ - extensions: ConsoleExtensionsJSON; -}; +export type ConsolePluginManifestJSON = AnyConsolePluginManifest; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-package.ts b/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-package.ts deleted file mode 100644 index e796221a9ff8..000000000000 --- a/frontend/packages/console-dynamic-plugin-sdk/src/schema/plugin-package.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as readPkg from 'read-pkg'; - -/** - * Console plugin metadata in `package.json` file. - */ -export type ConsolePluginMetadata = { - /** - * Plugin name. Should be the same as `metadata.name` of the corresponding - * `ConsolePlugin` resource used to represent the plugin on the cluster. - */ - name: string; - /** Plugin version. Must be semver compliant. */ - version: string; - /** User-friendly plugin name. */ - displayName?: string; - /** User-friendly plugin description. */ - description?: string; - /** Specific modules exposed through the plugin's remote entry. */ - exposedModules?: { [moduleName: string]: string }; - /** - * Additional dependencies required for this plugin to work. - * Values must be valid semver ranges or `*` representing any version. - * - * Console plugins may depend on other Console plugins or on Console - * application itself, represented as `@console/pluginAPI` dependency. - * - * If present, `@console/pluginAPI` version range is matched against - * the Console release version, as provided by the Console operator. - */ - dependencies?: { [pluginName: string]: string }; - /** Disable the given static plugins when this plugin gets loaded. */ - disableStaticPlugins?: string[]; -}; - -/** - * Schema of Console plugin's `package.json` file. - */ -export type ConsolePackageJSON = readPkg.PackageJson & { - /** Console plugin specific metadata. */ - consolePlugin: ConsolePluginMetadata; -}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules-init.ts b/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules-init.ts index e707ed53522b..fee2305bbeec 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules-init.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules-init.ts @@ -52,11 +52,5 @@ const sharedScope = Object.keys(modules).reduce( * @see https://webpack.js.org/concepts/module-federation/#dynamic-remote-containers */ export const initSharedPluginModules = (entryModule: RemoteEntryModule) => { - if (typeof entryModule.override === 'function') { - // Support plugins built with webpack 5.0.0-beta.16 - entryModule.override(modules); - return; - } - entryModule.init(sharedScope); }; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/types.ts index 934ccc4f0499..c2145c10c9e1 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/types.ts @@ -58,13 +58,6 @@ export type RemoteEntryModule = { */ init: (sharedScope: any) => void; - /** - * _For webpack 5.0.0-beta.16 compatibility_ - * - * Override module(s) that were flagged by the container as "overridable". - */ - override?: (modules: SharedModuleResolution) => void; - /** * Get a module exposed through the container. * diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/schema.ts b/frontend/packages/console-dynamic-plugin-sdk/src/utils/schema.ts new file mode 100644 index 000000000000..38c5f1390373 --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/schema.ts @@ -0,0 +1,14 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as findUp from 'find-up'; + +export const loadSchema = (relativePath: string) => { + const pkgDir = path.dirname(findUp.sync('package.json', { cwd: __dirname })); + + const schemaPath = [ + path.resolve(pkgDir, 'schema'), + path.resolve(pkgDir, 'generated/schema'), + ].find((p) => fs.existsSync(p) && fs.statSync(p).isDirectory()); + + return require(path.resolve(schemaPath, relativePath)); +}; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/utils/test-utils.ts b/frontend/packages/console-dynamic-plugin-sdk/src/utils/test-utils.ts index 230ba08b26fb..965235d6d8f5 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/utils/test-utils.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/test-utils.ts @@ -1,18 +1,21 @@ import { applyCodeRefSymbol } from '../coderefs/coderef-resolver'; -import { ConsoleExtensionsJSON } from '../schema/console-extensions'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; +import { StandardConsolePluginManifest } from '../build-types'; import { Extension, RemoteEntryModule, CodeRef, Update } from '../types'; +import { remoteEntryFile } from '../constants'; export const getPluginManifest = ( name: string, version: string, extensions: Extension[] = [], disableStaticPlugins?: string[], -): ConsolePluginManifestJSON => ({ +): StandardConsolePluginManifest => ({ name, version, - extensions: extensions as ConsoleExtensionsJSON, - disableStaticPlugins, + extensions, + customProperties: { console: { disableStaticPlugins } }, + baseURL: `http://example.com/${name}/`, + loadScripts: [remoteEntryFile], + registrationMethod: 'callback', }); export const getExecutableCodeRefMock = ( diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/BaseValidator.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/BaseValidator.ts new file mode 100644 index 000000000000..f49a3db939ec --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/validation/BaseValidator.ts @@ -0,0 +1,11 @@ +import { ValidationResult } from './ValidationResult'; + +export abstract class BaseValidator { + readonly result: ValidationResult; + + constructor(description: string) { + this.result = new ValidationResult(description); + } + + abstract validate(...args: any[]): ValidationResult; +} diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ExtensionValidator.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/ExtensionValidator.ts index c2dd8aee9a02..9c78fb038be0 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ExtensionValidator.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/validation/ExtensionValidator.ts @@ -1,18 +1,16 @@ import * as _ from 'lodash'; import * as webpack from 'webpack'; import { isEncodedCodeRef, parseEncodedCodeRefValue } from '../coderefs/coderef-resolver'; -import { ConsolePluginMetadata } from '../schema/plugin-package'; +import { ConsolePluginBuildMetadata } from '../build-types'; import { Extension, EncodedCodeRef } from '../types'; import { deepForOwn } from '../utils/object'; -import { ValidationResult } from './ValidationResult'; +import { BaseValidator } from './BaseValidator'; type ExtensionCodeRefData = { index: number; propToCodeRefValue: { [propName: string]: string }; }; -type ExposedPluginModules = ConsolePluginMetadata['exposedModules']; - export const collectCodeRefData = (extensions: Extension[]) => extensions.reduce((acc, e, index) => { const data: ExtensionCodeRefData = { index, propToCodeRefValue: {} }; @@ -30,7 +28,7 @@ export const collectCodeRefData = (extensions: Extension[]) => export const findWebpackModules = ( compilation: webpack.Compilation, - exposedModules: ExposedPluginModules, + exposedModules: ConsolePluginBuildMetadata['exposedModules'], ) => { const webpackModules = Array.from(compilation.modules); @@ -43,18 +41,11 @@ export const findWebpackModules = ( }, {} as { [moduleName: string]: webpack.Module }); }; -export class ExtensionValidator { - readonly result: ValidationResult; - - constructor(description: string) { - this.result = new ValidationResult(description); - } - +export class ExtensionValidator extends BaseValidator { validate( compilation: webpack.Compilation, extensions: Extension[], - exposedModules: ExposedPluginModules, - dataVar: string = 'extensions', + exposedModules: ConsolePluginBuildMetadata['exposedModules'], ) { const codeRefs = collectCodeRefData(extensions); const webpackModules = findWebpackModules(compilation, exposedModules); @@ -77,7 +68,7 @@ export class ExtensionValidator { codeRefs.forEach((data) => { Object.entries(data.propToCodeRefValue).forEach(([propName, codeRefValue]) => { const [moduleName, exportName] = parseEncodedCodeRefValue(codeRefValue); - const errorTrace = `in ${dataVar}[${data.index}] property '${propName}'`; + const errorTrace = `in extension [${data.index}] property '${propName}'`; if (!moduleName || !exportName) { this.result.addError(`Invalid code reference '${codeRefValue}' ${errorTrace}`); diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/SchemaValidator.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/SchemaValidator.ts index 78d2af1d7e02..9aa099d149ec 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/validation/SchemaValidator.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/validation/SchemaValidator.ts @@ -1,15 +1,9 @@ import * as Ajv from 'ajv'; -import { ValidationAssertions } from './ValidationAssertions'; -import { ValidationResult } from './ValidationResult'; - -export class SchemaValidator { - readonly result: ValidationResult; - - readonly assert: ValidationAssertions; +import { BaseValidator } from './BaseValidator'; +export class SchemaValidator extends BaseValidator { constructor(description: string, private readonly ajv = new Ajv({ allErrors: true })) { - this.result = new ValidationResult(description); - this.assert = new ValidationAssertions(this.result); + super(description); } validate(schema: {}, data: any, dataVar: string = 'obj') { diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationAssertions.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationAssertions.ts deleted file mode 100644 index 7fb3f040def0..000000000000 --- a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationAssertions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as semver from 'semver'; -import { ValidationResult } from './ValidationResult'; - -export class ValidationAssertions { - constructor(private readonly result: ValidationResult) {} - - // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names - validDNSSubdomainName(obj: any, objPath: string) { - if (typeof obj === 'string') { - this.result.assertThat( - obj.length <= 253, - `${objPath} must contain no more than 253 characters`, - ); - this.result.assertThat( - /^[a-z0-9-.]*$/.test(obj), - `${objPath} must contain only lowercase alphanumeric characters, '-' or '.'`, - ); - this.result.assertThat( - /^[a-z0-9]+/.test(obj) && /[a-z0-9]+$/.test(obj), - `${objPath} must start and end with an alphanumeric character`, - ); - } - } - - validSemverString(obj: any, objPath: string) { - if (typeof obj === 'string') { - this.result.assertThat(!!semver.valid(obj), `${objPath} must be semver compliant`); - } - } - - validSemverRangeString(obj: any, objPath: string) { - if (typeof obj === 'string') { - this.result.assertThat(!!semver.validRange(obj), `${objPath} semver range is not valid`); - } - } -} diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationResult.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationResult.ts index d002051f129c..7b2b93c70ce6 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationResult.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/validation/ValidationResult.ts @@ -1,8 +1,11 @@ import chalk from 'chalk'; +import * as semver from 'semver'; export class ValidationResult { private readonly errors: string[] = []; + readonly assertions = new ValidationAssertions(this); + constructor(private readonly description: string) {} assertThat(condition: boolean, message: string) { @@ -40,3 +43,37 @@ export class ValidationResult { } } } + +class ValidationAssertions { + constructor(private readonly result: ValidationResult) {} + + // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names + validDNSSubdomainName(obj: any, objPath: string) { + if (typeof obj === 'string') { + this.result.assertThat( + obj.length <= 253, + `${objPath} must contain no more than 253 characters`, + ); + this.result.assertThat( + /^[a-z0-9-.]*$/.test(obj), + `${objPath} must contain only lowercase alphanumeric characters, '-' or '.'`, + ); + this.result.assertThat( + /^[a-z0-9]+/.test(obj) && /[a-z0-9]+$/.test(obj), + `${objPath} must start and end with an alphanumeric character`, + ); + } + } + + validSemverString(obj: any, objPath: string) { + if (typeof obj === 'string') { + this.result.assertThat(!!semver.valid(obj), `${objPath} must be semver compliant`); + } + } + + validSemverRangeString(obj: any, objPath: string) { + if (typeof obj === 'string') { + this.result.assertThat(!!semver.validRange(obj), `${objPath} semver range is not valid`); + } + } +} diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/validation/__tests__/ExtensionValidator.spec.ts b/frontend/packages/console-dynamic-plugin-sdk/src/validation/__tests__/ExtensionValidator.spec.ts index 18e77926a70e..3eed43575dbc 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/validation/__tests__/ExtensionValidator.spec.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/validation/__tests__/ExtensionValidator.spec.ts @@ -79,7 +79,6 @@ describe('ExtensionValidator', () => { compilation, extensions, exposedModules, - 'testExtensions', ); afterResult(result, getProvidedExports); }; @@ -150,7 +149,7 @@ describe('ExtensionValidator', () => { (result, getProvidedExports) => { expect(result.getErrors().length).toBe(1); expect(result.getErrors()[0]).toBe( - "Invalid code reference '.fooExport' in testExtensions[1] property 'mux'", + "Invalid code reference '.fooExport' in extension [1] property 'mux'", ); expect(getProvidedExports).toHaveBeenCalledTimes(1); }, @@ -186,7 +185,7 @@ describe('ExtensionValidator', () => { (result, getProvidedExports) => { expect(result.getErrors().length).toBe(1); expect(result.getErrors()[0]).toBe( - "Invalid module 'barModule' in testExtensions[1] property 'mux'", + "Invalid module 'barModule' in extension [1] property 'mux'", ); expect(getProvidedExports).toHaveBeenCalledTimes(1); }, @@ -222,7 +221,7 @@ describe('ExtensionValidator', () => { (result, getProvidedExports) => { expect(result.getErrors().length).toBe(1); expect(result.getErrors()[0]).toBe( - "Invalid module export 'barExport' in testExtensions[1] property 'mux'", + "Invalid module export 'barExport' in extension [1] property 'mux'", ); expect(getProvidedExports).toHaveBeenCalledTimes(2); }, diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin.ts b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin.ts deleted file mode 100644 index 33e01de2907e..000000000000 --- a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as findUp from 'find-up'; -import * as webpack from 'webpack'; -import { extensionsFile, pluginManifestFile, remoteEntryFile } from '../constants'; -import { ConsoleExtensionsJSON } from '../schema/console-extensions'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; -import { ConsolePackageJSON } from '../schema/plugin-package'; -import { parseJSONC } from '../utils/jsonc'; -import { ExtensionValidator } from '../validation/ExtensionValidator'; -import { SchemaValidator } from '../validation/SchemaValidator'; - -export const loadSchema = (relativePath: string) => { - const pkgDir = path.dirname(findUp.sync('package.json', { cwd: __dirname })); - - const schemaPath = [ - path.resolve(pkgDir, 'schema'), - path.resolve(pkgDir, 'generated/schema'), - ].find((p) => fs.existsSync(p) && fs.statSync(p).isDirectory()); - - return require(path.resolve(schemaPath, relativePath)); -}; - -export const validateExtensionsFileSchema = ( - ext: ConsoleExtensionsJSON, - description = extensionsFile, -) => { - const schema = loadSchema('console-extensions.json'); - return new SchemaValidator(description).validate(schema, ext); -}; - -const getPluginManifest = ( - pkg: ConsolePackageJSON, - ext: ConsoleExtensionsJSON, -): ConsolePluginManifestJSON => ({ - name: pkg.consolePlugin.name, - version: pkg.consolePlugin.version, - displayName: pkg.consolePlugin.displayName, - description: pkg.consolePlugin.description, - dependencies: pkg.consolePlugin.dependencies, - disableStaticPlugins: pkg.consolePlugin.disableStaticPlugins, - extensions: ext, -}); - -export class ConsoleAssetPlugin { - private readonly ext: ConsoleExtensionsJSON; - - constructor( - private readonly pkg: ConsolePackageJSON, - private readonly remoteEntryCallback: string, - validateExtensionSchema: boolean, - private readonly validateExtensionIntegrity: boolean, - ) { - this.ext = parseJSONC(path.resolve(process.cwd(), extensionsFile)); - - if (validateExtensionSchema) { - validateExtensionsFileSchema(this.ext).report(); - } - } - - apply(compiler: webpack.Compiler) { - compiler.hooks.thisCompilation.tap(ConsoleAssetPlugin.name, (compilation) => { - // Generate additional assets - compilation.hooks.processAssets.tap( - { - name: ConsoleAssetPlugin.name, - stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, - }, - () => { - compilation.emitAsset( - pluginManifestFile, - new webpack.sources.RawSource( - Buffer.from(JSON.stringify(getPluginManifest(this.pkg, this.ext), null, 2)), - ), - ); - }, - ); - - // Post-process assets already present in the compilation - compilation.hooks.processAssets.tap( - { - name: ConsoleAssetPlugin.name, - stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, - }, - () => { - compilation.updateAsset(remoteEntryFile, (source) => { - const newSource = new webpack.sources.ReplaceSource(source); - const fromIndex = source.source().toString().indexOf(`${this.remoteEntryCallback}(`); - - if (fromIndex < 0) { - const error = new webpack.WebpackError(`Missing call to ${this.remoteEntryCallback}`); - error.file = remoteEntryFile; - compilation.errors.push(error); - } else { - newSource.insert( - fromIndex + this.remoteEntryCallback.length + 1, - `'${this.pkg.consolePlugin.name}@${this.pkg.consolePlugin.version}', `, - ); - } - - return newSource; - }); - }, - ); - }); - - if (this.validateExtensionIntegrity) { - compiler.hooks.emit.tap(ConsoleAssetPlugin.name, (compilation) => { - const result = new ExtensionValidator(extensionsFile).validate( - compilation, - this.ext, - this.pkg.consolePlugin.exposedModules || {}, - ); - - if (result.hasErrors()) { - const error = new webpack.WebpackError('ExtensionValidator has reported errors'); - error.details = result.formatErrors(); - error.file = extensionsFile; - compilation.errors.push(error); - } - }); - } - } -} diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts index 0cef14907b01..dbc8fee843cd 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts @@ -1,46 +1,71 @@ +import * as path from 'path'; +import { DynamicRemotePlugin, EncodedExtension } from '@openshift/dynamic-plugin-sdk-webpack'; import * as _ from 'lodash'; import * as readPkg from 'read-pkg'; import * as semver from 'semver'; import * as webpack from 'webpack'; -import { remoteEntryFile } from '../constants'; -import { ConsolePackageJSON } from '../schema/plugin-package'; +import { ConsolePluginBuildMetadata } from '../build-types'; +import { extensionsFile, pluginManifestFile, remoteEntryFile } from '../constants'; import { sharedPluginModules, sharedPluginModulesMetadata } from '../shared-modules'; +import { parseJSONC } from '../utils/jsonc'; +import { loadSchema } from '../utils/schema'; +import { ExtensionValidator } from '../validation/ExtensionValidator'; import { SchemaValidator } from '../validation/SchemaValidator'; import { ValidationResult } from '../validation/ValidationResult'; -import { loadSchema, ConsoleAssetPlugin } from './ConsoleAssetPlugin'; -const loadPackageManifest = (moduleName: string) => +type ConsolePluginPackageJSON = readPkg.PackageJson & { + consolePlugin?: ConsolePluginBuildMetadata; +}; + +const loadPluginPackageJSON = () => readPkg.sync({ normalize: false }) as ConsolePluginPackageJSON; + +const loadVendorPackageJSON = (moduleName: string) => // eslint-disable-next-line @typescript-eslint/no-var-requires require(`${moduleName}/package.json`) as readPkg.PackageJson; -const validatePackageFileSchema = (pkg: ConsolePackageJSON) => { - const schema = loadSchema('plugin-package.json'); - const validator = new SchemaValidator('package.json'); - - if (pkg.consolePlugin) { - validator.validate(schema, pkg.consolePlugin, 'pkg.consolePlugin'); +// https://webpack.js.org/plugins/module-federation-plugin/#sharing-hints +const getWebpackSharedModules = () => + Object.entries(sharedPluginModulesMetadata).reduce((acc, [moduleRequest, moduleMetadata]) => { + const adaptedMetadata = _.defaults({}, moduleMetadata, { + singleton: true, + allowFallback: false, + }); - validator.assert.validDNSSubdomainName(pkg.consolePlugin.name, 'pkg.consolePlugin.name'); - validator.assert.validSemverString(pkg.consolePlugin.version, 'pkg.consolePlugin.version'); + const moduleConfig: Record = { + singleton: adaptedMetadata.singleton, + }; - if (_.isPlainObject(pkg.consolePlugin.dependencies)) { - Object.entries(pkg.consolePlugin.dependencies).forEach(([depName, versionRange]) => { - validator.assert.validSemverRangeString( - versionRange, - `pkg.consolePlugin.dependencies['${depName}']`, - ); - }); + if (!adaptedMetadata.allowFallback) { + moduleConfig.import = false; } - } else { - validator.result.addError('pkg.consolePlugin object is missing'); - } - return validator.result; + acc[moduleRequest] = moduleConfig; + return acc; + }, {}); + +/** + * Perform (additional) build-time validation of Console plugin metadata. + * + * Note that `DynamicRemotePlugin` takes care of basic build metadata validation. + * Therefore, this function only performs additional Console specific validations. + */ +const validateConsoleBuildMetadata = (metadata: ConsolePluginBuildMetadata) => { + const result = new ValidationResult('Console plugin metadata'); + result.assertions.validDNSSubdomainName(metadata.name, 'metadata.name'); + return result; +}; + +export const validateConsoleExtensionsFileSchema = ( + extensions: EncodedExtension[], + description = 'console-extensions.json', +) => { + const schema = loadSchema('console-extensions.json'); + return new SchemaValidator(description).validate(schema, extensions); }; const validateConsoleProvidedSharedModules = ( - pkg: ConsolePackageJSON, - sdkPkg = loadPackageManifest('@openshift-console/dynamic-plugin-sdk'), + pkg = loadPluginPackageJSON(), + sdkPkg = loadVendorPackageJSON('@openshift-console/dynamic-plugin-sdk'), ) => { const pluginDeps = { ...pkg.devDependencies, ...pkg.dependencies }; const result = new ValidationResult('package.json'); @@ -53,7 +78,7 @@ const validateConsoleProvidedSharedModules = ( } const providedVersionRange = sdkPkg.dependencies[moduleName]; - const consumedVersion = loadPackageManifest(moduleName).version; + const consumedVersion = loadVendorPackageJSON(moduleName).version; if (semver.validRange(providedVersionRange) && semver.valid(consumedVersion)) { result.assertThat( @@ -68,165 +93,152 @@ const validateConsoleProvidedSharedModules = ( export type ConsoleRemotePluginOptions = Partial<{ /** - * Validate the plugin's `package.json` file schema? + * Console dynamic plugin metadata. * - * This controls the validation of `consolePlugin` object within the plugin's package - * manifest using the `plugin-package.json` schema. The `consolePlugin` object should - * exist and contain valid Console dynamic plugin metadata. + * If not specified, plugin metadata will be parsed from `consolePlugin` object within + * the `package.json` file. * - * @default true + * Plugin metadata should meet the following requirements: + * + * - `name` should be the same as `metadata.name` of the corresponding `ConsolePlugin` + * resource on the cluster. + * - `version` must be semver compliant. + * - `dependencies` values must be valid semver ranges or `*` representing any version. + * + * Additional runtime environment specific dependencies available to Console plugins: + * + * - `@console/pluginAPI` - Console web application. This dependency is matched against + * the Console release version, as provided by the Console operator. */ - validatePackageSchema: boolean; + pluginMetadata: ConsolePluginBuildMetadata; /** - * Validate Console provided shared modules which are consumed by the plugin? - * - * Console provided shared modules are reflected as `dependencies` in the core plugin - * SDK package manifest. For each shared module where a fallback version is not allowed, - * check that the version consumed by the plugin satisfies the expected semver range as - * declared in the core plugin SDK package manifest. + * List of extensions contributed by the plugin. * - * @default true + * If not specified, extensions will be parsed from `console-extensions.json` file. */ - validateSharedModules: boolean; + extensions: EncodedExtension[]; /** - * Validate the plugin's `console-extensions.json` file schema? - * - * This controls the validation of extension declarations using the `console-extensions.json` - * schema. + * Validate extension objects using the `console-extensions.json` schema? * * @default true */ validateExtensionSchema: boolean; /** - * Validate the integrity of extensions declared in `console-extensions.json` file? + * Validate integrity of extensions contributed by the plugin? * - * This controls whether to use `ExtensionValidator` to check extension declarations: + * This option controls whether to use `ExtensionValidator` to check the following criteria: * - each exposed module must have at least one code reference * - each code reference must point to a valid webpack module export * * @default true */ validateExtensionIntegrity: boolean; + + /** + * Validate Console provided shared module dependencies? + * + * Console provided shared modules are reflected as `dependencies` within the manifest of the + * `@openshift-console/dynamic-plugin-sdk` package. For each shared module where a fallback + * version is not allowed, check that the version consumed by the plugin satisfies the expected + * semver range as declared in the Console core SDK package manifest. + * + * @default true + */ + validateSharedModules: boolean; }>; /** * Generates Console dynamic plugin remote container and related assets. * - * Default configuration for modules shared between the Console application and its dynamic plugins: + * Default configuration for modules shared between the Console application and its plugins: * - shared modules are treated as singletons * - plugins won't bring their own fallback version of shared modules * - * If you're facing `ExtensionValidator` related issues, set the `validateExtensionIntegrity` option - * to `false` or pass `CONSOLE_PLUGIN_SKIP_EXT_VALIDATOR=true` env. variable to your webpack command. - * * @see {@link sharedPluginModulesMetadata} */ -export class ConsoleRemotePlugin { - private readonly options: Required; - - private readonly pkg: ConsolePackageJSON; +export class ConsoleRemotePlugin implements webpack.WebpackPluginInstance { + private readonly adaptedOptions: Required; constructor(options: ConsoleRemotePluginOptions = {}) { - this.pkg = readPkg.sync({ normalize: false }) as ConsolePackageJSON; - - this.options = { - validatePackageSchema: options.validatePackageSchema ?? true, - validateSharedModules: options.validateSharedModules ?? true, + this.adaptedOptions = { + pluginMetadata: options.pluginMetadata ?? loadPluginPackageJSON().consolePlugin, + extensions: options.extensions ?? parseJSONC(path.resolve(process.cwd(), extensionsFile)), validateExtensionSchema: options.validateExtensionSchema ?? true, validateExtensionIntegrity: options.validateExtensionIntegrity ?? true, + validateSharedModules: options.validateSharedModules ?? true, }; - if (this.options.validatePackageSchema) { - validatePackageFileSchema(this.pkg).report(); + if (this.adaptedOptions.validateExtensionSchema) { + validateConsoleExtensionsFileSchema(this.adaptedOptions.extensions).report(); } - if (this.options.validateSharedModules) { - validateConsoleProvidedSharedModules(this.pkg).report(); + if (this.adaptedOptions.validateSharedModules) { + validateConsoleProvidedSharedModules().report(); } } apply(compiler: webpack.Compiler) { + const { pluginMetadata, extensions, validateExtensionIntegrity } = this.adaptedOptions; + + const { + name, + version, + dependencies, + customProperties, + exposedModules, + displayName, + description, + disableStaticPlugins, + } = pluginMetadata; + const logger = compiler.getInfrastructureLogger(ConsoleRemotePlugin.name); - const publicPath = `/api/plugins/${this.pkg.consolePlugin.name}/`; - const containerName = this.pkg.consolePlugin.name; - const remoteEntryCallback = 'window.loadPluginEntry'; + const publicPath = `/api/plugins/${name}/`; - // Validate webpack options if (compiler.options.output.publicPath !== undefined) { logger.warn(`output.publicPath is defined, but will be overridden to ${publicPath}`); } - if (compiler.options.output.uniqueName !== undefined) { - logger.warn(`output.uniqueName is defined, but will be overridden to ${containerName}`); - } - const containerLibrary = { - type: 'jsonp', - name: remoteEntryCallback, - }; - - const containerModules = _.mapValues( - this.pkg.consolePlugin.exposedModules || {}, - (moduleRequest, moduleName) => ({ - import: moduleRequest, - name: `exposed-${moduleName}`, - }), - ); - - // https://webpack.js.org/plugins/module-federation-plugin/#sharing-hints - const sharedModules = Object.entries(sharedPluginModulesMetadata).reduce( - (acc, [moduleRequest, moduleMetadata]) => { - const adaptedMetadata = _.defaults({}, moduleMetadata, { - singleton: true, - allowFallback: false, - }); - - const moduleConfig: Record = { - singleton: adaptedMetadata.singleton, - }; - - if (!adaptedMetadata.allowFallback) { - moduleConfig.import = false; - } + compiler.options.output.publicPath = publicPath; - acc[moduleRequest] = moduleConfig; - return acc; + new DynamicRemotePlugin({ + pluginMetadata: { + name, + version, + dependencies, + customProperties: _.merge({}, customProperties, { + console: { displayName, description, disableStaticPlugins }, + }), + exposedModules, }, - {}, - ); - - compiler.options.output.publicPath = publicPath; - compiler.options.output.uniqueName = containerName; - - // Generate webpack federated module container assets - new webpack.container.ModuleFederationPlugin({ - name: containerName, - library: containerLibrary, - filename: remoteEntryFile, - exposes: containerModules, - shared: sharedModules, + extensions, + sharedModules: getWebpackSharedModules(), + entryCallbackSettings: { name: 'loadPluginEntry', pluginID: `${name}@${version}` }, + // TODO(vojtech): use [fullhash] placeholder for production builds once the runtime + // infrastructure is updated according to https://issues.redhat.com/browse/CONSOLE-3769 + entryScriptFilename: remoteEntryFile, + pluginManifestFilename: pluginManifestFile, }).apply(compiler); - // ModuleFederationPlugin does not generate a container entry when the provided - // exposes option is empty; we fix that by invoking the ContainerPlugin manually - if (_.isEmpty(containerModules)) { - new webpack.container.ContainerPlugin({ - name: containerName, - library: containerLibrary, - filename: remoteEntryFile, - exposes: containerModules, - }).apply(compiler); - } + validateConsoleBuildMetadata(pluginMetadata).report(); + + if (validateExtensionIntegrity) { + compiler.hooks.emit.tap(ConsoleRemotePlugin.name, (compilation) => { + const result = new ExtensionValidator('Console plugin extensions').validate( + compilation, + extensions, + exposedModules ?? {}, + ); - // Generate and/or post-process Console plugin assets - new ConsoleAssetPlugin( - this.pkg, - remoteEntryCallback, - this.options.validateExtensionSchema, - this.options.validateExtensionIntegrity && - process.env.CONSOLE_PLUGIN_SKIP_EXT_VALIDATOR !== 'true', - ).apply(compiler); + if (result.hasErrors()) { + const error = new webpack.WebpackError('ExtensionValidator has reported errors'); + error.details = result.formatErrors(); + error.file = extensionsFile; + compilation.errors.push(error); + } + }); + } } } diff --git a/frontend/packages/console-plugin-sdk/src/__tests__/store.spec.ts b/frontend/packages/console-plugin-sdk/src/__tests__/store.spec.ts index 61cb140ea95e..d2d6d07e53a1 100644 --- a/frontend/packages/console-plugin-sdk/src/__tests__/store.spec.ts +++ b/frontend/packages/console-plugin-sdk/src/__tests__/store.spec.ts @@ -608,7 +608,7 @@ describe('PluginStore', () => { { status: 'Loaded', pluginID: 'Test@1.2.3', - metadata: _.omit(manifest, 'extensions'), + metadata: _.omit(manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: false, }, ]); @@ -993,7 +993,7 @@ describe('PluginStore', () => { { status: 'Loaded', pluginID: 'TestA@1.2.3', - metadata: _.omit(manifest, 'extensions'), + metadata: _.omit(manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: false, }, { @@ -1008,7 +1008,7 @@ describe('PluginStore', () => { { status: 'Loaded', pluginID: 'TestA@1.2.3', - metadata: _.omit(manifest, 'extensions'), + metadata: _.omit(manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: true, }, { @@ -1023,7 +1023,7 @@ describe('PluginStore', () => { { status: 'Loaded', pluginID: 'TestA@1.2.3', - metadata: _.omit(manifest, 'extensions'), + metadata: _.omit(manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: true, }, { diff --git a/frontend/packages/console-plugin-sdk/src/codegen/__tests__/active-plugins.spec.ts b/frontend/packages/console-plugin-sdk/src/codegen/__tests__/active-plugins.spec.ts index 9dd2c61df98f..bb7d77885043 100644 --- a/frontend/packages/console-plugin-sdk/src/codegen/__tests__/active-plugins.spec.ts +++ b/frontend/packages/console-plugin-sdk/src/codegen/__tests__/active-plugins.spec.ts @@ -4,7 +4,7 @@ import { extensionsFile } from '@console/dynamic-plugin-sdk/src/constants'; import { EncodedCodeRef } from '@console/dynamic-plugin-sdk/src/types'; import * as jsoncModule from '@console/dynamic-plugin-sdk/src/utils/jsonc'; import { ValidationResult } from '@console/dynamic-plugin-sdk/src/validation/ValidationResult'; -import * as assetPluginModule from '@console/dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin'; +import * as remotePluginModule from '@console/dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin'; import { Extension } from '../../typings'; import { trimStartMultiLine } from '../../utils/string'; import { getTemplatePackage } from '../../utils/test-utils'; @@ -12,7 +12,11 @@ import * as activePluginsModule from '../active-plugins'; import { PluginPackage } from '../plugin-resolver'; const parseJSONC = jest.spyOn(jsoncModule, 'parseJSONC'); -const validateExtensionsFileSchema = jest.spyOn(assetPluginModule, 'validateExtensionsFileSchema'); + +const validateConsoleExtensionsFileSchema = jest.spyOn( + remotePluginModule, + 'validateConsoleExtensionsFileSchema', +); const { getActivePluginsModule, @@ -22,7 +26,7 @@ const { } = activePluginsModule; beforeEach(() => { - [parseJSONC, validateExtensionsFileSchema].forEach((mock) => mock.mockReset()); + [parseJSONC, validateConsoleExtensionsFileSchema].forEach((mock) => mock.mockReset()); }); describe('getActivePluginsModule', () => { @@ -280,7 +284,7 @@ describe('getDynamicExtensions', () => { fsExistsSync.mockImplementation(() => true); parseJSONC.mockImplementation(() => extensionsJSON); - validateExtensionsFileSchema.mockImplementation(() => new ValidationResult('test')); + validateConsoleExtensionsFileSchema.mockImplementation(() => new ValidationResult('test')); getExecutableCodeRefSourceMock.mockImplementation((ref: EncodedCodeRef) => { if (_.isEqual(ref, { $codeRef: 'a.b' })) { @@ -328,7 +332,10 @@ describe('getDynamicExtensions', () => { expect(fsExistsSync).toHaveBeenCalledWith(extensionsFilePath); expect(parseJSONC).toHaveBeenCalledWith(extensionsFilePath); - expect(validateExtensionsFileSchema).toHaveBeenCalledWith(extensionsJSON, extensionsFilePath); + expect(validateConsoleExtensionsFileSchema).toHaveBeenCalledWith( + extensionsJSON, + extensionsFilePath, + ); expect(getExecutableCodeRefSourceMock.mock.calls.length).toBe(2); expect(getExecutableCodeRefSourceMock.mock.calls[0]).toEqual([ @@ -367,7 +374,7 @@ describe('getDynamicExtensions', () => { expect(codeRefTransformer).not.toHaveBeenCalled(); expect(fsExistsSync).toHaveBeenCalledWith(extensionsFilePath); expect(parseJSONC).not.toHaveBeenCalled(); - expect(validateExtensionsFileSchema).not.toHaveBeenCalled(); + expect(validateConsoleExtensionsFileSchema).not.toHaveBeenCalled(); expect(getExecutableCodeRefSourceMock).not.toHaveBeenCalled(); }); @@ -386,7 +393,7 @@ describe('getDynamicExtensions', () => { fsExistsSync.mockImplementation(() => true); parseJSONC.mockImplementation(() => extensionsJSON); - validateExtensionsFileSchema.mockImplementation(() => { + validateConsoleExtensionsFileSchema.mockImplementation(() => { const result = new ValidationResult('test'); result.addError('schema validation error'); return result; @@ -400,7 +407,10 @@ describe('getDynamicExtensions', () => { expect(codeRefTransformer).not.toHaveBeenCalled(); expect(fsExistsSync).toHaveBeenCalledWith(extensionsFilePath); expect(parseJSONC).toHaveBeenCalledWith(extensionsFilePath); - expect(validateExtensionsFileSchema).toHaveBeenCalledWith(extensionsJSON, extensionsFilePath); + expect(validateConsoleExtensionsFileSchema).toHaveBeenCalledWith( + extensionsJSON, + extensionsFilePath, + ); expect(getExecutableCodeRefSourceMock).not.toHaveBeenCalled(); }); @@ -423,7 +433,7 @@ describe('getDynamicExtensions', () => { fsExistsSync.mockImplementation(() => true); parseJSONC.mockImplementation(() => extensionsJSON); - validateExtensionsFileSchema.mockImplementation(() => new ValidationResult('test')); + validateConsoleExtensionsFileSchema.mockImplementation(() => new ValidationResult('test')); getExecutableCodeRefSourceMock.mockImplementation( ( @@ -457,7 +467,10 @@ describe('getDynamicExtensions', () => { expect(fsExistsSync).toHaveBeenCalledWith(extensionsFilePath); expect(parseJSONC).toHaveBeenCalledWith(extensionsFilePath); - expect(validateExtensionsFileSchema).toHaveBeenCalledWith(extensionsJSON, extensionsFilePath); + expect(validateConsoleExtensionsFileSchema).toHaveBeenCalledWith( + extensionsJSON, + extensionsFilePath, + ); expect(getExecutableCodeRefSourceMock.mock.calls.length).toBe(2); expect(getExecutableCodeRefSourceMock.mock.calls[0]).toEqual([ diff --git a/frontend/packages/console-plugin-sdk/src/codegen/active-plugins.ts b/frontend/packages/console-plugin-sdk/src/codegen/active-plugins.ts index f3553036d63d..b3ba601d5580 100644 --- a/frontend/packages/console-plugin-sdk/src/codegen/active-plugins.ts +++ b/frontend/packages/console-plugin-sdk/src/codegen/active-plugins.ts @@ -8,7 +8,7 @@ import { ConsoleExtensionsJSON } from '@console/dynamic-plugin-sdk/src/schema/co import { EncodedCodeRef } from '@console/dynamic-plugin-sdk/src/types'; import { parseJSONC } from '@console/dynamic-plugin-sdk/src/utils/jsonc'; import { ValidationResult } from '@console/dynamic-plugin-sdk/src/validation/ValidationResult'; -import { validateExtensionsFileSchema } from '@console/dynamic-plugin-sdk/src/webpack/ConsoleAssetPlugin'; +import { validateConsoleExtensionsFileSchema } from '@console/dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin'; import { Extension, ActivePlugin } from '../typings'; import { trimStartMultiLine } from '../utils/string'; import { consolePkgScope, PluginPackage } from './plugin-resolver'; @@ -123,7 +123,7 @@ export const getDynamicExtensions = ( } const ext = parseJSONC(extensionsFilePath); - const schemaValidationResult = validateExtensionsFileSchema(ext, extensionsFilePath); + const schemaValidationResult = validateConsoleExtensionsFileSchema(ext, extensionsFilePath); if (schemaValidationResult.hasErrors()) { errorCallback(schemaValidationResult.formatErrors()); diff --git a/frontend/packages/console-plugin-sdk/src/store.ts b/frontend/packages/console-plugin-sdk/src/store.ts index 226ef31954de..c0e00db42ec7 100644 --- a/frontend/packages/console-plugin-sdk/src/store.ts +++ b/frontend/packages/console-plugin-sdk/src/store.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import * as _ from 'lodash'; -import { ConsolePluginManifestJSON } from '@console/dynamic-plugin-sdk/src/schema/plugin-manifest'; +import { StandardConsolePluginManifest } from '@console/dynamic-plugin-sdk/src/build-types'; import { Extension, LoadedExtension, ActivePlugin } from './typings'; export const sanitizeExtension = (e: E): E => { @@ -119,7 +119,7 @@ export class PluginStore { addDynamicPlugin( pluginID: string, - manifest: ConsolePluginManifestJSON, + manifest: StandardConsolePluginManifest, resolvedExtensions: Extension[], ) { if (this.loadedDynamicPlugins.has(pluginID)) { @@ -145,7 +145,7 @@ export class PluginStore { enabled: false, }); - (manifest.disableStaticPlugins || []) + (manifest.customProperties?.console?.disableStaticPlugins ?? []) .filter( (pluginName) => !this.disabledStaticPluginNames.has(pluginName) && @@ -228,7 +228,7 @@ export class PluginStore { acc.push({ status: 'Loaded', pluginID, - metadata: _.omit(plugin.manifest, 'extensions'), + metadata: _.omit(plugin.manifest, ['extensions', 'loadScripts', 'registrationMethod']), enabled: plugin.enabled, }); return acc; @@ -285,9 +285,12 @@ type StaticPlugin = { extensions: LoadedExtension[]; }; -type DynamicPluginManifest = Readonly; +type DynamicPluginManifest = Readonly; -type DynamicPluginMetadata = Omit; +type DynamicPluginMetadata = Omit< + DynamicPluginManifest, + 'extensions' | 'loadScripts' | 'registrationMethod' +>; type LoadedDynamicPlugin = { manifest: DynamicPluginManifest; diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 79b0fb1e5dac..97505b35dfd1 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -87,6 +87,9 @@ const config: Configuration = { { test: /\.glsl$/, loader: 'raw!glslify' }, { test: /(\.jsx?)|(\.tsx?)$/, + // Some vendor libraries may use newer ECMAScript language features + // so we need to process their code prior to webpack compilation. + // TODO: revisit the exclude list once Console upgrades to webpack 5+ exclude: /node_modules\/(?!(bitbucket|ky)\/)/, use: [ { loader: 'cache-loader' }, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 509e2f381df3..a29eb6e48219 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1961,6 +1961,13 @@ once "^1.4.0" universal-user-agent "^4.0.0" +"@openshift/dynamic-plugin-sdk-webpack@file:../../dynamic-plugin-sdk/packages/lib-webpack": + version "3.0.1" + dependencies: + lodash "^4.17.21" + semver "^7.3.7" + yup "^0.32.11" + "@patternfly/patternfly@4.139.2": version "4.139.2" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.139.2.tgz#c09b02e1d42e960d039ebb091393227ffe30f5f2" @@ -2867,6 +2874,11 @@ version "4.14.106" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd" +"@types/lodash@^4.14.175": + version "4.14.198" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" + integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== + "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -3763,6 +3775,11 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== +acorn@^8.7.1: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + adjust-sourcemap-loader@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz#e33fde95e50db9f2a802e3647e311d2fc5000c69" @@ -7818,18 +7835,18 @@ enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.8.0: - version "5.8.2" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" - integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== +enhanced-resolve@^5.10.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" -enhanced-resolve@^5.9.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" - integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== +enhanced-resolve@^5.8.0: + version "5.8.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -13694,6 +13711,11 @@ nan@^2.3.0, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^2.1.0: version "2.1.11" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" @@ -15203,6 +15225,11 @@ property-expr@^1.5.0: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== +property-expr@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== + protobufjs@^6.8.8: version "6.10.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" @@ -19440,10 +19467,10 @@ watchpack@^2.2.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -watchpack@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -19625,6 +19652,36 @@ webpack-virtual-modules@^0.4.3: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.3.tgz#cd597c6d51d5a5ecb473eea1983a58fa8a17ded9" integrity sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw== +webpack@5.75.0: + version "5.75.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152" + integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + webpack@^4.46.0: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" @@ -19684,36 +19741,6 @@ webpack@^5.38.1: watchpack "^2.2.0" webpack-sources "^3.2.0" -webpack@^5.73.0: - version "5.73.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" - integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.3" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" - webpack-sources "^3.2.3" - websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" @@ -20298,6 +20325,19 @@ yup@^0.27.0: synchronous-promise "^2.0.6" toposort "^2.0.2" +yup@^0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" + zen-observable-ts@^0.8.21: version "0.8.21" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"