diff --git a/dynamic-demo-plugin/package.json b/dynamic-demo-plugin/package.json index f7fcbf585d3..ac0200b34a5 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 4400ad680a6..e970fd8969b 100644 --- a/dynamic-demo-plugin/yarn.lock +++ b/dynamic-demo-plugin/yarn.lock @@ -44,6 +44,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.15.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.4.tgz#36fa1d2b36db873d25ec631dcc4923fdc1cf2e2e" + integrity sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg== + dependencies: + regenerator-runtime "^0.14.0" + "@discoveryjs/json-ext@^0.5.0": version "0.5.2" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" @@ -87,6 +94,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" "^4.0.0" ajv "^6.12.3" chalk "2.4.x" comment-json "4.x" @@ -94,7 +102,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,15 @@ sanitize-html "^2.3.2" showdown "1.8.6" +"@openshift/dynamic-plugin-sdk-webpack@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.0.0.tgz#5986f6a4eacd60c8f71ba7ef41d6891648eaa1d7" + integrity sha512-QaXCdI1kaRZsCTuKX0EcBy0QdS8GEqDICQO3KUVhcb/LCmJojC2AllTPuNSRH/WEnPEzL51F1lz3qrlJxQEtkQ== + dependencies: + lodash "^4.17.21" + semver "^7.3.7" + yup "^0.32.11" + "@patternfly/patternfly@5.0.2": version "5.0.2" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-5.0.2.tgz#f5daf2c98ccb85e6466d42fd61d39ba3c10ed532" @@ -217,6 +234,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.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -410,22 +432,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" @@ -440,10 +460,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.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== aggregate-error@^3.0.0: version "3.0.1" @@ -882,14 +902,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" @@ -1221,10 +1242,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" @@ -1333,20 +1354,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" @@ -1524,10 +1531,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" @@ -1744,10 +1747,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" @@ -1828,9 +1827,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" @@ -1864,12 +1864,6 @@ is-core-module@^2.13.0: 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" - dependencies: - has "^1.0.3" - is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1932,10 +1926,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" @@ -2228,10 +2218,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" @@ -2295,6 +2281,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" @@ -2356,12 +2347,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" @@ -2400,12 +2385,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" @@ -2525,14 +2504,10 @@ 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" -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" @@ -2866,6 +2841,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -3069,11 +3049,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" @@ -3192,12 +3173,14 @@ resolve@^1.10.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.9.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + 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" @@ -3309,6 +3292,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" @@ -3367,10 +3357,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" @@ -3514,10 +3500,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" @@ -3670,6 +3652,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" @@ -3876,30 +3863,31 @@ walk-sync@^2.2.0: matcher-collection "^2.0.0" minimatch "^3.0.4" -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: @@ -3921,21 +3909,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" @@ -3948,7 +3936,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: @@ -4044,3 +4032,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/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx index 6b75168f127..e8c84b8b21c 100644 --- a/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx +++ b/frontend/packages/console-app/src/components/console-operator/ConsoleOperatorConfig.tsx @@ -107,7 +107,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: !!obj?.spec?.plugins?.includes(plugin.metadata.name), status: plugin.status, }; @@ -156,7 +156,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 245ec0dd154..c258c760dc2 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/README.md +++ b/frontend/packages/console-dynamic-plugin-sdk/README.md @@ -9,14 +9,6 @@ released, installed and upgraded independently from each other. To ensure compat other plugins, each plugin must declare its dependencies using [semantic version](https://semver.org/) ranges. -## Related Documentation - -_[Extension Documentation](./docs/console-extensions.md)_ - Detailed documentation of every available console extension point. - -_[API Documentation](./docs/api.md)_ - Detailed documentation of hooks, components, and other APIs provided by this package. - -_[OpenShift Console Dynamic Plugins feature page](https://github.com/openshift/enhancements/blob/master/enhancements/console/dynamic-plugins.md)_ - A high level overview of dynamic plugins in relation to OLM operators and cluster administration. - Example project structure: ``` @@ -28,18 +20,30 @@ dynamic-demo-plugin/ └── webpack.config.ts ``` -## SDK packages +## Related Documentation + +_[Extension Documentation][console-doc-extensions]_ - Detailed documentation of all available Console +extension points. -| Package Name | Description | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `@openshift-console/dynamic-plugin-sdk` | Provides core APIs, types and utilities used by dynamic plugins at runtime. | -| `@openshift-console/dynamic-plugin-sdk-webpack` | Provides webpack plugin `ConsoleRemotePlugin` used to build all dynamic plugin assets. | -| `@openshift-console/dynamic-plugin-sdk-internal` | Internal package exposing additional code. | +_[API Documentation][console-doc-api]_ - Detailed documentation of React components, hooks and other APIs +provided by Console to its dynamic plugins. + +_[OpenShift Console Dynamic Plugins feature page][console-doc-feature-page]_ - A high-level overview of +dynamic plugins in relation to OLM operators and cluster administration. + +## Distributable SDK package overview + +| Package Name | Description | +| ------------ | ----------- | +| `@openshift-console/dynamic-plugin-sdk` | Provides core APIs, types and utilities used by dynamic plugins at runtime. | +| `@openshift-console/dynamic-plugin-sdk-webpack` | Provides webpack `ConsoleRemotePlugin` used to build all dynamic plugin assets. | +| `@openshift-console/dynamic-plugin-sdk-internal` | Internal package exposing additional code. | | `@openshift-console/plugin-shared` | Provides reusable components and utility functions to build OCP dynamic plugins. Compatible with multiple versions of OpenShift Console. | ## OpenShift Console Versions vs SDK Versions -Not all NPM packages are fully compatible with all versions of the Console. This table will help align compatible versions of the SDK Packages to versions of the OpenShift Console. +Not all NPM packages are fully compatible with all versions of the Console. This table will help align +compatible versions of distributable SDK packages to versions of the OpenShift Console. | Console Version | SDK Package | Last Package Version | | ----------------- | ----------------------------------------------- | -------------------- | @@ -51,20 +55,20 @@ Not all NPM packages are fully compatible with all versions of the Console. This | | `@openshift-console/dynamic-plugin-sdk-webpack` | 0.0.6 | | 4.9.x **[Dev]** | `@openshift-console/dynamic-plugin-sdk` | 0.0.0-alpha18 | -Notes +Notes: - **[Tech]** - Release 4.10 was Tech Preview for the SDK packages - **[Dev]** - Release 4.9 was Dev Preview for the SDK packages -## `package.json` +## Plugin metadata -Plugin metadata is declared via the `consolePlugin` object. +Older versions of webpack `ConsoleRemotePlugin` assumed that the plugin metadata is specified via +`consolePlugin` object within the `package.json` file, for example: ```jsonc { "name": "dynamic-demo-plugin", "version": "0.0.0", - "private": true, // scripts, dependencies, devDependencies, ... "consolePlugin": { "name": "console-demo-plugin", @@ -81,32 +85,40 @@ Plugin metadata is declared via the `consolePlugin` object. } ``` -`consolePlugin.name` is the plugin's unique identifier. It should be the same as `metadata.name` -of the corresponding `ConsolePlugin` resource used to represent the plugin on the cluster. -Therefore, it must be a valid +Newer versions of webpack `ConsoleRemotePlugin` allow passing the plugin metadata directly as an +object, for example: + +```ts +new ConsoleRemotePlugin({ + pluginMetadata: { /* same metadata like above */ }, +}) +``` + +`name` serves as the plugin's unique identifier. Its value should be the same as `metadata.name` +of the corresponding `ConsolePlugin` resource on the cluster. Therefore, it must be a valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names). -`consolePlugin.version` must be [semver](https://semver.org/) compliant. +`version` must be [semver](https://semver.org/) compliant version string. -Dynamic plugins can expose modules representing additional code to be referenced, loaded and executed +Dynamic plugins can expose modules representing plugin code that can be referenced, loaded and executed at runtime. A separate [webpack chunk](https://webpack.js.org/guides/code-splitting/) is generated for -each entry in `consolePlugin.exposedModules` object. Exposed modules are resolved relative to plugin's -webpack `context` option. +each entry in the `exposedModules` object. Exposed modules are resolved relative to the plugin's webpack +`context` option. The `@console/pluginAPI` dependency is optional and refers to Console versions this dynamic plugin is -compatible with. The `consolePlugin.dependencies` object may also refer to other dynamic plugins that -are required for this dynamic plugin to work correctly. For dependencies whose versions may include -a [semver pre-release](https://semver.org/#spec-item-9) identifier, adapt your semver range constraint -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. +compatible with. The `dependencies` object may also refer to other dynamic plugins that are required for +this plugin to work correctly. For dependencies where the version string may include a +[semver pre-release](https://semver.org/#spec-item-9) identifier, adapt your semver range constraint +(dependency value) 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`. -## `console-extensions.json` +## Extensions contributed by the plugin -Declares all extensions contributed by the plugin. +Older versions of webpack `ConsoleRemotePlugin` assumed that the list of extensions contributed by the +plugin is specified via the `console-extensions.json` file, for example: ```jsonc +// This file is parsed as JSONC (JSON with Comments) [ { "type": "console.flag", @@ -128,13 +140,30 @@ Declares all extensions contributed by the plugin. ] ``` -Depending on extension `type`, the `properties` object may contain code references, encoded as object -literals `{ $codeRef: string }`. When loading dynamic plugins, encoded code references are transformed -into functions `() => Promise` used to load the referenced objects. +Newer versions of webpack `ConsoleRemotePlugin` allow passing the extension list directly as an array +of objects, for example: + +```ts +new ConsoleRemotePlugin({ + extensions: [ /* same extensions like above */ ], +}) +``` + +Each extension a single instance of extending the Console application's functionality. Extensions are +declarative and expressed as plain static objects. + +Extension `type` determines the kind of extension to perform, while any data and/or code necessary to +interpret such extensions are declared through their `properties`. -The `$codeRef` value should be formatted as either `moduleName.exportName` (referring to a named export) -or `moduleName` (referring to the `default` export). Only the plugin's exposed modules (i.e. the keys of -`consolePlugin.exposedModules` object) may be used in code references. +Extensions may contain code references pointing to specific modules exposed by the plugin. For example: + +- `{ $codeRef: 'barUtils' }` - refers to `default` export of `barUtils` module +- `{ $codeRef: 'barUtils.testHandler' }` - refers to `testHandler` export of `barUtils` module + + +When loading dynamic plugins, all encoded code references `{ $codeRef: string }` are transformed into +functions `() => Promise` used to load the referenced objects on demand. Only the plugin's exposed +modules (i.e. the keys of `exposedModules` object) may be used in code references. ## Webpack config @@ -142,13 +171,14 @@ Dynamic plugins _must_ be built with [webpack](https://webpack.js.org/) in order seamlessly integrate with Console application at runtime. Use webpack version 5+ which includes native support for module federation. -All dynamic plugin assets are managed via webpack plugin `ConsoleRemotePlugin`. +All dynamic plugin assets are generated via webpack `ConsoleRemotePlugin`. ```ts -const { ConsoleRemotePlugin } = require('@openshift-console/dynamic-plugin-sdk-webpack'); +import { ConsoleRemotePlugin } from '@openshift-console/dynamic-plugin-sdk-webpack'; +import { Configuration } from 'webpack'; -const config = { - // 'entry' is optional, but unrelated to plugin assets +const config: Configuration = { + entry: {}, // Plugin container entry is generated by DynamicRemotePlugin plugins: [new ConsoleRemotePlugin()], // ... rest of webpack configuration }; @@ -156,11 +186,7 @@ const config = { export default config; ``` -`ConsoleRemotePlugin` automatically detects your plugin's metadata and extension declarations and -generates the corresponding assets. - -`ConsoleRemotePlugin` constructor supports an options object used to tweak its behavior. Refer to -`ConsoleRemotePluginOptions` type for details on supported options. +Refer to `ConsoleRemotePluginOptions` type for details on supported Console plugin build options. ## Generated assets @@ -168,20 +194,21 @@ Building the above example plugin produces the following assets: ``` dynamic-demo-plugin/dist/ +├── exposed-barUtils-chunk.js ├── plugin-entry.js -├── plugin-manifest.json -└── utils_bar_ts-chunk.js +└── plugin-manifest.json ``` -`plugin-manifest.json`: dynamic plugin manifest. Contains both metadata and extension declarations to -be parsed and interpreted by Console at runtime. This is the first plugin asset loaded by Console. +`plugin-manifest.json` is the dynamic plugin manifest. It contains both plugin metadata and extension +declarations to be loaded and interpreted by Console at runtime. This is the first plugin asset loaded +by Console. -`plugin-entry.js`: [webpack container entry chunk](https://webpack.js.org/concepts/module-federation/#low-level-concepts). -Provides asynchronous access to specific modules exposed by the plugin. Loaded right after the plugin -manifest. +`plugin-entry.js` is the +[webpack container entry chunk](https://webpack.js.org/concepts/module-federation/#low-level-concepts). +It provides access to specific modules exposed by the plugin. It's loaded right after the plugin manifest. -`utils_bar_ts-chunk.js`: webpack chunk for the exposed `barUtils` module. Loaded via the plugin entry -chunk when needed. +`exposed-barUtils-chunk.js` is the generated webpack chunk for `barUtils` exposed module. It's loaded +via the plugin entry chunk (`plugin-entry.js`) when needed. ## Plugin development @@ -189,6 +216,7 @@ Run Bridge locally and instruct it to proxy e.g. `/api/plugins/console-demo-plug to your local plugin asset server (web server hosting the plugin's generated assets): ```sh +# Note that the plugin's base URL should have a trailing slash ./bin/bridge -plugins console-demo-plugin=http://localhost:9001/ ``` @@ -220,8 +248,6 @@ list of plugin names (disable specific plugins) or an empty string (disable all to ensure a single version of React etc. is loaded and used by the application. - Enabling a plugin makes all of its extensions available for consumption. Individual extensions cannot be enabled or disabled separately. -- Failure to resolve a code reference (unable to load module, missing module export etc.) will disable - the plugin. ## Publishing SDK packages @@ -253,6 +279,14 @@ If the given package doesn't exist in npm registry, add `--access public` to `ya ## Future Deprecations in Shared Plugin Dependencies -Certain packages are currently in the shared plugin dependencies that will be removed in the future. Plugin authors will need to manually add these items to their configurations or chose other options: +Console provides certain packages as shared modules to all of its dynamic plugins. Some of these shared +modules may be removed in the future. Plugin authors will need to manually add these items to their webpack +configs or choose other options. + +The list of shared modules planned for deprecation: + +- `react-helmet` -_- react-helmet_ +[console-doc-extensions]: ./docs/console-extensions.md +[console-doc-api]: ./docs/api.md +[console-doc-feature-page]: https://github.com/openshift/enhancements/blob/master/enhancements/console/dynamic-plugins.md diff --git a/frontend/packages/console-dynamic-plugin-sdk/package.json b/frontend/packages/console-dynamic-plugin-sdk/package.json index 0068c293328..7bd06016f09 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": "^4.0.0", "@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 cf31b803060..43eb6d71271 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 c89ccdad198..6c25a97800e 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/scripts/package-definitions.ts @@ -151,7 +151,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/build-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts new file mode 100644 index 00000000000..3082c76835a --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/build-types.ts @@ -0,0 +1,54 @@ +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 + // eslint-disable-next-line dot-notation + m['baseURL'] && typeof m['baseURL'] === 'string'; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/constants.ts b/frontend/packages/console-dynamic-plugin-sdk/src/constants.ts index 65608428887..b80d727d3b2 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/constants.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/constants.ts @@ -1,3 +1 @@ export const extensionsFile = 'console-extensions.json'; -export const pluginManifestFile = 'plugin-manifest.json'; -export const remoteEntryFile = 'plugin-entry.js'; 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 d5ea58f246e..93395d76388 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 4a6c7f2e59c..9133afc1ac2 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 f58f26a563a..704bd979186 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'))); @@ -411,7 +473,7 @@ describe('loadAndEnablePlugin', () => { expect(onError).toHaveBeenCalledTimes(3); expect(onError).toHaveBeenLastCalledWith( - 'Failed to load entry script of plugin Test', + 'Failed to load scripts of plugin Test', new Error('boom3'), ); 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 97e03bda29a..f96d1f1a94f 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 032b7829a20..6c20c9425f2 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 @@ -4,12 +4,16 @@ import * as _ from 'lodash'; import * as semver from 'semver'; import { PluginStore } from '@console/plugin-sdk/src/store'; import { getRandomChars } from '@console/shared/src/utils/utils'; +import { + AnyConsolePluginManifest, + StandardConsolePluginManifest, + isStandardPluginManifest, +} from '../build-types'; import { resolveEncodedCodeRefs } from '../coderefs/coderef-resolver'; -import { remoteEntryFile } from '../constants'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; import { initSharedPluginModules } from '../shared-modules-init'; import { RemoteEntryModule } from '../types'; import { ErrorWithCause } from '../utils/error/custom-error'; +import { settleAllPromises } from '../utils/promise'; import { resolveURL } from '../utils/url'; import { resolvePluginDependencies } from './plugin-dependencies'; import { fetchPluginManifest } from './plugin-manifest'; @@ -17,18 +21,36 @@ import { getPluginID } from './plugin-utils'; 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 +64,47 @@ 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; - }); + console.info(`Loading scripts of plugin ${pluginID}`); - const script = document.createElement('script'); - script.id = getScriptElementID(manifest); - script.src = scriptURL; - script.async = true; + // eslint-disable-next-line promise/catch-or-return + settleAllPromises( + manifest.loadScripts.map((scriptName) => { + const scriptID = getScriptElementID(manifest.name, scriptName); - script.onload = () => { - if (pluginMap.get(pluginID).entryCallbackFired) { - resolve(pluginID); - } else { - reject(new Error(`Entry script for plugin ${pluginID} loaded without callback`)); + const scriptURL = resolveURL(manifest.baseURL, scriptName, (url) => { + url.search = `?cacheBuster=${getRandomChars()}`; + return url; + }); + + console.info(`Loading plugin script from ${scriptURL}`); + + 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 +153,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: ['plugin-entry.js'], + 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,9 +210,9 @@ export const loadAndEnablePlugin = async ( } try { - await loadDynamicPlugin(url, manifest); + await loadDynamicPlugin(manifest); } catch (e) { - onError(`Failed to load entry script of plugin ${pluginName}`, e); + onError(`Failed to load scripts of plugin ${pluginName}`, e); return; } @@ -164,7 +226,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 9ae6b96e226..f8f178dbdc2 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 @@ -1,6 +1,5 @@ import * as _ from 'lodash'; import { coFetch } from '@console/internal/co-fetch'; -import { pluginManifestFile } from '../constants'; import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; import { resolveURL } from '../utils/url'; @@ -19,12 +18,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}']`, + ); }); } @@ -32,7 +34,7 @@ export const validatePluginManifestSchema = async ( }; export const fetchPluginManifest = async (baseURL: string) => { - const url = resolveURL(baseURL, pluginManifestFile); + const url = resolveURL(baseURL, 'plugin-manifest.json'); // eslint-disable-next-line no-console console.info(`Loading plugin manifest from ${url}`); @@ -40,5 +42,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 e7150745dd7..209b249dfc2 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 51332de7bf5..e65ff2d2932 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 e796221a9ff..00000000000 --- 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.ts b/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules.ts index 92d29cf72bf..cb9e921491f 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/shared-modules.ts @@ -43,7 +43,7 @@ type SharedModuleNames = typeof sharedPluginModules[number]; /** * Metadata associated with the shared modules. */ -export const sharedPluginModulesMetadata: Record = { +const sharedPluginModulesMetadata: Record = { '@openshift-console/dynamic-plugin-sdk': { singleton: true, allowFallback: false }, '@openshift-console/dynamic-plugin-sdk-internal': { singleton: true, allowFallback: false }, '@patternfly/react-core': {}, @@ -60,6 +60,9 @@ export const sharedPluginModulesMetadata: Record => { 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 00000000000..4f43302a4fe --- /dev/null +++ b/frontend/packages/console-dynamic-plugin-sdk/src/utils/schema.ts @@ -0,0 +1,15 @@ +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()); + + // eslint-disable-next-line + 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 230ba08b26f..231de5c2058 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,6 +1,5 @@ +import { StandardConsolePluginManifest } from '../build-types'; import { applyCodeRefSymbol } from '../coderefs/coderef-resolver'; -import { ConsoleExtensionsJSON } from '../schema/console-extensions'; -import { ConsolePluginManifestJSON } from '../schema/plugin-manifest'; import { Extension, RemoteEntryModule, CodeRef, Update } from '../types'; export const getPluginManifest = ( @@ -8,11 +7,14 @@ export const getPluginManifest = ( 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: ['plugin-entry.js'], + 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 00000000000..f49a3db939e --- /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 c2dd8aee9a0..4fe8ae3108b 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 { ConsolePluginBuildMetadata } from '../build-types'; import { isEncodedCodeRef, parseEncodedCodeRefValue } from '../coderefs/coderef-resolver'; -import { ConsolePluginMetadata } from '../schema/plugin-package'; 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 78d2af1d7e0..9aa099d149e 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 7fb3f040def..00000000000 --- 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 d002051f129..f49e0974d4d 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,45 @@ import chalk from 'chalk'; +import * as semver from 'semver'; + +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`); + } + } +} export class ValidationResult { private readonly errors: string[] = []; + readonly assertions = new ValidationAssertions(this); + constructor(private readonly description: string) {} assertThat(condition: boolean, message: string) { 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 18e77926a70..3eed43575db 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 33e01de2907..00000000000 --- 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 5b115f261da..60177540c19 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,65 @@ +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 } from '../constants'; import { sharedPluginModules, getSharedModuleMetadata } 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'); +// https://webpack.js.org/plugins/module-federation-plugin/#sharing-hints +const getWebpackSharedModules = () => + sharedPluginModules.reduce((acc, moduleName) => { + const { singleton, allowFallback } = getSharedModuleMetadata(moduleName); + const moduleConfig: Record = { singleton }; - if (pkg.consolePlugin) { - validator.validate(schema, pkg.consolePlugin, 'pkg.consolePlugin'); + if (!allowFallback) { + moduleConfig.import = false; + } - validator.assert.validDNSSubdomainName(pkg.consolePlugin.name, 'pkg.consolePlugin.name'); - validator.assert.validSemverString(pkg.consolePlugin.version, 'pkg.consolePlugin.version'); + acc[moduleName] = moduleConfig; + return acc; + }, {}); - if (_.isPlainObject(pkg.consolePlugin.dependencies)) { - Object.entries(pkg.consolePlugin.dependencies).forEach(([depName, versionRange]) => { - validator.assert.validSemverRangeString( - versionRange, - `pkg.consolePlugin.dependencies['${depName}']`, - ); - }); - } - } else { - validator.result.addError('pkg.consolePlugin object is missing'); - } +/** + * 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; +}; - return validator.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'); @@ -55,7 +74,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( @@ -70,154 +89,155 @@ 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? + * List of extensions contributed by the plugin. * - * Console provided shared modules can be 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. - * - * @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 can be 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: - * - 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. + * Refer to `frontend/packages/console-dynamic-plugin-sdk/src/shared-modules.ts` for details on + * Console application vs. dynamic plugins shared module configuration. * * @see {@link sharedPluginModules} + * @see {@link getSharedModuleMetadata} */ -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 = sharedPluginModules.reduce((acc, moduleName) => { - const { singleton, allowFallback } = getSharedModuleMetadata(moduleName); - const moduleConfig: Record = { singleton }; - if (!allowFallback) { - moduleConfig.import = false; - } - acc[moduleName] = moduleConfig; - return acc; - }, {}); 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, + + new DynamicRemotePlugin({ + pluginMetadata: { + name, + version, + dependencies, + customProperties: _.merge({}, customProperties, { + console: { displayName, description, disableStaticPlugins }, + }), + exposedModules, + }, + extensions, + sharedModules: getWebpackSharedModules(), + entryCallbackSettings: { + name: 'loadPluginEntry', + pluginID: `${name}@${version}`, + }, + entryScriptFilename: + process.env.NODE_ENV === 'production' + ? 'plugin-entry.[fullhash].min.js' + : 'plugin-entry.js', }).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 61cb140ea95..d2d6d07e53a 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 9dd2c61df98..bb7d7788504 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 f3553036d63..b3ba601d558 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 226ef31954d..c0e00db42ec 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/yarn.lock b/frontend/yarn.lock index 4cc9975447a..6c046615f8f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1656,6 +1656,15 @@ once "^1.4.0" universal-user-agent "^4.0.0" +"@openshift/dynamic-plugin-sdk-webpack@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@openshift/dynamic-plugin-sdk-webpack/-/dynamic-plugin-sdk-webpack-4.0.0.tgz#5986f6a4eacd60c8f71ba7ef41d6891648eaa1d7" + integrity sha512-QaXCdI1kaRZsCTuKX0EcBy0QdS8GEqDICQO3KUVhcb/LCmJojC2AllTPuNSRH/WEnPEzL51F1lz3qrlJxQEtkQ== + dependencies: + lodash "^4.17.21" + semver "^7.3.7" + yup "^0.32.11" + "@patternfly-4/patternfly@npm:@patternfly/patternfly@4.224.5": version "4.224.5" resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.224.5.tgz#ad704848ce7754fcd6ffc5dc349f17beb84168ce" @@ -2557,6 +2566,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.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -3389,6 +3403,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.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + 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" @@ -7288,6 +7307,14 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" +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" @@ -12445,6 +12472,11 @@ nan@^2.12.1, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +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" @@ -13693,6 +13725,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.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + protobufjs@^6.8.8: version "6.10.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" @@ -17463,6 +17500,14 @@ watchpack@^2.3.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +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" + wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" @@ -17603,6 +17648,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" @@ -17632,7 +17707,7 @@ webpack@^4.46.0: watchpack "^1.7.4" webpack-sources "^1.4.1" -webpack@^5.38.1, webpack@^5.73.0: +webpack@^5.38.1: version "5.73.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== @@ -18153,6 +18228,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"