-
-);
+
+ );
+};
export default Plugins;
diff --git a/microsite/src/pages/plugins/plugins.module.scss b/microsite/src/pages/plugins/plugins.module.scss
index bb08c2fd33..d5ea5de6e7 100644
--- a/microsite/src/pages/plugins/plugins.module.scss
+++ b/microsite/src/pages/plugins/plugins.module.scss
@@ -28,10 +28,60 @@
:global(.pluginsContainer) {
gap: 1rem;
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ grid-template-columns: repeat(auto-fit, minmax(250px, 300px));
+ }
+
+ @media (max-width: 768px) {
+ :global(.pluginsContainer) {
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ }
+ }
+
+ :global(.hidePluginsHeader) {
+ display: none;
}
:global(.fit-content) {
width: fit-content;
}
+
+ :global(.pluginsFilterBox) {
+ width: 100%;
+ margin-bottom: 1rem;
+ height: 1rem;
+ }
+
+ :global(.dropdown) {
+ float: right;
+ margin-bottom: 1rem;
+ }
+
+ :global(.button--info) {
+ background-color: transparent;
+ border-color: var(--ifm-color-primary);
+ color: var(--ifm-color-primary);
+ min-width: 250px;
+ }
+
+ :global(label.dropdown__item) {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ margin-top: 5px;
+
+ &:hover {
+ background-color: var(--ifm-color-primary);
+ color: black;
+ }
+ }
+
+ :global(.dropdown__label) {
+ font-size: 15px;
+ cursor: pointer;
+ }
+
+ :global(.dropdown__checkbox) {
+ margin-right: 10px;
+ cursor: pointer;
+ }
}
diff --git a/microsite/src/util/types.ts b/microsite/src/util/types.ts
new file mode 100644
index 0000000000..fba7c0ba46
--- /dev/null
+++ b/microsite/src/util/types.ts
@@ -0,0 +1,4 @@
+export type ChipCategory = {
+ name: string;
+ isSelected: boolean;
+};
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index c16f65240e..fe1c123575 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -1620,9 +1620,9 @@ __metadata:
languageName: node
linkType: hard
-"@docusaurus/core@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/core@npm:0.0.0-5569"
+"@docusaurus/core@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/core@npm:0.0.0-5580"
dependencies:
"@babel/core": ^7.20.12
"@babel/generator": ^7.21.1
@@ -1634,13 +1634,13 @@ __metadata:
"@babel/runtime": ^7.21.0
"@babel/runtime-corejs3": ^7.21.0
"@babel/traverse": ^7.21.2
- "@docusaurus/cssnano-preset": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/mdx-loader": 0.0.0-5569
+ "@docusaurus/cssnano-preset": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/mdx-loader": 0.0.0-5580
"@docusaurus/react-loadable": 5.5.2
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
"@slorber/static-site-generator-webpack-plugin": ^4.0.7
"@svgr/webpack": ^6.5.1
autoprefixer: ^10.4.13
@@ -1700,41 +1700,41 @@ __metadata:
react-dom: ^16.8.4 || ^17.0.0
bin:
docusaurus: bin/docusaurus.mjs
- checksum: 573746ba85750381b110a98cfb529d8c961523b735c1945016bf8f79ba2cde38d08ecda88dc1be4852d8c9a49121e23ec964b05354fd6f6d26e6f31e71f175c1
+ checksum: 1160559109aa834bcd3dc0210ca824ad6cbdea363951c5cba690c1882c78a7da6e1e52b8a5c35fef3bfc23f9f66e84880c354727b7987d371cac8ec6fb77d1bb
languageName: node
linkType: hard
-"@docusaurus/cssnano-preset@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/cssnano-preset@npm:0.0.0-5569"
+"@docusaurus/cssnano-preset@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/cssnano-preset@npm:0.0.0-5580"
dependencies:
cssnano-preset-advanced: ^5.3.10
postcss: ^8.4.21
postcss-sort-media-queries: ^4.3.0
tslib: ^2.5.0
- checksum: 11408a0f9eb1a4a83b2045a0172ef1fbf0523a99fa9d81a8efe9a3a968b449887616ad8d95b0abb55b2bf82a178193c20c0304942c4f2ba51b693b9209031204
+ checksum: 4a46af1e9327d6ebbed3f0f6e60cc1ca59d5c07d39be35c42c13b0a64795b0b1565585adfca063798be79100aca425ee0c12f28a816b56686fb97726bec00f40
languageName: node
linkType: hard
-"@docusaurus/logger@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/logger@npm:0.0.0-5569"
+"@docusaurus/logger@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/logger@npm:0.0.0-5580"
dependencies:
chalk: ^4.1.2
tslib: ^2.5.0
- checksum: 26be75d7180d9b9d525ce5014e1e24c69fd54d1707cabc5acc76b47e3282cfb185613fb4d27f32b5cced92fe80384c68e2d991cf4cabbecca331f570c3e10d9b
+ checksum: 5792b6672623b70d38871713c47ba88cc0df6c8d224c28ac58d0512b8bf1b672786cc2fa0b1a79cc045c473e1e14b5a9cce4b01e623dd252893294d6aff9d1c2
languageName: node
linkType: hard
-"@docusaurus/mdx-loader@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/mdx-loader@npm:0.0.0-5569"
+"@docusaurus/mdx-loader@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/mdx-loader@npm:0.0.0-5580"
dependencies:
"@babel/parser": ^7.21.2
"@babel/traverse": ^7.21.2
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
"@mdx-js/mdx": ^2.1.5
escape-html: ^1.0.3
estree-util-value-to-estree: ^2.1.0
@@ -1744,6 +1744,7 @@ __metadata:
image-size: ^1.0.2
mdast-util-mdx: ^2.0.0
mdast-util-to-string: ^3.0.0
+ rehype-raw: ^6.1.1
remark-comment: ^1.0.0
remark-directive: ^2.0.1
remark-emoji: ^2.2.0
@@ -1757,16 +1758,16 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: bcf3b58ea3840d648ddc81c090c9905d559f642d8dc495f3fa86b377ae0bdabb6e1486ae2b258efa459a8bcf1da729a4a3a0c144f0de5dc083155b6d5c83099c
+ checksum: b32812e0bca91a29761fda742a790f67077bb7e8860e20e83f009e297ca49017192fa14051ab82f1a3e66800de9e5a0b561b32ce2dffb4eecc0aee48116d3490
languageName: node
linkType: hard
-"@docusaurus/module-type-aliases@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/module-type-aliases@npm:0.0.0-5569"
+"@docusaurus/module-type-aliases@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/module-type-aliases@npm:0.0.0-5580"
dependencies:
"@docusaurus/react-loadable": 5.5.2
- "@docusaurus/types": 0.0.0-5569
+ "@docusaurus/types": 0.0.0-5580
"@types/history": ^4.7.11
"@types/react": "*"
"@types/react-router-config": "*"
@@ -1776,19 +1777,19 @@ __metadata:
peerDependencies:
react: "*"
react-dom: "*"
- checksum: 08168317c606e44f8ead775bfc23feaeb3a03178a19a07504130a32cbf189926c08ef40cdc4f00a6df0983383ca0145a42df9112451b15a19067d8b6b10d5f89
+ checksum: b5b3544f2f2d395c1d406a1b3f03f719aee3aa3fd7603c54af3b09c4fb8bb153cf2ee6697bc9cfc9fb2550a11a3e474a52c9f2c132924e2be60662ffc6379ace
languageName: node
linkType: hard
-"@docusaurus/plugin-client-redirects@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-client-redirects@npm:0.0.0-5569"
+"@docusaurus/plugin-client-redirects@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-client-redirects@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
eta: ^2.0.1
fs-extra: ^11.1.0
lodash: ^4.17.21
@@ -1796,21 +1797,21 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: f082956089fc889ea67b89c5c19ad6cd0bc9bb35b663a701f05905786938b1302b8667c2b6d0354bcf9efd35496a5920e1871b488fbc6285d0e36cfe3187e84e
+ checksum: 7414d2026446862505d0c679ad3757107f28100ea76309d2a024ebe78865f2fcf3e3b85c3117361142e319362b5a5b22e6bce968763fdfd0741b6a60d2eb3f4b
languageName: node
linkType: hard
-"@docusaurus/plugin-content-blog@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-content-blog@npm:0.0.0-5569"
+"@docusaurus/plugin-content-blog@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-content-blog@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/mdx-loader": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/mdx-loader": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
cheerio: ^1.0.0-rc.12
feed: ^4.2.2
fs-extra: ^11.1.0
@@ -1823,21 +1824,21 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: b9eef1200e1b9bf1cfa7c4d4cb62b7344306d3a096c0cbfc36afd4b973a940b1bf9d6dc62124a0028bbd466313c7ee219444ebe2856588748d2fc33d1f48ae80
+ checksum: 0c5d37fa84298ce12b2a0f5c1dea1ee7d72d4b28c545fd081f3c3e5f93f15a980d877dcf88718aa1f4b8a03abf3d0c8c0df10d0a2ef22aac7a8a96d2786f91c2
languageName: node
linkType: hard
-"@docusaurus/plugin-content-docs@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-content-docs@npm:0.0.0-5569"
+"@docusaurus/plugin-content-docs@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-content-docs@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/mdx-loader": 0.0.0-5569
- "@docusaurus/module-type-aliases": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/mdx-loader": 0.0.0-5580
+ "@docusaurus/module-type-aliases": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
"@types/react-router-config": ^5.0.6
combine-promises: ^1.1.0
fs-extra: ^11.1.0
@@ -1850,133 +1851,133 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 544d51dbb6c9037916aa8e3ed493624029ffdac0d0da70cfdb74f1d431a8da071eeeb0d567be6f63d48f9922f1594f08f950478a4ea427fae8001ff0b4ac5d83
+ checksum: fa58801bf0cebf055ed11adbadc3f27f7ed0ddb95174ab60bab76a21f759dc7abdfdd15f32141f7e259090c456634ee2556ac9a7daaaf7f5f6e3666d9f247d00
languageName: node
linkType: hard
-"@docusaurus/plugin-content-pages@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-content-pages@npm:0.0.0-5569"
+"@docusaurus/plugin-content-pages@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-content-pages@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/mdx-loader": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/mdx-loader": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
fs-extra: ^11.1.0
tslib: ^2.5.0
webpack: ^5.76.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 16fa680c069d57d9d299ef515487a773c5b89d605cfe5b843392a7bce5e59b539b1855d7e0f2e11878b3021038bff6468b5f4adb9af7dc06884d7207772c2f95
+ checksum: 42bf814d9bf5540b8b2f8045f1577c49d3fca4a85069865695ff5dd9fa3f935283f9d46f920c4dd0a28190cf3b342d432b0985636bae9f05280835f73aec8c87
languageName: node
linkType: hard
-"@docusaurus/plugin-debug@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-debug@npm:0.0.0-5569"
+"@docusaurus/plugin-debug@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-debug@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
fs-extra: ^11.1.0
react-json-view: ^1.21.3
tslib: ^2.5.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 0e3b8f5a92bc2b2ab193d76be6174b521238da7b4627775a852a4b4ff8690af28ab4586adbb84fc03abc6da276efc7474eed9350577b457e532defeb638d3184
+ checksum: dc58659adab3c859daa2e0dfc0b5b588d490a4d2bb313dcfdc9e92d559d0c4145c686b001805ad44a1f2a0f55f87524637a34844a4438cfeec479a2c24ec1c4d
languageName: node
linkType: hard
-"@docusaurus/plugin-google-analytics@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-google-analytics@npm:0.0.0-5569"
+"@docusaurus/plugin-google-analytics@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-google-analytics@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
tslib: ^2.5.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: a3e55d3a616009db499ca61493f9cf308c8ae012ec1ff680bff20c3b0b486f8d5d9552fd5c3d7b198e6c72d8e13ace7808d978bd8d56496953d2fd51d5ce0c32
+ checksum: f66a737a2b8b08648f28a279784e527fff34e2dba6f3022ec83b01a7f1781bfa0f208fac1db1fc919c1a01d405755c0816304cf4912e7b434b3924d5a968e6b0
languageName: node
linkType: hard
-"@docusaurus/plugin-google-gtag@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-google-gtag@npm:0.0.0-5569"
+"@docusaurus/plugin-google-gtag@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-google-gtag@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
"@types/gtag.js": ^0.0.12
tslib: ^2.5.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: d0a7f58bea6d5bfd786120ab6c75ab10a4d67bae1d60aadedba649a516f2f9e49237a96a7764f964c79084ae500b9520761cbf6aab60bd5e43bb976f04dd3194
+ checksum: 6b05e7c2a8e4d757660acca1131a0e3abfd21c5544a31e93bd08ff9c75deb5f3b131ed0e47283bbc2f102e00947ab7a64ee4d5a53f300e98fc82ec13c4a6eda1
languageName: node
linkType: hard
-"@docusaurus/plugin-google-tag-manager@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-google-tag-manager@npm:0.0.0-5569"
+"@docusaurus/plugin-google-tag-manager@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-google-tag-manager@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
tslib: ^2.5.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: acafad7eea5ba06c62addcef4f3cfaa6c43193827ec4b6c2d39ed2799df5301defa8ac8c73d49e35c328bd118c78412745f088764d97eac00da090be8e7906e5
+ checksum: 8904b150b2faa05dc49fd957ca8c392605e53073d031dfedb098be2abae924c3faf4f5e26ecfea91fe452695699801de9882a85a4371a1a35ca05fd57de232ae
languageName: node
linkType: hard
-"@docusaurus/plugin-sitemap@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/plugin-sitemap@npm:0.0.0-5569"
+"@docusaurus/plugin-sitemap@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/plugin-sitemap@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
fs-extra: ^11.1.0
sitemap: ^7.1.1
tslib: ^2.5.0
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 53fd5944a1cfbbe252e3f4398acf8aea3605ca3c5fb9ec48700ce6c162744bae541ccecd1d71e884777dfc2e98c6fa9a01b0f98829e6018e59003c3d4f4a1612
+ checksum: 4243cf4ed649d8e9a29b122ce23f1a6194cc5d7fc244e33326320315d8ff2756b18b9d85a0f0d49df33b2e68224c1b747e87ad5fa9944e7572bc761f641c5b77
languageName: node
linkType: hard
-"@docusaurus/preset-classic@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/preset-classic@npm:0.0.0-5569"
+"@docusaurus/preset-classic@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/preset-classic@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/plugin-content-blog": 0.0.0-5569
- "@docusaurus/plugin-content-docs": 0.0.0-5569
- "@docusaurus/plugin-content-pages": 0.0.0-5569
- "@docusaurus/plugin-debug": 0.0.0-5569
- "@docusaurus/plugin-google-analytics": 0.0.0-5569
- "@docusaurus/plugin-google-gtag": 0.0.0-5569
- "@docusaurus/plugin-google-tag-manager": 0.0.0-5569
- "@docusaurus/plugin-sitemap": 0.0.0-5569
- "@docusaurus/theme-classic": 0.0.0-5569
- "@docusaurus/theme-common": 0.0.0-5569
- "@docusaurus/theme-search-algolia": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/plugin-content-blog": 0.0.0-5580
+ "@docusaurus/plugin-content-docs": 0.0.0-5580
+ "@docusaurus/plugin-content-pages": 0.0.0-5580
+ "@docusaurus/plugin-debug": 0.0.0-5580
+ "@docusaurus/plugin-google-analytics": 0.0.0-5580
+ "@docusaurus/plugin-google-gtag": 0.0.0-5580
+ "@docusaurus/plugin-google-tag-manager": 0.0.0-5580
+ "@docusaurus/plugin-sitemap": 0.0.0-5580
+ "@docusaurus/theme-classic": 0.0.0-5580
+ "@docusaurus/theme-common": 0.0.0-5580
+ "@docusaurus/theme-search-algolia": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: a20013db1bd1c77e6fc2b901326d62f3003b6f6e6191f26aae6968cc2402837c0e84cc54571849d2af811fdae74d9359c0e96775d61b9f3efbbe881898e7f9a3
+ checksum: 3aa48aea40e33c2c7adcad54918e117b1f1d917eba110889d85a273e82b4bbf0d73d9e5bb8c5aaadd846ec341981bbe701014cbd93ddf55c83245ce1bc4a56a6
languageName: node
linkType: hard
@@ -1992,22 +1993,22 @@ __metadata:
languageName: node
linkType: hard
-"@docusaurus/theme-classic@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/theme-classic@npm:0.0.0-5569"
+"@docusaurus/theme-classic@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/theme-classic@npm:0.0.0-5580"
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/mdx-loader": 0.0.0-5569
- "@docusaurus/module-type-aliases": 0.0.0-5569
- "@docusaurus/plugin-content-blog": 0.0.0-5569
- "@docusaurus/plugin-content-docs": 0.0.0-5569
- "@docusaurus/plugin-content-pages": 0.0.0-5569
- "@docusaurus/theme-common": 0.0.0-5569
- "@docusaurus/theme-translations": 0.0.0-5569
- "@docusaurus/types": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/mdx-loader": 0.0.0-5580
+ "@docusaurus/module-type-aliases": 0.0.0-5580
+ "@docusaurus/plugin-content-blog": 0.0.0-5580
+ "@docusaurus/plugin-content-docs": 0.0.0-5580
+ "@docusaurus/plugin-content-pages": 0.0.0-5580
+ "@docusaurus/theme-common": 0.0.0-5580
+ "@docusaurus/theme-translations": 0.0.0-5580
+ "@docusaurus/types": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
"@mdx-js/react": ^2.1.5
clsx: ^1.2.1
copy-text-to-clipboard: ^3.0.1
@@ -2024,21 +2025,21 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: ee25082a4abc0d812cc54ee1bea642766f903cfa47d28c6a6efbc53cdaacf67bee594a4745c7ae97fe3ba6ddb136de36dd0c8cda2c310c5b40c9023bee70fcbb
+ checksum: 6a8f99d5143017d4754601fbf6487b697dc77945f730763917578abe0a1ef694607f888d824146aef86fdc2a620f0ddbd4925a23dcfc15b57d8338c1de73d526
languageName: node
linkType: hard
-"@docusaurus/theme-common@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/theme-common@npm:0.0.0-5569"
+"@docusaurus/theme-common@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/theme-common@npm:0.0.0-5580"
dependencies:
- "@docusaurus/mdx-loader": 0.0.0-5569
- "@docusaurus/module-type-aliases": 0.0.0-5569
- "@docusaurus/plugin-content-blog": 0.0.0-5569
- "@docusaurus/plugin-content-docs": 0.0.0-5569
- "@docusaurus/plugin-content-pages": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-common": 0.0.0-5569
+ "@docusaurus/mdx-loader": 0.0.0-5580
+ "@docusaurus/module-type-aliases": 0.0.0-5580
+ "@docusaurus/plugin-content-blog": 0.0.0-5580
+ "@docusaurus/plugin-content-docs": 0.0.0-5580
+ "@docusaurus/plugin-content-pages": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-common": 0.0.0-5580
"@types/history": ^4.7.11
"@types/react": "*"
"@types/react-router-config": "*"
@@ -2051,22 +2052,22 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 26a2d7dd6fd8b97e67db3bc093fcb9ce6f14d561aeccf94c3334d18a9a2900483dcb0ec4bb28d31e910bea459f20e4445207d5156c9cc02bd72601eb0256e3cc
+ checksum: 99524cb36a520568f63140734daaea8d56b35c0c8de3c36cb0102a93a0714f31a68aa5302607b9113a68e1b9fb7d73fd53ac80aa82ce48bf6f2d9e304ce21da2
languageName: node
linkType: hard
-"@docusaurus/theme-search-algolia@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/theme-search-algolia@npm:0.0.0-5569"
+"@docusaurus/theme-search-algolia@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/theme-search-algolia@npm:0.0.0-5580"
dependencies:
"@docsearch/react": ^3.3.3
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/plugin-content-docs": 0.0.0-5569
- "@docusaurus/theme-common": 0.0.0-5569
- "@docusaurus/theme-translations": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
- "@docusaurus/utils-validation": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/plugin-content-docs": 0.0.0-5580
+ "@docusaurus/theme-common": 0.0.0-5580
+ "@docusaurus/theme-translations": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
+ "@docusaurus/utils-validation": 0.0.0-5580
algoliasearch: ^4.15.0
algoliasearch-helper: ^3.12.0
clsx: ^1.2.1
@@ -2078,23 +2079,23 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 16178b7b9278c91eaedbb1629192dc2255939de6f509b721305c4c1795283e0fd4f859b305ce051ec5efdbe71884ceede6239445916b60ebec1858e3b5161d8f
+ checksum: efc6054ad979b9fde43bec8befeb41c0f9ca609832b51e4da42fdcdfd7e4ee599fd3e6949c576d9ff348c0ffd044a4dbb07638a1990f57586f493ba394369c96
languageName: node
linkType: hard
-"@docusaurus/theme-translations@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/theme-translations@npm:0.0.0-5569"
+"@docusaurus/theme-translations@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/theme-translations@npm:0.0.0-5580"
dependencies:
fs-extra: ^11.1.0
tslib: ^2.5.0
- checksum: 695f94ddc66d54b649042f0fb0a88b692613d10898ee3306c6c9b52340373493ac9bcceec9263332454d7c9179339517e40b083e949510f9e48ec834bc61fbb3
+ checksum: 1555956d2a7cf3122ded374937559ab19e6a6620fd0910d6bdbf48be78a10e595cfd9a0b9157c129f36f0bfae4297f07991f071a394c87cb9d2facee04597dd2
languageName: node
linkType: hard
-"@docusaurus/types@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/types@npm:0.0.0-5569"
+"@docusaurus/types@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/types@npm:0.0.0-5580"
dependencies:
"@types/history": ^4.7.11
"@types/react": "*"
@@ -2107,13 +2108,13 @@ __metadata:
peerDependencies:
react: ^16.8.4 || ^17.0.0
react-dom: ^16.8.4 || ^17.0.0
- checksum: 956b7af3a75b3632d1ff330d018353b38b22b3f4af8b39f452fcde33d74c70312dadd3e3bb40e04ecbe02e2462a67bfcbd4ef631462a9c710616c83186930b69
+ checksum: 5df67f562c5d516eb020ecf63e6343e1d6d18e2716b1b530a24c5d63b48229623222861c1a69f0e4715c34a768f7c05599340f1af18ca1259709467fb1149af9
languageName: node
linkType: hard
-"@docusaurus/utils-common@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/utils-common@npm:0.0.0-5569"
+"@docusaurus/utils-common@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/utils-common@npm:0.0.0-5580"
dependencies:
tslib: ^2.5.0
peerDependencies:
@@ -2121,28 +2122,28 @@ __metadata:
peerDependenciesMeta:
"@docusaurus/types":
optional: true
- checksum: 7d98e31beea34be91de7fdc971033439e412f535617707c79c4d2c0653c769ff4ce7521bd87f038811ccbae3b1d5c0e5d26eab9935ee8bc3515b926cbe175365
+ checksum: 238b2f12607834164fa82f9a3154703b75609719888cf96f06c9f70c17ab76a46babcfd66dce4f2722ecaaf47742a40a606101f0578f2345845fa23b6f262b5d
languageName: node
linkType: hard
-"@docusaurus/utils-validation@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/utils-validation@npm:0.0.0-5569"
+"@docusaurus/utils-validation@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/utils-validation@npm:0.0.0-5580"
dependencies:
- "@docusaurus/logger": 0.0.0-5569
- "@docusaurus/utils": 0.0.0-5569
+ "@docusaurus/logger": 0.0.0-5580
+ "@docusaurus/utils": 0.0.0-5580
joi: ^17.8.3
js-yaml: ^4.1.0
tslib: ^2.5.0
- checksum: b16730923e0d6fa627ca3e3105c152e814d540badb93f0a2ae1194672a76f8269ed48ed5c8f3259c0e0b9dfc6dc939035e0ac2e675b283ac4d414c642a95c893
+ checksum: cf0602b2411738adc47f5596fd4a3076137aa754aca24a4630700dc6355fed07e9a66ec093e438fa1199d9076bebae036317d073a604e4f0666f8390ceb73b5d
languageName: node
linkType: hard
-"@docusaurus/utils@npm:0.0.0-5569":
- version: 0.0.0-5569
- resolution: "@docusaurus/utils@npm:0.0.0-5569"
+"@docusaurus/utils@npm:0.0.0-5580":
+ version: 0.0.0-5580
+ resolution: "@docusaurus/utils@npm:0.0.0-5580"
dependencies:
- "@docusaurus/logger": 0.0.0-5569
+ "@docusaurus/logger": 0.0.0-5580
"@svgr/webpack": ^6.5.1
escape-string-regexp: ^4.0.0
file-loader: ^6.2.0
@@ -2163,7 +2164,7 @@ __metadata:
peerDependenciesMeta:
"@docusaurus/types":
optional: true
- checksum: 33e044f9b5328cf9d1b17637fa41a6138c5e18b5f3d73389bbb9f71735a273a9e27660d9a9103766406549edfa0691e2524cc84e6ebc2b0d308991df3bee0dd9
+ checksum: c9b085d3724cba748b3db763f056742aace45cc58a636e23513ee6a7db781558377de5507b651b3566562fa71eebb42ac835034cdcbcc2c77edf9b4009c6a215
languageName: node
linkType: hard
@@ -2991,6 +2992,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/parse5@npm:^6.0.0":
+ version: 6.0.3
+ resolution: "@types/parse5@npm:6.0.3"
+ checksum: ddb59ee4144af5dfcc508a8dcf32f37879d11e12559561e65788756b95b33e6f03ea027d88e1f5408f9b7bfb656bf630ace31a2169edf44151daaf8dd58df1b7
+ languageName: node
+ linkType: hard
+
"@types/prop-types@npm:*":
version: 15.7.5
resolution: "@types/prop-types@npm:15.7.5"
@@ -3737,10 +3745,10 @@ __metadata:
version: 0.0.0-use.local
resolution: "backstage-microsite@workspace:."
dependencies:
- "@docusaurus/core": 0.0.0-5569
- "@docusaurus/module-type-aliases": 0.0.0-5569
- "@docusaurus/plugin-client-redirects": 0.0.0-5569
- "@docusaurus/preset-classic": 0.0.0-5569
+ "@docusaurus/core": 0.0.0-5580
+ "@docusaurus/module-type-aliases": 0.0.0-5580
+ "@docusaurus/plugin-client-redirects": 0.0.0-5580
+ "@docusaurus/preset-classic": 0.0.0-5580
"@spotify/prettier-config": ^14.0.0
"@swc/core": ^1.3.46
"@tsconfig/docusaurus": ^1.0.6
@@ -6132,6 +6140,21 @@ __metadata:
languageName: node
linkType: hard
+"hast-util-from-parse5@npm:^7.0.0":
+ version: 7.1.2
+ resolution: "hast-util-from-parse5@npm:7.1.2"
+ dependencies:
+ "@types/hast": ^2.0.0
+ "@types/unist": ^2.0.0
+ hastscript: ^7.0.0
+ property-information: ^6.0.0
+ vfile: ^5.0.0
+ vfile-location: ^4.0.0
+ web-namespaces: ^2.0.0
+ checksum: 7b4ed5b508b1352127c6719f7b0c0880190cf9859fe54ccaf7c9228ecf623d36cef3097910b3874d2fe1aac6bf4cf45d3cc2303daac3135a05e9ade6534ddddb
+ languageName: node
+ linkType: hard
+
"hast-util-parse-selector@npm:^3.0.0":
version: 3.1.1
resolution: "hast-util-parse-selector@npm:3.1.1"
@@ -6141,6 +6164,25 @@ __metadata:
languageName: node
linkType: hard
+"hast-util-raw@npm:^7.2.0":
+ version: 7.2.3
+ resolution: "hast-util-raw@npm:7.2.3"
+ dependencies:
+ "@types/hast": ^2.0.0
+ "@types/parse5": ^6.0.0
+ hast-util-from-parse5: ^7.0.0
+ hast-util-to-parse5: ^7.0.0
+ html-void-elements: ^2.0.0
+ parse5: ^6.0.0
+ unist-util-position: ^4.0.0
+ unist-util-visit: ^4.0.0
+ vfile: ^5.0.0
+ web-namespaces: ^2.0.0
+ zwitch: ^2.0.0
+ checksum: 21857eea3ffb8fd92d2d9be7793b56d0b2c40db03c4cfa14828855ae41d7c584917aa83efb7157220b2e41e25e95f81f24679ac342c35145e5f1c1d39015f81f
+ languageName: node
+ linkType: hard
+
"hast-util-to-estree@npm:^2.0.0":
version: 2.3.2
resolution: "hast-util-to-estree@npm:2.3.2"
@@ -6164,6 +6206,20 @@ __metadata:
languageName: node
linkType: hard
+"hast-util-to-parse5@npm:^7.0.0":
+ version: 7.1.0
+ resolution: "hast-util-to-parse5@npm:7.1.0"
+ dependencies:
+ "@types/hast": ^2.0.0
+ comma-separated-tokens: ^2.0.0
+ property-information: ^6.0.0
+ space-separated-tokens: ^2.0.0
+ web-namespaces: ^2.0.0
+ zwitch: ^2.0.0
+ checksum: 3a7f2175a3db599bbae7e49ba73d3e5e688e5efca7590ff50130ba108ad649f728402815d47db49146f6b94c14c934bf119915da9f6964e38802c122bcc8af6b
+ languageName: node
+ linkType: hard
+
"hast-util-whitespace@npm:^2.0.0":
version: 2.0.1
resolution: "hast-util-whitespace@npm:2.0.1"
@@ -6171,7 +6227,7 @@ __metadata:
languageName: node
linkType: hard
-"hastscript@npm:^7.1.0":
+"hastscript@npm:^7.0.0, hastscript@npm:^7.1.0":
version: 7.2.0
resolution: "hastscript@npm:7.2.0"
dependencies:
@@ -6276,6 +6332,13 @@ __metadata:
languageName: node
linkType: hard
+"html-void-elements@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "html-void-elements@npm:2.0.1"
+ checksum: 06d41f13b9d5d6e0f39861c4bec9a9196fa4906d56cd5cf6cf54ad2e52a85bf960cca2bf9600026bde16c8331db171bedba5e5a35e2e43630c8f1d497b2fb658
+ languageName: node
+ linkType: hard
+
"html-webpack-plugin@npm:^5.5.0":
version: 5.5.0
resolution: "html-webpack-plugin@npm:5.5.0"
@@ -8735,6 +8798,13 @@ __metadata:
languageName: node
linkType: hard
+"parse5@npm:^6.0.0":
+ version: 6.0.1
+ resolution: "parse5@npm:6.0.1"
+ checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd
+ languageName: node
+ linkType: hard
+
"parse5@npm:^7.0.0":
version: 7.1.2
resolution: "parse5@npm:7.1.2"
@@ -9901,6 +9971,17 @@ __metadata:
languageName: node
linkType: hard
+"rehype-raw@npm:^6.1.1":
+ version: 6.1.1
+ resolution: "rehype-raw@npm:6.1.1"
+ dependencies:
+ "@types/hast": ^2.0.0
+ hast-util-raw: ^7.2.0
+ unified: ^10.0.0
+ checksum: a1f9d309e609f49fb1f1e06e722705f4dd2e569653a89f756eaccb33b612cf1bb511216a81d10a619d11d047afc161e4b3cb99b957df05a8ba8fdbd5843f949a
+ languageName: node
+ linkType: hard
+
"relateurl@npm:^0.2.7":
version: 0.2.7
resolution: "relateurl@npm:0.2.7"
@@ -11526,6 +11607,13 @@ __metadata:
languageName: node
linkType: hard
+"web-namespaces@npm:^2.0.0":
+ version: 2.0.1
+ resolution: "web-namespaces@npm:2.0.1"
+ checksum: b6d9f02f1a43d0ef0848a812d89c83801d5bbad57d8bb61f02eb6d7eb794c3736f6cc2e1191664bb26136594c8218ac609f4069722c6f56d9fc2d808fa9271c6
+ languageName: node
+ linkType: hard
+
"webidl-conversions@npm:^3.0.0":
version: 3.0.1
resolution: "webidl-conversions@npm:3.0.1"
diff --git a/package.json b/package.json
index 44778ae926..3a7e05d65a 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"@types/react": "^17",
"@types/react-dom": "^17"
},
- "version": "1.14.0-next.1",
+ "version": "1.14.0",
"dependencies": {
"@backstage/errors": "workspace:^",
"@manypkg/get-packages": "^1.1.3"
diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md
index 5d781b58ca..7cb6737b73 100644
--- a/packages/app-defaults/CHANGELOG.md
+++ b/packages/app-defaults/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/app-defaults
+## 1.3.1
+
+### Patch Changes
+
+- 575d9178eff: Added a System Icon for resource entities.
+ This can be obtained using:
+
+ ```ts
+ useApp().getSystemIcon('kind:resource');
+ ```
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-app-api@1.8.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
+
+## 1.3.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-app-api@1.8.0-next.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
+
## 1.3.1-next.1
### Patch Changes
diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json
index 0a8f2e5dc1..90f50d0eff 100644
--- a/packages/app-defaults/package.json
+++ b/packages/app-defaults/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/app-defaults",
"description": "Provides the default wiring of a Backstage App",
- "version": "1.3.1-next.1",
+ "version": "1.3.1",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index b20fafb995..5fdee05829 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,145 @@
# example-app
+## 0.2.83
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-tech-insights@0.3.10
+ - @backstage/plugin-scaffolder@1.13.1
+ - @backstage/plugin-devtools@0.1.0
+ - @backstage/plugin-kubernetes@0.9.0
+ - @backstage/plugin-search@1.3.0
+ - @backstage/plugin-home@0.5.2
+ - @backstage/plugin-explore@0.4.3
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-techdocs@1.6.2
+ - @backstage/app-defaults@1.3.1
+ - @backstage/plugin-badges@0.2.42
+ - @backstage/plugin-tech-radar@0.6.4
+ - @backstage/cli@0.22.7
+ - @backstage/plugin-stack-overflow@0.1.15
+ - @backstage/plugin-gcalendar@0.3.14
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-app-api@1.8.0
+ - @backstage/plugin-scaffolder-react@1.4.0
+ - @backstage/plugin-catalog-graph@0.2.30
+ - @backstage/plugin-circleci@0.3.18
+ - @backstage/plugin-search-react@1.6.0
+ - @backstage/plugin-org@0.6.8
+ - @backstage/core-components@0.13.1
+ - @backstage/plugin-azure-devops@0.3.0
+ - @backstage/plugin-jenkins@0.8.0
+ - @backstage/plugin-octopus-deploy@0.2.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.0.13
+ - @backstage/plugin-user-settings@0.7.3
+ - @backstage/plugin-shortcuts@0.3.10
+ - @backstage/integration-react@1.1.13
+ - @backstage/plugin-airbrake@0.3.18
+ - @backstage/plugin-api-docs@0.9.3
+ - @backstage/plugin-azure-sites@0.1.7
+ - @backstage/plugin-cloudbuild@0.3.18
+ - @backstage/plugin-code-coverage@0.2.11
+ - @backstage/plugin-cost-insights@0.12.7
+ - @backstage/plugin-dynatrace@5.0.0
+ - @backstage/plugin-entity-feedback@0.2.1
+ - @backstage/plugin-gcp-projects@0.3.37
+ - @backstage/plugin-github-actions@0.5.18
+ - @backstage/plugin-gocd@0.1.24
+ - @backstage/plugin-graphiql@0.2.50
+ - @backstage/plugin-kafka@0.3.18
+ - @backstage/plugin-lighthouse@0.4.3
+ - @backstage/plugin-linguist@0.1.3
+ - @backstage/plugin-microsoft-calendar@0.1.3
+ - @backstage/plugin-newrelic@0.3.36
+ - @backstage/plugin-pagerduty@0.5.11
+ - @backstage/plugin-playlist@0.1.9
+ - @backstage/plugin-puppetdb@0.1.1
+ - @backstage/plugin-rollbar@0.4.18
+ - @backstage/plugin-sentry@0.5.3
+ - @backstage/plugin-stackstorm@0.1.2
+ - @backstage/plugin-techdocs-react@1.1.6
+ - @backstage/plugin-todo@0.2.20
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-apache-airflow@0.2.11
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-catalog-import@0.9.8
+ - @backstage/plugin-linguist-common@0.1.0
+ - @backstage/plugin-newrelic-dashboard@0.2.11
+ - @backstage/plugin-permission-react@0.4.12
+ - @backstage/plugin-search-common@1.2.3
+ - @internal/plugin-catalog-customized@0.0.10
+
+## 0.2.83-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools@0.1.0-next.0
+ - @backstage/plugin-kubernetes@0.9.0-next.2
+ - @backstage/plugin-home@0.5.2-next.2
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/plugin-badges@0.2.42-next.2
+ - @backstage/plugin-tech-radar@0.6.4-next.2
+ - @backstage/plugin-scaffolder-react@1.4.0-next.2
+ - @backstage/plugin-explore@0.4.3-next.2
+ - @backstage/plugin-org@0.6.8-next.2
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-search-react@1.6.0-next.2
+ - @backstage/plugin-azure-devops@0.3.0-next.2
+ - @backstage/plugin-stack-overflow@0.1.15-next.2
+ - @backstage/app-defaults@1.3.1-next.2
+ - @backstage/cli@0.22.7-next.0
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/plugin-airbrake@0.3.18-next.2
+ - @backstage/plugin-api-docs@0.9.3-next.2
+ - @backstage/plugin-azure-sites@0.1.7-next.2
+ - @backstage/plugin-catalog-graph@0.2.30-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/plugin-circleci@0.3.18-next.2
+ - @backstage/plugin-cloudbuild@0.3.18-next.2
+ - @backstage/plugin-code-coverage@0.2.11-next.2
+ - @backstage/plugin-cost-insights@0.12.7-next.2
+ - @backstage/plugin-dynatrace@5.0.0-next.2
+ - @backstage/plugin-entity-feedback@0.2.1-next.2
+ - @backstage/plugin-gcalendar@0.3.14-next.1
+ - @backstage/plugin-gcp-projects@0.3.37-next.1
+ - @backstage/plugin-github-actions@0.5.18-next.2
+ - @backstage/plugin-gocd@0.1.24-next.2
+ - @backstage/plugin-graphiql@0.2.50-next.1
+ - @backstage/plugin-jenkins@0.7.17-next.2
+ - @backstage/plugin-kafka@0.3.18-next.2
+ - @backstage/plugin-lighthouse@0.4.3-next.2
+ - @backstage/plugin-linguist@0.1.3-next.2
+ - @backstage/plugin-microsoft-calendar@0.1.3-next.1
+ - @backstage/plugin-newrelic@0.3.36-next.1
+ - @backstage/plugin-octopus-deploy@0.2.0-next.2
+ - @backstage/plugin-pagerduty@0.5.11-next.2
+ - @backstage/plugin-playlist@0.1.9-next.2
+ - @backstage/plugin-puppetdb@0.1.1-next.2
+ - @backstage/plugin-rollbar@0.4.18-next.2
+ - @backstage/plugin-scaffolder@1.13.1-next.2
+ - @backstage/plugin-search@1.3.0-next.2
+ - @backstage/plugin-sentry@0.5.3-next.2
+ - @backstage/plugin-shortcuts@0.3.10-next.2
+ - @backstage/plugin-stackstorm@0.1.2-next.1
+ - @backstage/plugin-tech-insights@0.3.10-next.2
+ - @backstage/plugin-techdocs@1.6.2-next.2
+ - @backstage/plugin-techdocs-module-addons-contrib@1.0.13-next.2
+ - @backstage/plugin-techdocs-react@1.1.6-next.1
+ - @backstage/plugin-todo@0.2.20-next.2
+ - @backstage/plugin-user-settings@0.7.3-next.2
+ - @backstage/plugin-apache-airflow@0.2.11-next.1
+ - @backstage/plugin-catalog-import@0.9.8-next.2
+ - @backstage/plugin-newrelic-dashboard@0.2.11-next.2
+ - @backstage/config@1.0.7
+ - @backstage/core-app-api@1.8.0-next.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
+ - @internal/plugin-catalog-customized@0.0.10-next.2
+
## 0.2.83-next.1
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index d4941ecd48..c2330244d0 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app",
- "version": "0.2.83-next.1",
+ "version": "0.2.83",
"private": true,
"backstage": {
"role": "frontend"
@@ -29,6 +29,7 @@
"@backstage/plugin-cloudbuild": "workspace:^",
"@backstage/plugin-code-coverage": "workspace:^",
"@backstage/plugin-cost-insights": "workspace:^",
+ "@backstage/plugin-devtools": "workspace:^",
"@backstage/plugin-dynatrace": "workspace:^",
"@backstage/plugin-entity-feedback": "workspace:^",
"@backstage/plugin-explore": "workspace:^",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 19edd9640d..bc9eac8c64 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -109,6 +109,8 @@ import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts
import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card';
import { StackstormPage } from '@backstage/plugin-stackstorm';
import { PuppetDbPage } from '@backstage/plugin-puppetdb';
+import { DevToolsPage } from '@backstage/plugin-devtools';
+import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
const app = createApp({
apis,
@@ -291,6 +293,9 @@ const routes = (
} />
} />
} />
+ }>
+ {customDevToolsPage}
+
);
diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx
index 904fa4a7d4..ffaf72296a 100644
--- a/packages/app/src/components/Root/Root.tsx
+++ b/packages/app/src/components/Root/Root.tsx
@@ -52,6 +52,7 @@ import { MyGroupsSidebarItem } from '@backstage/plugin-org';
import { SearchModal } from '../search/SearchModal';
import Score from '@material-ui/icons/Score';
import { useApp } from '@backstage/core-plugin-api';
+import BuildIcon from '@material-ui/icons/Build';
const useSidebarLogoStyles = makeStyles({
root: {
@@ -176,6 +177,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
to="/settings"
>
+
{children}
diff --git a/packages/app/src/components/devtools/CustomDevToolsPage.tsx b/packages/app/src/components/devtools/CustomDevToolsPage.tsx
new file mode 100644
index 0000000000..dfe70426aa
--- /dev/null
+++ b/packages/app/src/components/devtools/CustomDevToolsPage.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ConfigContent,
+ ExternalDependenciesContent,
+ InfoContent,
+} from '@backstage/plugin-devtools';
+import { DevToolsLayout } from '@backstage/plugin-devtools';
+import React from 'react';
+
+const DevToolsPage = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const customDevToolsPage = ;
diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx
index 26a7ab60f3..da1d0d425c 100644
--- a/packages/app/src/components/home/HomePage.tsx
+++ b/packages/app/src/components/home/HomePage.tsx
@@ -21,12 +21,15 @@ import {
ClockConfig,
HomePageStarredEntities,
CustomHomepageGrid,
+ HomePageToolkit,
+ HomePageCompanyLogo,
} from '@backstage/plugin-home';
import { Content, Header, Page } from '@backstage/core-components';
import { HomePageSearchBar } from '@backstage/plugin-search';
import { HomePageCalendar } from '@backstage/plugin-gcalendar';
import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar';
import React from 'react';
+import HomeIcon from '@material-ui/icons/Home';
const clockConfigs: ClockConfig[] = [
{
@@ -55,12 +58,26 @@ const timeFormat: Intl.DateTimeFormatOptions = {
const defaultConfig = [
{
- component: 'HomePageSearchBar',
+ component: 'CompanyLogo',
x: 0,
y: 0,
width: 12,
height: 1,
},
+ {
+ component: 'WelcomeTitle',
+ x: 0,
+ y: 1,
+ width: 12,
+ height: 1,
+ },
+ {
+ component: 'HomePageSearchBar',
+ x: 0,
+ y: 2,
+ width: 12,
+ height: 1,
+ },
];
export const homePage = (
@@ -78,6 +95,17 @@ export const homePage = (
+
+
+ ,
+ },
+ ]}
+ />
diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md
index 00e7582ad3..dcb0e722c0 100644
--- a/packages/backend-app-api/CHANGELOG.md
+++ b/packages/backend-app-api/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/backend-app-api
+## 0.4.3
+
+### Patch Changes
+
+- cf13b482f9e: Switch `configServiceFactory` to use `ConfigSources` from `@backstage/config-loader` to load config.
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config-loader@1.3.0
+ - @backstage/plugin-permission-node@0.7.8
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
## 0.4.3-next.1
### Patch Changes
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index a21484ff2e..dc5b38daae 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-app-api",
"description": "Core API used by Backstage backend apps",
- "version": "0.4.3-next.1",
+ "version": "0.4.3",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md
index fc6d99cf93..cdf7f6259d 100644
--- a/packages/backend-common/CHANGELOG.md
+++ b/packages/backend-common/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/backend-common
+## 0.18.5
+
+### Patch Changes
+
+- 0297f7a54af: Remove the direct dependency on deprecated "request" library
+- 284db225083: Updated the `DatabaseManager` to include the plugin id in the Postgres application name of the database connections created for each plugin.
+- 3659c71c5d9: Standardize `@aws-sdk` v3 versions
+- 42d817e76ab: Added `HostDiscovery` to supersede deprecated `SingleHostDiscovery` (deprecated due to name)
+- Updated dependencies
+ - @backstage/config-loader@1.3.0
+ - @backstage/integration@1.4.5
+ - @backstage/integration-aws-node@0.1.3
+ - @backstage/backend-app-api@0.4.3
+ - @backstage/backend-dev-utils@0.1.1
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
## 0.18.5-next.1
### Patch Changes
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index 045a385218..914400368a 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
- "version": "0.18.5-next.1",
+ "version": "0.18.5",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
@@ -46,10 +46,10 @@
"test:kubernetes": "backstage-cli package test -t KubernetesContainerRunner --no-watch"
},
"dependencies": {
- "@aws-sdk/abort-controller": "^3.208.0",
- "@aws-sdk/client-s3": "^3.208.0",
- "@aws-sdk/credential-providers": "^3.208.0",
- "@aws-sdk/types": "^3.208.0",
+ "@aws-sdk/abort-controller": "^3.310.0",
+ "@aws-sdk/client-s3": "^3.310.0",
+ "@aws-sdk/credential-providers": "^3.310.0",
+ "@aws-sdk/types": "^3.310.0",
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-dev-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
@@ -114,7 +114,7 @@
}
},
"devDependencies": {
- "@aws-sdk/util-stream-node": "^3.208.0",
+ "@aws-sdk/util-stream-node": "^3.310.0",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/archiver": "^5.1.0",
diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md
index 52043b4ecf..49d5a8bf1f 100644
--- a/packages/backend-defaults/CHANGELOG.md
+++ b/packages/backend-defaults/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/backend-defaults
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-app-api@0.4.3
+ - @backstage/backend-plugin-api@0.5.2
+
## 0.1.10-next.1
### Patch Changes
diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json
index d99e462efa..09b5b82dfc 100644
--- a/packages/backend-defaults/package.json
+++ b/packages/backend-defaults/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-defaults",
"description": "Backend defaults used by Backstage backend apps",
- "version": "0.1.10-next.1",
+ "version": "0.1.10",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md
index 2eea18b4af..8253a108b9 100644
--- a/packages/backend-next/CHANGELOG.md
+++ b/packages/backend-next/CHANGELOG.md
@@ -1,5 +1,37 @@
# example-backend-next
+## 0.0.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend@1.14.0
+ - @backstage/plugin-catalog-backend@1.9.1
+ - @backstage/plugin-kubernetes-backend@0.11.0
+ - @backstage/plugin-todo-backend@0.1.42
+ - @backstage/plugin-permission-node@0.7.8
+ - @backstage/plugin-search-backend@1.3.1
+ - @backstage/backend-defaults@0.1.10
+ - @backstage/plugin-app-backend@0.3.45
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/plugin-search-backend-module-catalog@0.1.1
+ - @backstage/plugin-search-backend-module-explore@0.1.1
+ - @backstage/plugin-search-backend-module-techdocs@0.1.1
+ - @backstage/plugin-techdocs-backend@1.6.2
+ - @backstage/plugin-permission-backend@0.5.20
+ - @backstage/plugin-search-backend-node@1.2.1
+ - @backstage/plugin-permission-common@0.7.5
+
+## 0.0.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1-next.2
+ - @backstage/plugin-kubernetes-backend@0.11.0-next.2
+ - @backstage/plugin-search-backend@1.3.1-next.2
+ - @backstage/plugin-scaffolder-backend@1.13.2-next.2
+
## 0.0.11-next.1
### Patch Changes
diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json
index bf913492ac..a50df741ec 100644
--- a/packages/backend-next/package.json
+++ b/packages/backend-next/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend-next",
- "version": "0.0.11-next.1",
+ "version": "0.0.11",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index 015a54cf45..0255bbbf73 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/backend-openapi-utils
+## 0.0.2
+
+### Patch Changes
+
+- fe16bd39e83: Use permalinks for links including a line number reference
+- 27956d78671: Adjusted README accordingly after the generated output now has a `.generated.ts` extension
+- 021cfbb5152: Corrected resolution of parameter nested schema to use central schemas.
+- 799c33047ed: Updated README to reflect changes in `@backstage/repo-tools`.
+
+## 0.0.2-next.1
+
+### Patch Changes
+
+- fe16bd39e83: Use permalinks for links including a line number reference
+- 021cfbb5152: Corrected resolution of parameter nested schema to use central schemas.
+
## 0.0.2-next.0
### Patch Changes
diff --git a/packages/backend-openapi-utils/README.md b/packages/backend-openapi-utils/README.md
index 122dca0201..ba286f94e8 100644
--- a/packages/backend-openapi-utils/README.md
+++ b/packages/backend-openapi-utils/README.md
@@ -8,7 +8,7 @@ This package is meant to provide a typed Express router for an OpenAPI spec. Bas
### Configuration
-1. Run `yarn --cwd backstage-cli package schema:openapi:generate` to translate your `src/schema/openapi.yaml` to a new Typescript file in `src/schema/openapi.generated.ts`. The command will try to execute both a lint and prettier step on the generated file, where applicable.
+1. Run `yarn --cwd backstage-cli package schema openapi generate` to translate your `src/schema/openapi.yaml` to a new Typescript file in `src/schema/openapi.generated.ts`. The command will try to execute both a lint and prettier step on the generated file, where applicable.
2. In your plugin's `src/service/createRouter.ts`,
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index e59da29840..89a4621a49 100644
--- a/packages/backend-openapi-utils/package.json
+++ b/packages/backend-openapi-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-openapi-utils",
"description": "OpenAPI typescript support.",
- "version": "0.0.2-next.0",
+ "version": "0.0.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index 9a28b1dd3a..cf22e7a9c3 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/backend-plugin-api
+## 0.5.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/config@1.0.7
+ - @backstage/types@1.0.2
+ - @backstage/plugin-permission-common@0.7.5
+
## 0.5.2-next.1
### Patch Changes
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index 58e103e647..d4c2bef5a2 100644
--- a/packages/backend-plugin-api/package.json
+++ b/packages/backend-plugin-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-plugin-api",
"description": "Core API used by Backstage backend plugins",
- "version": "0.5.2-next.1",
+ "version": "0.5.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md
index 1ef0203eb1..c7927d1e66 100644
--- a/packages/backend-tasks/CHANGELOG.md
+++ b/packages/backend-tasks/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/backend-tasks
+## 0.5.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
## 0.5.2-next.1
### Patch Changes
diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json
index b0f5c3afb3..6a87b91202 100644
--- a/packages/backend-tasks/package.json
+++ b/packages/backend-tasks/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-tasks",
"description": "Common distributed task management library for Backstage backends",
- "version": "0.5.2-next.1",
+ "version": "0.5.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index 3ce5006003..7a2ea9c2e6 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/backend-test-utils
+## 0.1.37
+
+### Patch Changes
+
+- 63af7f6d53f: Allow specifying custom Docker registry for database tests
+- b1eb268bf9d: Added `POSTGRES_11` and `POSTGRES_12` as supported test database IDs.
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-app-api@0.4.3
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/types@1.0.2
+
## 0.1.37-next.1
### Patch Changes
diff --git a/packages/backend-test-utils/README.md b/packages/backend-test-utils/README.md
index d0f52388bb..f22cb1bd3c 100644
--- a/packages/backend-test-utils/README.md
+++ b/packages/backend-test-utils/README.md
@@ -12,6 +12,26 @@ cd plugins/my-plugin-backend
yarn add --dev @backstage/backend-test-utils
```
+## Environment variables
+
+- `BACKSTAGE_TEST_DISABLE_DOCKER`
+ - Setting the value to `1` disables Docker for tests
+- `CI`
+ - Setting the value to `1` enables long-running tests, including the ones utilizing Docker
+- `BACKSTAGE_TEST_DOCKER_REGISTRY`
+ - Docker registry mirror address where to pull images for tests, for example `mycompany.docker.io/mirror`
+ - See [documentation](https://node.testcontainers.org/configuration/) for information
+ about authentication (`DOCKER_AUTH_CONFIG`)
+
+Connection strings for different databases that are used for testing. The value of the
+string should point to the running instance of the database.
+
+- `BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_POSTGRES11_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING`
+- `BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING`
+
## Documentation
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index b23d3b411e..5351ade035 100644
--- a/packages/backend-test-utils/package.json
+++ b/packages/backend-test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-test-utils",
"description": "Test helpers library for Backstage backends",
- "version": "0.1.37-next.1",
+ "version": "0.1.37",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-test-utils/src/database/types.ts b/packages/backend-test-utils/src/database/types.ts
index 89ec21e7cc..8c1d6f5193 100644
--- a/packages/backend-test-utils/src/database/types.ts
+++ b/packages/backend-test-utils/src/database/types.ts
@@ -16,6 +16,7 @@
import { DatabaseManager } from '@backstage/backend-common';
import { Knex } from 'knex';
+import { getDockerImageForName } from '../util/getDockerImageForName';
/**
* The possible databases to test against.
@@ -48,35 +49,35 @@ export const allDatabases: Record =
POSTGRES_13: {
name: 'Postgres 13.x',
driver: 'pg',
- dockerImageName: 'postgres:13',
+ dockerImageName: getDockerImageForName('postgres:13'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING',
},
POSTGRES_12: {
name: 'Postgres 12.x',
driver: 'pg',
- dockerImageName: 'postgres:12',
+ dockerImageName: getDockerImageForName('postgres:12'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING',
},
POSTGRES_11: {
name: 'Postgres 11.x',
driver: 'pg',
- dockerImageName: 'postgres:11',
+ dockerImageName: getDockerImageForName('postgres:11'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_DATABASE_POSTGRES11_CONNECTION_STRING',
},
POSTGRES_9: {
name: 'Postgres 9.x',
driver: 'pg',
- dockerImageName: 'postgres:9',
+ dockerImageName: getDockerImageForName('postgres:9'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING',
},
MYSQL_8: {
name: 'MySQL 8.x',
driver: 'mysql2',
- dockerImageName: 'mysql:8',
+ dockerImageName: getDockerImageForName('mysql:8'),
connectionStringEnvironmentVariableName:
'BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING',
},
diff --git a/packages/backend-test-utils/src/util/getDockerImageForName.ts b/packages/backend-test-utils/src/util/getDockerImageForName.ts
new file mode 100644
index 0000000000..7869e7b418
--- /dev/null
+++ b/packages/backend-test-utils/src/util/getDockerImageForName.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const getDockerImageForName = (name: string) => {
+ return process.env.BACKSTAGE_TEST_DOCKER_REGISTRY
+ ? `${process.env.BACKSTAGE_TEST_DOCKER_REGISTRY}/${name}`
+ : name;
+};
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index e676df6839..c775d70b2f 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,74 @@
# example-backend
+## 0.2.83
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend@1.14.0
+ - @backstage/plugin-devtools-backend@0.1.0
+ - @backstage/plugin-catalog-backend@1.9.1
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-kubernetes-backend@0.11.0
+ - @backstage/plugin-auth-backend@0.18.3
+ - @backstage/plugin-badges-backend@0.2.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.3.0
+ - @backstage/integration@1.4.5
+ - @backstage/plugin-todo-backend@0.1.42
+ - @backstage/plugin-jenkins-backend@0.2.0
+ - @backstage/plugin-azure-sites-backend@0.1.7
+ - @backstage/plugin-permission-node@0.7.8
+ - @backstage/plugin-search-backend@1.3.1
+ - example-app@0.2.83
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-app-backend@0.3.45
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/plugin-entity-feedback-backend@0.1.3
+ - @backstage/plugin-events-backend@0.2.6
+ - @backstage/plugin-playlist-backend@0.3.1
+ - @backstage/plugin-rollbar-backend@0.1.42
+ - @backstage/plugin-search-backend-module-pg@0.5.6
+ - @backstage/plugin-tech-insights-backend@0.5.11
+ - @backstage/plugin-techdocs-backend@1.6.2
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.14
+ - @backstage/plugin-adr-backend@0.3.3
+ - @backstage/plugin-azure-devops-backend@0.3.24
+ - @backstage/plugin-code-coverage-backend@0.2.11
+ - @backstage/plugin-explore-backend@0.0.7
+ - @backstage/plugin-graphql-backend@0.1.35
+ - @backstage/plugin-kafka-backend@0.2.38
+ - @backstage/plugin-lighthouse-backend@0.2.1
+ - @backstage/plugin-linguist-backend@0.2.2
+ - @backstage/plugin-permission-backend@0.5.20
+ - @backstage/plugin-proxy-backend@0.2.39
+ - @backstage/plugin-search-backend-node@1.2.1
+ - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29
+ - @backstage/plugin-tech-insights-node@0.4.3
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/plugin-events-node@0.2.6
+ - @backstage/plugin-permission-common@0.7.5
+ - @backstage/plugin-search-common@1.2.3
+
+## 0.2.83-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools-backend@0.1.0-next.0
+ - @backstage/plugin-catalog-backend@1.9.1-next.2
+ - @backstage/plugin-badges-backend@0.2.0-next.2
+ - @backstage/plugin-search-backend-module-elasticsearch@1.3.0-next.2
+ - @backstage/plugin-kubernetes-backend@0.11.0-next.2
+ - @backstage/plugin-auth-backend@0.18.3-next.2
+ - @backstage/plugin-search-backend@1.3.1-next.2
+ - example-app@0.2.83-next.2
+ - @backstage/plugin-scaffolder-backend@1.13.2-next.2
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.2
+ - @backstage/config@1.0.7
+
## 0.2.83-next.1
### Patch Changes
diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile
index eca6ce82c2..da25f9736c 100644
--- a/packages/backend/Dockerfile
+++ b/packages/backend/Dockerfile
@@ -13,11 +13,14 @@ FROM node:16-bullseye-slim
# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
+# Additionally, we install dependencies for `techdocs.generator.runIn: local`.
+# https://backstage.io/docs/features/techdocs/getting-started#disabling-docker-in-docker-situation-optional
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \
- yarn config set python /usr/bin/python3
+ apt-get install -y --no-install-recommends libsqlite3-dev python3 python3-pip build-essential && \
+ yarn config set python /usr/bin/python3 && \
+ pip3 install mkdocs-techdocs-core==1.1.7
# From here on we use the least-privileged `node` user to run the backend.
WORKDIR /app
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 900ee150fc..7d6c1011e4 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.2.83-next.1",
+ "version": "0.2.83",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -42,6 +42,7 @@
"@backstage/plugin-catalog-backend": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-code-coverage-backend": "workspace:^",
+ "@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-entity-feedback-backend": "workspace:^",
"@backstage/plugin-events-backend": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 593d30bc3b..976ad9a577 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -64,6 +64,7 @@ import playlist from './plugins/playlist';
import adr from './plugins/adr';
import lighthouse from './plugins/lighthouse';
import linguist from './plugins/linguist';
+import devTools from './plugins/devtools';
import { PluginEnvironment } from './types';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
@@ -158,8 +159,8 @@ async function main() {
const eventsEnv = useHotMemoize(module, () => createEnv('events'));
const exploreEnv = useHotMemoize(module, () => createEnv('explore'));
const lighthouseEnv = useHotMemoize(module, () => createEnv('lighthouse'));
-
const linguistEnv = useHotMemoize(module, () => createEnv('linguist'));
+ const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
const apiRouter = Router();
apiRouter.use('/catalog', await catalog(catalogEnv));
@@ -185,6 +186,7 @@ async function main() {
apiRouter.use('/entity-feedback', await entityFeedback(entityFeedbackEnv));
apiRouter.use('/adr', await adr(adrEnv));
apiRouter.use('/linguist', await linguist(linguistEnv));
+ apiRouter.use('/devtools', await devTools(devToolsEnv));
apiRouter.use(notFoundHandler());
await lighthouse(lighthouseEnv);
diff --git a/packages/backend/src/plugins/devtools.ts b/packages/backend/src/plugins/devtools.ts
new file mode 100644
index 0000000000..8e1767ddb1
--- /dev/null
+++ b/packages/backend/src/plugins/devtools.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createRouter } from '@backstage/plugin-devtools-backend';
+import { Router } from 'express';
+import type { PluginEnvironment } from '../types';
+
+export default async function createPlugin(
+ env: PluginEnvironment,
+): Promise {
+ return createRouter({
+ logger: env.logger,
+ config: env.config,
+ permissions: env.permissions,
+ });
+}
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index a8c7207650..b91dad32b7 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/cli
+## 0.22.7
+
+### Patch Changes
+
+- 473db605a4f: Enable strict config checking during `backstage-cli config:check` with the new `--strict` option which will surface schema errors.
+- d548886872d: Deprecated the use of React 16
+- Updated dependencies
+ - @backstage/config-loader@1.3.0
+ - @backstage/cli-common@0.1.12
+ - @backstage/cli-node@0.1.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/eslint-plugin@0.1.3
+ - @backstage/release-manifests@0.0.9
+ - @backstage/types@1.0.2
+
## 0.22.7-next.0
### Patch Changes
diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md
index bad849d283..39bee2ed05 100644
--- a/packages/cli/cli-report.md
+++ b/packages/cli/cli-report.md
@@ -58,6 +58,7 @@ Options:
--lax
--frontend
--deprecated
+ --strict
--config
-h, --help
```
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 9de7b3dbb2..28c5ce7030 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
- "version": "0.22.7-next.0",
+ "version": "0.22.7",
"publishConfig": {
"access": "public"
},
diff --git a/packages/cli/src/commands/config/validate.ts b/packages/cli/src/commands/config/validate.ts
index c3d05291e0..c81d82832e 100644
--- a/packages/cli/src/commands/config/validate.ts
+++ b/packages/cli/src/commands/config/validate.ts
@@ -24,5 +24,6 @@ export default async (opts: OptionValues) => {
mockEnv: opts.lax,
fullVisibility: !opts.frontend,
withDeprecatedKeys: opts.deprecated,
+ strict: opts.strict,
});
};
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index ba11a53cfd..3a40839e25 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -345,6 +345,10 @@ export function registerCommands(program: Command) {
.option('--lax', 'Do not require environment variables to be set')
.option('--frontend', 'Only validate the frontend configuration')
.option('--deprecated', 'Output deprecated configuration settings')
+ .option(
+ '--strict',
+ 'Enable strict config validation, forbidding errors and unknown keys',
+ )
.option(...configOption)
.description(
'Validate that the given configuration loads and matches schema',
diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts
index e066a40e7a..00f7818b41 100644
--- a/packages/cli/src/commands/start/startFrontend.ts
+++ b/packages/cli/src/commands/start/startFrontend.ts
@@ -32,6 +32,30 @@ interface StartAppOptions {
configPaths: string[];
}
+function checkReactVersion() {
+ try {
+ // Make sure we're looking at the root of the target repo
+ const reactPkgPath = require.resolve('react/package.json', {
+ paths: [paths.targetRoot],
+ });
+ const reactPkg = require(reactPkgPath);
+ if (reactPkg.version.startsWith('16.')) {
+ console.log(
+ chalk.yellow(
+ `
+⚠️ ⚠️
+⚠️ You are using React version 16, which is deprecated for use in Backstage. ⚠️
+⚠️ Please upgrade to React 17 by updating your packages/app dependencies. ⚠️
+⚠️ ⚠️
+`,
+ ),
+ );
+ }
+ } catch {
+ /* ignored */
+ }
+}
+
export async function startFrontend(options: StartAppOptions) {
if (options.verifyVersions) {
const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
@@ -65,6 +89,8 @@ export async function startFrontend(options: StartAppOptions) {
}
}
+ checkReactVersion();
+
const { name } = await fs.readJson(paths.resolveTarget('package.json'));
const config = await loadCliConfig({
args: options.configPaths,
diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts
index b34e1280c0..aaf3fcfcb9 100644
--- a/packages/cli/src/lib/config.ts
+++ b/packages/cli/src/lib/config.ts
@@ -32,6 +32,7 @@ type Options = {
withFilteredKeys?: boolean;
withDeprecatedKeys?: boolean;
fullVisibility?: boolean;
+ strict?: boolean;
};
export async function loadCliConfig(options: Options) {
@@ -70,6 +71,7 @@ export async function loadCliConfig(options: Options) {
dependencies: localPackageNames,
// Include the package.json in the project root if it exists
packagePaths: [paths.resolveTargetRoot('package.json')],
+ noUndeclaredProperties: options.strict,
});
const { appConfigs } = await loadConfig({
@@ -93,6 +95,7 @@ export async function loadCliConfig(options: Options) {
: ['frontend'],
withFilteredKeys: options.withFilteredKeys,
withDeprecatedKeys: options.withDeprecatedKeys,
+ ignoreSchemaErrors: !options.strict,
});
const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs);
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index 9b2d34f957..05e4398bc8 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,56 @@
# @backstage/config-loader
+## 1.3.0
+
+### Minor Changes
+
+- 201206132da: Introduced a new config source system to replace `loadConfig`. There is a new `ConfigSource` interface along with utilities provided by `ConfigSources`, as well as a number of built-in configuration source implementations. The new system is more flexible and makes it easier to create new and reusable sources of configuration, such as loading configuration from secret providers.
+
+ The following is an example of how to load configuration using the default behavior:
+
+ ```ts
+ const source = ConfigSources.default({
+ argv: options?.argv,
+ remote: options?.remote,
+ });
+ const config = await ConfigSources.toConfig(source);
+ ```
+
+ The `ConfigSource` interface looks like this:
+
+ ```ts
+ export interface ConfigSource {
+ readConfigData(options?: ReadConfigDataOptions): AsyncConfigSourceIterator;
+ }
+ ```
+
+ It is best implemented using an async iterator:
+
+ ```ts
+ class MyConfigSource implements ConfigSource {
+ async *readConfigData() {
+ yield {
+ config: [
+ {
+ context: 'example',
+ data: { backend: { baseUrl: 'http://localhost' } },
+ },
+ ],
+ };
+ }
+ }
+ ```
+
+### Patch Changes
+
+- 7c116bcac7f: Fixed the way that some request errors are thrown
+- 473db605a4f: Added a new `noUndeclaredProperties` option to `SchemaLoader` to support enforcing that there are no extra keys when verifying config.
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
## 1.3.0-next.0
### Minor Changes
diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md
index 1cc353fd4c..8cd469d4d7 100644
--- a/packages/config-loader/api-report.md
+++ b/packages/config-loader/api-report.md
@@ -180,14 +180,17 @@ export function loadConfigSchema(
): Promise;
// @public
-export type LoadConfigSchemaOptions =
+export type LoadConfigSchemaOptions = (
| {
dependencies: string[];
packagePaths?: string[];
}
| {
serialized: JsonObject;
- };
+ }
+) & {
+ noUndeclaredProperties?: boolean;
+};
// @public
export function mergeConfigSchemas(schemas: JSONSchema7[]): JSONSchema7;
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 0aa071c206..d453b40201 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
- "version": "1.3.0-next.0",
+ "version": "1.3.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
diff --git a/packages/config-loader/src/schema/compile.ts b/packages/config-loader/src/schema/compile.ts
index d193e7fbf2..f9e1a3bf76 100644
--- a/packages/config-loader/src/schema/compile.ts
+++ b/packages/config-loader/src/schema/compile.ts
@@ -25,6 +25,7 @@ import {
CONFIG_VISIBILITIES,
ConfigVisibility,
} from './types';
+import { SchemaObject } from 'json-schema-traverse';
/**
* This takes a collection of Backstage configuration schemas from various
@@ -35,6 +36,9 @@ import {
*/
export function compileConfigSchemas(
schemas: ConfigSchemaPackageEntry[],
+ options?: {
+ noUndeclaredProperties?: boolean;
+ },
): ValidationFunc {
// The ajv instance below is stateful and doesn't really allow for additional
// output during validation. We work around this by having this extra piece
@@ -102,6 +106,18 @@ export function compileConfigSchemas(
const merged = mergeConfigSchemas(schemas.map(_ => _.value));
+ if (options?.noUndeclaredProperties) {
+ traverse(merged, (schema: SchemaObject) => {
+ /**
+ * The `additionalProperties` key can only be applied to `type: object` in the JSON
+ * schema.
+ */
+ if (schema?.type === 'object') {
+ schema.additionalProperties ||= false;
+ }
+ });
+ }
+
const validate = ajv.compile(merged);
const visibilityBySchemaPath = new Map();
diff --git a/packages/config-loader/src/schema/load.ts b/packages/config-loader/src/schema/load.ts
index 0309f64ea0..1a96305ae0 100644
--- a/packages/config-loader/src/schema/load.ts
+++ b/packages/config-loader/src/schema/load.ts
@@ -32,12 +32,16 @@ import {
* @public
*/
export type LoadConfigSchemaOptions =
- | {
- dependencies: string[];
- packagePaths?: string[];
- }
- | {
- serialized: JsonObject;
+ | (
+ | {
+ dependencies: string[];
+ packagePaths?: string[];
+ }
+ | {
+ serialized: JsonObject;
+ }
+ ) & {
+ noUndeclaredProperties?: boolean;
};
function errorsToError(errors: ValidationError[]): Error {
@@ -77,7 +81,9 @@ export async function loadConfigSchema(
schemas = serialized.schemas as ConfigSchemaPackageEntry[];
}
- const validate = compileConfigSchemas(schemas);
+ const validate = compileConfigSchemas(schemas, {
+ noUndeclaredProperties: options.noUndeclaredProperties,
+ });
return {
process(
diff --git a/packages/config-loader/src/sources/FileConfigSource.test.ts b/packages/config-loader/src/sources/FileConfigSource.test.ts
index c6980006c0..61cdeed272 100644
--- a/packages/config-loader/src/sources/FileConfigSource.test.ts
+++ b/packages/config-loader/src/sources/FileConfigSource.test.ts
@@ -66,7 +66,7 @@ describe('FileConfigSource', () => {
setTimeout(() => {
tmp.write('a.yaml', 'a: 2');
- }, 10);
+ }, 100);
await expect(readN(source, 2)).resolves.toEqual([
[{ data: { a: 1 }, context: 'a.yaml', path: tmp.resolve('a.yaml') }],
@@ -132,7 +132,7 @@ describe('FileConfigSource', () => {
setTimeout(() => {
tmp.write('x.yaml', '${MY_OTHER_VALUE}');
- }, 10);
+ }, 100);
await expect(readN(source, 2)).resolves.toEqual([
[{ data: { a: '6' }, context: 'a.yaml', path: tmp.resolve('a.yaml') }],
@@ -152,7 +152,7 @@ describe('FileConfigSource', () => {
setTimeout(() => {
tmp.write('x.txt', '9');
- }, 10);
+ }, 100);
await expect(readN(source, 2)).resolves.toEqual([
[{ data: { a: '8' }, context: 'a.yaml', path: tmp.resolve('a.yaml') }],
diff --git a/packages/config-loader/src/sources/RemoteConfigSource.ts b/packages/config-loader/src/sources/RemoteConfigSource.ts
index ff75060f56..3a8f6bf812 100644
--- a/packages/config-loader/src/sources/RemoteConfigSource.ts
+++ b/packages/config-loader/src/sources/RemoteConfigSource.ts
@@ -149,7 +149,7 @@ export class RemoteConfigSource implements ConfigSource {
signal: signal as import('node-fetch').RequestInit['signal'],
});
if (!res.ok) {
- throw ResponseError.fromResponse(res);
+ throw await ResponseError.fromResponse(res);
}
const content = await res.text();
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index f4179e6fb6..f6df0da032 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/core-app-api
+## 1.8.0
+
+### Minor Changes
+
+- c89437db899: The analytics' `navigate` event will now include the route parameters as attributes of the navigate event
+
+### Patch Changes
+
+- b645d70034a: Fixed a bug in the Azure auth provider which prevented getting access tokens with multiple scopes for one resource
+- 42d817e76ab: Added `FrontendHostDiscovery` for config driven discovery implementation
+- Updated dependencies
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/types@1.0.2
+ - @backstage/version-bridge@1.0.4
+
## 1.8.0-next.1
### Minor Changes
diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts
index 2de1d96114..e8cc879c90 100644
--- a/packages/core-app-api/config.d.ts
+++ b/packages/core-app-api/config.d.ts
@@ -117,8 +117,8 @@ export interface Config {
};
/**
- $ Enable redirect authentication flow type, instead of a popup for authentication
- * default value: 'false'
+ * Enable redirect authentication flow type, instead of a popup for authentication.
+ * @defaultValue false
* @visibility frontend
*/
enableExperimentalRedirectFlow?: boolean;
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 5d99754068..4416fa99e1 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
- "version": "1.8.0-next.1",
+ "version": "1.8.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts
index c71799ed98..1c51dd0698 100644
--- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts
@@ -79,6 +79,16 @@ describe('MicrosoftAuth', () => {
'Requested access token with scopes from multiple Azure resources: one-resource, other-resource. Access tokens can only have a single audience.',
);
});
+
+ it('succeeds when requesting multiple scopes for the same resource', async () => {
+ const accessTokenPromise = microsoftAuth.getAccessToken(
+ 'same-resource/one-scope same-resource/other-scope',
+ );
+
+ await expect(accessTokenPromise).resolves.toEqual(
+ 'tokenForOtherResource',
+ );
+ });
});
describe('without a refresh token', () => {
diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts
index fdffb3fc45..c57c65d830 100644
--- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts
@@ -91,10 +91,15 @@ export default class MicrosoftAuth {
}
private static resourceForScopes(scope: string): Promise {
- const audiences = scope
- .split(' ')
- .map(MicrosoftAuth.resourceForScope)
- .filter(aud => aud !== 'openid');
+ const audiences = [
+ ...new Set(
+ scope
+ .split(' ')
+ .map(MicrosoftAuth.resourceForScope)
+ .filter(aud => aud !== 'openid'),
+ ),
+ ];
+
if (audiences.length > 1) {
return Promise.reject(
new Error(
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 825b793d2c..8ace83e62b 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/core-components
+## 0.13.1
+
+### Patch Changes
+
+- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
+- e97769f7c0b: Fix accessibility issue on controlled select input on tab navigation + keyboard enter/space action.
+- b1f13cb38aa: Fix accessibility issue with Edit Metadata Link on screen readers missing notice about opening in a new tab.
+- 26cff1a5dfb: Start capturing sidebar click events in analytics by default.
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/version-bridge@1.0.4
+
+## 0.13.1-next.1
+
+### Patch Changes
+
+- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
+- e97769f7c0b: Fix accessibility issue on controlled select input on tab navigation + keyboard enter/space action.
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.13.1-next.0
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index cdb5a6e4a8..872808622f 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
- "version": "0.13.1-next.0",
+ "version": "0.13.1",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx
index be91753ea3..4d58fdfa77 100644
--- a/packages/core-components/src/components/Link/Link.tsx
+++ b/packages/core-components/src/components/Link/Link.tsx
@@ -188,6 +188,9 @@ export const Link = React.forwardRef(
import('./openapi/verify').then(m => m.bulkCommand)));
+
+ openApiCommand
+ .command('generate [paths...]')
+ .description(
+ 'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.',
+ )
+ .action(lazy(() => import('./openapi/generate').then(m => m.bulkCommand)));
+}
+
export function registerCommands(program: Command) {
program
.command('api-reports [paths...]')
@@ -63,19 +87,7 @@ export function registerCommands(program: Command) {
.description('Find inconsistencies in types of all packages and plugins')
.action(lazy(() => import('./type-deps/type-deps').then(m => m.default)));
- program
- .command('schema:openapi:verify [paths...]')
- .description(
- 'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.',
- )
- .action(lazy(() => import('./openapi/verify').then(m => m.bulkCommand)));
-
- program
- .command('schema:openapi:generate [paths...]')
- .description(
- 'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.',
- )
- .action(lazy(() => import('./openapi/generate').then(m => m.bulkCommand)));
+ registerSchemaCommand(program);
}
// Wraps an action function so that it always exits and handles errors
diff --git a/packages/repo-tools/src/commands/openapi/verify.ts b/packages/repo-tools/src/commands/openapi/verify.ts
index f6dda66738..4c4f310f73 100644
--- a/packages/repo-tools/src/commands/openapi/verify.ts
+++ b/packages/repo-tools/src/commands/openapi/verify.ts
@@ -47,7 +47,7 @@ async function verify(directoryPath: string) {
if (!isEqual(schema.default, yaml)) {
const path = relativePath(cliPaths.targetRoot, directoryPath);
throw new Error(
- `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools schema:openapi:generate ${path}\` to regenerate \`${TS_SCHEMA_PATH}\`.`,
+ `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools schema openapi generate ${path}\` to regenerate \`${TS_SCHEMA_PATH}\`.`,
);
}
}
diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md
index 98bf3668d6..6886f89c11 100644
--- a/packages/techdocs-cli-embedded-app/CHANGELOG.md
+++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md
@@ -1,5 +1,42 @@
# techdocs-cli-embedded-app
+## 0.2.82
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-techdocs@1.6.2
+ - @backstage/app-defaults@1.3.1
+ - @backstage/cli@0.22.7
+ - @backstage/plugin-catalog@1.11.0
+ - @backstage/core-app-api@1.8.0
+ - @backstage/core-components@0.13.1
+ - @backstage/integration-react@1.1.13
+ - @backstage/test-utils@1.3.1
+ - @backstage/plugin-techdocs-react@1.1.6
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.2.82-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/app-defaults@1.3.1-next.2
+ - @backstage/cli@0.22.7-next.0
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/test-utils@1.3.1-next.2
+ - @backstage/plugin-catalog@1.11.0-next.2
+ - @backstage/plugin-techdocs@1.6.2-next.2
+ - @backstage/plugin-techdocs-react@1.1.6-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-app-api@1.8.0-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.82-next.1
### Patch Changes
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index 01f5f8128c..fb3715325b 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -1,6 +1,6 @@
{
"name": "techdocs-cli-embedded-app",
- "version": "0.2.82-next.1",
+ "version": "0.2.82",
"private": true,
"backstage": {
"role": "frontend"
diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md
index 35f3b4bc28..ce88475c7f 100644
--- a/packages/techdocs-cli/CHANGELOG.md
+++ b/packages/techdocs-cli/CHANGELOG.md
@@ -1,5 +1,16 @@
# @techdocs/cli
+## 1.4.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-techdocs-node@1.7.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.7
+
## 1.4.2-next.1
### Patch Changes
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index 8b533b4453..e61401e8ce 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@techdocs/cli",
"description": "Utility CLI for managing TechDocs sites in Backstage.",
- "version": "1.4.2-next.1",
+ "version": "1.4.2",
"publishConfig": {
"access": "public"
},
diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md
index 9fd73a6840..4bebf94052 100644
--- a/packages/test-utils/CHANGELOG.md
+++ b/packages/test-utils/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/test-utils
+## 1.3.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-app-api@1.8.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/types@1.0.2
+ - @backstage/plugin-permission-common@0.7.5
+ - @backstage/plugin-permission-react@0.4.12
+
+## 1.3.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/config@1.0.7
+ - @backstage/core-app-api@1.8.0-next.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
+
## 1.3.1-next.1
### Patch Changes
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index 80d5a2e620..3024b25c05 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
- "version": "1.3.1-next.1",
+ "version": "1.3.1",
"publishConfig": {
"access": "public"
},
diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md
index e2490f346e..0f8b4819b7 100644
--- a/packages/theme/CHANGELOG.md
+++ b/packages/theme/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/theme
+## 0.3.0
+
+### Minor Changes
+
+- 98c0c199b15: Updates light theme's primary foreground and `running` status indicator colours to meet WCAG. Previously #2E77D0 changed to #1F5493.
+
+### Patch Changes
+
+- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
+
+## 0.3.0-next.0
+
+### Minor Changes
+
+- 98c0c199b15: Updates light theme's primary foreground and `running` status indicator colours to meet WCAG. Previously #2E77D0 changed to #1F5493.
+
+### Patch Changes
+
+- 83b45f9df50: Fix accessibility issue with Backstage Table's header style
+
## 0.2.19
### Patch Changes
diff --git a/packages/theme/package.json b/packages/theme/package.json
index e102c067d7..fbc767fb2f 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/theme",
"description": "material-ui theme for use with Backstage.",
- "version": "0.2.19",
+ "version": "0.3.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts
index 53f3a3ef2b..c0a9022bd0 100644
--- a/packages/theme/src/baseTheme.ts
+++ b/packages/theme/src/baseTheme.ts
@@ -159,7 +159,6 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides {
head: {
wordBreak: 'break-word',
overflow: 'hidden',
- color: theme.palette.textSubtle,
fontWeight: 'normal',
lineHeight: '1',
},
@@ -198,14 +197,10 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides {
'&:hover': {
color: 'inherit',
},
- '&:focus': {
- color: 'inherit',
- },
},
// Bold font for highlighting selected column
active: {
fontWeight: 'bold',
- color: 'inherit',
},
},
MuiListItemText: {
diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts
index eb2ef7ce41..23438a1c14 100644
--- a/packages/theme/src/themes.ts
+++ b/packages/theme/src/themes.ts
@@ -33,7 +33,7 @@ export const lightTheme = createTheme({
ok: '#1DB954',
warning: '#FF9800',
error: '#E22134',
- running: '#2E77D0',
+ running: '#1F5493',
pending: '#FFED51',
aborted: '#757575',
},
@@ -48,7 +48,7 @@ export const lightTheme = createTheme({
},
},
primary: {
- main: '#2E77D0',
+ main: '#1F5493',
},
banner: {
info: '#2E77D0',
diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md
index 39e8bf7e4c..dd7823c048 100644
--- a/plugins/adr-backend/CHANGELOG.md
+++ b/plugins/adr-backend/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-adr-backend
+## 0.3.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-adr-common@0.2.9
+ - @backstage/plugin-search-common@1.2.3
+
## 0.3.3-next.1
### Patch Changes
diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json
index a1f6a859a0..a926239ba7 100644
--- a/plugins/adr-backend/package.json
+++ b/plugins/adr-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-adr-backend",
- "version": "0.3.3-next.1",
+ "version": "0.3.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md
index c3b8806c80..9fddae2588 100644
--- a/plugins/adr-common/CHANGELOG.md
+++ b/plugins/adr-common/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-adr-common
+## 0.2.9
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.4.5
+ - @backstage/catalog-model@1.3.0
+ - @backstage/plugin-search-common@1.2.3
+
## 0.2.9-next.0
### Patch Changes
diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json
index 0fdf94003a..adbb6bea58 100644
--- a/plugins/adr-common/package.json
+++ b/plugins/adr-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-adr-common",
"description": "Common functionalities for the adr plugin",
- "version": "0.2.9-next.0",
+ "version": "0.2.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md
index b0ce02fa19..b96e66e9d3 100644
--- a/plugins/adr/CHANGELOG.md
+++ b/plugins/adr/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-adr
+## 0.6.0
+
+### Minor Changes
+
+- b12cd5dc221: render SupportButton only if config is set
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-search-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/integration-react@1.1.13
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-adr-common@0.2.9
+ - @backstage/plugin-search-common@1.2.3
+
+## 0.5.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-search-react@1.6.0-next.2
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.5.1-next.1
### Patch Changes
diff --git a/plugins/adr/package.json b/plugins/adr/package.json
index 3202a8c14e..cac6b89fcc 100644
--- a/plugins/adr/package.json
+++ b/plugins/adr/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-adr",
- "version": "0.5.1-next.1",
+ "version": "0.6.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
index d136de4717..da575d884f 100644
--- a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
+++ b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx
@@ -29,7 +29,7 @@ import {
SupportButton,
WarningPanel,
} from '@backstage/core-components';
-import { useApi, useRouteRef } from '@backstage/core-plugin-api';
+import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import {
AdrFilePathFilterFn,
@@ -170,6 +170,9 @@ export const EntityAdrContent = (props: {
const adrApi = useApi(adrApiRef);
const entityHasAdrs = isAdrAvailable(entity);
+ const config = useApi(configApiRef);
+ const appSupportConfigured = config?.getOptionalConfig('app.support');
+
const { value, loading, error } = useAsync(async () => {
const url = getAdrLocationUrl(entity, scmIntegrations);
return adrApi.listAdrs(url);
@@ -212,7 +215,7 @@ export const EntityAdrContent = (props: {
return (
-
+ {appSupportConfigured && }
{!entityHasAdrs && (
diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md
index 8407f5822e..15c10734de 100644
--- a/plugins/airbrake-backend/CHANGELOG.md
+++ b/plugins/airbrake-backend/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-airbrake-backend
+## 0.2.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json
index c57ffa8be9..eaec28f797 100644
--- a/plugins/airbrake-backend/package.json
+++ b/plugins/airbrake-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-airbrake-backend",
- "version": "0.2.18-next.1",
+ "version": "0.2.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md
index 191207e591..854e9be255 100644
--- a/plugins/airbrake/CHANGELOG.md
+++ b/plugins/airbrake/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-airbrake
+## 0.3.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/dev-utils@1.0.15
+ - @backstage/test-utils@1.3.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/dev-utils@1.0.15-next.2
+ - @backstage/test-utils@1.3.1-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.18-next.1
### Patch Changes
diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json
index 6f7f404742..6933bad355 100644
--- a/plugins/airbrake/package.json
+++ b/plugins/airbrake/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-airbrake",
- "version": "0.3.18-next.1",
+ "version": "0.3.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md
index a20bd80961..e89f981487 100644
--- a/plugins/allure/CHANGELOG.md
+++ b/plugins/allure/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-allure
+## 0.1.34
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.34-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.34-next.1
### Patch Changes
diff --git a/plugins/allure/package.json b/plugins/allure/package.json
index 2a98ae1faf..e2f6c7241a 100644
--- a/plugins/allure/package.json
+++ b/plugins/allure/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-allure",
"description": "A Backstage plugin that integrates with Allure",
- "version": "0.1.34-next.1",
+ "version": "0.1.34",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md
index 4195e02248..fc0a434a5e 100644
--- a/plugins/analytics-module-ga/CHANGELOG.md
+++ b/plugins/analytics-module-ga/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-analytics-module-ga
+## 0.1.29
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.29-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.29-next.0
### Patch Changes
diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json
index 31215bd84f..8062c304fa 100644
--- a/plugins/analytics-module-ga/package.json
+++ b/plugins/analytics-module-ga/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-ga",
- "version": "0.1.29-next.0",
+ "version": "0.1.29",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/analytics-module-ga4/CHANGELOG.md b/plugins/analytics-module-ga4/CHANGELOG.md
index 59c03fec0f..c69204bcc0 100644
--- a/plugins/analytics-module-ga4/CHANGELOG.md
+++ b/plugins/analytics-module-ga4/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-analytics-module-ga4
+## 0.1.0
+
+### Minor Changes
+
+- 22b46f7f562: Plugin provides Backstage Analytics API for Google Analytics 4. Once installed and configured, analytics events will be sent to GA4 as your users navigate and use your Backstage instance
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.0-next.1
### Patch Changes
diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json
index 5e27228534..dba9d0c8f4 100644
--- a/plugins/analytics-module-ga4/package.json
+++ b/plugins/analytics-module-ga4/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-ga4",
- "version": "0.1.0-next.1",
+ "version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md
index 368995a369..f9f6958a87 100644
--- a/plugins/apache-airflow/CHANGELOG.md
+++ b/plugins/apache-airflow/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-apache-airflow
+## 0.2.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.2.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.11-next.0
### Patch Changes
diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json
index 6d503e972a..4ad427a972 100644
--- a/plugins/apache-airflow/package.json
+++ b/plugins/apache-airflow/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-apache-airflow",
- "version": "0.2.11-next.0",
+ "version": "0.2.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md
index 635b1e2dca..2455e7809c 100644
--- a/plugins/api-docs/CHANGELOG.md
+++ b/plugins/api-docs/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-api-docs
+## 0.9.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-catalog@1.11.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.9.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog@1.11.0-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.9.3-next.1
### Patch Changes
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 89e65dfcc3..651a606d9c 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-api-docs",
"description": "A Backstage plugin that helps represent API entities in the frontend",
- "version": "0.9.3-next.1",
+ "version": "0.9.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md
index 174a862d61..9efa02cea9 100644
--- a/plugins/apollo-explorer/CHANGELOG.md
+++ b/plugins/apollo-explorer/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-apollo-explorer
+## 0.1.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.11-next.0
### Patch Changes
diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json
index c1164cf1d6..b28293777d 100644
--- a/plugins/apollo-explorer/package.json
+++ b/plugins/apollo-explorer/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-apollo-explorer",
- "version": "0.1.11-next.0",
+ "version": "0.1.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md
index 1e98f25c64..d89ba0a4ec 100644
--- a/plugins/app-backend/CHANGELOG.md
+++ b/plugins/app-backend/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-app-backend
+## 0.3.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config-loader@1.3.0
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/types@1.0.2
+
## 0.3.45-next.1
### Patch Changes
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 85602a06e4..9a471f0021 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-app-backend",
"description": "A Backstage backend plugin that serves the Backstage frontend app",
- "version": "0.3.45-next.1",
+ "version": "0.3.45",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index c6ff454a7d..551872232d 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-auth-backend
+## 0.18.3
+
+### Patch Changes
+
+- 7c116bcac7f: Fixed the way that some request errors are thrown
+- 473db605a4f: Fix config schema definition.
+- 3ffcdac7d07: Added a persistent session store through the database
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
+## 0.18.3-next.2
+
+### Patch Changes
+
+- 3ffcdac7d07: Added a persistent session store through the database
+- Updated dependencies
+ - @backstage/config@1.0.7
+
## 0.18.3-next.1
### Patch Changes
diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts
index 8d76ca8bf5..c6579e08f8 100644
--- a/plugins/auth-backend/config.d.ts
+++ b/plugins/auth-backend/config.d.ts
@@ -59,6 +59,7 @@ export interface Config {
/**
* The available auth-provider options and attributes
+ * @additionalProperties true
*/
providers?: {
google?: {
diff --git a/plugins/auth-backend/migrations/20230428155633_sessions.js b/plugins/auth-backend/migrations/20230428155633_sessions.js
new file mode 100644
index 0000000000..49462ac503
--- /dev/null
+++ b/plugins/auth-backend/migrations/20230428155633_sessions.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @ts-check
+
+/**
+ * @param {import('knex').Knex} knex
+ */
+exports.up = async function up(knex) {
+ // See https://github.com/gx0r/connect-session-knex
+ // Modeled loosely after https://github.com/gx0r/connect-session-knex/blob/4e0e36a9afbb13c3000a89f5e341f2d2d4339a02/lib/index.js#L114
+ // For simplicity we always make the session a string
+ // Do NOT change around this table or column names; the connect-session-knex library makes assumptions about them
+ await knex.schema.createTable('sessions', table => {
+ table.comment('Session data');
+ table.string('sid').primary().notNullable().comment('ID of the session');
+ table
+ .text('sess', 'longtext')
+ .notNullable()
+ .comment('Session data, JSON serialized');
+ table
+ .timestamp('expired')
+ .notNullable()
+ .comment('The point in time when the session expires');
+ table.index('sid', 'sessions_sid_idx');
+ table.index('expired', 'sessions_expired_idx');
+ });
+};
+
+/**
+ * @param {import('knex').Knex} knex
+ */
+exports.down = async function down(knex) {
+ await knex.schema.alterTable('sessions', table => {
+ table.dropIndex([], 'sessions_sid_idx');
+ table.dropIndex([], 'sessions_expired_idx');
+ });
+ await knex.schema.dropTable('sessions');
+};
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index 61c3fe9a8d..fb53d9b17f 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-auth-backend",
"description": "A Backstage backend plugin that handles authentication",
- "version": "0.18.3-next.1",
+ "version": "0.18.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -44,6 +44,7 @@
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
"compression": "^1.7.4",
+ "connect-session-knex": "^3.0.1",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
diff --git a/plugins/auth-backend/src/database/AuthDatabase.ts b/plugins/auth-backend/src/database/AuthDatabase.ts
new file mode 100644
index 0000000000..97e4236754
--- /dev/null
+++ b/plugins/auth-backend/src/database/AuthDatabase.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ DatabaseManager,
+ PluginDatabaseManager,
+ resolvePackagePath,
+} from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
+import { Knex } from 'knex';
+
+const migrationsDir = resolvePackagePath(
+ '@backstage/plugin-auth-backend',
+ 'migrations',
+);
+
+/**
+ * Ensures that a database connection is established exactly once and only when
+ * asked for, and runs migrations.
+ */
+export class AuthDatabase {
+ readonly #database: PluginDatabaseManager;
+ #promise: Promise | undefined;
+
+ static create(database: PluginDatabaseManager): AuthDatabase {
+ return new AuthDatabase(database);
+ }
+
+ /** @internal */
+ static forTesting(): AuthDatabase {
+ const config = new ConfigReader({
+ backend: {
+ database: {
+ client: 'better-sqlite3',
+ connection: ':memory:',
+ useNullAsDefault: true,
+ },
+ },
+ });
+ const database = DatabaseManager.fromConfig(config).forPlugin('auth');
+ return new AuthDatabase(database);
+ }
+
+ static async runMigrations(knex: Knex): Promise {
+ await knex.migrate.latest({
+ directory: migrationsDir,
+ });
+ }
+
+ private constructor(database: PluginDatabaseManager) {
+ this.#database = database;
+ }
+
+ get(): Promise {
+ this.#promise ??= this.#database.getClient().then(async client => {
+ if (!this.#database.migrations?.skip) {
+ await AuthDatabase.runMigrations(client);
+ }
+ return client;
+ });
+
+ return this.#promise;
+ }
+}
diff --git a/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts b/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts
index da67a2f88f..cf415c7e13 100644
--- a/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts
+++ b/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts
@@ -14,33 +14,10 @@
* limitations under the License.
*/
-import Knex, { Knex as KnexType } from 'knex';
-import { DatabaseKeyStore } from './DatabaseKeyStore';
import { DateTime } from 'luxon';
-
-function createDatabaseManager(
- client: KnexType,
- skipMigrations: boolean = false,
-) {
- return {
- getClient: async () => client,
- migrations: {
- skip: skipMigrations,
- },
- };
-}
-
-function createDB() {
- const knex = Knex({
- client: 'better-sqlite3',
- connection: ':memory:',
- useNullAsDefault: true,
- });
- knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
- resource.run('PRAGMA foreign_keys = ON', () => {});
- });
- return knex;
-}
+import { AuthDatabase } from '../database/AuthDatabase';
+import { DatabaseKeyStore } from './DatabaseKeyStore';
+import { TestDatabases } from '@backstage/backend-test-utils';
const keyBase = {
use: 'sig',
@@ -48,95 +25,107 @@ const keyBase = {
alg: 'Base64',
} as const;
+jest.setTimeout(60_000);
+
describe('DatabaseKeyStore', () => {
- it('should store a key', async () => {
- const client = createDB();
- const store = await DatabaseKeyStore.create({
- database: createDatabaseManager(client),
- });
-
- const key = {
- kid: '123',
- ...keyBase,
- };
-
- await expect(store.listKeys()).resolves.toEqual({ items: [] });
- await store.addKey(key);
-
- const { items } = await store.listKeys();
- expect(items).toEqual([{ createdAt: expect.anything(), key }]);
- expect(
- Math.abs(
- DateTime.fromJSDate(items[0].createdAt).diffNow('seconds').seconds,
- ),
- ).toBeLessThan(10);
+ const databases = TestDatabases.create({
+ ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
});
- it('should remove stored keys', async () => {
- const client = createDB();
- const store = await DatabaseKeyStore.create({
- database: createDatabaseManager(client),
- });
+ it.each(databases.eachSupportedId())(
+ 'should store a key, %p',
+ async databaseId => {
+ const knex = await databases.init(databaseId);
+ await AuthDatabase.runMigrations(knex);
- const key1 = { kid: '1', ...keyBase };
- const key2 = { kid: '2', ...keyBase };
- const key3 = { kid: '3', ...keyBase };
+ const store = new DatabaseKeyStore(knex);
- await store.addKey(key1);
- await store.addKey(key2);
- await store.addKey(key3);
+ const key = {
+ kid: '123',
+ ...keyBase,
+ };
- await expect(store.listKeys()).resolves.toEqual({
- items: [
- { key: key1, createdAt: expect.anything() },
- { key: key2, createdAt: expect.anything() },
- { key: key3, createdAt: expect.anything() },
- ],
- });
+ await expect(store.listKeys()).resolves.toEqual({ items: [] });
+ await store.addKey(key);
- store.removeKeys(['1']);
+ const { items } = await store.listKeys();
+ expect(items).toEqual([{ createdAt: expect.anything(), key }]);
+ expect(
+ Math.abs(
+ DateTime.fromJSDate(items[0].createdAt).diffNow('seconds').seconds,
+ ),
+ ).toBeLessThan(10);
+ },
+ );
- await expect(store.listKeys()).resolves.toEqual({
- items: [
- { key: key2, createdAt: expect.anything() },
- { key: key3, createdAt: expect.anything() },
- ],
- });
+ it.each(databases.eachSupportedId())(
+ 'should remove stored keys, %p',
+ async databaseId => {
+ const knex = await databases.init(databaseId);
+ await AuthDatabase.runMigrations(knex);
- store.removeKeys(['1', '2']);
+ const store = new DatabaseKeyStore(knex);
- await expect(store.listKeys()).resolves.toEqual({
- items: [{ key: key3, createdAt: expect.anything() }],
- });
+ const key1 = { kid: '1', ...keyBase };
+ const key2 = { kid: '2', ...keyBase };
+ const key3 = { kid: '3', ...keyBase };
- store.removeKeys([]);
+ await store.addKey(key1);
+ await store.addKey(key2);
+ await store.addKey(key3);
- await expect(store.listKeys()).resolves.toEqual({
- items: [{ key: key3, createdAt: expect.anything() }],
- });
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [
+ { key: key1, createdAt: expect.anything() },
+ { key: key2, createdAt: expect.anything() },
+ { key: key3, createdAt: expect.anything() },
+ ],
+ });
- store.removeKeys(['3', '4']);
+ await store.removeKeys(['1']);
- await expect(store.listKeys()).resolves.toEqual({
- items: [],
- });
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [
+ { key: key2, createdAt: expect.anything() },
+ { key: key3, createdAt: expect.anything() },
+ ],
+ });
- await store.addKey(key1);
- await store.addKey(key2);
- await store.addKey(key3);
+ await store.removeKeys(['1', '2']);
- await expect(store.listKeys()).resolves.toEqual({
- items: [
- { key: key1, createdAt: expect.anything() },
- { key: key2, createdAt: expect.anything() },
- { key: key3, createdAt: expect.anything() },
- ],
- });
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [{ key: key3, createdAt: expect.anything() }],
+ });
- store.removeKeys(['1', '2', '3']);
+ await store.removeKeys([]);
- await expect(store.listKeys()).resolves.toEqual({
- items: [],
- });
- });
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [{ key: key3, createdAt: expect.anything() }],
+ });
+
+ await store.removeKeys(['3', '4']);
+
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [],
+ });
+
+ await store.addKey(key1);
+ await store.addKey(key2);
+ await store.addKey(key3);
+
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [
+ { key: key1, createdAt: expect.anything() },
+ { key: key2, createdAt: expect.anything() },
+ { key: key3, createdAt: expect.anything() },
+ ],
+ });
+
+ await store.removeKeys(['1', '2', '3']);
+
+ await expect(store.listKeys()).resolves.toEqual({
+ items: [],
+ });
+ },
+ );
});
diff --git a/plugins/auth-backend/src/identity/DatabaseKeyStore.ts b/plugins/auth-backend/src/identity/DatabaseKeyStore.ts
index 8039837469..0559b698e9 100644
--- a/plugins/auth-backend/src/identity/DatabaseKeyStore.ts
+++ b/plugins/auth-backend/src/identity/DatabaseKeyStore.ts
@@ -14,19 +14,10 @@
* limitations under the License.
*/
-import {
- PluginDatabaseManager,
- resolvePackagePath,
-} from '@backstage/backend-common';
import { Knex } from 'knex';
import { DateTime } from 'luxon';
import { AnyJWK, KeyStore, StoredKey } from './types';
-const migrationsDir = resolvePackagePath(
- '@backstage/plugin-auth-backend',
- 'migrations',
-);
-
const TABLE = 'signing_keys';
type Row = {
@@ -35,10 +26,6 @@ type Row = {
key: string;
};
-type Options = {
- database: PluginDatabaseManager;
-};
-
const parseDate = (date: string | Date) => {
const parsedDate =
typeof date === 'string'
@@ -55,24 +42,7 @@ const parseDate = (date: string | Date) => {
};
export class DatabaseKeyStore implements KeyStore {
- static async create(options: Options): Promise {
- const { database } = options;
- const client = await database.getClient();
-
- if (!database.migrations?.skip) {
- await client.migrate.latest({
- directory: migrationsDir,
- });
- }
-
- return new DatabaseKeyStore(client);
- }
-
- private readonly client: Knex;
-
- private constructor(client: Knex) {
- this.client = client;
- }
+ constructor(private readonly client: Knex) {}
async addKey(key: AnyJWK): Promise {
await this.client(TABLE).insert({
diff --git a/plugins/auth-backend/src/identity/KeyStores.test.ts b/plugins/auth-backend/src/identity/KeyStores.test.ts
index 375db4698b..6c034eb64b 100644
--- a/plugins/auth-backend/src/identity/KeyStores.test.ts
+++ b/plugins/auth-backend/src/identity/KeyStores.test.ts
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-import { DatabaseManager } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
-
-import { MemoryKeyStore } from './MemoryKeyStore';
+import { AuthDatabase } from '../database/AuthDatabase';
import { DatabaseKeyStore } from './DatabaseKeyStore';
import { FirestoreKeyStore } from './FirestoreKeyStore';
import { KeyStores } from './KeyStores';
+import { MemoryKeyStore } from './MemoryKeyStore';
+import { getVoidLogger } from '@backstage/backend-common';
describe('KeyStores', () => {
const defaultConfigOptions = {
@@ -34,7 +34,10 @@ describe('KeyStores', () => {
it('reads auth section from config', async () => {
const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig');
- const keyStore = await KeyStores.fromConfig(defaultConfig);
+ const keyStore = await KeyStores.fromConfig(defaultConfig, {
+ logger: getVoidLogger(),
+ database: AuthDatabase.forTesting(),
+ });
expect(keyStore).toBeInstanceOf(MemoryKeyStore);
expect(configSpy).toHaveBeenCalledWith('auth.keyStore');
@@ -46,18 +49,10 @@ describe('KeyStores', () => {
});
it('can handle without auth config', async () => {
- const config = new ConfigReader({
- backend: {
- database: {
- client: 'better-sqlite3',
- connection: ':memory:',
- },
- },
+ const keyStore = await KeyStores.fromConfig(new ConfigReader({}), {
+ logger: getVoidLogger(),
+ database: AuthDatabase.forTesting(),
});
- const database =
- DatabaseManager.fromConfig(config).forPlugin('auth-backend');
- const keyStore = await KeyStores.fromConfig(config, { database });
-
expect(keyStore).toBeInstanceOf(DatabaseKeyStore);
});
@@ -82,7 +77,10 @@ describe('KeyStores', () => {
},
};
const config = new ConfigReader(configOptions);
- const keyStore = await KeyStores.fromConfig(config);
+ const keyStore = await KeyStores.fromConfig(config, {
+ logger: getVoidLogger(),
+ database: AuthDatabase.forTesting(),
+ });
expect(keyStore).toBeInstanceOf(FirestoreKeyStore);
expect(createSpy).toHaveBeenCalledWith(
diff --git a/plugins/auth-backend/src/identity/KeyStores.ts b/plugins/auth-backend/src/identity/KeyStores.ts
index e29b4b6fc4..4cd8f91842 100644
--- a/plugins/auth-backend/src/identity/KeyStores.ts
+++ b/plugins/auth-backend/src/identity/KeyStores.ts
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-import { Logger } from 'winston';
import { pickBy } from 'lodash';
+import { Logger } from 'winston';
-import { PluginDatabaseManager } from '@backstage/backend-common';
import { Config } from '@backstage/config';
+import { AuthDatabase } from '../database/AuthDatabase';
import { DatabaseKeyStore } from './DatabaseKeyStore';
-import { MemoryKeyStore } from './MemoryKeyStore';
import { FirestoreKeyStore } from './FirestoreKeyStore';
+import { MemoryKeyStore } from './MemoryKeyStore';
import { KeyStore } from './types';
type Options = {
- logger?: Logger;
- database?: PluginDatabaseManager;
+ logger: Logger;
+ database: AuthDatabase;
};
export class KeyStores {
@@ -37,23 +37,16 @@ export class KeyStores {
*
* @returns a KeyStore store
*/
- static async fromConfig(
- config: Config,
- options?: Options,
- ): Promise {
- const { logger, database } = options ?? {};
+ static async fromConfig(config: Config, options: Options): Promise {
+ const { logger, database } = options;
const ks = config.getOptionalConfig('auth.keyStore');
const provider = ks?.getOptionalString('provider') ?? 'database';
- logger?.info(`Configuring "${provider}" as KeyStore provider`);
+ logger.info(`Configuring "${provider}" as KeyStore provider`);
if (provider === 'database') {
- if (!database) {
- throw new Error('This KeyStore provider requires a database');
- }
-
- return await DatabaseKeyStore.create({ database });
+ return new DatabaseKeyStore(await database.get());
}
if (provider === 'memory') {
diff --git a/plugins/auth-backend/src/migrations.test.ts b/plugins/auth-backend/src/migrations.test.ts
new file mode 100644
index 0000000000..194bbbf44c
--- /dev/null
+++ b/plugins/auth-backend/src/migrations.test.ts
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Knex } from 'knex';
+import { TestDatabases } from '@backstage/backend-test-utils';
+import fs from 'fs';
+
+const migrationsDir = `${__dirname}/../migrations`;
+const migrationsFiles = fs.readdirSync(migrationsDir).sort();
+
+async function migrateUpOnce(knex: Knex): Promise {
+ await knex.migrate.up({ directory: migrationsDir });
+}
+
+async function migrateDownOnce(knex: Knex): Promise {
+ await knex.migrate.down({ directory: migrationsDir });
+}
+
+async function migrateUntilBefore(knex: Knex, target: string): Promise {
+ const index = migrationsFiles.indexOf(target);
+ if (index === -1) {
+ throw new Error(`Migration ${target} not found`);
+ }
+ for (let i = 0; i < index; i++) {
+ await migrateUpOnce(knex);
+ }
+}
+
+jest.setTimeout(60_000);
+
+describe('migrations', () => {
+ const databases = TestDatabases.create({
+ ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
+ });
+
+ it.each(databases.eachSupportedId())(
+ '20230428155633_sessions.js, %p',
+ async databaseId => {
+ const knex = await databases.init(databaseId);
+
+ await migrateUntilBefore(knex, '20230428155633_sessions.js');
+ await migrateUpOnce(knex);
+
+ // Ensure that large cookies are supported
+ const data = `{"cookie":"${'a'.repeat(100_000)}"}`;
+ await knex
+ .insert({ sid: 'abc', expired: knex.fn.now(), sess: data })
+ .into('sessions');
+ await knex
+ .insert({ sid: 'def', expired: knex.fn.now(), sess: data })
+ .into('sessions');
+
+ await expect(knex('sessions').orderBy('sid', 'asc')).resolves.toEqual([
+ { sid: 'abc', expired: expect.anything(), sess: data },
+ { sid: 'def', expired: expect.anything(), sess: data },
+ ]);
+
+ await migrateDownOnce(knex);
+
+ await knex.destroy();
+ },
+ );
+});
diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts
index 0d8f736550..6313276508 100644
--- a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts
+++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts
@@ -227,7 +227,7 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers {
{ headers },
);
if (!res.ok) {
- throw ResponseError.fromResponse(res);
+ throw await ResponseError.fromResponse(res);
}
const cfIdentity = await res.json();
return cfIdentity as unknown as CloudflareAccessIdentityProfile;
diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts
index 8013777415..2996ff68be 100644
--- a/plugins/auth-backend/src/service/router.ts
+++ b/plugins/auth-backend/src/service/router.ts
@@ -32,9 +32,11 @@ import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { createOidcRouter, TokenFactory, KeyStores } from '../identity';
import session from 'express-session';
+import connectSessionKnex from 'connect-session-knex';
import passport from 'passport';
import { Minimatch } from 'minimatch';
import { CatalogAuthResolverContext } from '../lib/resolvers';
+import { AuthDatabase } from '../database/AuthDatabase';
/** @public */
export type ProviderFactories = { [s: string]: AuthProviderFactory };
@@ -70,7 +72,11 @@ export async function createRouter(
const appUrl = config.getString('app.baseUrl');
const authUrl = await discovery.getExternalBaseUrl('auth');
- const keyStore = await KeyStores.fromConfig(config, { logger, database });
+ const authDb = AuthDatabase.create(database);
+ const keyStore = await KeyStores.fromConfig(config, {
+ logger,
+ database: authDb,
+ });
const keyDurationSeconds = 3600;
const tokenIssuer = new TokenFactory({
@@ -84,14 +90,18 @@ export async function createRouter(
const secret = config.getOptionalString('auth.session.secret');
if (secret) {
router.use(cookieParser(secret));
- // TODO: Configure the server-side session storage. The default MemoryStore is not designed for production
const enforceCookieSSL = authUrl.startsWith('https');
+ const KnexSessionStore = connectSessionKnex(session);
router.use(
session({
secret,
saveUninitialized: false,
resave: false,
cookie: { secure: enforceCookieSSL ? 'auto' : false },
+ store: new KnexSessionStore({
+ createtable: false,
+ knex: await authDb.get(),
+ }),
}),
);
router.use(passport.initialize());
diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md
index 7cb9f4cc64..e299198f8f 100644
--- a/plugins/auth-node/CHANGELOG.md
+++ b/plugins/auth-node/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-auth-node
+## 0.2.14
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 0.2.14-next.1
### Patch Changes
diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json
index 1d8e71a4c0..25bcda593a 100644
--- a/plugins/auth-node/package.json
+++ b/plugins/auth-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-node",
- "version": "0.2.14-next.1",
+ "version": "0.2.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md
index 3dd206ae66..232347e752 100644
--- a/plugins/azure-devops-backend/CHANGELOG.md
+++ b/plugins/azure-devops-backend/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-azure-devops-backend
+## 0.3.24
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+ - @backstage/plugin-azure-devops-common@0.3.0
+
## 0.3.24-next.1
### Patch Changes
diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json
index f28bfa5cb4..2a0f3b76a5 100644
--- a/plugins/azure-devops-backend/package.json
+++ b/plugins/azure-devops-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-backend",
- "version": "0.3.24-next.1",
+ "version": "0.3.24",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md
index a0328bcb22..20bbec23b4 100644
--- a/plugins/azure-devops/CHANGELOG.md
+++ b/plugins/azure-devops/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-azure-devops
+## 0.3.0
+
+### Minor Changes
+
+- 877df261085: The getBuildRuns function now checks contains multiple comma-separated builds and splits them to send multiple requests for each and concatenates the results.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-azure-devops-common@0.3.0
+
+## 0.3.0-next.2
+
+### Minor Changes
+
+- 877df261085: The getBuildRuns function now checks contains multiple comma-separated builds and splits them to send multiple requests for each and concatenates the results.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.9-next.1
### Patch Changes
diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md
index 5079fa4f16..7eeae09813 100644
--- a/plugins/azure-devops/README.md
+++ b/plugins/azure-devops/README.md
@@ -70,7 +70,7 @@ dev.azure.com/project:
dev.azure.com/build-definition:
```
-In this case `` will be the name of your Team Project and `` will be the name of the Build Definition you would like to see Builds for. If the Build Definition name has spaces in it make sure to put quotes around it
+In this case `` will be the name of your Team Project and `` will be the name of the Build Definition you would like to see Builds for, and it's possible to add more Builds separated by a comma. If the Build Definition name has spaces in it make sure to put quotes around it.
### Azure Pipelines Component
diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json
index 914cc9a4ba..99122b62db 100644
--- a/plugins/azure-devops/package.json
+++ b/plugins/azure-devops/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops",
- "version": "0.2.9-next.1",
+ "version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
index 68d8896538..a202008798 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts
@@ -120,7 +120,23 @@ export class AzureDevOpsClient implements AzureDevOpsApi {
queryString.append('repoName', repoName);
}
if (definitionName) {
- queryString.append('definitionName', definitionName);
+ const definitionNames = definitionName.split(',');
+ if (definitionNames.length > 1) {
+ const buildRuns: BuildRun[] = [];
+ for (const name of definitionNames) {
+ queryString.set('definitionName', name.trim());
+ if (options?.top) {
+ queryString.set('top', options.top.toString());
+ }
+ const urlSegment = `builds/${encodeURIComponent(
+ projectName,
+ )}?${queryString}`;
+ const items = await this.get(urlSegment);
+ buildRuns.push(...items);
+ }
+ return { items: buildRuns };
+ }
+ queryString.append('definitionName', definitionName.trim());
}
if (options?.top) {
queryString.append('top', options.top.toString());
diff --git a/plugins/azure-sites-backend/CHANGELOG.md b/plugins/azure-sites-backend/CHANGELOG.md
index 9c25a70214..d8b123dec1 100644
--- a/plugins/azure-sites-backend/CHANGELOG.md
+++ b/plugins/azure-sites-backend/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-azure-sites-backend
+## 0.1.7
+
+### Patch Changes
+
+- d66d4f916aa: Updated URL to `/health` and corrected typos in the `README.md`
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+ - @backstage/plugin-azure-sites-common@0.1.0
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/azure-sites-backend/README.md b/plugins/azure-sites-backend/README.md
index 0d44b9a4e4..23152f9760 100644
--- a/plugins/azure-sites-backend/README.md
+++ b/plugins/azure-sites-backend/README.md
@@ -2,7 +2,7 @@
Simple plugin that proxies requests to the Azure Portal API through Azure SDK JavaScript libraries.
-_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_
+_Inspired by [roadie.io AWS Lambda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_
## Setup
@@ -65,7 +65,7 @@ Here's how to get the backend plugin up and running:
```ts
import azure from './plugins/azure';
- // Removed for clairty...
+ // Removed for clarity...
async function main() {
// ...
@@ -82,4 +82,4 @@ Here's how to get the backend plugin up and running:
4. Now run `yarn start-backend` from the repo root.
-5. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`.
+5. Finally, open `http://localhost:7007/api/azure-sites/health` in a browser, it should return `{"status":"ok"}`.
diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json
index a58459d2aa..61c23a4b0f 100644
--- a/plugins/azure-sites-backend/package.json
+++ b/plugins/azure-sites-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-sites-backend",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-sites/CHANGELOG.md b/plugins/azure-sites/CHANGELOG.md
index 1eea015834..bc7184c9ca 100644
--- a/plugins/azure-sites/CHANGELOG.md
+++ b/plugins/azure-sites/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-azure-sites
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-azure-sites-common@0.1.0
+
+## 0.1.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json
index 8863e76dae..44775bcab4 100644
--- a/plugins/azure-sites/package.json
+++ b/plugins/azure-sites/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-sites",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md
index 50bf55c918..e15a74e3de 100644
--- a/plugins/badges-backend/CHANGELOG.md
+++ b/plugins/badges-backend/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-badges-backend
+## 0.2.0
+
+### Minor Changes
+
+- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
+
+ Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
+
+ **BREAKING**: `createRouter` now require that `tokenManager`, `logger`, and `identityApi`, are passed in as options.
+
+### Patch Changes
+
+- 0cd552c28d8: Removed some unused dependencies
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
+## 0.2.0-next.2
+
+### Minor Changes
+
+- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
+
+ Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
+
+ **BREAKING**: `createRouter` now require that `tokenManager`, `logger`, and `identityApi`, are passed in as options.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.0.7
+
## 0.1.39-next.1
### Patch Changes
diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json
index 9c235255e5..f2db6ef5c5 100644
--- a/plugins/badges-backend/package.json
+++ b/plugins/badges-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-badges-backend",
"description": "A Backstage backend plugin that generates README badges for your entities",
- "version": "0.1.39-next.1",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -55,9 +55,6 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/cli": "workspace:^",
- "@backstage/core-app-api": "workspace:^",
- "@backstage/dev-utils": "workspace:^",
- "@backstage/test-utils": "workspace:^",
"@types/node": "*",
"cross-fetch": "^3.1.5"
},
diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md
index b4a0e6aec6..1ec81cb321 100644
--- a/plugins/badges/CHANGELOG.md
+++ b/plugins/badges/CHANGELOG.md
@@ -1,5 +1,39 @@
# @backstage/plugin-badges
+## 0.2.42
+
+### Patch Changes
+
+- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
+
+ Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
+
+ **BREAKING**: `createRouter` now require that `tokenManager`, `logger`, and `identityApi`, are passed in as options.
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.42-next.2
+
+### Patch Changes
+
+- a0108c49774: Fixing badges-backend plugin to get a token from the TokenManager instead of parsing the request header. Hence, it's now possible to disable the authMiddleware for the badges-backend plugin to expose publicly the badges.
+
+ Implementing an obfuscation feature to protect an open badges endpoint from being enumerated. The feature is disabled by default and the change is compatible with the previous version.
+
+ **BREAKING**: `createRouter` now require that `tokenManager`, `logger`, and `identityApi`, are passed in as options.
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.42-next.1
### Patch Changes
diff --git a/plugins/badges/package.json b/plugins/badges/package.json
index 714de3e3fb..fcfb55c386 100644
--- a/plugins/badges/package.json
+++ b/plugins/badges/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-badges",
"description": "A Backstage plugin that generates README badges for your entities",
- "version": "0.2.42-next.1",
+ "version": "0.2.42",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md
index 11d820a334..da83be709c 100644
--- a/plugins/bazaar-backend/CHANGELOG.md
+++ b/plugins/bazaar-backend/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-bazaar-backend
+## 0.2.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 0.2.8-next.1
### Patch Changes
diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json
index 60e549655e..9dd25233c6 100644
--- a/plugins/bazaar-backend/package.json
+++ b/plugins/bazaar-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar-backend",
- "version": "0.2.8-next.1",
+ "version": "0.2.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md
index 298c684d3e..4b1a86deb9 100644
--- a/plugins/bazaar/CHANGELOG.md
+++ b/plugins/bazaar/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-bazaar
+## 0.2.8
+
+### Patch Changes
+
+- 900880ab7c3: Fixed `validateDOMNesting` warnings
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/cli@0.22.7
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-catalog@1.11.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.8-next.2
+
+### Patch Changes
+
+- 900880ab7c3: Fixed `validateDOMNesting` warnings
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/cli@0.22.7-next.0
+ - @backstage/plugin-catalog@1.11.0-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.8-next.1
### Patch Changes
diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json
index c61db47231..d8d334b426 100644
--- a/plugins/bazaar/package.json
+++ b/plugins/bazaar/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar",
- "version": "0.2.8-next.1",
+ "version": "0.2.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md
index c3c31ed392..04b2cc787b 100644
--- a/plugins/bitbucket-cloud-common/CHANGELOG.md
+++ b/plugins/bitbucket-cloud-common/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-bitbucket-cloud-common
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.4.5
+
## 0.2.6-next.0
### Patch Changes
diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json
index 7bf2c5791e..a63b9588fb 100644
--- a/plugins/bitbucket-cloud-common/package.json
+++ b/plugins/bitbucket-cloud-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-bitbucket-cloud-common",
"description": "Common functionalities for bitbucket-cloud plugins",
- "version": "0.2.6-next.0",
+ "version": "0.2.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md
index 1ef42a6bbe..069fc0ca6e 100644
--- a/plugins/bitrise/CHANGELOG.md
+++ b/plugins/bitrise/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-bitrise
+## 0.1.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.45-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.45-next.1
### Patch Changes
diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json
index 1b3740a058..fe62603226 100644
--- a/plugins/bitrise/package.json
+++ b/plugins/bitrise/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-bitrise",
"description": "A Backstage plugin that integrates towards Bitrise",
- "version": "0.1.45-next.1",
+ "version": "0.1.45",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md
index f7ffc5e63c..029ab14592 100644
--- a/plugins/catalog-backend-module-aws/CHANGELOG.md
+++ b/plugins/catalog-backend-module-aws/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/plugin-catalog-backend-module-aws
+## 0.2.0
+
+### Minor Changes
+
+- 1a3b5f1e390: **BREAKING**: AwsOrganizationCloudAccountProcessor.fromConfig now returns a promise instead of the instance directly.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/integration-aws-node@0.1.3
+ - @backstage/plugin-kubernetes-common@0.6.3
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+
+## 0.1.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-kubernetes-common@0.6.3-next.0
+ - @backstage/config@1.0.7
+
## 0.1.19-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md
index 491dbf497e..83b072061b 100644
--- a/plugins/catalog-backend-module-aws/api-report.md
+++ b/plugins/catalog-backend-module-aws/api-report.md
@@ -3,11 +3,12 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
+import { AwsCredentialIdentity } from '@aws-sdk/types';
+import { AwsCredentialsManager } from '@backstage/integration-aws-node';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
import { CatalogProcessorParser } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
-import { Credentials } from 'aws-sdk';
import { EntityProvider } from '@backstage/plugin-catalog-node';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common';
@@ -19,11 +20,16 @@ import { UrlReader } from '@backstage/backend-common';
// @public
export type AWSCredentialFactory = (
awsAccountId: string,
-) => Promise;
+) => Promise;
// @public
export class AwsEKSClusterProcessor implements CatalogProcessor {
- constructor(options: { credentialsFactory?: AWSCredentialFactory });
+ constructor(options: {
+ credentialsFactory?: AWSCredentialFactory;
+ credentialsManager?: AwsCredentialsManager;
+ });
+ // (undocumented)
+ static fromConfig(configRoot: Config): AwsEKSClusterProcessor;
// (undocumented)
getProcessorName(): string;
// (undocumented)
@@ -44,7 +50,7 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
options: {
logger: Logger;
},
- ): AwsOrganizationCloudAccountProcessor;
+ ): Promise;
// (undocumented)
getProcessorName(): string;
// (undocumented)
diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json
index 73bd5123d2..a5e0aad55b 100644
--- a/plugins/catalog-backend-module-aws/package.json
+++ b/plugins/catalog-backend-module-aws/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-aws",
"description": "A Backstage catalog backend module that helps integrate towards AWS",
- "version": "0.1.19-next.1",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -45,7 +45,12 @@
"clean": "backstage-cli package clean"
},
"dependencies": {
- "@aws-sdk/client-s3": "^3.208.0",
+ "@aws-sdk/client-eks": "^3.310.0",
+ "@aws-sdk/client-organizations": "^3.310.0",
+ "@aws-sdk/client-s3": "^3.310.0",
+ "@aws-sdk/credential-providers": "^3.310.0",
+ "@aws-sdk/middleware-endpoint": "^3.310.0",
+ "@aws-sdk/types": "^3.310.0",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
@@ -53,23 +58,23 @@
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
+ "@backstage/integration-aws-node": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-kubernetes-common": "workspace:^",
"@backstage/types": "workspace:^",
- "aws-sdk": "^2.840.0",
"lodash": "^4.17.21",
"p-limit": "^3.0.2",
"uuid": "^8.0.0",
"winston": "^3.2.1"
},
"devDependencies": {
- "@aws-sdk/util-stream-node": "^3.208.0",
+ "@aws-sdk/util-stream-node": "^3.310.0",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/lodash": "^4.14.151",
"aws-sdk-client-mock": "^2.0.0",
- "aws-sdk-mock": "^5.2.1",
+ "aws-sdk-client-mock-jest": "^2.0.0",
"luxon": "^3.0.0",
"yaml": "^2.0.0"
},
diff --git a/plugins/catalog-backend-module-aws/src/credentials/AwsCredentials.ts b/plugins/catalog-backend-module-aws/src/credentials/AwsCredentials.ts
deleted file mode 100644
index fded78aed4..0000000000
--- a/plugins/catalog-backend-module-aws/src/credentials/AwsCredentials.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2022 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import aws, { Credentials } from 'aws-sdk';
-import { CredentialsOptions } from 'aws-sdk/lib/credentials';
-
-export class AwsCredentials {
- /**
- * If accessKeyId and secretAccessKey are missing, the DefaultAWSCredentialsProviderChain will be used:
- * https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html
- */
- static create(
- config: {
- accessKeyId?: string;
- secretAccessKey?: string;
- roleArn?: string;
- externalId?: string;
- },
- roleSessionName: string,
- ): Credentials | CredentialsOptions | undefined {
- if (!config) {
- return undefined;
- }
-
- const accessKeyId = config.accessKeyId;
- const secretAccessKey = config.secretAccessKey;
- let explicitCredentials: Credentials | undefined;
-
- if (accessKeyId && secretAccessKey) {
- explicitCredentials = new Credentials({
- accessKeyId,
- secretAccessKey,
- });
- }
-
- const roleArn = config.roleArn;
- if (roleArn) {
- return new aws.ChainableTemporaryCredentials({
- masterCredentials: explicitCredentials,
- params: {
- RoleArn: roleArn,
- RoleSessionName: roleSessionName,
- ExternalId: config.externalId,
- },
- });
- }
-
- return explicitCredentials;
- }
-}
diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts
index ecf3b1a961..6cec6ed66d 100644
--- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts
+++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts
@@ -15,24 +15,28 @@
*/
import { AwsEKSClusterProcessor } from './AwsEKSClusterProcessor';
-import AWSMock from 'aws-sdk-mock';
-import aws from 'aws-sdk';
+import { mockClient } from 'aws-sdk-client-mock';
+import {
+ ListClustersResponse,
+ DescribeClusterResponse,
+ ListClustersCommand,
+ DescribeClusterCommand,
+ EKSClient,
+} from '@aws-sdk/client-eks';
describe('AwsEKSClusterProcessor', () => {
- AWSMock.setSDKInstance(aws);
-
describe('readLocation', () => {
const processor = new (AwsEKSClusterProcessor as any)({});
const location = { type: 'aws-eks', target: '957140518395/us-west-2' };
const emit = jest.fn();
it('generates cluster correctly', async () => {
- const clusters: aws.EKS.Types.ListClustersResponse = {
+ const clusters: ListClustersResponse = {
clusters: ['backstage-test'],
nextToken: undefined,
};
- const cluster: aws.EKS.Types.DescribeClusterResponse = {
+ const cluster: DescribeClusterResponse = {
cluster: {
name: 'backstage-test',
arn: 'arn:aws:1',
@@ -42,9 +46,10 @@ describe('AwsEKSClusterProcessor', () => {
},
},
};
+ const mock = mockClient(EKSClient);
- AWSMock.mock('EKS', 'listClusters', clusters);
- AWSMock.mock('EKS', 'describeCluster', cluster);
+ mock.on(ListClustersCommand).resolves(clusters);
+ mock.on(DescribeClusterCommand).resolves(cluster);
await processor.readLocation(location, false, emit);
diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
index 05c85c7426..4a19b9eb7f 100644
--- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
+++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
@@ -24,8 +24,14 @@ import {
ANNOTATION_KUBERNETES_API_SERVER_CA,
ANNOTATION_KUBERNETES_AUTH_PROVIDER,
} from '@backstage/plugin-kubernetes-common';
-import { Credentials, EKS } from 'aws-sdk';
+import { EKS } from '@aws-sdk/client-eks';
import { AWSCredentialFactory } from '../types';
+import { AwsCredentialIdentity, Provider } from '@aws-sdk/types';
+import {
+ AwsCredentialsManager,
+ DefaultAwsCredentialsManager,
+} from '@backstage/integration-aws-node';
+import { Config } from '@backstage/config';
const ACCOUNTID_ANNOTATION: string = 'amazonaws.com/account-id';
const ARN_ANNOTATION: string = 'amazonaws.com/arn';
@@ -39,9 +45,22 @@ const ARN_ANNOTATION: string = 'amazonaws.com/arn';
*/
export class AwsEKSClusterProcessor implements CatalogProcessor {
private credentialsFactory?: AWSCredentialFactory;
+ private credentialsManager?: AwsCredentialsManager;
- constructor(options: { credentialsFactory?: AWSCredentialFactory }) {
+ static fromConfig(configRoot: Config): AwsEKSClusterProcessor {
+ const awsCredentaislManager =
+ DefaultAwsCredentialsManager.fromConfig(configRoot);
+ return new AwsEKSClusterProcessor({
+ credentialsManager: awsCredentaislManager,
+ });
+ }
+
+ constructor(options: {
+ credentialsFactory?: AWSCredentialFactory;
+ credentialsManager?: AwsCredentialsManager;
+ }) {
this.credentialsFactory = options.credentialsFactory;
+ this.credentialsManager = options.credentialsManager;
}
getProcessorName(): string {
@@ -73,20 +92,30 @@ export class AwsEKSClusterProcessor implements CatalogProcessor {
);
}
- let credentials: Credentials | undefined;
+ let credentials: AwsCredentialIdentity | undefined;
if (this.credentialsFactory) {
credentials = await this.credentialsFactory(accountId);
}
- const eksClient = new EKS({ credentials, region });
- const clusters = await eksClient.listClusters({}).promise();
+ let providerFunction: (() => Provider) | undefined;
+ if (this.credentialsManager) {
+ const credentialsProvider =
+ await this.credentialsManager.getCredentialProvider({ accountId });
+ providerFunction = () => credentialsProvider.sdkCredentialProvider;
+ }
+
+ const eksClient = new EKS({
+ credentials,
+ credentialDefaultProvider: providerFunction,
+ });
+ const clusters = await eksClient.listClusters({});
if (clusters.clusters === undefined) {
return true;
}
const results = clusters.clusters
- .map(cluster => eksClient.describeCluster({ name: cluster }).promise())
+ .map(cluster => eksClient.describeCluster({ name: cluster }))
.map(async describedClusterPromise => {
const describedCluster = await describedClusterPromise;
if (describedCluster.cluster) {
diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts
index b9b8ba7741..458a1f3531 100644
--- a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts
+++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts
@@ -14,7 +14,13 @@
* limitations under the License.
*/
+import { mockClient } from 'aws-sdk-client-mock';
+import 'aws-sdk-client-mock-jest';
import { AwsOrganizationCloudAccountProcessor } from './AwsOrganizationCloudAccountProcessor';
+import {
+ ListAccountsCommand,
+ OrganizationsClient,
+} from '@aws-sdk/client-organizations';
describe('AwsOrganizationCloudAccountProcessor', () => {
describe('readLocation', () => {
@@ -23,26 +29,21 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
});
const location = { type: 'aws-cloud-accounts', target: '' };
const emit = jest.fn();
- const listAccounts = jest.fn();
+ const mock = mockClient(OrganizationsClient);
- processor.organizations.listAccounts = listAccounts;
- afterEach(() => jest.resetAllMocks());
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
it('generates component entities for accounts', async () => {
- listAccounts.mockImplementation(() => {
- return {
- async promise() {
- return {
- Accounts: [
- {
- Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
- Name: 'testaccount',
- },
- ],
- NextToken: undefined,
- };
+ mock.on(ListAccountsCommand).resolves({
+ Accounts: [
+ {
+ Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
+ Name: 'testaccount',
},
- };
+ ],
+ NextToken: undefined,
});
await processor.readLocation(location, false, emit);
expect(emit).toHaveBeenCalledWith({
@@ -74,24 +75,18 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
type: 'aws-cloud-accounts',
target: 'o-1vl18kc5a3',
};
- listAccounts.mockImplementation(() => {
- return {
- async promise() {
- return {
- Accounts: [
- {
- Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
- Name: 'testaccount',
- },
- {
- Arn: 'arn:aws:organizations::192594491037:account/o-zzzzzzzzz/957140518395',
- Name: 'testaccount2',
- },
- ],
- NextToken: undefined,
- };
+ mock.on(ListAccountsCommand).resolves({
+ Accounts: [
+ {
+ Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395',
+ Name: 'testaccount',
},
- };
+ {
+ Arn: 'arn:aws:organizations::192594491037:account/o-zzzzzzzzz/957140518395',
+ Name: 'testaccount2',
+ },
+ ],
+ NextToken: undefined,
});
await processor.readLocation(locationTest, false, emit);
expect(emit).toHaveBeenCalledTimes(1);
diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts
index c0ba1e3e98..264fa7a50d 100644
--- a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts
+++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts
@@ -22,13 +22,17 @@ import {
processingResult,
} from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common';
-import AWS, { Credentials, Organizations } from 'aws-sdk';
-import { Account, ListAccountsResponse } from 'aws-sdk/clients/organizations';
-import { Logger } from 'winston';
import {
- AwsOrganizationProviderConfig,
- readAwsOrganizationConfig,
-} from '../awsOrganization/config';
+ Account,
+ ListAccountsResponse,
+ Organizations,
+} from '@aws-sdk/client-organizations';
+import { Logger } from 'winston';
+import { readAwsOrganizationConfig } from '../awsOrganization/config';
+import {
+ AwsCredentialProvider,
+ DefaultAwsCredentialsManager,
+} from '@backstage/integration-aws-node';
const AWS_ORGANIZATION_REGION = 'us-east-1';
const LOCATION_TYPE = 'aws-cloud-accounts';
@@ -47,39 +51,31 @@ const ORGANIZATION_ANNOTATION = 'amazonaws.com/organization-id';
*/
export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
private readonly organizations: Organizations;
- private readonly provider: AwsOrganizationProviderConfig;
+ private readonly logger: Logger;
- static fromConfig(config: Config, options: { logger: Logger }) {
+ static async fromConfig(config: Config, options: { logger: Logger }) {
const c = config.getOptionalConfig('catalog.processors.awsOrganization');
- return new AwsOrganizationCloudAccountProcessor({
- ...options,
- provider: c ? readAwsOrganizationConfig(c) : {},
+ const orgConfig = c ? readAwsOrganizationConfig(c) : undefined;
+ const awsCredentialsManager =
+ DefaultAwsCredentialsManager.fromConfig(config);
+ const credProvider = await awsCredentialsManager.getCredentialProvider({
+ arn: orgConfig?.roleArn,
});
- }
-
- private static buildCredentials(
- config: AwsOrganizationProviderConfig,
- ): Credentials | undefined {
- const roleArn = config.roleArn;
- if (!roleArn) {
- return undefined;
- }
-
- return new AWS.ChainableTemporaryCredentials({
- params: {
- RoleSessionName: 'backstage-aws-organization-processor',
- RoleArn: roleArn,
- },
- });
- }
-
- private constructor(options: { provider: AwsOrganizationProviderConfig }) {
- this.provider = options.provider;
- const credentials = AwsOrganizationCloudAccountProcessor.buildCredentials(
- this.provider,
+ return new AwsOrganizationCloudAccountProcessor(
+ credProvider,
+ options.logger,
);
- this.organizations = new AWS.Organizations({
- credentials,
+ }
+
+ private constructor(
+ private readonly credProvider: AwsCredentialProvider,
+ logger: Logger,
+ ) {
+ this.logger = logger?.child({
+ target: this.getProcessorName(),
+ });
+ this.organizations = new Organizations({
+ credentialDefaultProvider: () => this.credProvider.sdkCredentialProvider,
region: AWS_ORGANIZATION_REGION,
}); // Only available in us-east-1
}
@@ -97,6 +93,8 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
return false;
}
+ this.logger?.info('Discovering AWS Organization Account objects');
+
(await this.getAwsAccounts())
.map(account => this.mapAccountToComponent(account))
.filter(entity => {
@@ -143,9 +141,8 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor {
let nextToken = undefined;
while (isInitialAttempt || nextToken) {
isInitialAttempt = false;
- const orgAccounts: ListAccountsResponse = await this.organizations
- .listAccounts({ NextToken: nextToken })
- .promise();
+ const orgAccounts: ListAccountsResponse =
+ await this.organizations.listAccounts({ NextToken: nextToken });
if (orgAccounts.Accounts) {
awsAccounts = awsAccounts.concat(orgAccounts.Accounts);
}
diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts
index 821fb6d4f4..e9e17ad76a 100644
--- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts
+++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts
@@ -23,8 +23,9 @@ import {
import { ConfigReader } from '@backstage/config';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
import { AwsS3EntityProvider } from './AwsS3EntityProvider';
-import aws from 'aws-sdk';
-import AWSMock from 'aws-sdk-mock';
+import { mockClient } from 'aws-sdk-client-mock';
+import 'aws-sdk-client-mock-jest';
+import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3';
class PersistingTaskRunner implements TaskRunner {
private tasks: TaskInvocationDefinition[] = [];
@@ -42,38 +43,39 @@ class PersistingTaskRunner implements TaskRunner {
const logger = getVoidLogger();
describe('AwsS3EntityProvider', () => {
- AWSMock.setSDKInstance(aws);
- const createObjectList = (keys: string[]): aws.S3.ObjectList => {
+ const createObjectList = (keys: string[]) => {
const objects = keys.map(key => {
return {
Key: key,
- } as aws.S3.Types.Object;
+ };
});
-
- return objects as aws.S3.ObjectList;
+ return objects;
};
const keys = ['key1.yaml', 'key2.yaml', 'key3.yaml', 'key 4.yaml'];
+ const mock = mockClient(S3Client);
- AWSMock.mock('S3', 'listObjectsV2', async req => {
- const prefix = req.Prefix ?? '';
+ beforeEach(() => {
+ mock.on(ListObjectsV2Command).callsFake(async req => {
+ const prefix = req.Prefix ?? '';
+
+ if (!req.ContinuationToken) {
+ return {
+ Contents: createObjectList(
+ keys
+ .slice(0, Math.ceil(keys.length / 2))
+ .map(key => `${prefix}${key}`),
+ ),
+ NextContinuationToken: 'next-token',
+ };
+ }
- if (!req.ContinuationToken) {
return {
Contents: createObjectList(
- keys
- .slice(0, Math.ceil(keys.length / 2))
- .map(key => `${prefix}${key}`),
+ keys.slice(Math.ceil(keys.length / 2)).map(key => `${prefix}${key}`),
),
- NextContinuationToken: 'next-token',
- } as aws.S3.Types.ListObjectsV2Output;
- }
-
- return {
- Contents: createObjectList(
- keys.slice(Math.ceil(keys.length / 2)).map(key => `${prefix}${key}`),
- ),
- } as aws.S3.Types.ListObjectsV2Output;
+ };
+ });
});
afterEach(() => {
@@ -103,7 +105,9 @@ describe('AwsS3EntityProvider', () => {
});
const schedulingConfig: Record = {};
-
+ const normalizedExpectedBaseUrl = expectedBaseUrl.endsWith('/')
+ ? expectedBaseUrl
+ : `${expectedBaseUrl}/`;
const schedule = new PersistingTaskRunner();
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
@@ -131,7 +135,7 @@ describe('AwsS3EntityProvider', () => {
await (taskDef.fn as () => Promise)();
const expectedEntities = keys.map(key => {
- const url = encodeURI(`${expectedBaseUrl}${key}`);
+ const url = encodeURI(`${normalizedExpectedBaseUrl}${key}`);
return {
entity: {
apiVersion: 'backstage.io/v1alpha1',
@@ -161,6 +165,25 @@ describe('AwsS3EntityProvider', () => {
// eslint-disable-next-line jest/expect-expect
it('apply full update on scheduled execution', async () => {
+ return expectMutation(
+ 'regionalStatic',
+ {
+ bucketName: 'bucket-1',
+ prefix: 'sub/dir/',
+ region: 'eu-west-1',
+ },
+ 'https://bucket-1.s3.eu-west-1.amazonaws.com/sub/dir/',
+ {
+ 'key1.yaml': 'generated-8ece85ad90200c6577b99f553dcbedde05fa34bb',
+ 'key2.yaml': 'generated-6b54c6aaa44696f5e91ce0f54fb27bf837549d11',
+ 'key3.yaml': 'generated-88c703cf1aa66913db4033b029adc0b174574646',
+ 'key 4.yaml': 'generated-2b7e068bb4ec818c14f179a1e721843fc2dbc5f9',
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('apply full update on scheduled execution force path style', async () => {
return expectMutation(
'regionalStatic',
{
@@ -175,11 +198,72 @@ describe('AwsS3EntityProvider', () => {
'key3.yaml': 'generated-8d75f78ed9fa618ce433b226dc24eeab441f3a2d',
'key 4.yaml': 'generated-1e0249dcb5805fc2ce6ac2d3c4d2a3ef4f1270c0',
},
+ {
+ s3ForcePathStyle: true,
+ },
);
});
// eslint-disable-next-line jest/expect-expect
- it('us-east-1 has region-less URLs', async () => {
+ it('apply full update on scheduled execution no prefix', async () => {
+ return expectMutation(
+ 'regionalStatic',
+ {
+ bucketName: 'bucket-1',
+ region: 'eu-west-1',
+ },
+ 'https://bucket-1.s3.eu-west-1.amazonaws.com/',
+ {
+ 'key1.yaml': 'generated-ac6d99e56e0fbd6bf3719a5182f37bfa84b1cddc',
+ 'key2.yaml': 'generated-cc3e1145496cd95bf3898687bb5b7d2991a7b7fd',
+ 'key3.yaml': 'generated-a197557b0cb89d14ac1297231ed930bd4c40b664',
+ 'key 4.yaml': 'generated-a774e6c1d1c7ee511b7d79ebb99b08a39cf439c4',
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('apply full update on scheduled execution force path style no prefix', async () => {
+ return expectMutation(
+ 'regionalStatic',
+ {
+ bucketName: 'bucket-1',
+ region: 'eu-west-1',
+ },
+ 'https://s3.eu-west-1.amazonaws.com/bucket-1',
+ {
+ 'key1.yaml': 'generated-b390cd4de7ff00f6d51426870fc0e685029fbf09',
+ 'key2.yaml': 'generated-15ebeb1e53ffe71ea93f037076aefcdc5cc5e617',
+ 'key3.yaml': 'generated-fd0a33e70bafd707a60139decbd0383361ab3b4e',
+ 'key 4.yaml': 'generated-ac574fb266d9c8bd983b25a208c538e4ad2c1d06',
+ },
+ {
+ s3ForcePathStyle: true,
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('apply full update on scheduled execution bucket with dot in it', async () => {
+ return expectMutation(
+ 'regionalStatic',
+ {
+ bucketName: 'bucket.1',
+ prefix: 'sub/dir/',
+ region: 'eu-west-1',
+ },
+ 'https://s3.eu-west-1.amazonaws.com/bucket.1/sub/dir/',
+ {
+ 'key1.yaml': 'generated-651b5851d4670b7d5bb9a319d8f6eb295092e4ad',
+ 'key2.yaml': 'generated-ccddcdc3ad814aea072883118f3a9d0ac39a161e',
+ 'key3.yaml': 'generated-b2b8548903aa764544ab79dc81ab5093e6e4ba83',
+ 'key 4.yaml': 'generated-aafa9cc0422f5e8ccce1616dc0c74fa94f692af3',
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('us-east-1 does not have region-less URLs', async () => {
return expectMutation(
'usEast1',
{
@@ -187,12 +271,12 @@ describe('AwsS3EntityProvider', () => {
prefix: 'sub/dir/',
region: 'us-east-1',
},
- 'https://s3.amazonaws.com/bucket-1/sub/dir/',
+ 'https://bucket-1.s3.us-east-1.amazonaws.com/sub/dir/',
{
- 'key1.yaml': 'generated-f7e3f1c89f62cbf0d82db16452faaa7c040fc331',
- 'key2.yaml': 'generated-925173f7dba1acaa73cac5ef4d10c3ed7660aa25',
- 'key3.yaml': 'generated-d94cf017911ed7fb3be4a62a3dae1f5202879de3',
- 'key 4.yaml': 'generated-f917fca0cfacc2be478ca2a3cff94d00917a1cde',
+ 'key1.yaml': 'generated-980e6ad47fbfbfeead708a9c7c87331b7540296a',
+ 'key2.yaml': 'generated-266794d8e789089dddba2b42cd79e70b149aa61c',
+ 'key3.yaml': 'generated-96f0cdcd7e33aa687c19d160ec7d5b1975cb9ea1',
+ 'key 4.yaml': 'generated-4f6d3ffb232e4a5b82c3a52549e4c36dab1c2f8d',
},
);
});
@@ -209,18 +293,60 @@ describe('AwsS3EntityProvider', () => {
bucketName: 'bucket-1',
prefix: 'sub/dir/',
},
- 'https://s3.eu-central-1.amazonaws.com/bucket-1/sub/dir/',
+ 'https://bucket-1.s3.eu-central-1.amazonaws.com/sub/dir/',
{
- 'key1.yaml': 'generated-285d144b5c1e24e801b97f61f794d566dabc7236',
- 'key2.yaml': 'generated-36b78d7fa69690059797d4cc4f40c2a5eaed1d6d',
- 'key3.yaml': 'generated-a39ad804bf65993cbc0f3810ff128a3c91daf768',
- 'key 4.yaml': 'generated-bd7622ac86c313cb812d90e335b2f5f6bff14670',
+ 'key1.yaml': 'generated-bb17a9f9b420edd93137692f000b2d443a29239c',
+ 'key2.yaml': 'generated-b4559ecc8b615407e421dbaa3fcb9338e977b8ac',
+ 'key3.yaml': 'generated-bb3d500a6f8b351b0eed1aec77ebce65c1ab41fb',
+ 'key 4.yaml': 'generated-686de87f5882f6558b43280c5b6047e485199e88',
},
);
});
// eslint-disable-next-line jest/expect-expect
it('custom endpoint', async () => {
+ return expectMutation(
+ 'customEndpoint',
+ {
+ bucketName: 'bucket-1',
+ prefix: 'sub/dir/',
+ },
+ 'http://bucket-1.localhost:1234/sub/dir/',
+ {
+ 'key1.yaml': 'generated-79625971ead9c39c91b0bfef4d7c748325e31234',
+ 'key2.yaml': 'generated-eeae95f16605d3afdc615436c18c9fbd35781299',
+ 'key3.yaml': 'generated-692976561563c3fa55889ad090363716c7b7e915',
+ 'key 4.yaml': 'generated-c8f92ea0eb96c355b7e56cbc461cef29f830747e',
+ },
+ {
+ endpoint: 'http://localhost:1234/',
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('custom endpoint bucket dot', async () => {
+ return expectMutation(
+ 'customEndpoint',
+ {
+ bucketName: 'bucket.1',
+ prefix: 'sub/dir/',
+ },
+ 'http://localhost:1234/bucket.1/sub/dir/',
+ {
+ 'key1.yaml': 'generated-07be77ac8626ac76a924414e0edf5ce66d3e0b27',
+ 'key2.yaml': 'generated-47295ada3c894cf9bac0d570a34d909244ea2f74',
+ 'key3.yaml': 'generated-30e98f3ebd350f3b618acd024199b5f31545495d',
+ 'key 4.yaml': 'generated-b71bc7661b92cef0db62c9652912fd5e4b245aa7',
+ },
+ {
+ endpoint: 'http://localhost:1234/',
+ },
+ );
+ });
+
+ // eslint-disable-next-line jest/expect-expect
+ it('custom endpoint force path style', async () => {
return expectMutation(
'customEndpoint',
{
@@ -235,7 +361,8 @@ describe('AwsS3EntityProvider', () => {
'key 4.yaml': 'generated-659f17a2429f107db04850f9081e3e9c56084aeb',
},
{
- endpoint: 'http://localhost:1234',
+ endpoint: 'http://localhost:1234/',
+ s3ForcePathStyle: true,
},
);
});
@@ -305,12 +432,12 @@ describe('AwsS3EntityProvider', () => {
},
},
},
- 'https://s3.eu-west-1.amazonaws.com/bucket-1/sub/dir/',
+ 'https://bucket-1.s3.eu-west-1.amazonaws.com/sub/dir/',
{
- 'key1.yaml': 'generated-7f6d5861b0b3401a38b5fe62e6c7ca11da5fd6d8',
- 'key2.yaml': 'generated-a290be145586042af7d80715626399c9d661718d',
- 'key3.yaml': 'generated-8d75f78ed9fa618ce433b226dc24eeab441f3a2d',
- 'key 4.yaml': 'generated-1e0249dcb5805fc2ce6ac2d3c4d2a3ef4f1270c0',
+ 'key1.yaml': 'generated-8ece85ad90200c6577b99f553dcbedde05fa34bb',
+ 'key2.yaml': 'generated-6b54c6aaa44696f5e91ce0f54fb27bf837549d11',
+ 'key3.yaml': 'generated-88c703cf1aa66913db4033b029adc0b174574646',
+ 'key 4.yaml': 'generated-2b7e068bb4ec818c14f179a1e721843fc2dbc5f9',
},
undefined,
true,
diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
index 8212f9279a..4a87a42e75 100644
--- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
+++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
@@ -23,13 +23,20 @@ import {
locationSpecToLocationEntity,
} from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common';
-import { AwsCredentials } from '../credentials/AwsCredentials';
import { readAwsS3Configs } from './config';
import { AwsS3Config } from './types';
-import { S3 } from 'aws-sdk';
-import { ListObjectsV2Output } from 'aws-sdk/clients/s3';
+import {
+ ListObjectsV2Command,
+ ListObjectsV2Output,
+ S3,
+} from '@aws-sdk/client-s3';
import * as uuid from 'uuid';
import { Logger } from 'winston';
+import { getEndpointFromInstructions } from '@aws-sdk/middleware-endpoint';
+import {
+ AwsCredentialsManager,
+ DefaultAwsCredentialsManager,
+} from '@backstage/integration-aws-node';
// TODO: event-based updates using S3 events (+ queue like SQS)?
/**
@@ -41,9 +48,10 @@ import { Logger } from 'winston';
*/
export class AwsS3EntityProvider implements EntityProvider {
private readonly logger: Logger;
- private readonly s3: S3;
+ private s3?: S3;
private readonly scheduleFn: () => Promise;
private connection?: EntityProviderConnection;
+ private endpoint?: string;
static fromConfig(
configRoot: Config,
@@ -78,7 +86,8 @@ export class AwsS3EntityProvider implements EntityProvider {
`No schedule provided neither via code nor config for awsS3-provider:${providerConfig.id}.`,
);
}
-
+ const awsCredentialsManager =
+ DefaultAwsCredentialsManager.fromConfig(configRoot);
const taskRunner =
options.schedule ??
options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
@@ -86,6 +95,7 @@ export class AwsS3EntityProvider implements EntityProvider {
return new AwsS3EntityProvider(
providerConfig,
integration,
+ awsCredentialsManager,
options.logger,
taskRunner,
);
@@ -94,7 +104,8 @@ export class AwsS3EntityProvider implements EntityProvider {
private constructor(
private readonly config: AwsS3Config,
- integration: AwsS3Integration,
+ private readonly integration: AwsS3Integration,
+ private readonly awsCredentialsManager: AwsCredentialsManager,
logger: Logger,
taskRunner: TaskRunner,
) {
@@ -102,17 +113,6 @@ export class AwsS3EntityProvider implements EntityProvider {
target: this.getProviderName(),
});
- this.s3 = new S3({
- apiVersion: '2006-03-01',
- credentials: AwsCredentials.create(
- integration.config,
- 'backstage-aws-s3-provider',
- ),
- endpoint: integration.config.endpoint,
- region: this.config.region,
- s3ForcePathStyle: integration.config.s3ForcePathStyle,
- });
-
this.scheduleFn = this.createScheduleFn(taskRunner);
}
@@ -146,6 +146,28 @@ export class AwsS3EntityProvider implements EntityProvider {
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
async connect(connection: EntityProviderConnection): Promise {
this.connection = connection;
+ const credProvider =
+ await this.awsCredentialsManager.getCredentialProvider();
+ this.s3 = new S3({
+ apiVersion: '2006-03-01',
+ credentialDefaultProvider: () => credProvider.sdkCredentialProvider,
+ endpoint: this.integration.config.endpoint,
+ region: this.config.region,
+ forcePathStyle: this.integration.config.s3ForcePathStyle,
+ });
+
+ // https://github.com/aws/aws-sdk-js-v3/issues/4122#issuecomment-1298968804
+ const endpoint = await getEndpointFromInstructions(
+ {
+ Bucket: this.config.bucketName,
+ },
+ ListObjectsV2Command,
+ this.s3.config as unknown as Record,
+ );
+ if (endpoint?.url)
+ this.endpoint = endpoint.url.href.endsWith('/')
+ ? endpoint.url.href
+ : `${endpoint.url.href}/`;
await this.scheduleFn();
}
@@ -175,18 +197,21 @@ export class AwsS3EntityProvider implements EntityProvider {
}
private async listAllObjectKeys(): Promise {
+ if (!this.s3) {
+ throw new Error('Not initialized');
+ }
+
const keys: string[] = [];
let continuationToken: string | undefined = undefined;
let output: ListObjectsV2Output;
do {
- const request = this.s3.listObjectsV2({
+ output = await this.s3.listObjectsV2({
Bucket: this.config.bucketName,
ContinuationToken: continuationToken,
Prefix: this.config.prefix,
});
- output = await request.promise();
if (output.Contents) {
output.Contents.forEach(item => {
if (item.Key && !item.Key.endsWith('/')) {
@@ -209,9 +234,6 @@ export class AwsS3EntityProvider implements EntityProvider {
}
private createObjectUrl(key: string): string {
- const bucketName = this.config.bucketName;
- const endpoint = this.s3.endpoint.href;
-
- return encodeURI(`${endpoint}${bucketName}/${key}`);
+ return new URL(key, this.endpoint).href;
}
}
diff --git a/plugins/catalog-backend-module-aws/src/types.ts b/plugins/catalog-backend-module-aws/src/types.ts
index 3d9df38181..23460c104e 100644
--- a/plugins/catalog-backend-module-aws/src/types.ts
+++ b/plugins/catalog-backend-module-aws/src/types.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Credentials } from 'aws-sdk';
+import { AwsCredentialIdentity } from '@aws-sdk/types';
/**
* A factory for providing user-specified AWS credentials for a given AWS account.
@@ -22,4 +22,4 @@ import { Credentials } from 'aws-sdk';
*/
export type AWSCredentialFactory = (
awsAccountId: string,
-) => Promise;
+) => Promise;
diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md
index e8458d82e7..21b95b5861 100644
--- a/plugins/catalog-backend-module-azure/CHANGELOG.md
+++ b/plugins/catalog-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-catalog-backend-module-azure
+## 0.1.16
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+
## 0.1.16-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json
index 2e2ff96e7e..e9d6803571 100644
--- a/plugins/catalog-backend-module-azure/package.json
+++ b/plugins/catalog-backend-module-azure/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-azure",
"description": "A Backstage catalog backend module that helps integrate towards Azure",
- "version": "0.1.16-next.1",
+ "version": "0.1.16",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
index af6e6c3a7f..94c83e6b2e 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-catalog-backend-module-bitbucket-cloud
+## 0.1.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/plugin-bitbucket-cloud-common@0.2.6
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.12-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json
index 77df5b12b4..00e4269342 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/package.json
+++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud",
- "version": "0.1.12-next.1",
+ "version": "0.1.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
index 3c9e51b08e..ae894c2e12 100644
--- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-backend-module-bitbucket-server
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 0.1.10-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json
index 68960fbf81..3dc8aca390 100644
--- a/plugins/catalog-backend-module-bitbucket-server/package.json
+++ b/plugins/catalog-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-server",
- "version": "0.1.10-next.1",
+ "version": "0.1.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
index 0193d551a5..b7a9f2d0a2 100644
--- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-backend-module-bitbucket
+## 0.2.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-bitbucket-cloud-common@0.2.6
+
## 0.2.12-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json
index 64269a9102..1dbbb4bb45 100644
--- a/plugins/catalog-backend-module-bitbucket/package.json
+++ b/plugins/catalog-backend-module-bitbucket/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket",
- "version": "0.2.12-next.1",
+ "version": "0.2.12",
"deprecated": true,
"main": "src/index.ts",
"types": "src/index.ts",
diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
index ea3d4af960..8f94db98f3 100644
--- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-backend-module-gerrit
+## 0.1.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 0.1.13-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json
index 22bc3a95e5..a69aac5034 100644
--- a/plugins/catalog-backend-module-gerrit/package.json
+++ b/plugins/catalog-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gerrit",
- "version": "0.1.13-next.1",
+ "version": "0.1.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md
index f54a55f9e9..427b48e7bb 100644
--- a/plugins/catalog-backend-module-github/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/plugin-catalog-backend-module-github
+## 0.3.0
+
+### Minor Changes
+
+- 970678adbe2: Implement events support for `GithubMultiOrgEntityProvider`
+
+ **BREAKING:** Passing in a custom `teamTransformer` will now correctly completely override the default transformer behavior
+
+### Patch Changes
+
+- 78bb674a713: Fixed bug in queryWithPaging that caused secondary rate limit errors in GitHub with organizations having more than 1000 repositories. This change makes one request per second to avoid concurrency issues.
+- bd101cefd37: Updated the `team.edited` event emitted from `GithubOrgEntityProvider` to also include teams description.
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-events-node@0.2.6
+
+## 0.3.0-next.2
+
+### Minor Changes
+
+- 970678adbe2: Implement events support for `GithubMultiOrgEntityProvider`
+
+ **BREAKING:** Passing in a custom `teamTransformer` will now correctly completely override the default transformer behavior
+
+### Patch Changes
+
+- bd101cefd37: Updated the `team.edited` event emitted from `GithubOrgEntityProvider` to also include teams description.
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1-next.2
+ - @backstage/config@1.0.7
+
## 0.2.8-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md
index 0698fae8a0..7b944b33a9 100644
--- a/plugins/catalog-backend-module-github/api-report.md
+++ b/plugins/catalog-backend-module-github/api-report.md
@@ -10,6 +10,7 @@ import { Config } from '@backstage/config';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-node';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
+import { EventBroker } from '@backstage/plugin-events-node';
import { EventParams } from '@backstage/plugin-events-node';
import { EventSubscriber } from '@backstage/plugin-events-node';
import { GithubCredentialsProvider } from '@backstage/integration';
@@ -158,6 +159,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
// @public
export interface GithubMultiOrgEntityProviderOptions {
+ eventBroker?: EventBroker;
githubCredentialsProvider?: GithubCredentialsProvider;
githubUrl: string;
id: string;
diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json
index 5b29baa05c..029acdf0e0 100644
--- a/plugins/catalog-backend-module-github/package.json
+++ b/plugins/catalog-backend-module-github/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-github",
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
- "version": "0.2.8-next.1",
+ "version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts
index df4565a174..7a0a29e098 100644
--- a/plugins/catalog-backend-module-github/src/lib/github.test.ts
+++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts
@@ -21,6 +21,7 @@ import { graphql as graphqlMsw } from 'msw';
import { setupServer } from 'msw/node';
import { TeamTransformer, UserTransformer } from './defaultTransformers';
import {
+ getOrganizationsFromUser,
getOrganizationTeams,
getOrganizationUsers,
getTeamMembers,
@@ -448,6 +449,37 @@ describe('github', () => {
});
});
+ describe('getOrganizationsFromUser', () => {
+ it('reads orgs from user', async () => {
+ const input: QueryResponse = {
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'a',
+ },
+ {
+ login: 'b',
+ },
+ {
+ login: 'c',
+ },
+ ],
+ },
+ },
+ };
+
+ server.use(
+ graphqlMsw.query('orgs', (_req, res, ctx) => res(ctx.data(input))),
+ );
+
+ await expect(getOrganizationsFromUser(graphql, 'foo')).resolves.toEqual({
+ orgs: ['a', 'b', 'c'],
+ });
+ });
+ });
+
describe('getTeamMembers', () => {
it('reads team members', async () => {
const input: QueryResponse = {
diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts
index d0b629092a..412b751c38 100644
--- a/plugins/catalog-backend-module-github/src/lib/github.ts
+++ b/plugins/catalog-backend-module-github/src/lib/github.ts
@@ -33,6 +33,7 @@ import { DeferredEntity } from '@backstage/plugin-catalog-node';
export type QueryResponse = {
organization?: OrganizationResponse;
repositoryOwner?: RepositoryOwnerResponse;
+ user?: UserResponse;
};
type RepositoryOwnerResponse = {
@@ -46,11 +47,19 @@ export type OrganizationResponse = {
repositories?: Connection;
};
+export type UserResponse = {
+ organizations?: Connection;
+};
+
export type PageInfo = {
hasNextPage: boolean;
endCursor?: string;
};
+export type GithubOrg = {
+ login: string;
+};
+
/**
* Github User
*
@@ -334,6 +343,34 @@ export async function getOrganizationTeamsFromUsers(
return { groups };
}
+export async function getOrganizationsFromUser(
+ client: typeof graphql,
+ user: string,
+): Promise<{
+ orgs: string[];
+}> {
+ const query = `
+ query orgs($user: String!) {
+ user(login: $user) {
+ organizations(first: 100) {
+ nodes { login }
+ pageInfo { hasNextPage, endCursor }
+ }
+ }
+ }`;
+
+ const orgs = await queryWithPaging(
+ client,
+ query,
+ '',
+ r => r.user?.organizations,
+ async o => o.login,
+ { user },
+ );
+
+ return { orgs };
+}
+
export async function getOrganizationTeam(
client: typeof graphql,
org: string,
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts
index 76ab55552d..99bef6e074 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts
@@ -16,11 +16,13 @@
import { getVoidLogger } from '@backstage/backend-common';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
+import { ConfigReader } from '@backstage/config';
import {
GithubCredentialsProvider,
GithubIntegrationConfig,
} from '@backstage/integration';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
+import { EventSubscriber } from '@backstage/plugin-events-node';
import { graphql } from '@octokit/graphql';
import {
GithubMultiOrgEntityProvider,
@@ -160,7 +162,7 @@ describe('GithubMultiOrgEntityProvider', () => {
const logger = getVoidLogger();
const gitHubConfig: GithubIntegrationConfig = {
- host: 'https://github.com',
+ host: 'github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
@@ -201,9 +203,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/a',
+ 'url:https://github.com/a',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/a',
+ 'url:https://github.com/a',
'github.com/user-login': 'a',
},
description: 'c',
@@ -227,9 +229,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/x',
+ 'url:https://github.com/x',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/x',
+ 'url:https://github.com/x',
'github.com/user-login': 'x',
},
description: 'z',
@@ -253,9 +255,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/q',
+ 'url:https://github.com/q',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/q',
+ 'url:https://github.com/q',
'github.com/user-login': 'q',
},
description: 's',
@@ -279,9 +281,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/orgs/orgA/teams/team',
+ 'url:https://github.com/orgs/orgA/teams/team',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/orgs/orgA/teams/team',
+ 'url:https://github.com/orgs/orgA/teams/team',
'github.com/team-slug': 'orgA/team',
},
namespace: 'orga',
@@ -308,9 +310,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/orgs/orgB/teams/team',
+ 'url:https://github.com/orgs/orgB/teams/team',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/orgs/orgB/teams/team',
+ 'url:https://github.com/orgs/orgB/teams/team',
'github.com/team-slug': 'orgB/team',
},
namespace: 'orgb',
@@ -467,7 +469,7 @@ describe('GithubMultiOrgEntityProvider', () => {
const logger = getVoidLogger();
const gitHubConfig: GithubIntegrationConfig = {
- host: 'https://github.com',
+ host: 'github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
@@ -507,9 +509,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/a',
+ 'url:https://github.com/a',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/a',
+ 'url:https://github.com/a',
'github.com/user-login': 'a',
},
description: 'c',
@@ -533,9 +535,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/x',
+ 'url:https://github.com/x',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/x',
+ 'url:https://github.com/x',
'github.com/user-login': 'x',
},
description: 'z',
@@ -559,9 +561,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/q',
+ 'url:https://github.com/q',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/q',
+ 'url:https://github.com/q',
'github.com/user-login': 'q',
},
description: 's',
@@ -585,9 +587,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/orgs/orgC/teams/team',
+ 'url:https://github.com/orgs/orgC/teams/team',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/orgs/orgC/teams/team',
+ 'url:https://github.com/orgs/orgC/teams/team',
'github.com/team-slug': 'orgC/team',
},
namespace: 'orgc',
@@ -614,9 +616,9 @@ describe('GithubMultiOrgEntityProvider', () => {
metadata: {
annotations: {
'backstage.io/managed-by-location':
- 'url:https://https://github.com/orgs/orgD/teams/team',
+ 'url:https://github.com/orgs/orgD/teams/team',
'backstage.io/managed-by-origin-location':
- 'url:https://https://github.com/orgs/orgD/teams/team',
+ 'url:https://github.com/orgs/orgD/teams/team',
'github.com/team-slug': 'orgD/team',
},
namespace: 'orgd',
@@ -713,4 +715,1223 @@ describe('GithubMultiOrgEntityProvider', () => {
});
});
});
+
+ describe('events', () => {
+ let onEvent: Function;
+
+ const entityProviderConnection: EntityProviderConnection = {
+ applyMutation: jest.fn(),
+ refresh: jest.fn(),
+ };
+
+ beforeEach(() => {
+ const logger = getVoidLogger();
+ const config = new ConfigReader({
+ integrations: {
+ github: [
+ {
+ host: 'github.com',
+ },
+ ],
+ },
+ });
+
+ const mockGetCredentials = jest.fn().mockReturnValue({
+ headers: { token: 'blah' },
+ type: 'app',
+ });
+
+ const githubCredentialsProvider: GithubCredentialsProvider = {
+ getCredentials: mockGetCredentials,
+ };
+
+ const mockEventBroker = {
+ publish: async () => {},
+ subscribe: (subscriber: EventSubscriber) => {
+ onEvent = subscriber.onEvent;
+ },
+ };
+
+ const entityProvider = GithubMultiOrgEntityProvider.fromConfig(config, {
+ id: 'my-id',
+ githubCredentialsProvider,
+ githubUrl: 'https://github.com',
+ logger,
+ orgs: ['orgA', 'orgB'],
+ eventBroker: mockEventBroker,
+ });
+
+ entityProvider.connect(entityProviderConnection);
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('should ignore events from non-applicable orgs', async () => {
+ await onEvent({
+ topic: 'github.organization',
+ eventPayload: {
+ action: 'member_added',
+ membership: {
+ user: {
+ name: 'githubuser',
+ login: 'githubuser',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ },
+ },
+ organization: {
+ login: 'test-org',
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled();
+
+ await onEvent({
+ topic: 'github.installation',
+ eventPayload: {
+ action: 'created',
+ installation: {
+ account: {
+ login: 'test-org',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled();
+ });
+
+ describe('installation', () => {
+ it('adds users and groups from new org', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ organization: {
+ membersWithRole: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'a',
+ name: 'b',
+ bio: 'c',
+ email: 'd',
+ avatarUrl: 'e',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgB/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ parentTeam: {
+ slug: 'parent',
+ combinedSlug: '',
+ members: { pageInfo: { hasNextPage: false }, nodes: [] },
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ parentTeam: {
+ slug: 'parent',
+ combinedSlug: '',
+ members: { pageInfo: { hasNextPage: false }, nodes: [] },
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgB/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ parentTeam: {
+ slug: 'parent',
+ combinedSlug: '',
+ members: { pageInfo: { hasNextPage: false }, nodes: [] },
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.installation',
+ eventPayload: {
+ action: 'created',
+ installation: {
+ account: {
+ login: 'orgB',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ description: 'c',
+ name: 'a',
+ },
+ spec: {
+ memberOf: ['orga/team', 'orgb/team'],
+ profile: {
+ displayName: 'b',
+ email: 'd',
+ picture: 'e',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgB/teams/team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgB/teams/team',
+ 'github.com/team-slug': 'orgB/team',
+ },
+ namespace: 'orgb',
+ name: 'team',
+ description: 'The one and only team',
+ },
+ spec: {
+ children: [],
+ parent: 'parent',
+ profile: {
+ displayName: 'TeamB',
+ picture: 'http://example.com/team.jpeg',
+ },
+ type: 'team',
+ members: ['default/a'],
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+ });
+
+ describe('organization', () => {
+ it('should add a new user', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'orgA',
+ },
+ {
+ login: 'orgB',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.organization',
+ eventPayload: {
+ action: 'member_added',
+ organization: {
+ login: 'orgB',
+ },
+ membership: {
+ user: {
+ name: 'a',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ login: 'a',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ },
+ spec: {
+ memberOf: ['orga/team'],
+ profile: {
+ displayName: 'a',
+ email: 'user1@test.com',
+ picture: 'https://avatars.githubusercontent.com/u/83820368',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+
+ it('should remove a user', async () => {
+ const mockClient = jest.fn();
+
+ mockClient.mockResolvedValueOnce({
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'orgC',
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.organization',
+ eventPayload: {
+ action: 'member_removed',
+ organization: {
+ login: 'orgB',
+ },
+ membership: {
+ user: {
+ name: 'a',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ login: 'a',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [],
+ removed: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ },
+ spec: {
+ memberOf: [],
+ profile: {
+ displayName: 'a',
+ email: 'user1@test.com',
+ picture: 'https://avatars.githubusercontent.com/u/83820368',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ });
+ });
+
+ it('should only update group memberships of a user that still belongs to an applicable org', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'orgA',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.organization',
+ eventPayload: {
+ action: 'member_removed',
+ organization: {
+ login: 'orgB',
+ },
+ membership: {
+ user: {
+ name: 'a',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ login: 'a',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ },
+ spec: {
+ memberOf: ['orga/team'],
+ profile: {
+ displayName: 'a',
+ email: 'user1@test.com',
+ picture: 'https://avatars.githubusercontent.com/u/83820368',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+ });
+
+ describe('team', () => {
+ it('should create a new group from a new team', async () => {
+ await onEvent({
+ topic: 'github.team',
+ eventPayload: {
+ action: 'created',
+ organization: {
+ login: 'orgB',
+ },
+ team: {
+ name: 'New Team',
+ slug: 'new-team',
+ description: 'description from the new team',
+ html_url: 'https://github.com/orgs/orgB/teams/new-team',
+ parent: {
+ slug: 'father-team',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ name: 'new-team',
+ namespace: 'orgb',
+ description: 'description from the new team',
+ annotations: {
+ 'backstage.io/edit-url':
+ 'https://github.com/orgs/orgB/teams/new-team/edit',
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgB/teams/new-team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgB/teams/new-team',
+ 'github.com/team-slug': 'orgB/new-team',
+ },
+ },
+ spec: {
+ type: 'team',
+ children: [],
+ members: [],
+ parent: 'father-team',
+ profile: {
+ displayName: 'New Team',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+
+ it('should remove a group from a deleted team', async () => {
+ await onEvent({
+ topic: 'github.team',
+ eventPayload: {
+ action: 'deleted',
+ organization: {
+ login: 'orgB',
+ },
+ team: {
+ name: 'New Team',
+ slug: 'new-team',
+ description: 'description from the new team',
+ html_url: 'https://github.com/orgs/orgB/teams/new-team',
+ parent: {
+ slug: 'father-team',
+ },
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [],
+ removed: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ name: 'new-team',
+ namespace: 'orgb',
+ description: 'description from the new team',
+ annotations: {
+ 'backstage.io/edit-url':
+ 'https://github.com/orgs/orgB/teams/new-team/edit',
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgB/teams/new-team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgB/teams/new-team',
+ 'github.com/team-slug': 'orgB/new-team',
+ },
+ },
+ spec: {
+ type: 'team',
+ children: [],
+ members: [],
+ parent: 'father-team',
+ profile: {
+ displayName: 'New Team',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ });
+ });
+
+ it('should update group and user entities when a team is edited', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ organization: {
+ team: {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ membersWithRole: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'a',
+ name: 'b',
+ bio: 'c',
+ email: 'd',
+ avatarUrl: 'e',
+ },
+ {
+ login: 'w',
+ name: 'x',
+ bio: 'y',
+ email: 'z',
+ avatarUrl: 'q',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgB/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.team',
+ eventPayload: {
+ action: 'edited',
+ changes: {
+ name: {
+ from: 'oldName',
+ },
+ description: {
+ from: 'oldDescription',
+ },
+ },
+ team: {
+ slug: 'team',
+ parent: {
+ slug: 'parent',
+ },
+ },
+ organization: {
+ login: 'orgA',
+ },
+ },
+ });
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ description: 'c',
+ },
+ spec: {
+ memberOf: ['orga/team', 'orgb/team'],
+ profile: {
+ displayName: 'b',
+ email: 'd',
+ picture: 'e',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/edit-url': 'https://example.com',
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'github.com/team-slug': 'orgA/team',
+ },
+ namespace: 'orga',
+ name: 'team',
+ description: 'The one and only team',
+ },
+ spec: {
+ children: [],
+ parent: 'parent',
+ profile: {
+ displayName: 'TeamA',
+ picture: 'http://example.com/team.jpeg',
+ },
+ type: 'team',
+ members: ['default/a'],
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgA/teams/oldname',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgA/teams/oldname',
+ 'github.com/team-slug': 'orgA/oldname',
+ },
+ namespace: 'orga',
+ name: 'oldname',
+ description: 'oldDescription',
+ },
+ spec: {
+ children: [],
+ parent: 'parent',
+ profile: {
+ displayName: 'oldName',
+ },
+ type: 'team',
+ members: [],
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ });
+ });
+ });
+
+ describe('membership', () => {
+ it('should update group and user entities when a member is added', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ organization: {
+ team: {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'orgA',
+ },
+ {
+ login: 'orgB',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgB/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.membership',
+ eventPayload: {
+ action: 'added',
+ organization: {
+ login: 'orgA',
+ },
+ team: {
+ slug: 'teama',
+ },
+ member: {
+ name: 'a',
+ login: 'a',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ },
+ },
+ });
+ await new Promise(process.nextTick);
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ },
+ spec: {
+ memberOf: ['orga/team', 'orgb/team'],
+ profile: {
+ displayName: 'a',
+ email: 'user1@test.com',
+ picture: 'https://avatars.githubusercontent.com/u/83820368',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/edit-url': 'https://example.com',
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'github.com/team-slug': 'orgA/team',
+ },
+ namespace: 'orga',
+ name: 'team',
+ description: 'The one and only team',
+ },
+ spec: {
+ children: [],
+ parent: 'parent',
+ profile: {
+ displayName: 'TeamA',
+ picture: 'http://example.com/team.jpeg',
+ },
+ type: 'team',
+ members: ['default/a'],
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+
+ it('should update group and user entities when a member is removed', async () => {
+ const mockClient = jest.fn();
+
+ mockClient
+ .mockResolvedValueOnce({
+ organization: {
+ team: {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [],
+ },
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ user: {
+ organizations: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ login: 'orgA',
+ },
+ {
+ login: 'orgB',
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgA/team',
+ name: 'TeamA',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [],
+ },
+ },
+ ],
+ },
+ },
+ })
+ .mockResolvedValueOnce({
+ organization: {
+ teams: {
+ pageInfo: { hasNextPage: false },
+ nodes: [
+ {
+ slug: 'team',
+ combinedSlug: 'orgB/team',
+ name: 'TeamB',
+ description: 'The one and only team',
+ avatarUrl: 'http://example.com/team.jpeg',
+ editTeamUrl: 'https://example.com',
+ parentTeam: {
+ slug: 'parent',
+ },
+ members: {
+ pageInfo: { hasNextPage: false },
+ nodes: [{ login: 'a', name: 'a' }],
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ (graphql.defaults as jest.Mock).mockReturnValue(mockClient);
+
+ await onEvent({
+ topic: 'github.membership',
+ eventPayload: {
+ action: 'removed',
+ organization: {
+ login: 'orgA',
+ },
+ team: {
+ slug: 'teama',
+ },
+ member: {
+ name: 'a',
+ login: 'a',
+ avatar_url: 'https://avatars.githubusercontent.com/u/83820368',
+ email: 'user1@test.com',
+ },
+ },
+ });
+ await new Promise(process.nextTick);
+
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'User',
+ metadata: {
+ annotations: {
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/a',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/a',
+ 'github.com/user-login': 'a',
+ },
+ name: 'a',
+ },
+ spec: {
+ memberOf: ['orgb/team'],
+ profile: {
+ displayName: 'a',
+ email: 'user1@test.com',
+ picture: 'https://avatars.githubusercontent.com/u/83820368',
+ },
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Group',
+ metadata: {
+ annotations: {
+ 'backstage.io/edit-url': 'https://example.com',
+ 'backstage.io/managed-by-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'backstage.io/managed-by-origin-location':
+ 'url:https://github.com/orgs/orgA/teams/team',
+ 'github.com/team-slug': 'orgA/team',
+ },
+ namespace: 'orga',
+ name: 'team',
+ description: 'The one and only team',
+ },
+ spec: {
+ children: [],
+ parent: 'parent',
+ profile: {
+ displayName: 'TeamA',
+ picture: 'http://example.com/team.jpeg',
+ },
+ type: 'team',
+ members: [],
+ },
+ },
+ locationKey: 'github-multi-org-provider:my-id',
+ },
+ ],
+ removed: [],
+ });
+ });
+ });
+ });
});
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts
index ce45f30d96..51796c2116 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts
@@ -21,7 +21,9 @@ import {
DEFAULT_NAMESPACE,
Entity,
GroupEntity,
+ parseEntityRef,
stringifyEntityRef,
+ UserEntity,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
@@ -32,10 +34,24 @@ import {
ScmIntegrations,
} from '@backstage/integration';
import {
+ DeferredEntity,
EntityProvider,
EntityProviderConnection,
} from '@backstage/plugin-catalog-node';
+import { EventBroker, EventParams } from '@backstage/plugin-events-node';
import { graphql } from '@octokit/graphql';
+import {
+ InstallationCreatedEvent,
+ InstallationEvent,
+ OrganizationEvent,
+ OrganizationMemberAddedEvent,
+ OrganizationMemberRemovedEvent,
+ MembershipEvent,
+ TeamCreatedEvent,
+ TeamDeletedEvent,
+ TeamEditedEvent,
+ TeamEvent,
+} from '@octokit/webhooks-types';
import { merge } from 'lodash';
import * as uuid from 'uuid';
import { Logger } from 'winston';
@@ -44,6 +60,7 @@ import {
assignGroupsToUsers,
buildOrgHierarchy,
defaultOrganizationTeamTransformer,
+ defaultUserTransformer,
getOrganizationTeams,
getOrganizationUsers,
GithubTeam,
@@ -55,6 +72,11 @@ import {
ANNOTATION_GITHUB_TEAM_SLUG,
ANNOTATION_GITHUB_USER_LOGIN,
} from '../lib/annotation';
+import {
+ getOrganizationsFromUser,
+ getOrganizationTeam,
+ getOrganizationTeamsFromUsers,
+} from '../lib/github';
import { splitTeamSlug } from '../lib/util';
/**
@@ -118,8 +140,18 @@ export interface GithubMultiOrgEntityProviderOptions {
* By default groups will be namespaced according to their GitHub org.
*/
teamTransformer?: TeamTransformer;
+
+ /**
+ * An EventBroker to subscribe this provider to GitHub events to trigger delta mutations
+ */
+ eventBroker?: EventBroker;
}
+type CreateDeltaOperation = (entities: Entity[]) => {
+ added: DeferredEntity[];
+ removed: DeferredEntity[];
+};
+
/**
* Ingests org data (users and groups) from GitHub.
*
@@ -161,6 +193,13 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
provider.schedule(options.schedule);
+ if (options.eventBroker) {
+ options.eventBroker.subscribe({
+ supportsEventTopics: provider.supportsEventTopics.bind(provider),
+ onEvent: provider.onEvent.bind(provider),
+ });
+ }
+
return provider;
}
@@ -271,6 +310,476 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
markCommitComplete();
}
+ private supportsEventTopics(): string[] {
+ return [
+ 'github.installation',
+ 'github.organization',
+ 'github.team',
+ 'github.membership',
+ ];
+ }
+
+ private async onEvent(params: EventParams): Promise {
+ const { logger } = this.options;
+ logger.debug(`Received event from ${params.topic}`);
+
+ const orgs = this.options.orgs?.length
+ ? this.options.orgs
+ : await this.getAllOrgs(this.options.gitHubConfig);
+
+ const eventPayload = params.eventPayload as
+ | InstallationEvent
+ | OrganizationEvent
+ | MembershipEvent
+ | TeamEvent;
+
+ if (
+ !orgs.includes(
+ (eventPayload as InstallationEvent).installation?.account?.login,
+ ) &&
+ !orgs.includes(
+ (eventPayload as OrganizationEvent | MembershipEvent | TeamEvent)
+ .organization?.login,
+ )
+ ) {
+ return;
+ }
+
+ // https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#installation
+ if (
+ params.topic.includes('installation') &&
+ eventPayload.action === 'created'
+ ) {
+ // We can only respond to installation.created events to add new users/groups since a
+ // installation.deleted event won't provide us info on what user/groups we should remove and
+ // we can't query the uninstalled org since we will no longer have access. This will need to be
+ // eventually resolved via occasional full mutation runs by calling read()
+ await this.onInstallationChange(
+ eventPayload as InstallationCreatedEvent,
+ orgs,
+ );
+ }
+
+ // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#organization
+ if (
+ params.topic.includes('organization') &&
+ (eventPayload.action === 'member_added' ||
+ eventPayload.action === 'member_removed')
+ ) {
+ await this.onMemberChangeInOrganization(eventPayload, orgs);
+ }
+
+ // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#team
+ if (params.topic.includes('team')) {
+ if (
+ eventPayload.action === 'created' ||
+ eventPayload.action === 'deleted'
+ ) {
+ await this.onTeamChangeInOrganization(
+ eventPayload as TeamCreatedEvent | TeamDeletedEvent,
+ );
+ } else if (eventPayload.action === 'edited') {
+ await this.onTeamEditedInOrganization(
+ eventPayload as TeamEditedEvent,
+ orgs,
+ );
+ }
+ }
+
+ // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
+ if (params.topic.includes('membership')) {
+ this.onMembershipChangedInTeam(eventPayload as MembershipEvent, orgs);
+ }
+
+ return;
+ }
+
+ private async onInstallationChange(
+ event: InstallationCreatedEvent,
+ applicableOrgs: string[],
+ ) {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ const org = event.installation.account.login;
+ const { headers, type: tokenType } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${org}`,
+ });
+ const client = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers,
+ });
+
+ const { users } = await getOrganizationUsers(
+ client,
+ org,
+ tokenType,
+ this.options.userTransformer,
+ );
+
+ const { groups } = await getOrganizationTeams(
+ client,
+ org,
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ // Fetch group memberships of users in case they already exist and
+ // have memberships in groups from other applicable orgs
+ for (const userOrg of applicableOrgs) {
+ const { headers: orgHeaders } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${userOrg}`,
+ });
+ const orgClient = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers: orgHeaders,
+ });
+
+ const { groups: userGroups } = await getOrganizationTeamsFromUsers(
+ orgClient,
+ userOrg,
+ users.map(
+ u =>
+ u.metadata.annotations?.[ANNOTATION_GITHUB_USER_LOGIN] ||
+ u.metadata.name,
+ ),
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ assignGroupsToUsers(users, userGroups);
+ }
+
+ const { added, removed } = this.createAddEntitiesOperation([
+ ...users,
+ ...groups,
+ ]);
+ await this.connection.applyMutation({
+ type: 'delta',
+ removed,
+ added,
+ });
+ }
+
+ private async onMemberChangeInOrganization(
+ event: OrganizationMemberAddedEvent | OrganizationMemberRemovedEvent,
+ applicableOrgs: string[],
+ ) {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ const userTransformer =
+ this.options.userTransformer || defaultUserTransformer;
+ const { name, avatar_url: avatarUrl, email, login } = event.membership.user;
+ const org = event.organization.login;
+ const { headers } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${org}`,
+ });
+ const client = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers,
+ });
+
+ const { orgs } = await getOrganizationsFromUser(client, login);
+ const userApplicableOrgs = orgs.filter(o => applicableOrgs.includes(o));
+
+ let updateMemberships: boolean;
+ let createDeltaOperation: CreateDeltaOperation;
+ if (event.action === 'member_removed') {
+ if (userApplicableOrgs.length) {
+ // If the user is still associated with another applicable org then we don't want to remove
+ // them, just update the entity to remove any potential group memberships from the old org
+ createDeltaOperation = this.createAddEntitiesOperation.bind(this);
+ updateMemberships = true;
+ } else {
+ // User is no longer part of any applicable orgs so we can remove it,
+ // no need to take memberships into account
+ createDeltaOperation = this.createRemoveEntitiesOperation.bind(this);
+ updateMemberships = false;
+ }
+ } else {
+ // We're not sure if this user was already added as part of another applicable org
+ // so grab the latest memberships (potentially from teams of other orgs) to ensure
+ // we're not accidentally omitting them
+ createDeltaOperation = this.createAddEntitiesOperation.bind(this);
+ updateMemberships = true;
+ }
+
+ const user = (await userTransformer(
+ {
+ name,
+ avatarUrl,
+ login,
+ email: email ?? undefined,
+ },
+ {
+ org,
+ client,
+ query: '',
+ },
+ )) as UserEntity;
+
+ if (updateMemberships) {
+ for (const userOrg of userApplicableOrgs) {
+ const { headers: orgHeaders } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${userOrg}`,
+ });
+ const orgClient = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers: orgHeaders,
+ });
+
+ const { groups } = await getOrganizationTeamsFromUsers(
+ orgClient,
+ userOrg,
+ [login],
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ assignGroupsToUsers([user], groups);
+ }
+ }
+
+ const { added, removed } = createDeltaOperation([user]);
+ await this.connection.applyMutation({
+ type: 'delta',
+ removed,
+ added,
+ });
+ }
+
+ private async onTeamChangeInOrganization(
+ event: TeamCreatedEvent | TeamDeletedEvent,
+ ) {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ const org = event.organization.login;
+ const { headers } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${org}`,
+ });
+ const client = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers,
+ });
+
+ const { name, html_url: url, description, slug } = event.team;
+ const group = (await this.defaultMultiOrgTeamTransformer(
+ {
+ name,
+ slug,
+ editTeamUrl: `${url}/edit`,
+ combinedSlug: `${org}/${slug}`,
+ description: description ?? undefined,
+ parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam,
+ // entity will be removed or is new
+ members: [],
+ },
+ {
+ org,
+ client,
+ query: '',
+ },
+ )) as Entity;
+
+ const createDeltaOperation =
+ event.action === 'created'
+ ? this.createAddEntitiesOperation.bind(this)
+ : this.createRemoveEntitiesOperation.bind(this);
+ const { added, removed } = createDeltaOperation([group]);
+
+ await this.connection.applyMutation({
+ type: 'delta',
+ removed,
+ added,
+ });
+ }
+
+ private async onTeamEditedInOrganization(
+ event: TeamEditedEvent,
+ applicableOrgs: string[],
+ ) {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ const org = event.organization.login;
+ const { headers, type: tokenType } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${org}`,
+ });
+ const client = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers,
+ });
+
+ const teamSlug = event.team.slug;
+ const { group } = await getOrganizationTeam(
+ client,
+ org,
+ teamSlug,
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ const { users } = await getOrganizationUsers(
+ client,
+ org,
+ tokenType,
+ this.options.userTransformer,
+ );
+
+ const usersFromChangedGroup =
+ group.spec.members?.map(m =>
+ stringifyEntityRef(parseEntityRef(m, { defaultKind: 'user' })),
+ ) || [];
+ const usersToRebuild = users.filter(u =>
+ usersFromChangedGroup.includes(stringifyEntityRef(u)),
+ );
+
+ // Update memberships of associated members of this group in case the group entity ref changed
+ for (const userOrg of applicableOrgs) {
+ const { headers: orgHeaders } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${userOrg}`,
+ });
+ const orgClient = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers: orgHeaders,
+ });
+
+ const { groups } = await getOrganizationTeamsFromUsers(
+ orgClient,
+ userOrg,
+ usersToRebuild.map(
+ u =>
+ u.metadata.annotations?.[ANNOTATION_GITHUB_USER_LOGIN] ||
+ u.metadata.name,
+ ),
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ assignGroupsToUsers(usersToRebuild, groups);
+ }
+
+ const oldName = event.changes.name?.from || '';
+ const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-');
+ const oldGroup = (await this.defaultMultiOrgTeamTransformer(
+ {
+ name: event.changes.name?.from,
+ slug: oldSlug,
+ combinedSlug: `${org}/${oldSlug}`,
+ description: event.changes.description?.from,
+ parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam,
+ // entity will be removed
+ members: [],
+ },
+ {
+ org,
+ client,
+ query: '',
+ },
+ )) as Entity;
+
+ // Remove the old group entity in case the entity ref is now different
+ const { removed } = this.createRemoveEntitiesOperation([oldGroup]);
+ const { added } = this.createAddEntitiesOperation([
+ ...usersToRebuild,
+ group,
+ ]);
+ await this.connection.applyMutation({
+ type: 'delta',
+ removed,
+ added,
+ });
+ }
+
+ private async onMembershipChangedInTeam(
+ event: MembershipEvent,
+ applicableOrgs: string[],
+ ) {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ // The docs are saying I will receive the slug for the removed event,
+ // but the types don't reflect that,
+ // so I will just check to be sure the slug is there
+ // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership
+ if (!('slug' in event.team)) {
+ return;
+ }
+
+ const org = event.organization.login;
+ const { headers } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${org}`,
+ });
+ const client = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers,
+ });
+
+ const teamSlug = event.team.slug;
+ const { group } = await getOrganizationTeam(
+ client,
+ org,
+ teamSlug,
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ const userTransformer =
+ this.options.userTransformer || defaultUserTransformer;
+ const { name, avatar_url: avatarUrl, email, login } = event.member;
+ const user = (await userTransformer(
+ {
+ name,
+ avatarUrl,
+ login,
+ email: email ?? undefined,
+ },
+ {
+ org,
+ client,
+ query: '',
+ },
+ )) as UserEntity;
+
+ const { orgs } = await getOrganizationsFromUser(client, login);
+ const userApplicableOrgs = orgs.filter(o => applicableOrgs.includes(o));
+ for (const userOrg of userApplicableOrgs) {
+ const { headers: orgHeaders } =
+ await this.options.githubCredentialsProvider.getCredentials({
+ url: `${this.options.githubUrl}/${userOrg}`,
+ });
+ const orgClient = graphql.defaults({
+ baseUrl: this.options.gitHubConfig.apiBaseUrl,
+ headers: orgHeaders,
+ });
+
+ const { groups } = await getOrganizationTeamsFromUsers(
+ orgClient,
+ userOrg,
+ [login],
+ this.defaultMultiOrgTeamTransformer.bind(this),
+ );
+
+ assignGroupsToUsers([user], groups);
+ }
+
+ const { added, removed } = this.createAddEntitiesOperation([user, group]);
+ await this.connection.applyMutation({
+ type: 'delta',
+ removed,
+ added,
+ });
+ }
+
private schedule(schedule: GithubMultiOrgEntityProviderOptions['schedule']) {
if (!schedule || schedule === 'manual') {
return;
@@ -301,9 +810,11 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
team: GithubTeam,
ctx: TransformerContext,
): Promise {
- const result = this.options.teamTransformer
- ? await this.options.teamTransformer(team, ctx)
- : await defaultOrganizationTeamTransformer(team, ctx);
+ if (this.options.teamTransformer) {
+ return await this.options.teamTransformer(team, ctx);
+ }
+
+ const result = await defaultOrganizationTeamTransformer(team, ctx);
if (result) {
result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US');
@@ -333,6 +844,32 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
)
.filter(Boolean) as string[];
}
+
+ private createAddEntitiesOperation(entities: Entity[]) {
+ return {
+ removed: [],
+ added: entities.map(entity => ({
+ locationKey: `github-multi-org-provider:${this.options.id}`,
+ entity: withLocations(
+ `https://${this.options.gitHubConfig.host}`,
+ entity,
+ ),
+ })),
+ };
+ }
+
+ private createRemoveEntitiesOperation(entities: Entity[]) {
+ return {
+ added: [],
+ removed: entities.map(entity => ({
+ locationKey: `github-multi-org-provider:${this.options.id}`,
+ entity: withLocations(
+ `https://${this.options.gitHubConfig.host}`,
+ entity,
+ ),
+ })),
+ };
+ }
}
// Helps wrap the timing and logging behaviors
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts
index ff42e42718..3e58c1473f 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts
@@ -833,6 +833,7 @@ describe('GithubOrgEntityProvider', () => {
'url:https://github.com/orgs/backstage/teams/mygroup-with-spaces',
},
name: 'mygroup-with-spaces',
+ description: 'description-from-the-new-team',
},
apiVersion: 'backstage.io/v1alpha1',
kind: 'Group',
diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts
index 02e225c938..c181510a67 100644
--- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts
+++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts
@@ -371,14 +371,21 @@ export class GithubOrgEntityProvider
assignGroupsToUsers(usersToRebuild, groups);
buildOrgHierarchy(groups);
- const oldName = event.changes.name?.from || '';
+ const oldName = event.changes.name?.from || event.team.name;
const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-');
+ const oldDescription =
+ event.changes.description?.from || event.team.description;
+ const oldDescriptionSlug = oldDescription
+ ?.toLowerCase()
+ .replaceAll(/\s/gi, '-');
+
const { removed } = createDeltaOperation(org, [
{
...group,
metadata: {
name: oldSlug,
+ description: oldDescriptionSlug,
},
},
]);
diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
index 9882178a2b..a8c58d386a 100644
--- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-catalog-backend-module-gitlab
+## 0.2.1
+
+### Patch Changes
+
+- b12c41fafc4: Fix a corner case where returned users are null for bots
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
## 0.2.1-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json
index 22237862aa..223d578da1 100644
--- a/plugins/catalog-backend-module-gitlab/package.json
+++ b/plugins/catalog-backend-module-gitlab/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab",
"description": "A Backstage catalog backend module that helps integrate towards GitLab",
- "version": "0.2.1-next.1",
+ "version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
index 8f8c67895c..46932c95c6 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
@@ -128,10 +128,11 @@ export class GitLabClient {
throw new Error(`GraphQL errors: ${JSON.stringify(response.errors)}`);
}
memberIds.push(
- ...response.data.group.groupMembers.nodes.map(
- (node: { user: { id: string } }) =>
+ ...response.data.group.groupMembers.nodes
+ .filter(n => n.user)
+ .map(node =>
Number(node.user.id.replace(/^gid:\/\/gitlab\/User\//, '')),
- ),
+ ),
);
({ hasNextPage, endCursor } = response.data.group.groupMembers.pageInfo);
} while (hasNextPage);
diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
index 9921b0dabc..fdf0de5b0b 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend-module-incremental-ingestion
+## 0.3.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-events-node@0.2.6
+ - @backstage/plugin-permission-common@0.7.5
+
+## 0.3.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1-next.2
+ - @backstage/config@1.0.7
+
## 0.3.2-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json
index 686c89cd25..b37f2bfb6c 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/package.json
+++ b/plugins/catalog-backend-module-incremental-ingestion/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
"description": "An entity provider for streaming large asset sources into the catalog",
- "version": "0.3.2-next.1",
+ "version": "0.3.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md
index 104e3f1c0f..052f1cc473 100644
--- a/plugins/catalog-backend-module-ldap/CHANGELOG.md
+++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-catalog-backend-module-ldap
+## 0.5.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+
## 0.5.12-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json
index 120131d95a..6943df23fe 100644
--- a/plugins/catalog-backend-module-ldap/package.json
+++ b/plugins/catalog-backend-module-ldap/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-ldap",
"description": "A Backstage catalog backend module that helps integrate towards LDAP",
- "version": "0.5.12-next.1",
+ "version": "0.5.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
index c0c0888f38..f45e0c3440 100644
--- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md
+++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-catalog-backend-module-msgraph
+## 0.5.4
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/plugin-catalog-common@1.0.13
+
## 0.5.4-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json
index 026386457a..ae836681b4 100644
--- a/plugins/catalog-backend-module-msgraph/package.json
+++ b/plugins/catalog-backend-module-msgraph/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-msgraph",
"description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph",
- "version": "0.5.4-next.1",
+ "version": "0.5.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md
index 5fc587a4f9..0598ea46ef 100644
--- a/plugins/catalog-backend-module-openapi/CHANGELOG.md
+++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend-module-openapi
+## 0.1.11
+
+### Patch Changes
+
+- accaceadffa: Fixed bug in `jsonSchemaRefPlaceholderResolver` where relative $ref files were resolved through file system instead of base URL of file
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+
+## 0.1.11-next.2
+
+### Patch Changes
+
+- accaceadffa: Fixed bug in `jsonSchemaRefPlaceholderResolver` where relative $ref files were resolved through file system instead of base URL of file
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.9.1-next.2
+ - @backstage/config@1.0.7
+
## 0.1.11-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json
index e02490ef59..980a8920cd 100644
--- a/plugins/catalog-backend-module-openapi/package.json
+++ b/plugins/catalog-backend-module-openapi/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-openapi",
"description": "A Backstage catalog backend module that helps with OpenAPI specifications",
- "version": "0.1.11-next.1",
+ "version": "0.1.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
index 39bd1e3cdc..e8750bc1c4 100644
--- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
+++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-catalog-backend-module-puppetdb
+## 0.1.2
+
+### Patch Changes
+
+- 95b2168d71b: Fixes import paths and updates documentation
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
+## 0.1.2-next.2
+
+### Patch Changes
+
+- 95b2168d71b: Fixes import paths and updates documentation
+- Updated dependencies
+ - @backstage/config@1.0.7
+
## 0.1.2-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json
index 65bda327dd..7286f66095 100644
--- a/plugins/catalog-backend-module-puppetdb/package.json
+++ b/plugins/catalog-backend-module-puppetdb/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-puppetdb",
"description": "A Backstage catalog backend module that helps integrate towards PuppetDB",
- "version": "0.1.2-next.1",
+ "version": "0.1.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md
index 1a715e26bc..e8c18be461 100644
--- a/plugins/catalog-backend/CHANGELOG.md
+++ b/plugins/catalog-backend/CHANGELOG.md
@@ -1,5 +1,46 @@
# @backstage/plugin-catalog-backend
+## 1.9.1
+
+### Patch Changes
+
+- ce8d203235b: Ensure that entity cache state is only written to the database when actually changed
+- 485a6c5f7b5: Internal refactoring for performance in the service handlers
+- 3587a968dcd: Fixed a bug in the `queryEntities` endpoint that was causing filtered entities to be included in cursor requests.
+- ce335df9d1c: Improve the query for orphan pruning
+- 27956d78671: Adjusted the OpenAPI schema file name according to the new structure
+- 51064e6e5ee: Change orphan cleanup task to only log a message if it deleted entities.
+- 12a345317ab: Remove unnecessary join in the entity facets endpoint, exclude nulls
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/plugin-scaffolder-common@1.3.0
+ - @backstage/plugin-permission-node@0.7.8
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/plugin-catalog-node@1.3.6
+ - @backstage/plugin-search-backend-module-catalog@0.1.1
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-permission-common@0.7.5
+ - @backstage/plugin-search-common@1.2.3
+
+## 1.9.1-next.2
+
+### Patch Changes
+
+- ce8d203235b: Ensure that entity cache state is only written to the database when actually changed
+- 485a6c5f7b5: Internal refactoring for performance in the service handlers
+- 3587a968dcd: Fixed a bug in the `queryEntities` endpoint that was causing filtered entities to be included in cursor requests.
+- 12a345317ab: Remove unnecessary join in the entity facets endpoint, exclude nulls
+- Updated dependencies
+ - @backstage/plugin-scaffolder-common@1.3.0-next.0
+ - @backstage/config@1.0.7
+
## 1.9.1-next.1
### Patch Changes
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index 289a9a801d..1a2ca55253 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend",
"description": "The Backstage backend plugin that provides the Backstage catalog",
- "version": "1.9.1-next.1",
+ "version": "1.9.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.ts b/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.ts
index 920acae7bd..eeab85f59e 100644
--- a/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.ts
+++ b/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.ts
@@ -33,13 +33,16 @@ export async function deleteOrphanedEntities(options: {
// Limit iterations for sanity
for (let i = 0; i < 100; ++i) {
const candidates = await tx
- .with('orphans', orphans =>
+ .with('orphans', ['entity_id', 'entity_ref'], orphans =>
orphans
- .from('refresh_state')
- .select('entity_id', 'entity_ref')
- .whereNotIn('entity_ref', keep =>
- keep.distinct('target_entity_ref').from('refresh_state_references'),
- ),
+ .from('refresh_state')
+ .select('refresh_state.entity_id', 'refresh_state.entity_ref')
+ .leftOuterJoin(
+ 'refresh_state_references',
+ 'refresh_state_references.target_entity_ref',
+ 'refresh_state.entity_ref',
+ )
+ .whereNull('refresh_state_references.target_entity_ref'),
)
.select({
entityId: 'orphans.entity_id',
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
index e89d53f79f..8e2f8b2f6f 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
@@ -317,7 +317,9 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
try {
await this.processingDatabase.transaction(async tx => {
const n = await this.processingDatabase.deleteOrphanedEntities(tx);
- this.logger.info(`Deleted ${n} orphaned entities`);
+ if (n > 0) {
+ this.logger.info(`Deleted ${n} orphaned entities`);
+ }
});
} catch (error) {
this.logger.warn(`Failed to delete orphaned entities`, error);
diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts
index 93a37fb04a..4f011813e6 100644
--- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts
+++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts
@@ -1729,6 +1729,17 @@ describe('DefaultEntitiesCatalog', () => {
],
},
});
+
+ await expect(
+ catalog.facets({
+ facets: ['kind'],
+ filter: { not: { key: 'metadata.name', values: ['two'] } },
+ }),
+ ).resolves.toEqual({
+ facets: {
+ kind: [{ value: 'k', count: 1 }],
+ },
+ });
},
);
diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts
index 5fca950cac..c328c83823 100644
--- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts
+++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts
@@ -64,43 +64,47 @@ const defaultSortField: EntityOrder = {
const DEFAULT_LIMIT = 20;
-function parsePagination(input?: EntityPagination): {
- limit?: number;
- offset?: number;
-} {
+function parsePagination(input?: EntityPagination): EntityPagination {
if (!input) {
return {};
}
let { limit, offset } = input;
- if (input.after !== undefined) {
- let cursor;
- try {
- const json = Buffer.from(input.after, 'base64').toString('utf8');
- cursor = JSON.parse(json);
- } catch {
- throw new InputError('Malformed after cursor, could not be parsed');
+ if (input.after === undefined) {
+ return { limit, offset };
+ }
+
+ let cursor;
+ try {
+ const json = Buffer.from(input.after, 'base64').toString('utf8');
+ cursor = JSON.parse(json);
+ } catch {
+ throw new InputError('Malformed after cursor, could not be parsed');
+ }
+
+ if (cursor.limit !== undefined) {
+ if (!Number.isInteger(cursor.limit)) {
+ throw new InputError('Malformed after cursor, limit was not an number');
}
- if (cursor.limit !== undefined) {
- if (!Number.isInteger(cursor.limit)) {
- throw new InputError('Malformed after cursor, limit was not an number');
- }
- limit = cursor.limit;
- }
- if (cursor.offset !== undefined) {
- if (!Number.isInteger(cursor.offset)) {
- throw new InputError('Malformed after cursor, offset was not a number');
- }
- offset = cursor.offset;
+ limit = cursor.limit;
+ }
+
+ if (cursor.offset !== undefined) {
+ if (!Number.isInteger(cursor.offset)) {
+ throw new InputError('Malformed after cursor, offset was not a number');
}
+ offset = cursor.offset;
}
return { limit, offset };
}
-function stringifyPagination(input: { limit: number; offset: number }) {
- const json = JSON.stringify({ limit: input.limit, offset: input.offset });
+function stringifyPagination(
+ input: Required>,
+): string {
+ const { limit, offset } = input;
+ const json = JSON.stringify({ limit, offset });
const base64 = Buffer.from(json, 'utf8').toString('base64');
return base64;
}
@@ -111,24 +115,21 @@ function addCondition(
filter: EntitiesSearchFilter,
negate: boolean = false,
entityIdField = 'entity_id',
-) {
+): void {
+ const key = filter.key.toLowerCase();
+ const values = filter.values?.map(v => v.toLowerCase());
+
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
// make a lot of sense. However, it had abysmal performance on sqlite
// when datasets grew large, so we're using IN instead.
const matchQuery = db('search')
.select('search.entity_id')
- .where({ key: filter.key.toLowerCase() })
+ .where({ key })
.andWhere(function keyFilter() {
- if (filter.values) {
- if (filter.values.length === 1) {
- this.where({ value: filter.values[0].toLowerCase() });
- } else {
- this.andWhere(
- 'value',
- 'in',
- filter.values.map(v => v.toLowerCase()),
- );
- }
+ if (values?.length === 1) {
+ this.where({ value: values.at(0) });
+ } else if (values) {
+ this.andWhere('value', 'in', values);
}
});
queryBuilder.andWhere(entityIdField, negate ? 'not in' : 'in', matchQuery);
@@ -159,16 +160,16 @@ function parseFilter(
negate: boolean = false,
entityIdField = 'entity_id',
): Knex.QueryBuilder {
+ if (isNegationEntityFilter(filter)) {
+ return parseFilter(filter.not, query, db, !negate, entityIdField);
+ }
+
if (isEntitiesSearchFilter(filter)) {
return query.andWhere(function filterFunction() {
addCondition(this, db, filter, negate, entityIdField);
});
}
- if (isNegationEntityFilter(filter)) {
- return parseFilter(filter.not, query, db, !negate, entityIdField);
- }
-
return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() {
if (isOrEntityFilter(filter)) {
for (const subFilter of filter.anyOf ?? []) {
@@ -678,10 +679,9 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
for (const facet of request.facets) {
const dbQuery = db('search')
- .join('final_entities', 'search.entity_id', 'final_entities.entity_id')
.where('search.key', facet.toLocaleLowerCase('en-US'))
- .count('search.entity_id as count')
- .select({ value: 'search.original_value' })
+ .whereNotNull('search.original_value')
+ .select({ value: 'search.original_value', count: db.raw('count(*)') })
.groupBy('search.original_value');
if (request?.filter) {
diff --git a/plugins/catalog-customized/CHANGELOG.md b/plugins/catalog-customized/CHANGELOG.md
index 138edaee90..00d769e71e 100644
--- a/plugins/catalog-customized/CHANGELOG.md
+++ b/plugins/catalog-customized/CHANGELOG.md
@@ -1,5 +1,21 @@
# @internal/plugin-catalog-customized
+## 0.0.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-catalog@1.11.0
+
+## 0.0.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@1.11.0-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+
## 0.0.10-next.1
### Patch Changes
diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json
index 808f7a40e4..12f9bc584f 100644
--- a/plugins/catalog-customized/package.json
+++ b/plugins/catalog-customized/package.json
@@ -1,7 +1,7 @@
{
"name": "@internal/plugin-catalog-customized",
"description": "The internal Backstage Customizable plugin for browsing the Backstage catalog",
- "version": "0.0.10-next.1",
+ "version": "0.0.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md
index eabfdaff12..277f0a70cf 100644
--- a/plugins/catalog-graph/CHANGELOG.md
+++ b/plugins/catalog-graph/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-catalog-graph
+## 0.2.30
+
+### Patch Changes
+
+- d446f8fb0a8: Expose all `EntityRelationsGraphProps` to Catalog Graph Page
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.2.30-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.30-next.1
### Patch Changes
diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json
index b9ac5ea1c1..fd4d3d0114 100644
--- a/plugins/catalog-graph/package.json
+++ b/plugins/catalog-graph/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-graph",
- "version": "0.2.30-next.1",
+ "version": "0.2.30",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md
index 738a28f3af..50dee98951 100644
--- a/plugins/catalog-import/CHANGELOG.md
+++ b/plugins/catalog-import/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-catalog-import
+## 0.9.8
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/integration-react@1.1.13
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-catalog-common@1.0.13
+
+## 0.9.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.9.8-next.1
### Patch Changes
diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json
index 7e7113a16c..a6cad7a986 100644
--- a/plugins/catalog-import/package.json
+++ b/plugins/catalog-import/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-import",
"description": "A Backstage plugin the helps you import entities into your catalog",
- "version": "0.9.8-next.1",
+ "version": "0.9.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md
index a1ca2c37c6..5ed8a9aba1 100644
--- a/plugins/catalog-node/CHANGELOG.md
+++ b/plugins/catalog-node/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-node
+## 1.3.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+
## 1.3.6-next.1
### Patch Changes
diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json
index 95136c2da1..7adf03eed2 100644
--- a/plugins/catalog-node/package.json
+++ b/plugins/catalog-node/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-node",
"description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend",
- "version": "1.3.6-next.1",
+ "version": "1.3.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md
index bd9d13ace5..7d96283320 100644
--- a/plugins/catalog-react/CHANGELOG.md
+++ b/plugins/catalog-react/CHANGELOG.md
@@ -1,5 +1,56 @@
# @backstage/plugin-catalog-react
+## 1.6.0
+
+### Minor Changes
+
+- 2258dcae970: Added an entity namespace filter and column on the default catalog page.
+
+ If you have a custom version of the catalog page, you can add this filter in your CatalogPage code:
+
+ ```ts
+
+
+
+
+
+ /* if you want namespace picker */
+
+
+
+
+
+
+ ```
+
+ The namespace column can be added using `createNamespaceColumn();`. This is only needed if you customized the columns for CatalogTable.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/version-bridge@1.0.4
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-permission-common@0.7.5
+ - @backstage/plugin-permission-react@0.4.12
+
+## 1.6.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
+
## 1.6.0-next.1
### Patch Changes
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 37190aa4c8..569e9d5d8a 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-react",
"description": "A frontend library that helps other Backstage plugins interact with the catalog",
- "version": "1.6.0-next.1",
+ "version": "1.6.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md
index 1082396da1..41fdf9aad7 100644
--- a/plugins/catalog/CHANGELOG.md
+++ b/plugins/catalog/CHANGELOG.md
@@ -1,5 +1,58 @@
# @backstage/plugin-catalog
+## 1.11.0
+
+### Minor Changes
+
+- 2258dcae970: Added an entity namespace filter and column on the default catalog page.
+
+ If you have a custom version of the catalog page, you can add this filter in your CatalogPage code:
+
+ ```ts
+
+
+
+
+
+ /* if you want namespace picker */
+
+
+
+
+
+
+ ```
+
+ The namespace column can be added using `createNamespaceColumn();`. This is only needed if you customized the columns for CatalogTable.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-search-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/integration-react@1.1.13
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-catalog-common@1.0.13
+ - @backstage/plugin-search-common@1.2.3
+
+## 1.11.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-search-react@1.6.0-next.2
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 1.11.0-next.1
### Patch Changes
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 0d5a9badcd..21983f762e 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog",
"description": "The Backstage plugin for browsing the Backstage catalog",
- "version": "1.11.0-next.1",
+ "version": "1.11.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md
index d895633ad4..fed7193859 100644
--- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md
+++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-cicd-statistics-module-gitlab
+## 0.1.14
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-cicd-statistics@0.1.20
+
+## 0.1.14-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-cicd-statistics@0.1.20-next.2
+
## 0.1.14-next.1
### Patch Changes
diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json
index 20d82e485f..ac719e1c1b 100644
--- a/plugins/cicd-statistics-module-gitlab/package.json
+++ b/plugins/cicd-statistics-module-gitlab/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-cicd-statistics-module-gitlab",
"description": "CI/CD Statistics plugin module; Gitlab CICD",
- "version": "0.1.14-next.1",
+ "version": "0.1.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md
index 3a802df672..b1b539c55e 100644
--- a/plugins/cicd-statistics/CHANGELOG.md
+++ b/plugins/cicd-statistics/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-cicd-statistics
+## 0.1.20
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.20-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.20-next.1
### Patch Changes
diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json
index a6a01ae05c..1eaebdf236 100644
--- a/plugins/cicd-statistics/package.json
+++ b/plugins/cicd-statistics/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-cicd-statistics",
"description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)",
- "version": "0.1.20-next.1",
+ "version": "0.1.20",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md
index 8afa59660d..b2947ce517 100644
--- a/plugins/circleci/CHANGELOG.md
+++ b/plugins/circleci/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-circleci
+## 0.3.18
+
+### Patch Changes
+
+- 451b3cadb3d: Fixes row display for in progress jobs to not display trailing "took"
+- 1c4958d905f: Hide empty time field data for queued builds which haven't started yet
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.18-next.1
### Patch Changes
diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json
index 683b43795c..87620319b2 100644
--- a/plugins/circleci/package.json
+++ b/plugins/circleci/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-circleci",
"description": "A Backstage plugin that integrates towards Circle CI",
- "version": "0.3.18-next.1",
+ "version": "0.3.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md
index ba462e356c..3eb8b09596 100644
--- a/plugins/cloudbuild/CHANGELOG.md
+++ b/plugins/cloudbuild/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-cloudbuild
+## 0.3.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.18-next.1
### Patch Changes
diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json
index 26a72a6c84..231e6c319c 100644
--- a/plugins/cloudbuild/package.json
+++ b/plugins/cloudbuild/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-cloudbuild",
"description": "A Backstage plugin that integrates towards Google Cloud Build",
- "version": "0.3.18-next.1",
+ "version": "0.3.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md
index 405e65e814..1b732c2154 100644
--- a/plugins/code-climate/CHANGELOG.md
+++ b/plugins/code-climate/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-code-climate
+## 0.1.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.18-next.1
### Patch Changes
diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json
index 2edda59078..b2da33c3f6 100644
--- a/plugins/code-climate/package.json
+++ b/plugins/code-climate/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-code-climate",
- "version": "0.1.18-next.1",
+ "version": "0.1.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md
index a63405bd75..10acc2a11a 100644
--- a/plugins/code-coverage-backend/CHANGELOG.md
+++ b/plugins/code-coverage-backend/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-code-coverage-backend
+## 0.2.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/integration@1.4.5
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 0.2.11-next.1
### Patch Changes
diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json
index 549e0a1b2c..4444287b23 100644
--- a/plugins/code-coverage-backend/package.json
+++ b/plugins/code-coverage-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-code-coverage-backend",
"description": "A Backstage backend plugin that helps you keep track of your code coverage",
- "version": "0.2.11-next.1",
+ "version": "0.2.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md
index 7f5789d3a5..b514b137a1 100644
--- a/plugins/code-coverage/CHANGELOG.md
+++ b/plugins/code-coverage/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-code-coverage
+## 0.2.11
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.11-next.1
### Patch Changes
diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json
index 412c7d3281..bb6fb411d5 100644
--- a/plugins/code-coverage/package.json
+++ b/plugins/code-coverage/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-code-coverage",
"description": "A Backstage plugin that helps you keep track of your code coverage",
- "version": "0.2.11-next.1",
+ "version": "0.2.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md
index 3c6338dfb0..242d8f6a4a 100644
--- a/plugins/codescene/CHANGELOG.md
+++ b/plugins/codescene/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-codescene
+## 0.1.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.1.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.13-next.0
### Patch Changes
diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json
index 744634130c..13d5662812 100644
--- a/plugins/codescene/package.json
+++ b/plugins/codescene/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-codescene",
- "version": "0.1.13-next.0",
+ "version": "0.1.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md
index e744ff73b1..059a476aed 100644
--- a/plugins/config-schema/CHANGELOG.md
+++ b/plugins/config-schema/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-config-schema
+## 0.1.41
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+
+## 0.1.41-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.41-next.0
### Patch Changes
diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json
index 893048274e..3f1488178e 100644
--- a/plugins/config-schema/package.json
+++ b/plugins/config-schema/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-config-schema",
"description": "A Backstage plugin that lets you browse the configuration schema of your app",
- "version": "0.1.41-next.0",
+ "version": "0.1.41",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md
index 89ba2ebaa0..63ac24c646 100644
--- a/plugins/cost-insights/CHANGELOG.md
+++ b/plugins/cost-insights/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-cost-insights
+## 0.12.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-cost-insights-common@0.1.1
+
+## 0.12.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.12.7-next.1
### Patch Changes
diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json
index 4855aae01a..8607bff31e 100644
--- a/plugins/cost-insights/package.json
+++ b/plugins/cost-insights/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-cost-insights",
"description": "A Backstage plugin that helps you keep track of your cloud spend",
- "version": "0.12.7-next.1",
+ "version": "0.12.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/devtools-backend/.eslintrc.js b/plugins/devtools-backend/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/devtools-backend/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md
new file mode 100644
index 0000000000..45f9f7174d
--- /dev/null
+++ b/plugins/devtools-backend/CHANGELOG.md
@@ -0,0 +1,33 @@
+# @backstage/plugin-devtools-backend
+
+## 0.1.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools-common@0.1.0
+ - @backstage/backend-common@0.18.5
+ - @backstage/config-loader@1.3.0
+ - @backstage/plugin-permission-node@0.7.8
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-permission-common@0.7.5
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools-common@0.1.0-next.0
+ - @backstage/config@1.0.7
diff --git a/plugins/devtools-backend/README.md b/plugins/devtools-backend/README.md
new file mode 100644
index 0000000000..e505a04963
--- /dev/null
+++ b/plugins/devtools-backend/README.md
@@ -0,0 +1,56 @@
+# DevTools Backend
+
+Welcome to the DevTools backend plugin! This plugin provides data for the [DevTools frontend](../devtools/) features.
+
+## Setup
+
+Here's how to get the DevTools Backend up and running:
+
+1. First we need to add the `@backstage/plugin-devtools-backend` package to your backend:
+
+ ```sh
+ # From the Backstage root directory
+ cd packages/backend
+ yarn add @backstage/plugin-devtools-backend
+ ```
+
+2. Then we will create a new file named `packages/backend/src/plugins/devtools.ts`, and add the
+ following to it:
+
+ ```ts
+ import { createRouter } from '@backstage/plugin-devtools-backend';
+ import { Router } from 'express';
+ import type { PluginEnvironment } from '../types';
+
+ export default function createPlugin(
+ env: PluginEnvironment,
+ ): Promise {
+ return createRouter({
+ logger: env.logger,
+ config: env.config,
+ permissions: env.permissions,
+ });
+ }
+ ```
+
+3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
+
+ ```ts
+ import devTools from './plugins/devtools';
+ // ...
+ async function main() {
+ // ...
+ // Add this line under the other lines that follow the useHotMemoize pattern
+ const devToolsEnv = useHotMemoize(module, () => createEnv('devtools'));
+ // ...
+ // Insert this line under the other lines that add their routers to apiRouter in the same way
+ apiRouter.use('/devtools', await devTools(devToolsEnv));
+ ```
+
+4. Now run `yarn start-backend` from the repo root
+5. Finally open `http://localhost:7007/api/devtools/health` in a browser and it should return `{"status":"ok"}`
+
+## Links
+
+- [Frontend part of the plugin](../devtools/README.md)
+- [The Backstage homepage](https://backstage.io)
diff --git a/plugins/devtools-backend/api-report.md b/plugins/devtools-backend/api-report.md
new file mode 100644
index 0000000000..e28f0697ab
--- /dev/null
+++ b/plugins/devtools-backend/api-report.md
@@ -0,0 +1,39 @@
+## API Report File for "@backstage/plugin-devtools-backend"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { Config } from '@backstage/config';
+import { ConfigInfo } from '@backstage/plugin-devtools-common';
+import { DevToolsInfo } from '@backstage/plugin-devtools-common';
+import express from 'express';
+import { ExternalDependency } from '@backstage/plugin-devtools-common';
+import { Logger } from 'winston';
+import { PermissionEvaluator } from '@backstage/plugin-permission-common';
+
+// @public (undocumented)
+export function createRouter(options: RouterOptions): Promise;
+
+// @public (undocumented)
+export class DevToolsBackendApi {
+ constructor(logger: Logger, config: Config);
+ // (undocumented)
+ listConfig(): Promise;
+ // (undocumented)
+ listExternalDependencyDetails(): Promise;
+ // (undocumented)
+ listInfo(): Promise;
+}
+
+// @public (undocumented)
+export interface RouterOptions {
+ // (undocumented)
+ config: Config;
+ // (undocumented)
+ devToolsBackendApi?: DevToolsBackendApi;
+ // (undocumented)
+ logger: Logger;
+ // (undocumented)
+ permissions: PermissionEvaluator;
+}
+```
diff --git a/plugins/devtools-backend/config.d.ts b/plugins/devtools-backend/config.d.ts
new file mode 100644
index 0000000000..15e70afcf8
--- /dev/null
+++ b/plugins/devtools-backend/config.d.ts
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export interface Config {
+ /**
+ * DevTools configuration.
+ */
+ devTools?: {
+ /**
+ * External dependency configuration.
+ */
+ externalDependencies?: {
+ /**
+ * The list of endpoints to check.
+ */
+ endpoints?: Array<{
+ /**
+ * The name of the endpoint.
+ */
+ name: string;
+ /**
+ * Type of check to perform; currently fetch or ping
+ */
+ type: string;
+ /**
+ * The target of the endpoint; currently either a URL for fetch or server name for ping.
+ */
+ target: string;
+ }>;
+ };
+ };
+}
diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json
new file mode 100644
index 0000000000..77eaf43901
--- /dev/null
+++ b/plugins/devtools-backend/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "@backstage/plugin-devtools-backend",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "backend-plugin"
+ },
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/backend-common": "workspace:^",
+ "@backstage/cli-common": "workspace:^",
+ "@backstage/config": "workspace:^",
+ "@backstage/config-loader": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "@backstage/plugin-auth-node": "workspace:^",
+ "@backstage/plugin-devtools-common": "workspace:^",
+ "@backstage/plugin-permission-common": "workspace:^",
+ "@backstage/plugin-permission-node": "workspace:^",
+ "@backstage/types": "workspace:^",
+ "@manypkg/get-packages": "^1.1.3",
+ "@types/express": "*",
+ "@yarnpkg/lockfile": "^1.1.0",
+ "@yarnpkg/parsers": "^3.0.0-rc.4",
+ "express": "^4.18.1",
+ "express-promise-router": "^4.1.0",
+ "fs-extra": "^10.0.0",
+ "lodash": "^4.17.21",
+ "node-fetch": "^2.6.7",
+ "ping": "^0.4.1",
+ "semver": "^7.3.2",
+ "winston": "^3.2.1",
+ "yn": "^4.0.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^",
+ "@types/minimist": "^1.2.0",
+ "@types/ping": "^0.4.1",
+ "@types/supertest": "^2.0.8",
+ "@types/yarnpkg__lockfile": "^1.1.4",
+ "msw": "^0.47.0",
+ "supertest": "^6.2.4"
+ },
+ "files": [
+ "dist",
+ "config.d.ts"
+ ],
+ "configSchema": "config.d.ts"
+}
diff --git a/plugins/devtools-backend/src/api/DevToolsBackendApi.ts b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts
new file mode 100644
index 0000000000..efd0407b31
--- /dev/null
+++ b/plugins/devtools-backend/src/api/DevToolsBackendApi.ts
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Config, ConfigReader } from '@backstage/config';
+import { loadConfigSchema } from '@backstage/config-loader';
+import {
+ PackageDependency,
+ DevToolsInfo,
+ ExternalDependency,
+ Endpoint,
+ ExternalDependencyStatus,
+ ConfigInfo,
+} from '@backstage/plugin-devtools-common';
+
+import { JsonObject } from '@backstage/types';
+import { Logger } from 'winston';
+import fetch from 'node-fetch';
+import { findPaths } from '@backstage/cli-common';
+import { getPackages } from '@manypkg/get-packages';
+import ping from 'ping';
+import os from 'os';
+import fs from 'fs-extra';
+import { Lockfile } from '../util/Lockfile';
+import { memoize } from 'lodash';
+import { assertError } from '@backstage/errors';
+
+/** @public */
+export class DevToolsBackendApi {
+ public constructor(
+ private readonly logger: Logger,
+ private readonly config: Config,
+ ) {}
+
+ public async listExternalDependencyDetails(): Promise {
+ const result: ExternalDependency[] = [];
+
+ const endpoints = this.config.getOptional(
+ 'devTools.externalDependencies.endpoints',
+ );
+ if (!endpoints) {
+ // No external dependency endpoints configured
+ return result;
+ }
+ for (const endpoint of endpoints) {
+ this.logger?.info(
+ `Checking external dependency "${endpoint.name}" at "${endpoint.target}"`,
+ );
+
+ switch (endpoint.type) {
+ case 'ping': {
+ const pingResult = await this.pingExternalDependency(endpoint);
+ result.push(pingResult);
+ break;
+ }
+ case 'fetch': {
+ const fetchResult = await this.fetchExternalDependency(endpoint);
+ result.push(fetchResult);
+ break;
+ }
+ default:
+ return result;
+ }
+ }
+
+ return result;
+ }
+
+ private async fetchExternalDependency(
+ endpoint: Endpoint,
+ ): Promise {
+ let status;
+ let error;
+
+ await fetch(endpoint.target)
+ .then(res => {
+ status =
+ res.status === 200
+ ? ExternalDependencyStatus.healthy
+ : ExternalDependencyStatus.unhealthy;
+ this.logger.debug(
+ `Fetch for ${endpoint.name} resulted in status code "${res.status}"`,
+ );
+ })
+ .catch((err: Error) => {
+ this.logger.error(`Fetch failed for ${endpoint.name} - ${err.message}`);
+ error = err.message;
+ });
+
+ const result: ExternalDependency = {
+ name: endpoint.name,
+ type: endpoint.type,
+ target: endpoint.target,
+ status: status ?? ExternalDependencyStatus.unhealthy,
+ error: error ?? undefined,
+ };
+
+ return result;
+ }
+
+ private async pingExternalDependency(
+ endpoint: Endpoint,
+ ): Promise {
+ const pingResult = await ping.promise.probe(endpoint.target);
+
+ let error;
+ if (
+ pingResult.packetLoss === '100.000' ||
+ pingResult.packetLoss === 'unknown'
+ ) {
+ this.logger.error(
+ `Ping failed for ${endpoint.name} - ${pingResult.output}`,
+ );
+ error =
+ pingResult.output === ''
+ ? `${endpoint.target} - Unknown`
+ : pingResult.output;
+ }
+
+ this.logger.debug(
+ `Ping results for ${endpoint.name}: ${pingResult.output}`,
+ );
+
+ const result: ExternalDependency = {
+ name: endpoint.name,
+ type: endpoint.type,
+ target: endpoint.target,
+ status: pingResult.alive
+ ? ExternalDependencyStatus.healthy
+ : ExternalDependencyStatus.unhealthy,
+ error: error ?? undefined,
+ };
+
+ return result;
+ }
+
+ public async listConfig(): Promise {
+ /* eslint-disable-next-line no-restricted-syntax */
+ const paths = findPaths(__dirname);
+
+ const { packages } = await getPackages(paths.targetDir);
+ const schemaFunc = async () => {
+ return await loadConfigSchema({
+ dependencies: packages.map(p => p.packageJson.name),
+ });
+ };
+
+ const schemaMemo = memoize(schemaFunc);
+ const schema = await schemaMemo();
+
+ const configInfo: ConfigInfo = {
+ config: undefined,
+ error: undefined,
+ };
+ try {
+ const config = {
+ data: this.config.get() as JsonObject,
+ context: 'inline',
+ };
+ const sanitizedConfigs = schema.process([config], {
+ ignoreSchemaErrors: false,
+ valueTransform: (value, context) =>
+ context.visibility === 'secret' ? '' : value,
+ });
+
+ const data = ConfigReader.fromConfigs(sanitizedConfigs).get();
+ configInfo.config = data;
+ } catch (error) {
+ assertError(error);
+ // The config is not valid for some reason but we want to be able to see it still
+ const config = {
+ data: this.config.get() as JsonObject,
+ context: 'inline',
+ };
+ const sanitizedConfigs = schema.process([config], {
+ ignoreSchemaErrors: true,
+ valueTransform: (value, context) =>
+ context.visibility === 'secret' ? '' : value,
+ });
+
+ const data = ConfigReader.fromConfigs(sanitizedConfigs).get();
+ configInfo.config = data;
+ configInfo.error = {
+ name: error.name,
+ message: error.message,
+ messages: error.messages as string[] | undefined,
+ stack: error.stack,
+ };
+ }
+
+ return configInfo;
+ }
+
+ public async listInfo(): Promise {
+ const operatingSystem = `${os.type} ${os.release} - ${os.platform}/${os.arch}`;
+ const nodeJsVersion = process.version;
+
+ /* eslint-disable-next-line no-restricted-syntax */
+ const paths = findPaths(__dirname);
+ const backstageFile = paths.resolveTargetRoot('backstage.json');
+ let backstageJson = undefined;
+ if (fs.existsSync(backstageFile)) {
+ const buffer = await fs.readFile(backstageFile);
+ backstageJson = JSON.parse(buffer.toString());
+ }
+
+ const lockfilePath = paths.resolveTargetRoot('yarn.lock');
+ const lockfile = await Lockfile.load(lockfilePath);
+
+ const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/'));
+
+ const infoDependencies: PackageDependency[] = [];
+ for (const dep of deps) {
+ const versions = new Set(lockfile.get(dep)!.map(i => i.version));
+ const infoDependency: PackageDependency = {
+ name: dep,
+ versions: [...versions].join(', '),
+ };
+ infoDependencies.push(infoDependency);
+ }
+
+ const info: DevToolsInfo = {
+ operatingSystem: operatingSystem ?? 'N/A',
+ nodeJsVersion: nodeJsVersion ?? 'N/A',
+ backstageVersion:
+ backstageJson && backstageJson.version ? backstageJson.version : 'N/A',
+ dependencies: infoDependencies,
+ };
+
+ return info;
+ }
+}
+
+export function isValidUrl(url: string): boolean {
+ try {
+ // eslint-disable-next-line no-new
+ new URL(url);
+ return true;
+ } catch {
+ return false;
+ }
+}
diff --git a/plugins/devtools-backend/src/api/index.ts b/plugins/devtools-backend/src/api/index.ts
new file mode 100644
index 0000000000..ed3390b113
--- /dev/null
+++ b/plugins/devtools-backend/src/api/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DevToolsBackendApi } from './DevToolsBackendApi';
diff --git a/plugins/devtools-backend/src/index.ts b/plugins/devtools-backend/src/index.ts
new file mode 100644
index 0000000000..e1545e0022
--- /dev/null
+++ b/plugins/devtools-backend/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Backstage DevTools backend plugin that contains the API for retrieving package dependencies and current configuration which is used by the Backstage DevTools frontend plugin.
+ *
+ * @packageDocumentation
+ */
+
+export { DevToolsBackendApi } from './api';
+export * from './service/router';
diff --git a/plugins/devtools-backend/src/run.ts b/plugins/devtools-backend/src/run.ts
new file mode 100644
index 0000000000..d945aa13f0
--- /dev/null
+++ b/plugins/devtools-backend/src/run.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getRootLogger } from '@backstage/backend-common';
+import yn from 'yn';
+import { startStandaloneServer } from './service/standaloneServer';
+
+const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
+const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
+const logger = getRootLogger();
+
+startStandaloneServer({ port, enableCors, logger }).catch(err => {
+ logger.error(err);
+ process.exit(1);
+});
+
+process.on('SIGINT', () => {
+ logger.info('CTRL+C pressed; exiting.');
+ process.exit(0);
+});
diff --git a/plugins/devtools-backend/src/service/router.test.ts b/plugins/devtools-backend/src/service/router.test.ts
new file mode 100644
index 0000000000..f3c78242cf
--- /dev/null
+++ b/plugins/devtools-backend/src/service/router.test.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { getVoidLogger } from '@backstage/backend-common';
+import { ConfigReader } from '@backstage/config';
+import express from 'express';
+import request from 'supertest';
+import { PermissionEvaluator } from '@backstage/plugin-permission-common';
+import { createRouter } from './router';
+
+const mockedAuthorize: jest.MockedFunction =
+ jest.fn();
+const mockedPermissionQuery: jest.MockedFunction<
+ PermissionEvaluator['authorizeConditional']
+> = jest.fn();
+
+const permissionEvaluator: PermissionEvaluator = {
+ authorize: mockedAuthorize,
+ authorizeConditional: mockedPermissionQuery,
+};
+
+describe('createRouter', () => {
+ let app: express.Express;
+
+ beforeAll(async () => {
+ const router = await createRouter({
+ logger: getVoidLogger(),
+ config: new ConfigReader({
+ healthCheck: {
+ endpoint: [
+ {
+ name: '',
+ type: '',
+ target: '',
+ },
+ ],
+ },
+ }),
+ permissions: permissionEvaluator,
+ });
+ app = express().use(router);
+ });
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('GET /health', () => {
+ it('returns ok', async () => {
+ const response = await request(app).get('/health');
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ status: 'ok' });
+ });
+ });
+});
diff --git a/plugins/devtools-backend/src/service/router.ts b/plugins/devtools-backend/src/service/router.ts
new file mode 100644
index 0000000000..a346ad3183
--- /dev/null
+++ b/plugins/devtools-backend/src/service/router.ts
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ AuthorizeResult,
+ PermissionEvaluator,
+} from '@backstage/plugin-permission-common';
+import {
+ devToolsConfigReadPermission,
+ devToolsExternalDependenciesReadPermission,
+ devToolsInfoReadPermission,
+} from '@backstage/plugin-devtools-common';
+
+import { Config } from '@backstage/config';
+import { DevToolsBackendApi } from '../api';
+import { Logger } from 'winston';
+import { NotAllowedError } from '@backstage/errors';
+import Router from 'express-promise-router';
+import { errorHandler } from '@backstage/backend-common';
+import express from 'express';
+import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';
+
+/** @public */
+export interface RouterOptions {
+ devToolsBackendApi?: DevToolsBackendApi;
+ logger: Logger;
+ config: Config;
+ permissions: PermissionEvaluator;
+}
+
+/** @public */
+export async function createRouter(
+ options: RouterOptions,
+): Promise {
+ const { logger, config, permissions } = options;
+
+ const devToolsBackendApi =
+ options.devToolsBackendApi || new DevToolsBackendApi(logger, config);
+
+ const router = Router();
+ router.use(express.json());
+
+ router.get('/health', (_req, res) => {
+ res.status(200).json({ status: 'ok' });
+ });
+
+ router.get('/info', async (req, response) => {
+ const token = getBearerTokenFromAuthorizationHeader(
+ req.header('authorization'),
+ );
+
+ const decision = (
+ await permissions.authorize(
+ [{ permission: devToolsInfoReadPermission }],
+ {
+ token,
+ },
+ )
+ )[0];
+
+ if (decision.result === AuthorizeResult.DENY) {
+ throw new NotAllowedError('Unauthorized');
+ }
+
+ const info = await devToolsBackendApi.listInfo();
+
+ response.status(200).json(info);
+ });
+
+ router.get('/config', async (req, response) => {
+ const token = getBearerTokenFromAuthorizationHeader(
+ req.header('authorization'),
+ );
+
+ const decision = (
+ await permissions.authorize(
+ [{ permission: devToolsConfigReadPermission }],
+ {
+ token,
+ },
+ )
+ )[0];
+
+ if (decision.result === AuthorizeResult.DENY) {
+ throw new NotAllowedError('Unauthorized');
+ }
+
+ const configList = await devToolsBackendApi.listConfig();
+
+ response.status(200).json(configList);
+ });
+
+ router.get('/external-dependencies', async (req, response) => {
+ const token = getBearerTokenFromAuthorizationHeader(
+ req.header('authorization'),
+ );
+
+ const decision = (
+ await permissions.authorize(
+ [{ permission: devToolsExternalDependenciesReadPermission }],
+ {
+ token,
+ },
+ )
+ )[0];
+
+ if (decision.result === AuthorizeResult.DENY) {
+ throw new NotAllowedError('Unauthorized');
+ }
+
+ const health = await devToolsBackendApi.listExternalDependencyDetails();
+
+ response.status(200).json(health);
+ });
+
+ router.use(errorHandler());
+ return router;
+}
diff --git a/plugins/devtools-backend/src/service/standaloneServer.ts b/plugins/devtools-backend/src/service/standaloneServer.ts
new file mode 100644
index 0000000000..d2fdce169d
--- /dev/null
+++ b/plugins/devtools-backend/src/service/standaloneServer.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ServerTokenManager,
+ SingleHostDiscovery,
+ createServiceBuilder,
+ loadBackendConfig,
+} from '@backstage/backend-common';
+
+import { Logger } from 'winston';
+import { Server } from 'http';
+import { ServerPermissionClient } from '@backstage/plugin-permission-node';
+import { createRouter } from './router';
+
+export interface ServerOptions {
+ port: number;
+ enableCors: boolean;
+ logger: Logger;
+}
+
+export async function startStandaloneServer(
+ options: ServerOptions,
+): Promise {
+ const logger = options.logger.child({ service: 'devtools-backend-backend' });
+ const config = await loadBackendConfig({ logger, argv: process.argv });
+ const discovery = SingleHostDiscovery.fromConfig(config);
+ const tokenManager = ServerTokenManager.fromConfig(config, {
+ logger,
+ });
+ const permissions = ServerPermissionClient.fromConfig(config, {
+ discovery,
+ tokenManager,
+ });
+ logger.debug('Starting application server...');
+ const router = await createRouter({
+ logger,
+ config,
+ permissions,
+ });
+
+ let service = createServiceBuilder(module)
+ .setPort(options.port)
+ .addRouter('/devtools-backend', router);
+ if (options.enableCors) {
+ service = service.enableCors({ origin: 'http://localhost:3000' });
+ }
+
+ return await service.start().catch(err => {
+ logger.error(err);
+ process.exit(1);
+ });
+}
+
+module.hot?.accept();
diff --git a/plugins/devtools-backend/src/setupTests.ts b/plugins/devtools-backend/src/setupTests.ts
new file mode 100644
index 0000000000..813cdeaae3
--- /dev/null
+++ b/plugins/devtools-backend/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export {};
diff --git a/plugins/devtools-backend/src/util/Lockfile.ts b/plugins/devtools-backend/src/util/Lockfile.ts
new file mode 100644
index 0000000000..52dbb847bc
--- /dev/null
+++ b/plugins/devtools-backend/src/util/Lockfile.ts
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import fs from 'fs-extra';
+import semver from 'semver';
+import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
+import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
+
+const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
+
+type LockfileData = {
+ [entry: string]: {
+ version: string;
+ resolved?: string;
+ integrity?: string;
+ dependencies?: { [name: string]: string };
+ };
+};
+
+type LockfileQueryEntry = {
+ range: string;
+ version: string;
+};
+
+/** Entries that have an invalid version range, for example an npm tag */
+type AnalyzeResultInvalidRange = {
+ name: string;
+ range: string;
+};
+
+/** Entries that can be deduplicated by bumping to an existing higher version */
+type AnalyzeResultNewVersion = {
+ name: string;
+ range: string;
+ oldVersion: string;
+ newVersion: string;
+};
+
+/** Entries that would need a dependency update in package.json to be deduplicated */
+type AnalyzeResultNewRange = {
+ name: string;
+ oldRange: string;
+ newRange: string;
+ oldVersion: string;
+ newVersion: string;
+};
+
+type AnalyzeResult = {
+ invalidRanges: AnalyzeResultInvalidRange[];
+ newVersions: AnalyzeResultNewVersion[];
+ newRanges: AnalyzeResultNewRange[];
+};
+
+function parseLockfile(lockfileContents: string) {
+ try {
+ return {
+ object: parseSyml(lockfileContents),
+ type: 'success',
+ };
+ } catch (err) {
+ return {
+ object: null,
+ type: err,
+ };
+ }
+}
+
+// the new yarn header is handled out of band of the parsing
+// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
+const NEW_HEADER = `${[
+ `# This file is generated by running "yarn install" inside your project.\n`,
+ `# Manual changes might be lost - proceed with caution!\n`,
+].join(``)}\n`;
+
+function stringifyLockfile(data: LockfileData, legacy: boolean) {
+ return legacy
+ ? legacyStringifyLockfile(data)
+ : NEW_HEADER + stringifySyml(data);
+}
+// taken from yarn parser package
+// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136
+const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
+
+// these are special top level yarn keys.
+// https://github.com/yarnpkg/berry/blob/9bd61fbffb83d0b8166a9cc26bec3a58743aa453/packages/yarnpkg-parsers/sources/syml.ts#L9
+const SPECIAL_OBJECT_KEYS = [
+ `__metadata`,
+ `version`,
+ `resolution`,
+ `dependencies`,
+ `peerDependencies`,
+ `dependenciesMeta`,
+ `peerDependenciesMeta`,
+ `binaries`,
+];
+
+export class Lockfile {
+ static async load(path: string) {
+ const lockfileContents = await fs.readFile(path, 'utf8');
+ const legacy = LEGACY_REGEX.test(lockfileContents);
+ const lockfile = parseLockfile(lockfileContents);
+ if (lockfile.type !== 'success') {
+ throw new Error(`Failed yarn.lock parse with ${lockfile.type}`);
+ }
+
+ const data = lockfile.object as LockfileData;
+ const packages = new Map();
+
+ for (const [key, value] of Object.entries(data)) {
+ if (SPECIAL_OBJECT_KEYS.includes(key)) continue;
+
+ const [, name, range] = ENTRY_PATTERN.exec(key) ?? [];
+ if (!name) {
+ throw new Error(`Failed to parse yarn.lock entry '${key}'`);
+ }
+
+ let queries = packages.get(name);
+ if (!queries) {
+ queries = [];
+ packages.set(name, queries);
+ }
+ queries.push({ range, version: value.version });
+ }
+
+ return new Lockfile(path, packages, data, legacy);
+ }
+
+ private constructor(
+ private readonly path: string,
+ private readonly packages: Map,
+ private readonly data: LockfileData,
+ private readonly legacy: boolean = false,
+ ) {}
+
+ /** Get the entries for a single package in the lockfile */
+ get(name: string): LockfileQueryEntry[] | undefined {
+ return this.packages.get(name);
+ }
+
+ /** Returns the name of all packages available in the lockfile */
+ keys(): IterableIterator {
+ return this.packages.keys();
+ }
+
+ /** Analyzes the lockfile to identify possible actions and warnings for the entries */
+ analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult {
+ const { filter } = options ?? {};
+ const result: AnalyzeResult = {
+ invalidRanges: [],
+ newVersions: [],
+ newRanges: [],
+ };
+
+ for (const [name, allEntries] of this.packages) {
+ if (filter && !filter(name)) {
+ continue;
+ }
+
+ // Get rid of and signal any invalid ranges upfront
+ const invalid = allEntries.filter(e => !semver.validRange(e.range));
+ result.invalidRanges.push(
+ ...invalid.map(({ range }) => ({ name, range })),
+ );
+
+ // Grab all valid entries, if there aren't at least 2 different valid ones we're done
+ const entries = allEntries.filter(e => semver.validRange(e.range));
+ if (entries.length < 2) {
+ continue;
+ }
+
+ // Find all versions currently in use
+ const versions = Array.from(new Set(entries.map(e => e.version))).sort(
+ (v1, v2) => semver.rcompare(v1, v2),
+ );
+
+ // If we're not using at least 2 different versions we're done
+ if (versions.length < 2) {
+ continue;
+ }
+
+ const acceptedVersions = new Set();
+ for (const { version, range } of entries) {
+ // Finds the highest matching version from the the known versions
+ // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one
+ const acceptedVersion = versions.find(v => semver.satisfies(v, range));
+ if (!acceptedVersion) {
+ throw new Error(
+ `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`,
+ );
+ }
+
+ if (acceptedVersion !== version) {
+ result.newVersions.push({
+ name,
+ range,
+ newVersion: acceptedVersion,
+ oldVersion: version,
+ });
+ }
+
+ acceptedVersions.add(acceptedVersion);
+ }
+
+ // If all ranges were able to accept the same version, we're done
+ if (acceptedVersions.size === 1) {
+ continue;
+ }
+
+ // Find the max version that we may want bump older packages to
+ const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0];
+ // Find all existing ranges that satisfy the new max version, and pick the one that
+ // results in the highest minimum allowed version, usually being the more specific one
+ const maxEntry = entries
+ .filter(e => semver.satisfies(maxVersion, e.range))
+ .map(e => ({ e, min: semver.minVersion(e.range) }))
+ .filter(p => p.min)
+ .sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e;
+ if (!maxEntry) {
+ throw new Error(
+ `No entry found that satisfies max version '${maxVersion}'`,
+ );
+ }
+
+ // Find all entries that don't satisfy the max version
+ for (const { version, range } of entries) {
+ if (semver.satisfies(maxVersion, range)) {
+ continue;
+ }
+
+ result.newRanges.push({
+ name,
+ oldRange: range,
+ newRange: maxEntry.range,
+ oldVersion: version,
+ newVersion: maxVersion,
+ });
+ }
+ }
+
+ return result;
+ }
+
+ remove(name: string, range: string): boolean {
+ const query = `${name}@${range}`;
+ const existed = Boolean(this.data[query]);
+ delete this.data[query];
+
+ const newEntries = this.packages.get(name)?.filter(e => e.range !== range);
+ if (newEntries) {
+ this.packages.set(name, newEntries);
+ }
+
+ return existed;
+ }
+
+ /** Modifies the lockfile by bumping packages to the suggested versions */
+ replaceVersions(results: AnalyzeResultNewVersion[]) {
+ for (const { name, range, oldVersion, newVersion } of results) {
+ const query = `${name}@${range}`;
+
+ // Update the backing data
+ const entryData = this.data[query];
+ if (!entryData) {
+ throw new Error(`No entry data for ${query}`);
+ }
+ if (entryData.version !== oldVersion) {
+ throw new Error(
+ `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
+ );
+ }
+
+ // Modifying the data in the entry is not enough, we need to reference an existing version object
+ const matchingEntry = Object.entries(this.data).find(
+ ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion,
+ );
+ if (!matchingEntry) {
+ throw new Error(
+ `No matching entry found for ${name} at version ${newVersion}`,
+ );
+ }
+ this.data[query] = matchingEntry[1];
+
+ // Update our internal data structure
+ const entry = this.packages.get(name)?.find(e => e.range === range);
+ if (!entry) {
+ throw new Error(`No entry data for ${query}`);
+ }
+ if (entry.version !== oldVersion) {
+ throw new Error(
+ `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
+ );
+ }
+ entry.version = newVersion;
+ }
+ }
+
+ async save() {
+ await fs.writeFile(this.path, this.toString(), 'utf8');
+ }
+
+ toString() {
+ return stringifyLockfile(this.data, this.legacy);
+ }
+}
diff --git a/plugins/devtools-common/.eslintrc.js b/plugins/devtools-common/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/devtools-common/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md
new file mode 100644
index 0000000000..6a566d0a67
--- /dev/null
+++ b/plugins/devtools-common/CHANGELOG.md
@@ -0,0 +1,19 @@
+# @backstage/plugin-devtools-common
+
+## 0.1.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/types@1.0.2
+ - @backstage/plugin-permission-common@0.7.5
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
diff --git a/plugins/devtools-common/README.md b/plugins/devtools-common/README.md
new file mode 100644
index 0000000000..5be5e58773
--- /dev/null
+++ b/plugins/devtools-common/README.md
@@ -0,0 +1,3 @@
+# DevTools Common
+
+Common types and permissions for the DevTools plugin.
diff --git a/plugins/devtools-common/api-report.md b/plugins/devtools-common/api-report.md
new file mode 100644
index 0000000000..7e38ff05b0
--- /dev/null
+++ b/plugins/devtools-common/api-report.md
@@ -0,0 +1,72 @@
+## API Report File for "@backstage/plugin-devtools-common"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { BasicPermission } from '@backstage/plugin-permission-common';
+import { JsonValue } from '@backstage/types';
+
+// @public (undocumented)
+export type ConfigError = {
+ name: string;
+ message: string;
+ messages?: string[];
+ stack?: string;
+};
+
+// @public (undocumented)
+export type ConfigInfo = {
+ config?: JsonValue;
+ error?: ConfigError;
+};
+
+// @public (undocumented)
+export const devToolsAdministerPermission: BasicPermission;
+
+// @public (undocumented)
+export const devToolsConfigReadPermission: BasicPermission;
+
+// @public (undocumented)
+export const devToolsExternalDependenciesReadPermission: BasicPermission;
+
+// @public (undocumented)
+export type DevToolsInfo = {
+ operatingSystem: string;
+ nodeJsVersion: string;
+ backstageVersion: string;
+ dependencies: PackageDependency[];
+};
+
+// @public (undocumented)
+export const devToolsInfoReadPermission: BasicPermission;
+
+// @public (undocumented)
+export type Endpoint = {
+ name: string;
+ type: string;
+ target: string;
+};
+
+// @public (undocumented)
+export type ExternalDependency = {
+ name: string;
+ type: string;
+ target: string;
+ status: string;
+ error?: string;
+};
+
+// @public (undocumented)
+export enum ExternalDependencyStatus {
+ // (undocumented)
+ healthy = 'Healthy',
+ // (undocumented)
+ unhealthy = 'Unhealthy',
+}
+
+// @public (undocumented)
+export type PackageDependency = {
+ name: string;
+ versions: string;
+};
+```
diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json
new file mode 100644
index 0000000000..0e114e39b9
--- /dev/null
+++ b/plugins/devtools-common/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@backstage/plugin-devtools-common",
+ "description": "Common functionalities for the devtools plugin",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "module": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "common-library"
+ },
+ "scripts": {
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/plugin-permission-common": "workspace:^",
+ "@backstage/types": "workspace:^"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/devtools-common/src/index.ts b/plugins/devtools-common/src/index.ts
new file mode 100644
index 0000000000..be7426394b
--- /dev/null
+++ b/plugins/devtools-common/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Common types and permissions for the DevTools frontend and backend plugins.
+ *
+ * @packageDocumentation
+ */
+
+export * from './types';
+export * from './permissions';
diff --git a/plugins/devtools-common/src/permissions.ts b/plugins/devtools-common/src/permissions.ts
new file mode 100644
index 0000000000..ae62cfdc19
--- /dev/null
+++ b/plugins/devtools-common/src/permissions.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createPermission } from '@backstage/plugin-permission-common';
+
+/**
+ * @public
+ */
+export const devToolsAdministerPermission = createPermission({
+ name: 'devtools.administer',
+ attributes: { action: 'read' },
+});
+
+/**
+ * @public
+ */
+export const devToolsInfoReadPermission = createPermission({
+ name: 'devtools.info',
+ attributes: { action: 'read' },
+});
+
+/**
+ * @public
+ */
+export const devToolsConfigReadPermission = createPermission({
+ name: 'devtools.config',
+ attributes: { action: 'read' },
+});
+
+/**
+ * @public
+ */
+export const devToolsExternalDependenciesReadPermission = createPermission({
+ name: 'devtools.external-dependencies',
+ attributes: { action: 'read' },
+});
diff --git a/plugins/devtools-common/src/setupTests.ts b/plugins/devtools-common/src/setupTests.ts
new file mode 100644
index 0000000000..813cdeaae3
--- /dev/null
+++ b/plugins/devtools-common/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export {};
diff --git a/plugins/devtools-common/src/types.ts b/plugins/devtools-common/src/types.ts
new file mode 100644
index 0000000000..d08232b155
--- /dev/null
+++ b/plugins/devtools-common/src/types.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { JsonValue } from '@backstage/types';
+
+/** @public */
+export type Endpoint = {
+ name: string;
+ type: string;
+ target: string;
+};
+
+/** @public */
+export type ExternalDependency = {
+ name: string;
+ type: string;
+ target: string;
+ status: string;
+ error?: string;
+};
+
+/** @public */
+export type DevToolsInfo = {
+ operatingSystem: string;
+ nodeJsVersion: string;
+ backstageVersion: string;
+ dependencies: PackageDependency[];
+};
+
+/** @public */
+export type PackageDependency = {
+ name: string;
+ versions: string;
+};
+
+/** @public */
+export enum ExternalDependencyStatus {
+ healthy = 'Healthy',
+ unhealthy = 'Unhealthy',
+}
+
+/** @public */
+export type ConfigInfo = {
+ config?: JsonValue;
+ error?: ConfigError;
+};
+
+/** @public */
+export type ConfigError = {
+ name: string;
+ message: string;
+ messages?: string[];
+ stack?: string;
+};
diff --git a/plugins/devtools/.eslintrc.js b/plugins/devtools/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/devtools/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md
new file mode 100644
index 0000000000..2e0e2a2de0
--- /dev/null
+++ b/plugins/devtools/CHANGELOG.md
@@ -0,0 +1,33 @@
+# @backstage/plugin-devtools
+
+## 0.1.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools-common@0.1.0
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/types@1.0.2
+ - @backstage/plugin-permission-react@0.4.12
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- 347aeca204c: Introduced the DevTools plugin, checkout the plugin's [`README.md`](https://github.com/backstage/backstage/tree/master/plugins/devtools) for more details!
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-devtools-common@0.1.0-next.0
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/plugin-permission-react@0.4.12
diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md
new file mode 100644
index 0000000000..fc9f1f7312
--- /dev/null
+++ b/plugins/devtools/README.md
@@ -0,0 +1,375 @@
+# DevTools
+
+Welcome to the DevTools plugin! This plugin helps elevate useful information about a Backstage instance through the frontend, which can be helpful for an integrator for troubleshooting, reviewing, and understanding their installation.
+
+## Features
+
+The DevTools plugin comes with two tabs out of the box.
+
+### Info
+
+Lists helpful information about your current running Backstage instance such as: OS, NodeJS version, Backstage version, and package versions.
+
+
+
+### Config
+
+Lists the configuration being used by your current running Backstage instance.
+
+
+
+## Optional Features
+
+The DevTools plugin can be setup with other tabs with additional helpful features.
+
+### External Dependencies
+
+Lists the status of configured External Dependencies based on your current running Backstage instance's ability to reach them.
+
+
+
+## Setup
+
+The following sections will help you get the DevTools plugin setup and running.
+
+### Backend
+
+You need to setup the [DevTools backend plugin](../devtools-backend/README.md) before you move forward with any of the following steps if you haven't already.
+
+### Frontend
+
+To setup the DevTools frontend you'll need to do the following steps:
+
+1. First we need to add the `@backstage/plugin-devtools` package to your frontend app:
+
+ ```sh
+ # From your Backstage root directory
+ yarn add --cwd packages/app @backstage/plugin-devtools
+ ```
+
+2. Now open the `packages/app/src/App.tsx` file
+3. Then after all the import statements add the following line:
+
+ ```ts
+ import { DevToolsPage } from '@backstage/plugin-devtools';
+ ```
+
+4. In this same file just before the closing ` FlatRoutes>`, this will be near the bottom of the file, add this line:
+
+ ```ts
+ } />
+ ```
+
+5. Next open the `packages/app/src/components/Root/Root.tsx` file
+6. We want to add this icon import after all the existing import statements:
+
+ ```ts
+ import BuildIcon from '@material-ui/icons/Build';
+ ```
+
+7. Then add this line just after the `` line:
+
+ ```ts
+
+ ```
+
+8. Now run `yarn dev` from the root of your project and you should see the DevTools option show up just below Settings in your sidebar and clicking on it will get you to the [Info tab](#info)
+
+## Customizing
+
+The DevTools plugin has been designed so that you can customize the tabs to suite your needs. You may only want some or none of the out of the box tabs or you may want to add your own. The following sections explains how to do that (assuming you've already done the [setup steps](#setup)). As part of this example we'll also be showing how you can add the optional [External Dependencies](#external-dependencies) tab.
+
+1. In the `packages/app/src/components` folder create a new sub-folder called `devtools`
+2. Then in this new `devtools` folder add a file called `CustomDevToolsPage.tsx`
+3. In the `CustomDevToolsPage.tsx` file add the following content:
+
+ ```tsx
+ import {
+ ConfigContent,
+ ExternalDependenciesContent,
+ InfoContent,
+ } from '@backstage/plugin-devtools';
+ import { DevToolsLayout } from '@backstage/plugin-devtools';
+ import React from 'react';
+
+ export const DevToolsPage = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ };
+
+ export const customDevToolsPage = ;
+ ```
+
+4. Now open the `packages/app/src/App.tsx` file and add the following import after all the existing import statements:
+
+ ```ts
+ import { customDevToolsPage } from './components/devtools/CustomDevToolsPage';
+ ```
+
+5. Then we need to adjust our route as follows
+
+ ```diff
+ - } />
+ + } >
+ + {customDevToolsPage}
+ +
+ ```
+
+6. Now run `yarn dev` from the root of your project. When you go to the DevTools you'll now see you have a third tab for [External Dependencies](#external-dependencies)
+
+With this setup you can add or remove the tabs as you'd like or add your own simply by editing your `CustomDevToolsPage.tsx` file
+
+## Permissions
+
+The DevTools plugin supports the [permissions framework](https://backstage.io/docs/permissions/overview), the following sections outline how you can use them with the assumption that you have the permissions framework setup and working.
+
+**Note:** These sections are intended as guidance and are completely optional. The DevTools plugin will work with the permission framework off or on without any specific policy setup.
+
+### Secure Sidebar Option
+
+To use the permission framework to secure the DevTools sidebar option you'll want to do the following:
+
+1. First we need to add the `@backstage/plugin-devtools-common` package to your frontend app:
+
+ ```sh
+ # From your Backstage root directory
+ yarn add --cwd packages/app @backstage/plugin-devtools
+ ```
+
+2. Then open the `packages/app/src/components/Root/Root.tsx` file
+3. The add these imports after all the existing import statements:
+
+ ```ts
+ import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
+ import { RequirePermission } from '@backstage/plugin-permission-react';
+ ```
+
+4. Then make the following change:
+
+ ```diff
+ -
+ + >}>
+ +
+ +
+ ```
+
+### Secure the DevTools Route
+
+To use the permission framework to secure the DevTools route you'll want to do the following:
+
+1. First we need to add the `@backstage/plugin-devtools-common` package to your frontend app (skip this step if you've already done this):
+
+ ```sh
+ # From your Backstage root directory
+ yarn add --cwd packages/app @backstage/plugin-devtools-common
+ ```
+
+2. Then open the `packages/app/src/App.tsx` file
+3. The add this import after all the existing import statements:
+
+ ```ts
+ import { devToolsAdministerPermission } from '@backstage/plugin-devtools-common';
+ ```
+
+4. Then make the following change:
+
+ ```diff
+ - } />
+ +
+ +
+ +
+ + }
+ + />
+ ```
+
+Note: if you are using a `customDevToolsPage` as per the [Customizing](#customizing) documentation the changes for Step 4 will be:
+
+```diff
+- } />
++
++
++
++ }
++ >
++ {customDevToolsPage}
++
+```
+
+### Permission Policy
+
+Here is an example permission policy that you might use to secure the DevTools plugin:
+
+```ts
+// packages/backend/src/plugins/permission.ts
+
+class TestPermissionPolicy implements PermissionPolicy {
+ async handle(request: PolicyQuery): Promise {
+ if (isPermission(request.permission, devToolsAdministerPermission)) {
+ if (
+ user?.identity.ownershipEntityRefs.includes(
+ 'group:default/backstage-admins',
+ )
+ ) {
+ return { result: AuthorizeResult.ALLOW };
+ }
+ return { result: AuthorizeResult.DENY };
+ }
+
+ if (isPermission(request.permission, devToolsInfoReadPermission)) {
+ if (
+ user?.identity.ownershipEntityRefs.includes(
+ 'group:default/backstage-admins',
+ )
+ ) {
+ return { result: AuthorizeResult.ALLOW };
+ }
+ return { result: AuthorizeResult.DENY };
+ }
+
+ if (isPermission(request.permission, devToolsConfigReadPermission)) {
+ if (
+ user?.identity.ownershipEntityRefs.includes(
+ 'group:default/backstage-admins',
+ )
+ ) {
+ return { result: AuthorizeResult.ALLOW };
+ }
+ return { result: AuthorizeResult.DENY };
+ }
+
+ if (
+ isPermission(
+ request.permission,
+ devToolsExternalDependenciesReadPermission,
+ )
+ ) {
+ if (
+ user?.identity.ownershipEntityRefs.includes(
+ 'group:default/backstage-admins',
+ )
+ ) {
+ return { result: AuthorizeResult.ALLOW };
+ }
+ return { result: AuthorizeResult.DENY };
+ }
+
+ return { result: AuthorizeResult.ALLOW };
+ }
+}
+```
+
+To use this policy you'll need to make sure to add the `@backstage/plugin-devtools-common` package to your backend you can do that by running this command:
+
+```sh
+# From your Backstage root directory
+yarn add --cwd packages/backend @backstage/plugin-devtools-common
+```
+
+You'll also need to add these imports:
+
+```ts
+import {
+ devToolsAdministerPermission,
+ devToolsConfigReadPermission,
+ devToolsExternalDependenciesReadPermission,
+ devToolsInfoReadPermission,
+} from '@backstage/plugin-devtools-common';
+```
+
+**Note:** The group "group:default/backstage-admins" is simply an example and does not exist. You can point this to any group you have in your catalog instead.
+
+### Customizing with Permissions
+
+If you followed the [Customizing](#customizing) documentation and want to use permission there this is what your `CustomDevToolsPage.tsx` would look like:
+
+```tsx
+import {
+ ConfigContent,
+ ExternalDependenciesContent,
+ InfoContent,
+} from '@backstage/plugin-devtools';
+import { DevToolsLayout } from '@backstage/plugin-devtools';
+import {
+ devToolsConfigReadPermission,
+ devToolsExternalDependenciesReadPermission,
+ devToolsInfoReadPermission,
+} from '@backstage/plugin-devtools-common';
+import { RequirePermission } from '@backstage/plugin-permission-react';
+import React from 'react';
+
+const DevToolsPage = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const customDevToolsPage = ;
+```
+
+## Configuration
+
+The following sections outline the configuration for the DevTools plugin
+
+### External Dependencies Configuration
+
+If you decide to use the External Dependencies tab then you'll need to setup the configuration for it in your `app-config.yaml`, if there is no config setup then the tab will be empty. Here's an example:
+
+```yaml
+devTools:
+ externalDependencies:
+ endpoints:
+ - name: 'Google'
+ type: 'fetch'
+ target: 'https://google.ca'
+ - name: 'Google Public DNS'
+ type: 'ping'
+ target: '8.8.8.8'
+```
+
+Configuration details:
+
+- `endpoints` is an array
+- `name` is the friendly name for your endpoint
+- `type` can be either `ping` or `fetch` and will perform the respective action on the `target`
+- `target` is either a URL or server that you want to trigger a `type` action on
diff --git a/plugins/devtools/api-report.md b/plugins/devtools/api-report.md
new file mode 100644
index 0000000000..c9cfbf77e5
--- /dev/null
+++ b/plugins/devtools/api-report.md
@@ -0,0 +1,57 @@
+## API Report File for "@backstage/plugin-devtools"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+///
+
+import { BackstagePlugin } from '@backstage/core-plugin-api';
+import { default as default_2 } from 'react';
+import { RouteRef } from '@backstage/core-plugin-api';
+import { TabProps } from '@material-ui/core';
+
+// @public (undocumented)
+export const ConfigContent: () => JSX.Element;
+
+// @public
+export const DevToolsLayout: {
+ ({ children }: DevToolsLayoutProps): JSX.Element;
+ Route: (props: SubRoute) => null;
+};
+
+// @public (undocumented)
+export type DevToolsLayoutProps = {
+ children?: default_2.ReactNode;
+};
+
+// @public (undocumented)
+export const DevToolsPage: () => JSX.Element;
+
+// @public (undocumented)
+export const devToolsPlugin: BackstagePlugin<
+ {
+ root: RouteRef;
+ },
+ {},
+ {}
+>;
+
+// @public (undocumented)
+export const ExternalDependenciesContent: () => JSX.Element;
+
+// @public (undocumented)
+export const InfoContent: () => JSX.Element;
+
+// @public (undocumented)
+export type SubRoute = {
+ path: string;
+ title: string;
+ children: JSX.Element;
+ tabProps?: TabProps<
+ default_2.ElementType,
+ {
+ component?: default_2.ElementType;
+ }
+ >;
+};
+```
diff --git a/plugins/devtools/dev/index.tsx b/plugins/devtools/dev/index.tsx
new file mode 100644
index 0000000000..00c966a801
--- /dev/null
+++ b/plugins/devtools/dev/index.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+import { createDevApp } from '@backstage/dev-utils';
+import { devToolsPlugin, DevToolsPage } from '../src/plugin';
+
+createDevApp()
+ .registerPlugin(devToolsPlugin)
+ .addPage({
+ element: ,
+ title: 'Root Page',
+ path: '/devtools',
+ })
+ .render();
diff --git a/plugins/devtools/docs/devtools-config-tab.png b/plugins/devtools/docs/devtools-config-tab.png
new file mode 100644
index 0000000000..b5c22f691b
Binary files /dev/null and b/plugins/devtools/docs/devtools-config-tab.png differ
diff --git a/plugins/devtools/docs/devtools-external-dependencies.png b/plugins/devtools/docs/devtools-external-dependencies.png
new file mode 100644
index 0000000000..9356d83ef5
Binary files /dev/null and b/plugins/devtools/docs/devtools-external-dependencies.png differ
diff --git a/plugins/devtools/docs/devtools-info-tab.png b/plugins/devtools/docs/devtools-info-tab.png
new file mode 100644
index 0000000000..739045a77f
Binary files /dev/null and b/plugins/devtools/docs/devtools-info-tab.png differ
diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json
new file mode 100644
index 0000000000..d5bd0c567a
--- /dev/null
+++ b/plugins/devtools/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@backstage/plugin-devtools",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "frontend-plugin"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/devtools"
+ },
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "dependencies": {
+ "@backstage/core-components": "workspace:^",
+ "@backstage/core-plugin-api": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "@backstage/plugin-devtools-common": "workspace:^",
+ "@backstage/plugin-permission-react": "workspace:^",
+ "@backstage/theme": "workspace:^",
+ "@backstage/types": "workspace:^",
+ "@material-ui/core": "^4.9.13",
+ "@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "^4.0.0-alpha.57",
+ "react-json-view": "^1.21.3",
+ "react-use": "^17.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.13.1 || ^17.0.0",
+ "react": "^16.13.1 || ^17.0.0",
+ "react-router-dom": "6.0.0-beta.0 || ^6.3.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^",
+ "@backstage/core-app-api": "workspace:^",
+ "@backstage/dev-utils": "workspace:^",
+ "@backstage/test-utils": "workspace:^",
+ "@testing-library/jest-dom": "^5.10.1",
+ "@testing-library/react": "^12.1.3",
+ "@testing-library/user-event": "^14.0.0",
+ "@types/node": "*",
+ "cross-fetch": "^3.1.5",
+ "msw": "^0.47.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/devtools/src/api/DevToolsApi.ts b/plugins/devtools/src/api/DevToolsApi.ts
new file mode 100644
index 0000000000..ff58a4ea16
--- /dev/null
+++ b/plugins/devtools/src/api/DevToolsApi.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createApiRef } from '@backstage/core-plugin-api';
+import {
+ ConfigInfo,
+ DevToolsInfo,
+ ExternalDependency,
+} from '@backstage/plugin-devtools-common';
+
+export const devToolsApiRef = createApiRef({
+ id: 'plugin.devtools.service',
+});
+
+export interface DevToolsApi {
+ getConfig(): Promise;
+ getExternalDependencies(): Promise;
+ getInfo(): Promise;
+}
diff --git a/plugins/devtools/src/api/DevToolsClient.ts b/plugins/devtools/src/api/DevToolsClient.ts
new file mode 100644
index 0000000000..0be8294b3f
--- /dev/null
+++ b/plugins/devtools/src/api/DevToolsClient.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
+import {
+ ConfigInfo,
+ DevToolsInfo,
+ ExternalDependency,
+} from '@backstage/plugin-devtools-common';
+import { ResponseError } from '@backstage/errors';
+import { DevToolsApi } from './DevToolsApi';
+
+export class DevToolsClient implements DevToolsApi {
+ private readonly discoveryApi: DiscoveryApi;
+ private readonly identityApi: IdentityApi;
+
+ public constructor(options: {
+ discoveryApi: DiscoveryApi;
+ identityApi: IdentityApi;
+ }) {
+ this.discoveryApi = options.discoveryApi;
+ this.identityApi = options.identityApi;
+ }
+
+ public async getConfig(): Promise {
+ const urlSegment = 'config';
+
+ const configInfo = await this.get(urlSegment);
+ return configInfo;
+ }
+
+ public async getExternalDependencies(): Promise<
+ ExternalDependency[] | undefined
+ > {
+ const urlSegment = 'external-dependencies';
+
+ const externalDependencies = await this.get<
+ ExternalDependency[] | undefined
+ >(urlSegment);
+ return externalDependencies;
+ }
+
+ public async getInfo(): Promise {
+ const urlSegment = 'info';
+
+ const info = await this.get(urlSegment);
+ return info;
+ }
+
+ private async get(path: string): Promise {
+ const baseUrl = `${await this.discoveryApi.getBaseUrl('devtools')}/`;
+ const url = new URL(path, baseUrl);
+
+ const { token } = await this.identityApi.getCredentials();
+ const response = await fetch(url.toString(), {
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
+ });
+
+ if (!response.ok) {
+ throw await ResponseError.fromResponse(response);
+ }
+
+ return response.json() as Promise;
+ }
+}
diff --git a/plugins/devtools/src/api/index.ts b/plugins/devtools/src/api/index.ts
new file mode 100644
index 0000000000..6bea0a07aa
--- /dev/null
+++ b/plugins/devtools/src/api/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './DevToolsApi';
+export * from './DevToolsClient';
diff --git a/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx b/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx
new file mode 100644
index 0000000000..66129b5dae
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Progress, WarningPanel } from '@backstage/core-components';
+import {
+ Box,
+ createStyles,
+ makeStyles,
+ Paper,
+ Theme,
+ Typography,
+ useTheme,
+} from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import React from 'react';
+import ReactJson from 'react-json-view';
+import { useConfig } from '../../../hooks';
+import { ConfigError } from '@backstage/plugin-devtools-common';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ warningStyle: {
+ paddingBottom: theme.spacing(2),
+ },
+ paperStyle: {
+ padding: theme.spacing(2),
+ },
+ }),
+);
+
+export const WarningContent = ({ error }: { error: ConfigError }) => {
+ if (!error.messages) {
+ return {error.message};
+ }
+
+ const messages = error.messages as string[];
+
+ return (
+
+ {messages.map(message => (
+ {message}
+ ))}
+
+ );
+};
+
+/** @public */
+export const ConfigContent = () => {
+ const classes = useStyles();
+ const theme = useTheme();
+ const { configInfo, loading, error } = useConfig();
+
+ if (loading) {
+ return ;
+ } else if (error) {
+ return {error.message};
+ }
+
+ if (!configInfo) {
+ return Unable to load config data;
+ }
+
+ return (
+
+ {configInfo && configInfo.error && (
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
diff --git a/plugins/devtools/src/components/Content/ConfigContent/index.ts b/plugins/devtools/src/components/Content/ConfigContent/index.ts
new file mode 100644
index 0000000000..b1cc671de8
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ConfigContent/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { ConfigContent } from './ConfigContent';
diff --git a/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx b/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx
new file mode 100644
index 0000000000..a5dd825fe5
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ Progress,
+ StatusError,
+ StatusOK,
+ StatusWarning,
+ Table,
+ TableColumn,
+} from '@backstage/core-components';
+import { ExternalDependency } from '@backstage/plugin-devtools-common';
+import {
+ Box,
+ createStyles,
+ Grid,
+ makeStyles,
+ Paper,
+ Theme,
+ Typography,
+} from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import React from 'react';
+import { useExternalDependencies } from '../../../hooks';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ paperStyle: {
+ padding: theme.spacing(2),
+ },
+ }),
+);
+
+export const getExternalDependencyStatus = (
+ result: Partial | undefined,
+) => {
+ switch (result?.status) {
+ case 'Healthy':
+ return (
+
+ {result.status}
+
+ );
+ case 'Unhealthy':
+ return (
+
+ {`${result.status}`}
+
+ );
+ case undefined:
+ default:
+ return (
+
+ Unknown
+
+ );
+ }
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Name',
+ width: 'auto',
+ field: 'name',
+ },
+ {
+ title: 'Target',
+ width: 'auto',
+ field: 'target',
+ },
+ {
+ title: 'Type',
+ width: 'auto',
+ field: 'type',
+ },
+ {
+ title: 'Status',
+ width: 'auto',
+ render: (row: Partial) => (
+
+
+
+ {getExternalDependencyStatus(row)}
+
+
+ {row.error && {row.error}}
+
+ ),
+ },
+];
+
+/** @public */
+export const ExternalDependenciesContent = () => {
+ const classes = useStyles();
+ const { externalDependencies, loading, error } = useExternalDependencies();
+
+ if (loading) {
+ return ;
+ } else if (error) {
+ return {error.message};
+ }
+
+ if (!externalDependencies || externalDependencies.length === 0) {
+ return (
+
+
+ No external dependencies found
+
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/plugins/devtools/src/components/Content/ExternalDependenciesContent/index.ts b/plugins/devtools/src/components/Content/ExternalDependenciesContent/index.ts
new file mode 100644
index 0000000000..bf747f3065
--- /dev/null
+++ b/plugins/devtools/src/components/Content/ExternalDependenciesContent/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { ExternalDependenciesContent } from './ExternalDependenciesContent';
diff --git a/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx b/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx
new file mode 100644
index 0000000000..32f35600f2
--- /dev/null
+++ b/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { SvgIcon, SvgIconProps } from '@material-ui/core';
+
+import React from 'react';
+
+export const BackstageLogoIcon = (props: SvgIconProps) => (
+
+
+
+);
diff --git a/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx b/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx
new file mode 100644
index 0000000000..7a0ebb3e06
--- /dev/null
+++ b/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Progress } from '@backstage/core-components';
+import {
+ Avatar,
+ Box,
+ createStyles,
+ Divider,
+ List,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ makeStyles,
+ Paper,
+ Theme,
+} from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import React from 'react';
+import { useInfo } from '../../../hooks';
+import { InfoDependenciesTable } from './InfoDependenciesTable';
+import DescriptionIcon from '@material-ui/icons/Description';
+import DeveloperBoardIcon from '@material-ui/icons/DeveloperBoard';
+import { BackstageLogoIcon } from './BackstageLogoIcon';
+import FileCopyIcon from '@material-ui/icons/FileCopy';
+import { DevToolsInfo } from '@backstage/plugin-devtools-common';
+
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ paperStyle: {
+ marginBottom: theme.spacing(2),
+ },
+ flexContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ padding: 0,
+ },
+ copyButton: {
+ float: 'left',
+ margin: theme.spacing(2),
+ },
+ }),
+);
+
+const copyToClipboard = ({ about }: { about: DevToolsInfo | undefined }) => {
+ if (about) {
+ let formatted = `OS: ${about.operatingSystem}\nnode: ${about.nodeJsVersion}\nbackstage: ${about.backstageVersion}\nDependencies:\n`;
+ const deps = about.dependencies;
+ for (const key in deps) {
+ if (Object.prototype.hasOwnProperty.call(deps, key)) {
+ formatted = `${formatted} ${deps[key].name}: ${deps[key].versions}\n`;
+ }
+ }
+ window.navigator.clipboard.writeText(formatted);
+ }
+};
+
+/** @public */
+export const InfoContent = () => {
+ const classes = useStyles();
+ const { about, loading, error } = useInfo();
+
+ if (loading) {
+ return ;
+ } else if (error) {
+ return {error.message};
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ copyToClipboard({ about });
+ }}
+ className={classes.copyButton}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/devtools/src/components/Content/InfoContent/InfoDependenciesTable.tsx b/plugins/devtools/src/components/Content/InfoContent/InfoDependenciesTable.tsx
new file mode 100644
index 0000000000..e1ab59f279
--- /dev/null
+++ b/plugins/devtools/src/components/Content/InfoContent/InfoDependenciesTable.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Table, TableColumn } from '@backstage/core-components';
+import { PackageDependency } from '@backstage/plugin-devtools-common';
+
+import React from 'react';
+
+const columns: TableColumn[] = [
+ {
+ title: 'Name',
+ width: 'auto',
+ field: 'name',
+ defaultSort: 'asc',
+ },
+ {
+ title: 'Versions',
+ width: 'auto',
+ field: 'versions',
+ },
+];
+
+export const InfoDependenciesTable = ({
+ infoDependencies,
+}: {
+ infoDependencies: PackageDependency[] | undefined;
+}) => {
+ return (
+
+ );
+};
diff --git a/plugins/devtools/src/components/Content/InfoContent/index.ts b/plugins/devtools/src/components/Content/InfoContent/index.ts
new file mode 100644
index 0000000000..6d5bad53f8
--- /dev/null
+++ b/plugins/devtools/src/components/Content/InfoContent/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { InfoContent } from './InfoContent';
diff --git a/plugins/devtools/src/components/Content/index.ts b/plugins/devtools/src/components/Content/index.ts
new file mode 100644
index 0000000000..f5bd64f7c0
--- /dev/null
+++ b/plugins/devtools/src/components/Content/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './ConfigContent';
+export * from './InfoContent';
+export * from './ExternalDependenciesContent';
diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
new file mode 100644
index 0000000000..a5e4f2439b
--- /dev/null
+++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ devToolsConfigReadPermission,
+ devToolsInfoReadPermission,
+} from '@backstage/plugin-devtools-common';
+
+import { ConfigContent } from '../Content/ConfigContent';
+import { DevToolsLayout } from '../DevToolsLayout';
+import { InfoContent } from '../Content/InfoContent';
+import React from 'react';
+import { RequirePermission } from '@backstage/plugin-permission-react';
+
+/** @public */
+export const DefaultDevToolsPage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/index.ts b/plugins/devtools/src/components/DefaultDevToolsPage/index.ts
new file mode 100644
index 0000000000..133dcf6e3a
--- /dev/null
+++ b/plugins/devtools/src/components/DefaultDevToolsPage/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DefaultDevToolsPage } from './DefaultDevToolsPage';
diff --git a/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx b/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx
new file mode 100644
index 0000000000..12cd3f7666
--- /dev/null
+++ b/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Header, Page, RoutedTabs } from '@backstage/core-components';
+import {
+ attachComponentData,
+ useElementFilter,
+} from '@backstage/core-plugin-api';
+import { TabProps } from '@material-ui/core';
+import { default as React } from 'react';
+
+/** @public */
+export type SubRoute = {
+ path: string;
+ title: string;
+ children: JSX.Element;
+ tabProps?: TabProps;
+};
+
+const dataKey = 'plugin.devtools.devtoolsLayoutRoute';
+
+const Route: (props: SubRoute) => null = () => null;
+attachComponentData(Route, dataKey, true);
+
+// This causes all mount points that are discovered within this route to use the path of the route itself
+attachComponentData(Route, 'core.gatherMountPoints', true);
+
+/** @public */
+export type DevToolsLayoutProps = {
+ children?: React.ReactNode;
+};
+
+/**
+ * DevTools is a compound component, which allows you to define a custom layout
+ *
+ * @example
+ * ```jsx
+ *
+ *
+ *
This is rendered under /example/anything-here route
+ *
+ *
+ * ```
+ * @public
+ */
+export const DevToolsLayout = ({ children }: DevToolsLayoutProps) => {
+ const routes = useElementFilter(children, elements =>
+ elements
+ .selectByComponentData({
+ key: dataKey,
+ withStrictError:
+ 'Child of DevToolsLayout must be an DevToolsLayout.Route',
+ })
+ .getElements()
+ .map(child => child.props),
+ );
+
+ return (
+
+
+
+
+ );
+};
+
+DevToolsLayout.Route = Route;
diff --git a/plugins/devtools/src/components/DevToolsLayout/index.tsx b/plugins/devtools/src/components/DevToolsLayout/index.tsx
new file mode 100644
index 0000000000..0bef371b05
--- /dev/null
+++ b/plugins/devtools/src/components/DevToolsLayout/index.tsx
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export type { DevToolsLayoutProps, SubRoute } from './DevToolsLayout';
+export { DevToolsLayout } from './DevToolsLayout';
diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx
new file mode 100644
index 0000000000..880efed68c
--- /dev/null
+++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { useOutlet } from 'react-router-dom';
+import { DefaultDevToolsPage } from '../DefaultDevToolsPage';
+
+export const DevToolsPage = () => {
+ const outlet = useOutlet();
+
+ return <>{outlet || }>;
+};
diff --git a/plugins/devtools/src/components/DevToolsPage/index.ts b/plugins/devtools/src/components/DevToolsPage/index.ts
new file mode 100644
index 0000000000..88d8bc06fa
--- /dev/null
+++ b/plugins/devtools/src/components/DevToolsPage/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { DevToolsPage } from './DevToolsPage';
diff --git a/plugins/devtools/src/components/index.ts b/plugins/devtools/src/components/index.ts
new file mode 100644
index 0000000000..9938866bcc
--- /dev/null
+++ b/plugins/devtools/src/components/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './Content';
+export * from './DevToolsLayout';
diff --git a/plugins/devtools/src/hooks/index.ts b/plugins/devtools/src/hooks/index.ts
new file mode 100644
index 0000000000..bb2e83d06f
--- /dev/null
+++ b/plugins/devtools/src/hooks/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { useConfig } from './useConfig';
+export { useExternalDependencies } from './useExternalDependencies';
+export { useInfo } from './useInfo';
diff --git a/plugins/devtools/src/hooks/useConfig.ts b/plugins/devtools/src/hooks/useConfig.ts
new file mode 100644
index 0000000000..6703e7ea59
--- /dev/null
+++ b/plugins/devtools/src/hooks/useConfig.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { devToolsApiRef } from '../api';
+import { useApi } from '@backstage/core-plugin-api';
+import useAsync from 'react-use/lib/useAsync';
+import { ConfigInfo } from '@backstage/plugin-devtools-common';
+
+export function useConfig(): {
+ configInfo?: ConfigInfo;
+ loading: boolean;
+ error?: Error;
+} {
+ const api = useApi(devToolsApiRef);
+ const { value, loading, error } = useAsync(() => {
+ return api.getConfig();
+ }, [api]);
+
+ return {
+ configInfo: value,
+ loading,
+ error,
+ };
+}
diff --git a/plugins/devtools/src/hooks/useExternalDependencies.ts b/plugins/devtools/src/hooks/useExternalDependencies.ts
new file mode 100644
index 0000000000..df2f3e193b
--- /dev/null
+++ b/plugins/devtools/src/hooks/useExternalDependencies.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { devToolsApiRef } from '../api';
+import { useApi } from '@backstage/core-plugin-api';
+import useAsync from 'react-use/lib/useAsync';
+import { ExternalDependency } from '@backstage/plugin-devtools-common';
+
+export function useExternalDependencies(): {
+ externalDependencies?: ExternalDependency[];
+ loading: boolean;
+ error?: Error;
+} {
+ const api = useApi(devToolsApiRef);
+ const { value, loading, error } = useAsync(() => {
+ return api.getExternalDependencies();
+ }, [api]);
+
+ return {
+ externalDependencies: value,
+ loading,
+ error,
+ };
+}
diff --git a/plugins/devtools/src/hooks/useInfo.ts b/plugins/devtools/src/hooks/useInfo.ts
new file mode 100644
index 0000000000..ad700cb3fe
--- /dev/null
+++ b/plugins/devtools/src/hooks/useInfo.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { devToolsApiRef } from '../api';
+import { useApi } from '@backstage/core-plugin-api';
+import useAsync from 'react-use/lib/useAsync';
+import { DevToolsInfo } from '@backstage/plugin-devtools-common';
+
+export function useInfo(): {
+ about?: DevToolsInfo;
+ loading: boolean;
+ error?: Error;
+} {
+ const api = useApi(devToolsApiRef);
+ const { value, loading, error } = useAsync(() => {
+ return api.getInfo();
+ }, [api]);
+
+ return {
+ about: value,
+ loading,
+ error,
+ };
+}
diff --git a/plugins/devtools/src/index.ts b/plugins/devtools/src/index.ts
new file mode 100644
index 0000000000..1077a2518c
--- /dev/null
+++ b/plugins/devtools/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Backstage DevTools frontend plugin with a tab that shows package dependencies and another tab that shows your current configuration.
+ *
+ * @packageDocumentation
+ */
+
+export { devToolsPlugin, DevToolsPage } from './plugin';
+export * from './components';
diff --git a/plugins/devtools/src/plugin.test.ts b/plugins/devtools/src/plugin.test.ts
new file mode 100644
index 0000000000..62f4ae5a61
--- /dev/null
+++ b/plugins/devtools/src/plugin.test.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { devToolsPlugin } from './plugin';
+
+describe('devtools', () => {
+ it('should export plugin', () => {
+ expect(devToolsPlugin).toBeDefined();
+ });
+});
diff --git a/plugins/devtools/src/plugin.ts b/plugins/devtools/src/plugin.ts
new file mode 100644
index 0000000000..df2aca3447
--- /dev/null
+++ b/plugins/devtools/src/plugin.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ createApiFactory,
+ createPlugin,
+ createRoutableExtension,
+ discoveryApiRef,
+ identityApiRef,
+} from '@backstage/core-plugin-api';
+import { devToolsApiRef, DevToolsClient } from './api';
+
+import { rootRouteRef } from './routes';
+
+/** @public */
+export const devToolsPlugin = createPlugin({
+ id: 'devtools',
+ apis: [
+ createApiFactory({
+ api: devToolsApiRef,
+ deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
+ factory: ({ discoveryApi, identityApi }) =>
+ new DevToolsClient({ discoveryApi, identityApi }),
+ }),
+ ],
+ routes: {
+ root: rootRouteRef,
+ },
+});
+
+/** @public */
+export const DevToolsPage = devToolsPlugin.provide(
+ createRoutableExtension({
+ name: 'DevToolsPage',
+ component: () =>
+ import('./components/DevToolsPage').then(m => m.DevToolsPage),
+ mountPoint: rootRouteRef,
+ }),
+);
diff --git a/plugins/devtools/src/routes.ts b/plugins/devtools/src/routes.ts
new file mode 100644
index 0000000000..7825f928be
--- /dev/null
+++ b/plugins/devtools/src/routes.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createRouteRef } from '@backstage/core-plugin-api';
+
+export const rootRouteRef = createRouteRef({
+ id: 'devtools',
+});
diff --git a/plugins/devtools/src/setupTests.ts b/plugins/devtools/src/setupTests.ts
new file mode 100644
index 0000000000..7a459ed24e
--- /dev/null
+++ b/plugins/devtools/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '@testing-library/jest-dom';
diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md
index 5ede228812..fe0d954772 100644
--- a/plugins/dynatrace/CHANGELOG.md
+++ b/plugins/dynatrace/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-dynatrace
+## 5.0.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 5.0.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 5.0.0-next.1
### Patch Changes
diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json
index b7c8244753..1546562169 100644
--- a/plugins/dynatrace/package.json
+++ b/plugins/dynatrace/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-dynatrace",
- "version": "5.0.0-next.1",
+ "version": "5.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/entity-feedback-backend/CHANGELOG.md b/plugins/entity-feedback-backend/CHANGELOG.md
index 2ee2ffb799..82fd67a96c 100644
--- a/plugins/entity-feedback-backend/CHANGELOG.md
+++ b/plugins/entity-feedback-backend/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-entity-feedback-backend
+## 0.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/plugin-entity-feedback-common@0.1.1
+
## 0.1.3-next.1
### Patch Changes
diff --git a/plugins/entity-feedback-backend/package.json b/plugins/entity-feedback-backend/package.json
index bfda97de8f..ec5a379830 100644
--- a/plugins/entity-feedback-backend/package.json
+++ b/plugins/entity-feedback-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-entity-feedback-backend",
- "version": "0.1.3-next.1",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/entity-feedback/CHANGELOG.md b/plugins/entity-feedback/CHANGELOG.md
index 3f4575ca8a..312f6c1fd4 100644
--- a/plugins/entity-feedback/CHANGELOG.md
+++ b/plugins/entity-feedback/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-entity-feedback
+## 0.2.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-entity-feedback-common@0.1.1
+
+## 0.2.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.1-next.1
### Patch Changes
diff --git a/plugins/entity-feedback/package.json b/plugins/entity-feedback/package.json
index 0c15a0f3c8..90a9f6e4ab 100644
--- a/plugins/entity-feedback/package.json
+++ b/plugins/entity-feedback/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-entity-feedback",
- "version": "0.2.1-next.1",
+ "version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/entity-validation/CHANGELOG.md b/plugins/entity-validation/CHANGELOG.md
index 9b63d46c5b..648e6fc107 100644
--- a/plugins/entity-validation/CHANGELOG.md
+++ b/plugins/entity-validation/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-entity-validation
+## 0.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-catalog-common@1.0.13
+
+## 0.1.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.3-next.1
### Patch Changes
diff --git a/plugins/entity-validation/package.json b/plugins/entity-validation/package.json
index 674c5c6a56..0d375ccca9 100644
--- a/plugins/entity-validation/package.json
+++ b/plugins/entity-validation/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-entity-validation",
- "version": "0.1.3-next.1",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
index 9b076afbd3..4b55725420 100644
--- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md
+++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/plugin-events-backend-module-aws-sqs
+## 0.2.0
+
+### Minor Changes
+
+- 2c5661f3899: Allow endpoint configuration for sqs, enabling use of localstack for testing.
+
+### Patch Changes
+
+- 3659c71c5d9: Standardize `@aws-sdk` v3 versions
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-tasks@0.5.2
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/types@1.0.2
+ - @backstage/plugin-events-node@0.2.6
+
+## 0.2.0-next.2
+
+### Minor Changes
+
+- 2c5661f3899: Allow endpoint configuration for sqs, enabling use of localstack for testing.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.0.7
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json
index 0125d5e5b4..e89ea4413b 100644
--- a/plugins/events-backend-module-aws-sqs/package.json
+++ b/plugins/events-backend-module-aws-sqs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-aws-sqs",
- "version": "0.1.7-next.1",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -35,7 +35,7 @@
"postpack": "backstage-cli package postpack"
},
"dependencies": {
- "@aws-sdk/client-sqs": "^3.208.0",
+ "@aws-sdk/client-sqs": "^3.310.0",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
@@ -46,7 +46,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@aws-sdk/types": "^3.208.0",
+ "@aws-sdk/types": "^3.310.0",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts
index e649e2ee83..4f4e6ea55d 100644
--- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts
+++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts
@@ -68,7 +68,11 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher {
VisibilityTimeout: config.visibilityTimeout?.as('seconds'),
WaitTimeSeconds: config.pollingWaitTime.as('seconds'),
};
- this.sqs = new SQSClient({ region: config.region });
+
+ this.sqs = new SQSClient({
+ region: config.region,
+ endpoint: config.endpoint,
+ });
this.queueUrl = config.queueUrl;
this.taskTimeoutSeconds = config.timeout.as('seconds');
diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts
index 1fa9df4ce3..6d7a08d7a7 100644
--- a/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts
+++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.test.ts
@@ -79,6 +79,7 @@ describe('readConfig', () => {
url: 'https://fake1.queue.url',
visibilityTimeout: { minutes: 5 },
waitTime: { seconds: 10 },
+ endpoint: 'https://fake.endpoint.url',
},
timeout: { minutes: 5 },
waitTimeAfterEmptyReceive: { seconds: 30 },
@@ -97,6 +98,7 @@ describe('readConfig', () => {
expect(publisherConfigs[0].topic).toEqual('fake1');
expect(publisherConfigs[0].region).toEqual('eu-west-1');
expect(publisherConfigs[0].queueUrl).toEqual('https://fake1.queue.url');
+ expect(publisherConfigs[0].endpoint).toEqual('https://fake.endpoint.url');
expect(publisherConfigs[0].pollingWaitTime.as('seconds')).toBe(10);
expect(publisherConfigs[0].timeout.as('seconds')).toBe(300);
expect(publisherConfigs[0].waitTimeAfterEmptyReceive.as('seconds')).toBe(
diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts
index c6b74ea8d3..eeb650ff6c 100644
--- a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts
+++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts
@@ -31,6 +31,7 @@ export interface AwsSqsEventSourceConfig {
topic: string;
visibilityTimeout?: Duration;
waitTimeAfterEmptyReceive: Duration;
+ endpoint?: string;
}
// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks
@@ -74,6 +75,7 @@ export function readConfig(config: Config): AwsSqsEventSourceConfig[] {
}
const queueUrl = topicConfig.getString('queue.url');
const region = topicConfig.getString('queue.region');
+ const endpoint = topicConfig.getOptionalString('queue.endpoint');
const visibilityTimeout = readOptionalDuration(
topicConfig,
'queue.visibilityTimeout',
@@ -106,6 +108,7 @@ export function readConfig(config: Config): AwsSqsEventSourceConfig[] {
return {
pollingWaitTime,
queueUrl,
+ endpoint,
region,
timeout,
topic,
diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md
index b89363b7c3..0e096953a4 100644
--- a/plugins/events-backend-module-azure/CHANGELOG.md
+++ b/plugins/events-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-azure
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json
index 3667e73ebd..099da38f40 100644
--- a/plugins/events-backend-module-azure/package.json
+++ b/plugins/events-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-azure",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
index ea70f7686e..dc861fc027 100644
--- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-bitbucket-cloud
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json
index ea04a4faaa..b30f50265b 100644
--- a/plugins/events-backend-module-bitbucket-cloud/package.json
+++ b/plugins/events-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-bitbucket-cloud",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md
index 411d9ffff8..e0e19a0899 100644
--- a/plugins/events-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/events-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-gerrit
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json
index 64fe31d31f..e18616d98d 100644
--- a/plugins/events-backend-module-gerrit/package.json
+++ b/plugins/events-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gerrit",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md
index 0e2b2ae8de..7f88bb5d13 100644
--- a/plugins/events-backend-module-github/CHANGELOG.md
+++ b/plugins/events-backend-module-github/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-events-backend-module-github
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json
index 74b74469ca..0117792b8d 100644
--- a/plugins/events-backend-module-github/package.json
+++ b/plugins/events-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-github",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md
index d8d48674c8..f6827b90f4 100644
--- a/plugins/events-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/events-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-events-backend-module-gitlab
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json
index 07102c0b6f..fc33203460 100644
--- a/plugins/events-backend-module-gitlab/package.json
+++ b/plugins/events-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gitlab",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md
index 3d665a44ce..14d73356e0 100644
--- a/plugins/events-backend-test-utils/CHANGELOG.md
+++ b/plugins/events-backend-test-utils/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-events-backend-test-utils
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.2.6
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json
index 3d46ea9210..d3340d5baf 100644
--- a/plugins/events-backend-test-utils/package.json
+++ b/plugins/events-backend-test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-events-backend-test-utils",
"description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node",
- "version": "0.1.7-next.1",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md
index 929a003cf9..25b0561c74 100644
--- a/plugins/events-backend/CHANGELOG.md
+++ b/plugins/events-backend/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-events-backend
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/plugin-events-node@0.2.6
+
## 0.2.6-next.1
### Patch Changes
diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json
index fdaee7d4a8..8d75caf02b 100644
--- a/plugins/events-backend/package.json
+++ b/plugins/events-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend",
- "version": "0.2.6-next.1",
+ "version": "0.2.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md
index c44e1e3999..a8ff8ed2e5 100644
--- a/plugins/events-node/CHANGELOG.md
+++ b/plugins/events-node/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-events-node
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.5.2
+
## 0.2.6-next.1
### Patch Changes
diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json
index 7a368206bf..497d668cde 100644
--- a/plugins/events-node/package.json
+++ b/plugins/events-node/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-events-node",
"description": "The plugin-events-node module for @backstage/plugin-events-backend",
- "version": "0.2.6-next.1",
+ "version": "0.2.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md
index 3fb578f05a..b71a26219e 100644
--- a/plugins/example-todo-list-backend/CHANGELOG.md
+++ b/plugins/example-todo-list-backend/CHANGELOG.md
@@ -1,5 +1,16 @@
# @internal/plugin-todo-list-backend
+## 1.0.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/backend-plugin-api@0.5.2
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+
## 1.0.13-next.1
### Patch Changes
diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json
index 0b635586b7..bec7f06a4c 100644
--- a/plugins/example-todo-list-backend/package.json
+++ b/plugins/example-todo-list-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/plugin-todo-list-backend",
- "version": "1.0.13-next.1",
+ "version": "1.0.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md
index 87cc779cde..fad5737566 100644
--- a/plugins/example-todo-list/CHANGELOG.md
+++ b/plugins/example-todo-list/CHANGELOG.md
@@ -1,5 +1,23 @@
# @internal/plugin-todo-list
+## 1.0.13
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 1.0.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 1.0.13-next.0
### Patch Changes
diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json
index fcd193bd5f..5dff8b4a15 100644
--- a/plugins/example-todo-list/package.json
+++ b/plugins/example-todo-list/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/plugin-todo-list",
- "version": "1.0.13-next.0",
+ "version": "1.0.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/explore-backend/CHANGELOG.md b/plugins/explore-backend/CHANGELOG.md
index d178b091b1..a1763f7a0c 100644
--- a/plugins/explore-backend/CHANGELOG.md
+++ b/plugins/explore-backend/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-explore-backend
+## 0.0.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-search-backend-module-explore@0.1.1
+ - @backstage/config@1.0.7
+ - @backstage/plugin-explore-common@0.0.1
+ - @backstage/plugin-search-common@1.2.3
+
## 0.0.7-next.1
### Patch Changes
diff --git a/plugins/explore-backend/package.json b/plugins/explore-backend/package.json
index 7b21e885c1..538468a118 100644
--- a/plugins/explore-backend/package.json
+++ b/plugins/explore-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-explore-backend",
- "version": "0.0.7-next.1",
+ "version": "0.0.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md
index 3ad9bdb917..fbfe54f16b 100644
--- a/plugins/explore/CHANGELOG.md
+++ b/plugins/explore/CHANGELOG.md
@@ -1,5 +1,38 @@
# @backstage/plugin-explore
+## 0.4.3
+
+### Patch Changes
+
+- 1996920782b: Make sure that the first support button row does not break across lines
+- 4851581deb6: Display the title of the entity on the explore card if present, otherwise stick to the name
+- a6025e25d99: Updated the example code in the "Customization" section of the README to make the imports match the components used.
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/plugin-search-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-explore-common@0.0.1
+ - @backstage/plugin-explore-react@0.0.28
+ - @backstage/plugin-search-common@1.2.3
+
+## 0.4.3-next.2
+
+### Patch Changes
+
+- 4851581deb6: Display the title of the entity on the explore card if present, otherwise stick to the name
+- a6025e25d99: Updated the example code in the "Customization" section of the README to make the imports match the components used.
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-search-react@1.6.0-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/plugin-explore-react@0.0.28
+ - @backstage/core-plugin-api@1.5.1
+
## 0.4.3-next.1
### Patch Changes
diff --git a/plugins/explore/package.json b/plugins/explore/package.json
index bbe114c1f0..b0c5bf3fd9 100644
--- a/plugins/explore/package.json
+++ b/plugins/explore/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-explore",
"description": "A Backstage plugin for building an exploration page of your software ecosystem",
- "version": "0.4.3-next.1",
+ "version": "0.4.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md
index 27c6a1b62e..4353f8e6ba 100644
--- a/plugins/firehydrant/CHANGELOG.md
+++ b/plugins/firehydrant/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-firehydrant
+## 0.2.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.2.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.2-next.1
### Patch Changes
diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json
index d5e136229c..f2eda9092c 100644
--- a/plugins/firehydrant/package.json
+++ b/plugins/firehydrant/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-firehydrant",
"description": "A Backstage plugin that integrates towards FireHydrant",
- "version": "0.2.2-next.1",
+ "version": "0.2.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md
index 0399ad0ce2..2ca2c69658 100644
--- a/plugins/fossa/CHANGELOG.md
+++ b/plugins/fossa/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-fossa
+## 0.2.50
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.50-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.50-next.1
### Patch Changes
diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json
index 985202a403..caaa497130 100644
--- a/plugins/fossa/package.json
+++ b/plugins/fossa/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-fossa",
"description": "A Backstage plugin that integrates towards FOSSA",
- "version": "0.2.50-next.1",
+ "version": "0.2.50",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md
index 1c23885154..cb1f1e451b 100644
--- a/plugins/gcalendar/CHANGELOG.md
+++ b/plugins/gcalendar/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-gcalendar
+## 0.3.14
+
+### Patch Changes
+
+- f493ccb9589: Pass user info email scope on auth refresh to resolve invalid credentials error
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.3.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.14-next.0
### Patch Changes
diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json
index 0cc77cabad..eb19dd4e6f 100644
--- a/plugins/gcalendar/package.json
+++ b/plugins/gcalendar/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gcalendar",
- "version": "0.3.14-next.0",
+ "version": "0.3.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/gcalendar/src/hooks/useSignIn.ts b/plugins/gcalendar/src/hooks/useSignIn.ts
index 351707b54e..3dbc778cf1 100644
--- a/plugins/gcalendar/src/hooks/useSignIn.ts
+++ b/plugins/gcalendar/src/hooks/useSignIn.ts
@@ -25,7 +25,10 @@ export const useSignIn = () => {
const signIn = useCallback(
async (optional = false) => {
const token = await authApi.getAccessToken(
- 'https://www.googleapis.com/auth/calendar.readonly',
+ [
+ 'https://www.googleapis.com/auth/calendar.readonly',
+ 'https://www.googleapis.com/auth/userinfo.email',
+ ],
{
optional,
instantPopup: !optional,
diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md
index 7a5ea25c0d..54265c8c59 100644
--- a/plugins/gcp-projects/CHANGELOG.md
+++ b/plugins/gcp-projects/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-gcp-projects
+## 0.3.37
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.37-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.37-next.0
### Patch Changes
diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json
index 039bcd094c..6d320cad6d 100644
--- a/plugins/gcp-projects/package.json
+++ b/plugins/gcp-projects/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-gcp-projects",
"description": "A Backstage plugin that helps you manage projects in GCP",
- "version": "0.3.37-next.0",
+ "version": "0.3.37",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md
index 976585c656..80f09319d0 100644
--- a/plugins/git-release-manager/CHANGELOG.md
+++ b/plugins/git-release-manager/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-git-release-manager
+## 0.3.31
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.31-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.31-next.1
### Patch Changes
diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json
index 7bc4df059e..76d18dc224 100644
--- a/plugins/git-release-manager/package.json
+++ b/plugins/git-release-manager/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-git-release-manager",
"description": "A Backstage plugin that helps you manage releases in git",
- "version": "0.3.31-next.1",
+ "version": "0.3.31",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md
index 09b205c9f1..199051dd04 100644
--- a/plugins/github-actions/CHANGELOG.md
+++ b/plugins/github-actions/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-github-actions
+## 0.5.18
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.5.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.5.18-next.1
### Patch Changes
diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json
index 25fac2b774..98a558cd0d 100644
--- a/plugins/github-actions/package.json
+++ b/plugins/github-actions/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-github-actions",
"description": "A Backstage plugin that integrates towards GitHub Actions",
- "version": "0.5.18-next.1",
+ "version": "0.5.18",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md
index 41475febd3..0a699bdc57 100644
--- a/plugins/github-deployments/CHANGELOG.md
+++ b/plugins/github-deployments/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-github-deployments
+## 0.1.49
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/integration-react@1.1.13
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.1.49-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/integration-react@1.1.13-next.2
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.49-next.1
### Patch Changes
diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json
index dcb4156f9d..08c7e1aa9d 100644
--- a/plugins/github-deployments/package.json
+++ b/plugins/github-deployments/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-github-deployments",
"description": "A Backstage plugin that integrates towards GitHub Deployments",
- "version": "0.1.49-next.1",
+ "version": "0.1.49",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md
index d1e2103800..6f83ae81cf 100644
--- a/plugins/github-issues/CHANGELOG.md
+++ b/plugins/github-issues/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-github-issues
+## 0.2.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json
index ed982410e9..c6685eff13 100644
--- a/plugins/github-issues/package.json
+++ b/plugins/github-issues/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-issues",
- "version": "0.2.7-next.1",
+ "version": "0.2.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md
index 7421c6e015..704dc495c1 100644
--- a/plugins/github-pull-requests-board/CHANGELOG.md
+++ b/plugins/github-pull-requests-board/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-github-pull-requests-board
+## 0.1.12
+
+### Patch Changes
+
+- cf125c36569: The `EntityTeamPullRequestsContent` and `EntityTeamPullRequestsCard` support the ability to view the labels/tags added to each PR
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/integration@1.4.5
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.12-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.12-next.1
### Patch Changes
diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json
index e805cd99ce..da3c929b43 100644
--- a/plugins/github-pull-requests-board/package.json
+++ b/plugins/github-pull-requests-board/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-github-pull-requests-board",
"description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team",
- "version": "0.1.12-next.1",
+ "version": "0.1.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md
index 99122116fb..4aed3f9ab3 100644
--- a/plugins/gitops-profiles/CHANGELOG.md
+++ b/plugins/gitops-profiles/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-gitops-profiles
+## 0.3.36
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.3.36-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.3.36-next.0
### Patch Changes
diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json
index 2bb6f145a4..e374b49335 100644
--- a/plugins/gitops-profiles/package.json
+++ b/plugins/gitops-profiles/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-gitops-profiles",
"description": "A Backstage plugin that helps you manage GitOps profiles",
- "version": "0.3.36-next.0",
+ "version": "0.3.36",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md
index 14a0149ca5..0014eebd8c 100644
--- a/plugins/gocd/CHANGELOG.md
+++ b/plugins/gocd/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-gocd
+## 0.1.24
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.1.24-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.24-next.1
### Patch Changes
diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json
index 2e41e2c217..11ee841aa2 100644
--- a/plugins/gocd/package.json
+++ b/plugins/gocd/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-gocd",
"description": "A Backstage plugin that integrates towards GoCD",
- "version": "0.1.24-next.1",
+ "version": "0.1.24",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md
index e831845d49..5f395bda29 100644
--- a/plugins/graphiql/CHANGELOG.md
+++ b/plugins/graphiql/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-graphiql
+## 0.2.50
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.2.50-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.50-next.0
### Patch Changes
diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json
index 08fc2a6ec0..d5dae8c2ec 100644
--- a/plugins/graphiql/package.json
+++ b/plugins/graphiql/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphiql",
"description": "Backstage plugin for browsing GraphQL APIs",
- "version": "0.2.50-next.0",
+ "version": "0.2.50",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md
index 898e185d5d..14f2b75eac 100644
--- a/plugins/graphql-backend/CHANGELOG.md
+++ b/plugins/graphql-backend/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-graphql-backend
+## 0.1.35
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/config@1.0.7
+ - @backstage/plugin-catalog-graphql@0.3.20
+
## 0.1.35-next.1
### Patch Changes
diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json
index 388b587040..cc8c3e3059 100644
--- a/plugins/graphql-backend/package.json
+++ b/plugins/graphql-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphql-backend",
"description": "An experimental Backstage backend plugin for GraphQL",
- "version": "0.1.35-next.1",
+ "version": "0.1.35",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/graphql-voyager/CHANGELOG.md b/plugins/graphql-voyager/CHANGELOG.md
index 77586073af..394b99f5c7 100644
--- a/plugins/graphql-voyager/CHANGELOG.md
+++ b/plugins/graphql-voyager/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-graphql-voyager
+## 0.1.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/core-components@0.13.1
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.1.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/core-plugin-api@1.5.1
+
## 0.1.3-next.0
### Patch Changes
diff --git a/plugins/graphql-voyager/package.json b/plugins/graphql-voyager/package.json
index d356dee1c5..683b664b33 100644
--- a/plugins/graphql-voyager/package.json
+++ b/plugins/graphql-voyager/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphql-voyager",
"description": "Backstage plugin for GraphQL Voyager",
- "version": "0.1.3-next.0",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md
index 868b1a6c3b..b626f5c5e6 100644
--- a/plugins/home/CHANGELOG.md
+++ b/plugins/home/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/plugin-home
+## 0.5.2
+
+### Patch Changes
+
+- acca8966465: Remove object-hash dependency
+- 957cd9b8958: Use the semantic time tag for rendering world clocks on homepage headers.
+- 0e19e7b0f3a: Bump to using the later v5 versions of `@rjsf/*`
+- 5272cfabc3b: Add missing @rjsf/core dependency
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
+## 0.5.2-next.2
+
+### Patch Changes
+
+- acca8966465: Remove object-hash dependency
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/config@1.0.7
+ - @backstage/core-plugin-api@1.5.1
+
## 0.5.2-next.1
### Patch Changes
diff --git a/plugins/home/package.json b/plugins/home/package.json
index ef9267234b..ff714b16ca 100644
--- a/plugins/home/package.json
+++ b/plugins/home/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-home",
"description": "A Backstage plugin that helps you build a home page",
- "version": "0.5.2-next.1",
+ "version": "0.5.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -48,7 +48,6 @@
"@rjsf/validator-ajv8": "5.6.0",
"@types/react": "^16.13.1 || ^17.0.0",
"lodash": "^4.17.21",
- "object-hash": "^3.0.0",
"react-grid-layout": "^1.3.4",
"react-resizable": "^3.0.4",
"react-use": "^17.2.4",
diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx
index 232fbd12c2..6af9044cbd 100644
--- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx
+++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx
@@ -46,7 +46,6 @@ import { CardConfig } from '../../extensions';
// eslint-disable-next-line new-cap
const ResponsiveGrid = WidthProvider(Responsive);
-const hash = require('object-hash');
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -336,14 +335,14 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
...widget.component.props,
...(w.settings ?? {}),
};
- const propsHash = hash(widgetProps, {});
+
return (
-
+
{editMode && (
{
/>,
);
- expect(rendered.getByText('09:10')).toBeInTheDocument();
+ expect(rendered.getByText('09:10')).toHaveAttribute('datetime', '09:10');
});
});
diff --git a/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.tsx b/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.tsx
index a45a51e0ad..394af1a27b 100644
--- a/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.tsx
+++ b/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.tsx
@@ -23,8 +23,9 @@ const timeFormat: Intl.DateTimeFormatOptions = {
};
type TimeObj = {
- time: string;
label: string;
+ value: string;
+ dateTime: string;
};
/** @public */
@@ -65,8 +66,14 @@ function getTimes(
label = 'GMT';
}
- const time = d.toLocaleTimeString(lang, options);
- clocks.push({ time, label });
+ const value = d.toLocaleTimeString(lang, options);
+ const dateTime = d.toLocaleTimeString(lang, {
+ timeZone: options.timeZone,
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false,
+ });
+ clocks.push({ label, value, dateTime });
}
return clocks;
@@ -129,9 +136,9 @@ export const HeaderWorldClock = (props: {
<>
{clocks.map(clock => (
{clock.value}}
/>
))}
>
diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md
index 0f5b044bfb..0b1c5b5696 100644
--- a/plugins/ilert/CHANGELOG.md
+++ b/plugins/ilert/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-ilert
+## 0.2.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+
+## 0.2.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json
index 17e94d54e5..396d5f6860 100644
--- a/plugins/ilert/package.json
+++ b/plugins/ilert/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-ilert",
"description": "A Backstage plugin that integrates towards iLert",
- "version": "0.2.7-next.1",
+ "version": "0.2.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md
index 8e4463f46b..6bb9cba867 100644
--- a/plugins/jenkins-backend/CHANGELOG.md
+++ b/plugins/jenkins-backend/CHANGELOG.md
@@ -1,5 +1,26 @@
# @backstage/plugin-jenkins-backend
+## 0.2.0
+
+### Minor Changes
+
+- cf95c5137c9: Updated rebuild to use Jenkins API replay build, which works for Jenkins jobs that have required parameters. Jenkins SDK could not be used for this request because it does not have support for replay.
+
+ Added link to view build in Jenkins CI/CD table action column.
+
+### Patch Changes
+
+- 670a2dd6f4e: Fix handling of slashes in branch names
+- Updated dependencies
+ - @backstage/backend-common@0.18.5
+ - @backstage/plugin-auth-node@0.2.14
+ - @backstage/catalog-client@1.4.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/config@1.0.7
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-jenkins-common@0.1.15
+ - @backstage/plugin-permission-common@0.7.5
+
## 0.1.35-next.1
### Patch Changes
diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json
index 4f58776df7..f45908c04e 100644
--- a/plugins/jenkins-backend/package.json
+++ b/plugins/jenkins-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-jenkins-backend",
"description": "A Backstage backend plugin that integrates towards Jenkins",
- "version": "0.1.35-next.1",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -36,6 +36,7 @@
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"jenkins": "^1.0.0",
+ "node-fetch": "^2.6.7",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts
index c89a3866c7..8e80de34a3 100644
--- a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts
+++ b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts
@@ -19,9 +19,10 @@ import jenkins from 'jenkins';
import { JenkinsInfo } from './jenkinsInfoProvider';
import { JenkinsBuild, JenkinsProject } from '../types';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
-import { NotAllowedError } from '@backstage/errors';
+import fetch, { Response } from 'node-fetch';
jest.mock('jenkins');
+jest.mock('node-fetch');
const mockedJenkinsClient = {
job: {
get: jest.fn(),
@@ -54,6 +55,11 @@ const fakePermissionApi = {
describe('JenkinsApi', () => {
const jenkinsApi = new JenkinsApiImpl(fakePermissionApi);
+ const mockFetch = fetch as jest.MockedFunction;
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
describe('getProjects', () => {
const project: JenkinsProject = {
@@ -689,55 +695,63 @@ describe('JenkinsApi', () => {
buildNumber,
);
});
- it('buildProject', async () => {
- await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef);
+ it('getBuildUrl', async () => {
+ const jenkinsApiProto = Object.getPrototypeOf(jenkinsApi);
+ const buildUrl = jenkinsApiProto.getBuildUrl(
+ jenkinsInfo,
+ jobFullName,
+ buildNumber,
+ );
+ expect(buildUrl).toEqual(
+ 'https://jenkins.example.com/job/example-jobName/job/foo/19',
+ );
- expect(mockedJenkins).toHaveBeenCalledWith({
- baseUrl: jenkinsInfo.baseUrl,
- headers: jenkinsInfo.headers,
- promisify: true,
- });
- expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
+ const buildUrlTriple = jenkinsApiProto.getBuildUrl(
+ jenkinsInfo,
+ 'example-jobName/foo/bar',
+ buildNumber,
+ );
+ expect(buildUrlTriple).toEqual(
+ 'https://jenkins.example.com/job/example-jobName/job/foo/job/bar/19',
+ );
});
-
- it('buildProject should fail if it does not have required permissions', async () => {
- fakePermissionApi.authorize.mockResolvedValueOnce([
- {
- result: AuthorizeResult.DENY,
- },
- ]);
-
- await expect(() =>
- jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef),
- ).rejects.toThrow(NotAllowedError);
- });
-
- it('buildProject should succeed if it have required permissions', async () => {
- fakePermissionApi.authorize.mockResolvedValueOnce([
- {
- result: AuthorizeResult.ALLOW,
- },
- ]);
-
- await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef);
- expect(mockedJenkins).toHaveBeenCalledWith({
- baseUrl: jenkinsInfo.baseUrl,
- headers: jenkinsInfo.headers,
- promisify: true,
+ describe('rebuildProject', () => {
+ it('successfully rebuilds', async () => {
+ mockFetch.mockResolvedValueOnce({ status: 200 } as Response);
+ const status = await jenkinsApi.rebuildProject(
+ jenkinsInfo,
+ jobFullName,
+ buildNumber,
+ resourceRef,
+ );
+ expect(status).toEqual(200);
});
- expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
- });
-
- it('buildProject with crumbIssuer option', async () => {
- const info: JenkinsInfo = { ...jenkinsInfo, crumbIssuer: true };
- await jenkinsApi.buildProject(info, jobFullName, resourceRef);
-
- expect(mockedJenkins).toHaveBeenCalledWith({
- baseUrl: jenkinsInfo.baseUrl,
- headers: jenkinsInfo.headers,
- promisify: true,
- crumbIssuer: true,
+ it('fails to rebuild', async () => {
+ mockFetch.mockResolvedValueOnce({ status: 401 } as Response);
+ const status = await jenkinsApi.rebuildProject(
+ jenkinsInfo,
+ jobFullName,
+ buildNumber,
+ resourceRef,
+ );
+ expect(status).toEqual(401);
+ });
+
+ it('should fail if it does not have required permissions', async () => {
+ fakePermissionApi.authorize.mockResolvedValueOnce([
+ {
+ result: AuthorizeResult.DENY,
+ },
+ ]);
+
+ mockFetch.mockResolvedValueOnce({ status: 200 } as Response);
+ const status = await jenkinsApi.rebuildProject(
+ jenkinsInfo,
+ jobFullName,
+ buildNumber,
+ resourceRef,
+ );
+ expect(status).toEqual(401);
});
- expect(mockedJenkinsClient.job.build).toHaveBeenCalledWith(jobFullName);
});
});
diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.ts b/plugins/jenkins-backend/src/service/jenkinsApi.ts
index e14437fa68..85271462eb 100644
--- a/plugins/jenkins-backend/src/service/jenkinsApi.ts
+++ b/plugins/jenkins-backend/src/service/jenkinsApi.ts
@@ -28,7 +28,7 @@ import {
PermissionEvaluator,
} from '@backstage/plugin-permission-common';
import { jenkinsExecutePermission } from '@backstage/plugin-jenkins-common';
-import { NotAllowedError } from '@backstage/errors';
+import fetch, { HeaderInit } from 'node-fetch';
export class JenkinsApiImpl {
private static readonly lastBuildTreeSpec = `lastBuild[
@@ -144,14 +144,13 @@ export class JenkinsApiImpl {
* Trigger a build of a project
* @see ../../../jenkins/src/api/JenkinsApi.ts#retry
*/
- async buildProject(
+ async rebuildProject(
jenkinsInfo: JenkinsInfo,
jobFullName: string,
+ buildNumber: number,
resourceRef: string,
options?: { token?: string },
- ) {
- const client = await JenkinsApiImpl.getClient(jenkinsInfo);
-
+ ): Promise {
if (this.permissionApi) {
const response = await this.permissionApi.authorize(
[{ permission: jenkinsExecutePermission, resourceRef }],
@@ -160,16 +159,19 @@ export class JenkinsApiImpl {
// permission api returns always at least one item, we need to check only one result since we do not expect any additional results
const { result } = response[0];
if (result === AuthorizeResult.DENY) {
- throw new NotAllowedError();
+ return 401;
}
}
- // looks like the current SDK only supports triggering a new build
- // can't see any support for replay (re-running the specific build with the same SCM info)
+ const buildUrl = this.getBuildUrl(jenkinsInfo, jobFullName, buildNumber);
- // Note Jenkins itself has concepts of rebuild and replay on a job.
- // The latter should be possible to trigger with a POST to /replay/rebuild
- await client.job.build(jobFullName);
+ // the current SDK only supports triggering a new build
+ // replay the job by triggering request directly from Jenkins api
+ const response = await fetch(`${buildUrl}/replay/rebuild`, {
+ method: 'post',
+ headers: jenkinsInfo.headers as HeaderInit,
+ });
+ return response.status;
}
// private helper methods
@@ -318,4 +320,13 @@ export class JenkinsApiImpl {
})
.pop();
}
+
+ private getBuildUrl(
+ jenkinsInfo: JenkinsInfo,
+ jobFullName: string,
+ buildId: number,
+ ): string {
+ const jobs = jobFullName.split('/');
+ return `${jenkinsInfo.baseUrl}/job/${jobs.join('/job/')}/${buildId}`;
+ }
}
diff --git a/plugins/jenkins-backend/src/service/router.ts b/plugins/jenkins-backend/src/service/router.ts
index df4c3b5a11..484bfa273f 100644
--- a/plugins/jenkins-backend/src/service/router.ts
+++ b/plugins/jenkins-backend/src/service/router.ts
@@ -145,9 +145,10 @@ export async function createRouter(
);
router.post(
- '/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber::rebuild',
+ '/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber',
async (request, response) => {
- const { namespace, kind, name, jobFullName } = request.params;
+ const { namespace, kind, name, jobFullName, buildNumber } =
+ request.params;
const token = getBearerTokenFromAuthorizationHeader(
request.header('authorization'),
);
@@ -162,10 +163,16 @@ export async function createRouter(
});
const resourceRef = stringifyEntityRef({ kind, namespace, name });
- await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef, {
- token,
- });
- response.json({});
+ const status = await jenkinsApi.rebuildProject(
+ jenkinsInfo,
+ jobFullName,
+ parseInt(buildNumber, 10),
+ resourceRef,
+ {
+ token,
+ },
+ );
+ response.json({}).status(status);
},
);
router.use(errorHandler());
diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md
index 6b900dcb18..1b289faef2 100644
--- a/plugins/jenkins/CHANGELOG.md
+++ b/plugins/jenkins/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-jenkins
+## 0.8.0
+
+### Minor Changes
+
+- cf95c5137c9: Updated rebuild to use Jenkins API replay build, which works for Jenkins jobs that have required parameters. Jenkins SDK could not be used for this request because it does not have support for replay.
+
+ Added link to view build in Jenkins CI/CD table action column.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0
+ - @backstage/plugin-catalog-react@1.6.0
+ - @backstage/core-components@0.13.1
+ - @backstage/catalog-model@1.3.0
+ - @backstage/core-plugin-api@1.5.1
+ - @backstage/errors@1.1.5
+ - @backstage/plugin-jenkins-common@0.1.15
+
+## 0.7.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.3.0-next.0
+ - @backstage/core-components@0.13.1-next.1
+ - @backstage/plugin-catalog-react@1.6.0-next.2
+ - @backstage/core-plugin-api@1.5.1
+
## 0.7.17-next.1
### Patch Changes
diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json
index e57c1c77f2..1f980215c2 100644
--- a/plugins/jenkins/package.json
+++ b/plugins/jenkins/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-jenkins",
"description": "A Backstage plugin that integrates towards Jenkins",
- "version": "0.7.17-next.1",
+ "version": "0.8.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/jenkins/src/assets/folder-results.png b/plugins/jenkins/src/assets/folder-results.png
index 2c14d5d927..340b03549d 100644
Binary files a/plugins/jenkins/src/assets/folder-results.png and b/plugins/jenkins/src/assets/folder-results.png differ
diff --git a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
index 1bb6dbaefc..f2d108eaf7 100644
--- a/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
+++ b/plugins/jenkins/src/components/BuildsPage/lib/CITable/CITable.tsx
@@ -18,6 +18,7 @@ import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha';
import { Box, IconButton, Tooltip, Typography } from '@material-ui/core';
import RetryIcon from '@material-ui/icons/Replay';
+import VisibilityIcon from '@material-ui/icons/Visibility';
import { default as React, useState } from 'react';
import { Project } from '../../../../api/JenkinsApi';
import JenkinsLogo from '../../../../assets/JenkinsLogo.svg';
@@ -203,16 +204,23 @@ const generatedColumns: TableColumn[] = [
};
return (
-
- <>
- {isLoadingRebuild && }
- {!isLoadingRebuild && (
+