Merge branch 'master' into timbonicus/catalog-entity-context
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-api': patch
|
||||
---
|
||||
|
||||
Add a global type definition for `Symbol.observable`, fix type checking in projects that didn't already have it defined.
|
||||
@@ -16,6 +16,7 @@
|
||||
/plugins/search-* @backstage/techdocs-core
|
||||
/plugins/techdocs @backstage/techdocs-core
|
||||
/plugins/techdocs-backend @backstage/techdocs-core
|
||||
/plugins/ilert @yacut
|
||||
/packages/search-common @backstage/techdocs-core
|
||||
/packages/techdocs-common @backstage/techdocs-core
|
||||
/.changeset/cost-insights-* @backstage/silver-lining
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Apdex
|
||||
Api
|
||||
automations
|
||||
Autoscaling
|
||||
Avro
|
||||
Bigtable
|
||||
@@ -32,12 +33,16 @@ GitLab
|
||||
Grafana
|
||||
GraphQL
|
||||
Hackathons
|
||||
Helidon
|
||||
Heroku
|
||||
hoc
|
||||
Hostname
|
||||
Iain
|
||||
JaCoCo
|
||||
JavaScript
|
||||
Jira
|
||||
Kaewkasi
|
||||
kubernetes
|
||||
Knex
|
||||
Leasot
|
||||
Lerna
|
||||
@@ -47,6 +52,8 @@ Minikube
|
||||
Minio
|
||||
Mkdocs
|
||||
Monorepo
|
||||
microservices
|
||||
microservice
|
||||
Namespaces
|
||||
OAuth
|
||||
Okta
|
||||
|
||||
@@ -20,3 +20,4 @@ jobs:
|
||||
version: yarn release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
|
||||
HUSKY: '0'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
_
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
@@ -1,27 +1,31 @@
|
||||
| Organization | Contact | Description of Use |
|
||||
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
|
||||
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
|
||||
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
|
||||
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
|
||||
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
|
||||
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
|
||||
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
|
||||
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
|
||||
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
|
||||
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
|
||||
| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit |
|
||||
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
|
||||
| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling |
|
||||
| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. |
|
||||
| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
|
||||
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
|
||||
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
|
||||
| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. |
|
||||
| Organization | Contact | Description of Use |
|
||||
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. |
|
||||
| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. |
|
||||
| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. |
|
||||
| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up |
|
||||
| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. |
|
||||
| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. |
|
||||
| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. |
|
||||
| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications |
|
||||
| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. |
|
||||
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
|
||||
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
|
||||
| [Telenor Sweden](https://www.telenor.se) | [@O5ten](https://github.com/O5ten) | Building a developer portal for scaffolding projects towards our unified build environment and microservice stacks |
|
||||
| [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. |
|
||||
| [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. |
|
||||
| [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. |
|
||||
| [Expedia Group](https://www.expediagroup.com) | [Mike Turner](mailto:miturner@expediagroup.com), [Sneha Kumar](mailto:snkumar@expediagroup.com), [@guillermomanzo](https://github.com/guillermomanzo), [Erik Lindgren](https://github.com/lindgren) | EG Common Developer Toolkit |
|
||||
| [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go |
|
||||
| [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling |
|
||||
| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. |
|
||||
| [Trendyol](https://trendyol.com) | [Erdogan Oksuz](https://github.com/erdoganoksuz) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. |
|
||||
| [Peloton](https://www.onepeloton.com/) | [Jim Haughwout](https://github.com/JimHaughwout) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. |
|
||||
| [TELUS](https://telus.com) | [Seb Barre](https://github.com/sbarre) | The Go-to place to find answers about development and delivery at TELUS. |
|
||||
| [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. |
|
||||
| [Oriflame](https://www.oriflame.com/) | [Oriflame](https://github.com/oriflame) | Internal developer portal for services, single page apps and packages overview, API documentation, technical guides, tech-radar and more. |
|
||||
| [Booz Allen Hamilton](https://www.boozallen.com/) | [Jason Miller](https://github.com/JasonMiller-BAH) | Developer portal for a full-stack software development ecosystem that accelerates consistent and repeatable Modern Software Development practices for internal innovation and investments. |
|
||||
| [Netflix](https://www.netflix.com/) | [bleathem](https://github.com/bleathem) | Our Backstage implementation will be the front door to a unified experience connecting our internal platform products across important workflows with integrated knowledge and support. |
|
||||
| [b.well](https://www.icanbwell.com/) | [Jacob Rosales](https://github.com/jrosales) | Foundation for our engineering portal and cloud insights. |
|
||||
|
||||
@@ -27,5 +27,5 @@ People that have made significant contributions to the project and earned write
|
||||
- Andrew Thauer - Wealthsimple (GitHub: [andrewthauer](https://github.com/andrewthauer))
|
||||
- Oliver Sand - SDA SE (GitHub: [Fox32](https://github.com/Fox32))
|
||||
- David Tuite - Roadie (GitHub: [dtuite](https://github.com/dtuite))
|
||||
- Adam Harvey - DXC Technology (GitHub: [adamdmharvey](https://github.com/adamdmharvey))
|
||||
- Adam Harvey - Cisco (GitHub: [adamdmharvey](https://github.com/adamdmharvey))
|
||||
- Dominik Henneke - SDA SE (GitHub: [dhenneke](https://github.com/dhenneke))
|
||||
|
||||
@@ -81,6 +81,13 @@ proxy:
|
||||
headers:
|
||||
Authorization: ${SENTRY_TOKEN}
|
||||
|
||||
'/ilert':
|
||||
target: https://api.ilert.com
|
||||
allowedMethods: ['GET', 'POST', 'PUT']
|
||||
allowedHeaders: ['Authorization']
|
||||
headers:
|
||||
Authorization: ${ILERT_AUTH_HEADER}
|
||||
|
||||
organization:
|
||||
name: My Company
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"cypress": "^6.4.0",
|
||||
"cypress": "^7.3.0",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,12 @@
|
||||
dependencies:
|
||||
any-observable "^0.3.0"
|
||||
|
||||
"@types/sinonjs__fake-timers@^6.0.1":
|
||||
"@types/node@^14.14.31":
|
||||
version "14.17.0"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-14.17.0.tgz#3ba770047723b3eeb8dc9fca02cce8a7fb6378da"
|
||||
integrity sha512-w8VZUN/f7SSbvVReb9SWp6cJFevxb4/nkG65yLAya//98WgocKm5PLDAtSs5CtJJJM+kHmJjO/6mmYW4MHShZA==
|
||||
|
||||
"@types/sinonjs__fake-timers@^6.0.2":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae"
|
||||
integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==
|
||||
@@ -117,7 +122,7 @@ any-observable@^0.3.0:
|
||||
resolved "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
|
||||
integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
|
||||
|
||||
arch@^2.1.2:
|
||||
arch@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
|
||||
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
|
||||
@@ -171,7 +176,7 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
blob-util@2.0.2:
|
||||
blob-util@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
|
||||
integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
|
||||
@@ -242,10 +247,10 @@ check-more-types@^2.24.0:
|
||||
resolved "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||
integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
|
||||
|
||||
ci-info@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
|
||||
ci-info@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz#9a32fcefdf7bcdb6f0a7e1c0f8098ec57897b80a"
|
||||
integrity sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==
|
||||
|
||||
cli-cursor@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -359,18 +364,19 @@ cross-spawn@^7.0.0:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
cypress@^6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.npmjs.org/cypress/-/cypress-6.4.0.tgz#432c516bf4f1a0f042a6aa1f2c3a4278fa35a8b2"
|
||||
integrity sha512-SrsPsZ4IBterudkoFYBvkQmXOVxclh1/+ytbzpV8AH/D2FA+s2Qy5ISsaRzOFsbQa4KZWoi3AKwREmF1HucYkg==
|
||||
cypress@^7.3.0:
|
||||
version "7.3.0"
|
||||
resolved "https://registry.npmjs.org/cypress/-/cypress-7.3.0.tgz#17345b8d18681c120f033e7d8fd0f0271e9d0d51"
|
||||
integrity sha512-aseRCH1tRVCrM6oEfja6fR/bo5l6e4SkHRRSATh27UeN4f/ANC8U7tGIulmrISJVy9xuOkOdbYKbUb2MNM+nrw==
|
||||
dependencies:
|
||||
"@cypress/listr-verbose-renderer" "^0.4.1"
|
||||
"@cypress/request" "^2.88.5"
|
||||
"@cypress/xvfb" "^1.2.4"
|
||||
"@types/sinonjs__fake-timers" "^6.0.1"
|
||||
"@types/node" "^14.14.31"
|
||||
"@types/sinonjs__fake-timers" "^6.0.2"
|
||||
"@types/sizzle" "^2.3.2"
|
||||
arch "^2.1.2"
|
||||
blob-util "2.0.2"
|
||||
arch "^2.2.0"
|
||||
blob-util "^2.0.2"
|
||||
bluebird "^3.7.2"
|
||||
cachedir "^2.3.0"
|
||||
chalk "^4.1.0"
|
||||
@@ -378,27 +384,26 @@ cypress@^6.4.0:
|
||||
cli-table3 "~0.6.0"
|
||||
commander "^5.1.0"
|
||||
common-tags "^1.8.0"
|
||||
dayjs "^1.9.3"
|
||||
debug "^4.1.1"
|
||||
eventemitter2 "^6.4.2"
|
||||
execa "^4.0.2"
|
||||
dayjs "^1.10.4"
|
||||
debug "4.3.2"
|
||||
eventemitter2 "^6.4.3"
|
||||
execa "4.1.0"
|
||||
executable "^4.1.1"
|
||||
extract-zip "^1.7.0"
|
||||
fs-extra "^9.0.1"
|
||||
fs-extra "^9.1.0"
|
||||
getos "^3.2.1"
|
||||
is-ci "^2.0.0"
|
||||
is-installed-globally "^0.3.2"
|
||||
is-ci "^3.0.0"
|
||||
is-installed-globally "~0.4.0"
|
||||
lazy-ass "^1.6.0"
|
||||
listr "^0.14.3"
|
||||
lodash "^4.17.19"
|
||||
lodash "^4.17.21"
|
||||
log-symbols "^4.0.0"
|
||||
minimist "^1.2.5"
|
||||
moment "^2.29.1"
|
||||
ospath "^1.2.2"
|
||||
pretty-bytes "^5.4.1"
|
||||
ramda "~0.26.1"
|
||||
pretty-bytes "^5.6.0"
|
||||
ramda "~0.27.1"
|
||||
request-progress "^3.0.0"
|
||||
supports-color "^7.2.0"
|
||||
supports-color "^8.1.1"
|
||||
tmp "~0.2.1"
|
||||
untildify "^4.0.0"
|
||||
url "^0.11.0"
|
||||
@@ -416,11 +421,18 @@ date-fns@^1.27.2:
|
||||
resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||
|
||||
dayjs@^1.9.3:
|
||||
dayjs@^1.10.4:
|
||||
version "1.10.4"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
|
||||
integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
|
||||
|
||||
debug@4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@@ -435,13 +447,6 @@ debug@^3.1.0:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.1.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
@@ -477,12 +482,12 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
eventemitter2@^6.4.2:
|
||||
version "6.4.3"
|
||||
resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz#35c563619b13f3681e7eb05cbdaf50f56ba58820"
|
||||
integrity sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==
|
||||
eventemitter2@^6.4.3:
|
||||
version "6.4.4"
|
||||
resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b"
|
||||
integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw==
|
||||
|
||||
execa@^4.0.2:
|
||||
execa@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
|
||||
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
|
||||
@@ -580,7 +585,7 @@ form-data@~2.3.2:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^9.0.1:
|
||||
fs-extra@^9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
||||
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
||||
@@ -628,12 +633,12 @@ glob@^7.1.3:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
global-dirs@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"
|
||||
integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==
|
||||
global-dirs@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
|
||||
integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==
|
||||
dependencies:
|
||||
ini "1.3.7"
|
||||
ini "2.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.5"
|
||||
@@ -702,17 +707,17 @@ inherits@2, inherits@^2.0.3, inherits@~2.0.3:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
|
||||
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
|
||||
|
||||
is-ci@^2.0.0:
|
||||
ini@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
|
||||
integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
|
||||
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
|
||||
|
||||
is-ci@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994"
|
||||
integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==
|
||||
dependencies:
|
||||
ci-info "^2.0.0"
|
||||
ci-info "^3.1.1"
|
||||
|
||||
is-fullwidth-code-point@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -731,13 +736,13 @@ is-fullwidth-code-point@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||
|
||||
is-installed-globally@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
|
||||
integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
|
||||
is-installed-globally@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
|
||||
integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
|
||||
dependencies:
|
||||
global-dirs "^2.0.1"
|
||||
is-path-inside "^3.0.1"
|
||||
global-dirs "^3.0.0"
|
||||
is-path-inside "^3.0.2"
|
||||
|
||||
is-observable@^1.1.0:
|
||||
version "1.1.0"
|
||||
@@ -746,10 +751,10 @@ is-observable@^1.1.0:
|
||||
dependencies:
|
||||
symbol-observable "^1.1.0"
|
||||
|
||||
is-path-inside@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
|
||||
integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
|
||||
is-path-inside@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||
|
||||
is-promise@^2.1.0:
|
||||
version "2.2.2"
|
||||
@@ -879,10 +884,10 @@ lodash.once@^4.1.1:
|
||||
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-symbols@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -953,11 +958,6 @@ mkdirp@^0.5.4:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
@@ -1056,10 +1056,10 @@ pify@^2.2.0:
|
||||
resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
||||
|
||||
pretty-bytes@^5.4.1:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e"
|
||||
integrity sha512-p+T744ZyjjiaFlMUZZv6YPC5JrkNj8maRmPaQCWFJFplUAzpIUTRaTcS+7wmZtUoFXHtESJb23ISliaWyz3SHA==
|
||||
pretty-bytes@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -1099,10 +1099,10 @@ querystring@0.2.0:
|
||||
resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
ramda@~0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
|
||||
ramda@~0.27.1:
|
||||
version "0.27.1"
|
||||
resolved "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
|
||||
integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.7"
|
||||
@@ -1277,13 +1277,20 @@ supports-color@^5.3.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.1.0, supports-color@^7.2.0:
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
symbol-observable@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||
|
||||
@@ -125,8 +125,8 @@ entity, and for machines and other components to reference the entity (e.g. in
|
||||
URLs or from other entity specification files).
|
||||
|
||||
Names must be unique per kind, within a given namespace (if specified), at any
|
||||
point in time. Names may be reused at a later time, after an entity is deleted
|
||||
from the registry.
|
||||
point in time. This uniqueness constraint is also case insensitive. Names may be
|
||||
reused at a later time, after an entity is deleted from the registry.
|
||||
|
||||
Names are required to follow a certain format. Entities that do not follow those
|
||||
rules will not be accepted for registration in the catalog. The ruleset is
|
||||
@@ -139,18 +139,6 @@ follows.
|
||||
|
||||
Example: `visits-tracking-service`, `CircleciBuildsDs_avro_gcs`
|
||||
|
||||
In addition to this, names are passed through a normalization function and then
|
||||
compared to the same normalized form of other entity names and made sure to not
|
||||
collide. This rule of uniqueness exists to avoid situations where e.g. both
|
||||
`my-component` and `MyComponent` are registered side by side, which leads to
|
||||
confusion and risk. The normalization function is also configurable, but the
|
||||
default behavior is as follows.
|
||||
|
||||
- Strip out all characters outside of the set `[a-zA-Z0-9]`
|
||||
- Convert to lowercase
|
||||
|
||||
Example: `CircleciBuildsDs_avro_gcs` -> `circlecibuildsdsavrogcs`
|
||||
|
||||
### `namespace`
|
||||
|
||||
The `name` of a namespace that the entity belongs to. This field is optional,
|
||||
|
||||
@@ -23,6 +23,7 @@ we recommend that you name them `catalog-info.yaml`.
|
||||
- [Common to All Kinds: The Envelope](#common-to-all-kinds-the-envelope)
|
||||
- [Common to All Kinds: The Metadata](#common-to-all-kinds-the-metadata)
|
||||
- [Common to All Kinds: Relations](#common-to-all-kinds-relations)
|
||||
- [Common to All Kinds: Status](#common-to-all-kinds-status)
|
||||
- [Kind: Component](#kind-component)
|
||||
- [Kind: Template](#kind-template)
|
||||
- [Kind: API](#kind-api)
|
||||
@@ -396,6 +397,54 @@ with it (such as the default kind being `Group` if not specified).
|
||||
See the [well-known relations section](well-known-relations.md) for a list of
|
||||
well-known / common relations and their semantics.
|
||||
|
||||
## Common to All Kinds: Status
|
||||
|
||||
The `status` root field is a read-only set of statuses, pertaining to the
|
||||
current state or health of the entity, described in the
|
||||
[well-known statuses section](well-known-statuses.md). Each status field
|
||||
contains a specific blob of data that describes some aspect of the state of the
|
||||
entity, as seen from the point of view of some specific system. Different
|
||||
systems may contribute to this status object, under their own respective keys.
|
||||
|
||||
The current main use case for this field is for the ingestion processes of the
|
||||
catalog itself to convey information about failures and warnings back to the
|
||||
user.
|
||||
|
||||
A status field as part of a single entity that's read out of the API may look as
|
||||
follows.
|
||||
|
||||
```js
|
||||
{
|
||||
// ...
|
||||
"status": {
|
||||
"backstage.io/catalog-processing": {
|
||||
"errors": []
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The keys of the `status` object are arbitrary strings. We recommend that any
|
||||
statuses that are not strictly private within the organization be namespaced to
|
||||
avoid collisions. Statuses emitted by Backstage core processes will for example
|
||||
be prefixed with `backstage.io/` as in the example above.
|
||||
|
||||
The values of the `status` object are currently left unrestricted, except that
|
||||
they must be objects. We reserve the right to extend this model in the future,
|
||||
such that some fields of those value objects gain standardized meaning. We may
|
||||
for example want to add a standard concept of "severity" or "level" to these.
|
||||
|
||||
Entity descriptor YAML files are not supposed to contain this field. Instead,
|
||||
catalog processors analyze the entity descriptor data and its surroundings, and
|
||||
deduce status entries that are then attached onto the entity as read from the
|
||||
catalog.
|
||||
|
||||
See the [well-known statuses section](well-known-statuses.md) for a list of
|
||||
well-known / common relations and their semantics.
|
||||
|
||||
## Kind: Component
|
||||
|
||||
Describes the following entity kind:
|
||||
@@ -657,6 +706,25 @@ You can find out more about the `parameters` key
|
||||
You can find out more about the `steps` key
|
||||
[here](../software-templates/writing-templates.md)
|
||||
|
||||
### `spec.owner` [optional]
|
||||
|
||||
An [entity reference](#string-references) to the owner of the component, e.g.
|
||||
`artist-relations-team`. This field is required.
|
||||
|
||||
In Backstage, the owner of a Template is the singular entity (commonly a team)
|
||||
that bears ultimate responsibility for the Template, and has the authority and
|
||||
capability to develop and maintain it. They will be the point of contact if
|
||||
something goes wrong, or if features are to be requested. The main purpose of
|
||||
this field is for display purposes in Backstage, so that people looking at
|
||||
catalog items can get an understanding of to whom this Template belongs. It is
|
||||
not to be used by automated processes to for example assign authorization in
|
||||
runtime systems. There may be others that also develop or otherwise touch the
|
||||
Template, but there will always be one ultimate owner.
|
||||
|
||||
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
|
||||
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
||||
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
|
||||
|
||||
## Kind: API
|
||||
|
||||
Describes the following entity kind:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
id: extending-the-model
|
||||
title: Extending the model
|
||||
# prettier-ignore
|
||||
description: Documentation on extending the catalog model
|
||||
---
|
||||
|
||||
@@ -301,9 +302,9 @@ Example intents:
|
||||
> "We have this concept of service maintainership, separate from ownership, that
|
||||
> we would like to make relations to individual users for."
|
||||
|
||||
> We feel that we want to explicitly model the team-to-global-department mapping
|
||||
> as a relation, because it is core to our org setup and we frequently query for
|
||||
> it.
|
||||
> "We feel that we want to explicitly model the team-to-global-department
|
||||
> mapping as a relation, because it is core to our org setup and we frequently
|
||||
> query for it."
|
||||
|
||||
Any processor can emit relations for entities as they are being processed, and
|
||||
new processors can be added when building the backend catalog using the
|
||||
@@ -349,3 +350,21 @@ If you want to extend the use of an established relation type in a way that has
|
||||
an effect outside of your organization, reach out to the Backstage maintainers
|
||||
or a support partner to discuss risk/impact. It may even be that one end of the
|
||||
relation could be considered for addition to the core.
|
||||
|
||||
## Adding a New Status field
|
||||
|
||||
Example intents:
|
||||
|
||||
> "We would like to convey entity statuses through the catalog in a generic way,
|
||||
> as an integration layer. Our monitoring and alerting system has a plugin with
|
||||
> Backstage, and it would be useful if the entity's status field contained the
|
||||
> current alert state close to the actual entity data for anyone to consume.
|
||||
|
||||
While we are considering a mechanism for contributing generic statuses to
|
||||
entities, no such mechanism has yet been built. If you are interested in that
|
||||
topic, [this issue](https://github.com/backstage/backstage/issues/2292) contains
|
||||
more context.
|
||||
|
||||
But in general, errors emitted (and exceptions thrown) by any processor
|
||||
including custom ones, end up in the [well known key](well-known-statuses.md)
|
||||
for ingestion status.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: well-known-statuses
|
||||
title: Well-known Status fields of Catalog Entities
|
||||
sidebar_label: Well-known Statuses
|
||||
# prettier-ignore
|
||||
description: Lists a number of well known entity statuses, that have defined semantics. They can be attached to catalog entities and consumed by plugins as needed.
|
||||
---
|
||||
|
||||
This section lists a number of well known
|
||||
[entity status fields](descriptor-format.md#common-to-all-kinds-status), that
|
||||
have defined semantics. They can be attached to catalog entities and consumed by
|
||||
plugins as needed.
|
||||
|
||||
If you are looking to extend the set of statuses, see
|
||||
[Extending the model](extending-the-model.md).
|
||||
|
||||
## Common Fields
|
||||
|
||||
The values of statuses are currently left unrestricted, except that they must be
|
||||
objects. They therefore currently formally have no common fields.
|
||||
|
||||
We reserve the right to extend this model in the future, such that some fields
|
||||
of those value objects gain standardized meaning. We may for example want to add
|
||||
a standard concept of "severity" or "level" to these.
|
||||
|
||||
## Statuses
|
||||
|
||||
This is a (non-exhaustive) list of statuses that are known to be in active use.
|
||||
|
||||
### `backstage.io/catalog-processing`
|
||||
|
||||
Contains the current status of the catalog's ingestion of this entity. Errors
|
||||
that may appear here include inability to read from the remote SCM provider,
|
||||
syntax errors in the YAML file, and similar.
|
||||
|
||||
Note that the entity data itself may be of an older version, when errors are
|
||||
present. The ingestion system keeps the old, valid entity data untouched when
|
||||
possible, so the errors described in this state may not seem to align with the
|
||||
rest of the entity, because they pertain to a remote that could not be
|
||||
successfully ingested. This is normal.
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
status:
|
||||
backstage.io/catalog-processing:
|
||||
errors: []
|
||||
```
|
||||
|
||||
This status is in active development and its format will change unexpectedly. Do
|
||||
not consume it in your own code until such a time that this documentation has
|
||||
been updated.
|
||||
@@ -0,0 +1,334 @@
|
||||
---
|
||||
id: migrating-from-v1alpha1-to-v1beta2
|
||||
title: Migrating to v1beta2 templates
|
||||
# prettier-ignore
|
||||
description:
|
||||
How to move your old templates from v1alpha1 to the more declarative v1beta2
|
||||
---
|
||||
|
||||
# What's new?
|
||||
|
||||
Previously, the scaffolder was very restricted in what you could do when
|
||||
creating new software components from templates. There were three scaffolding
|
||||
steps which was pretty hard to extend and add new functionality to, difficult to
|
||||
re-use logic between templates. There used to be a fixed pipeline of
|
||||
`preparers`, `templaters`, and `publishers`, which were defined by the backend
|
||||
and needed to be run for each template. This is now changed, to give the
|
||||
template total control over what should be executed as part of the templating
|
||||
run. This makes templates a little more declarative as you can now register
|
||||
different `actions` or `functions` with the `scaffolder-backend` which you then
|
||||
can decide how, and in what order, to run using the template definition YAML
|
||||
file.
|
||||
|
||||
We've also made some improvements, and added some helpers to work with
|
||||
cookiecutter. The skeleton for a template can now be stored in a different place
|
||||
to where your entity definition is: previously you needed to have your
|
||||
`template.yaml` next to the skeleton source (`{{cookiecutter.component_id}}`
|
||||
directory), but now that's not the case. Part of the changes with the `v1beta2`
|
||||
syntax is that you can grab your template source from any repository, and re-use
|
||||
them between templates.
|
||||
|
||||
We've also renamed the `schema` property to `parameters` as this makes more
|
||||
sense when using them as parameters to the actions or steps that you've setup
|
||||
for your templates. There's the added benefit that you can now assign an array
|
||||
to the `parameters` property, which will then give you multiple steps in the UI,
|
||||
so you can split apart your input parameters and group them as needed rather
|
||||
than having one long list of input fields.
|
||||
|
||||
## The `parameters` property
|
||||
|
||||
The `schema` key has now been renamed to `parameters` with a few more features.
|
||||
You can pass an array now to break apart the input form into different steps in
|
||||
the UI. You can also specify `ui:schema` fields that are passed along to
|
||||
[`react-jsonschema-form`](https://rjsf-team.github.io/react-jsonschema-form/)
|
||||
inline with the JSON schema.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
parameters:
|
||||
- title: Fill in some steps
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
ui:autofocus: true
|
||||
ui:options:
|
||||
rows: 5
|
||||
```
|
||||
|
||||
## The `steps` property
|
||||
|
||||
`v1beta2` template syntax introduces the new `steps` property, which is an array
|
||||
of `actions` that the scaffolder will run in combination with the user input
|
||||
that is declared in the `schema`. Actions look like the following:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
- id: publish # a unique id for the step, can be anything you like
|
||||
name: Publish # a user friendly name for the step, this is what is shown in the frontend
|
||||
action: publish:github # the action ID that has been registered with the scaffolder-backend
|
||||
input: # parameters that are passed as input to the action handler function
|
||||
allowedHosts: ['github.com']
|
||||
description: 'This is {{ parameters.name }}' # handlebars templating is supported with the values from the parameters section in the same file.
|
||||
repoUrl: '{{ parameters.repoUrl }}'
|
||||
```
|
||||
|
||||
# Migrating a `v1alpha1` template
|
||||
|
||||
## The template definition (.yaml)
|
||||
|
||||
### `parameters`
|
||||
|
||||
Because of the changes to invert the control to the `template.yaml` definition
|
||||
for running the workflow, we need to adjust the `schema` property and we also
|
||||
now need to define what the template is actually going to do as part of the
|
||||
template run.
|
||||
|
||||
A simple migration would move the following yaml:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Template
|
||||
metadata:
|
||||
name: react-ssr-template
|
||||
title: React SSR Template
|
||||
description: Create a website powered with Next.js
|
||||
tags:
|
||||
- recommended
|
||||
- react
|
||||
spec:
|
||||
owner: web@example.com
|
||||
templater: cookiecutter
|
||||
type: website
|
||||
path: '.'
|
||||
schema:
|
||||
required:
|
||||
- component_id
|
||||
- description
|
||||
properties:
|
||||
component_id:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: Help others understand what this website is for.
|
||||
```
|
||||
|
||||
To something that looks like the following:
|
||||
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1beta2
|
||||
kind: Template
|
||||
metadata:
|
||||
name: react-ssr-template
|
||||
title: React SSR Template
|
||||
description: Create a website powered with Next.js
|
||||
tags:
|
||||
- recommended
|
||||
- react
|
||||
spec:
|
||||
owner: web@example.com
|
||||
type: website
|
||||
parameters:
|
||||
- title: Add some input
|
||||
required:
|
||||
- component_id
|
||||
- description
|
||||
properties:
|
||||
component_id:
|
||||
title: Name
|
||||
type: string
|
||||
description: Unique name of the component
|
||||
description:
|
||||
title: Description
|
||||
type: string
|
||||
description: Help others understand what this website is for.
|
||||
- title: Some more additional info that was previously provided automatically
|
||||
required:
|
||||
- owner
|
||||
- repoUrl
|
||||
properties:
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
- title: Choose a location
|
||||
repoUrl:
|
||||
title: Repository Location
|
||||
type: string
|
||||
ui:field: RepoUrlPicker
|
||||
ui:options:
|
||||
allowedHosts:
|
||||
- github.com
|
||||
```
|
||||
|
||||
There are a few things to note here. On the `alpha` version, the second step of
|
||||
the template flow in the frontend was provided by Backstage for free, so we used
|
||||
to collect the user input for the `owner` field and the `repositoryUrl` that you
|
||||
were going to publish to. Now because `actions` can have any workflow they like,
|
||||
it doesn't make sense to still provide these fields for every scaffolding
|
||||
workflow, as you might not need these anymore. That's why we now manually add
|
||||
those fields back into the template parameters that are shown to the user:
|
||||
|
||||
```yaml
|
||||
- title: Some more additional info that was previously provided automatically
|
||||
required:
|
||||
- owner
|
||||
- repoUrl
|
||||
properties:
|
||||
owner:
|
||||
title: Owner
|
||||
type: string
|
||||
description: Owner of the component
|
||||
ui:field: OwnerPicker
|
||||
ui:options:
|
||||
allowedKinds:
|
||||
- Group
|
||||
- title: Choose a location
|
||||
repoUrl:
|
||||
title: Repository Location
|
||||
type: string
|
||||
ui:field: RepoUrlPicker
|
||||
ui:options:
|
||||
allowedHosts:
|
||||
- github.com
|
||||
```
|
||||
|
||||
Maybe you also don't need to publish to `github.com`, you should replace this
|
||||
with your VCS provider URL that is listed in your `integrations` config instead.
|
||||
|
||||
### `steps`
|
||||
|
||||
So now we should have all the required information that we need from the user in
|
||||
a much more extensible way. We now need to tell the scaffolder what to do with
|
||||
these parameters and what to do with the user input.
|
||||
|
||||
We've made templating using `cookiecutter` a little simpler. You don't need to
|
||||
store the `cookiecutter` skeleton in the same directory as the `template.yaml`
|
||||
definition, it can live wherever you like - maybe a shared repository somewhere
|
||||
so you can re-use the skeletons but apply different actions for different
|
||||
templates depending on your use case.
|
||||
|
||||
We also no longer need to have a directory called
|
||||
`{{cookiecutter.component_id}}`. This is because now we can't ensure that
|
||||
`component_id` will be a parameter that is provided from the frontend, this
|
||||
could break `cookiecutter`. If your directory structure used to look like this:
|
||||
|
||||
```
|
||||
my-awesome-template
|
||||
-> {{cookiecutter.component_id}}
|
||||
-> file.txt
|
||||
-> some_more_files.ts
|
||||
-> hooks
|
||||
-> post_gen_project.sh
|
||||
-> template.yaml
|
||||
```
|
||||
|
||||
We now recommend that you move to the following structure:
|
||||
|
||||
```
|
||||
my-awesome-template
|
||||
-> skeleton
|
||||
-> file.txt
|
||||
-> some_more_files.ts
|
||||
-> template.yaml
|
||||
```
|
||||
|
||||
This migration renames the skeleton folder to something more semantic, and also
|
||||
drops support for `cookiecutter` hooks. We've dropped support for `cookiecutter`
|
||||
hooks for now, as hopefully everything that is stored in these hooks can be
|
||||
moved to `actions` instead, and for security reasons, it's more secure to run
|
||||
trusted code that you ship with Backstage as an action rather than some script
|
||||
that can be pulled in from anywhere which doesn't get vetted first. It's a
|
||||
pretty big security risk that those scripts will be run on Backstage instances
|
||||
inside your infrastructure, especially `.sh` files.
|
||||
|
||||
If you really need hooks and can't find a suitable solution by using actions
|
||||
please reach out to us through a ticket and we'll see what we can do to assist
|
||||
:)
|
||||
|
||||
You'll notice that we removed the `templater` property from the `spec`
|
||||
definition in the template `yaml`, so there's no way to define that this is a
|
||||
`cookiecutter` `templater`.
|
||||
|
||||
We've created a built-in action that you can use which will when run, go grab a
|
||||
directory from anywhere and run `cookiecutter` on top of it, and then extract
|
||||
the contents into the working directory for the scaffolder.
|
||||
|
||||
Adding the `steps` for a simple template should look something like the
|
||||
following:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
# this action will go use cookiecutter to template some files into the working directory
|
||||
- id: template # an ID for the templating step
|
||||
name: Create skeleton # A user friendly name for the action
|
||||
action: fetch:cookiecutter
|
||||
input:
|
||||
url: ./skeleton # this is the directory for your skeleton files.
|
||||
# If it's located next to the `template.yaml` then you can use a relative path,
|
||||
# otherwise you can use absolute URLs that point at the VCS: https://github.com/backstage/backstage/tree/master/some_folder_somewhere
|
||||
values:
|
||||
# for each value that you need to pass to cookiecutter, they should be listed here and set in this values object.
|
||||
# You can use the handlebars templating syntax to pull them from the input parameters listed in the same file
|
||||
name: '{{ parameters.name }}'
|
||||
owner: '{{ parameters.owner }}'
|
||||
destination: '{{ parseRepoUrl parameters.repoUrl }}'
|
||||
|
||||
# this action is for publishing the working directory to the VCS
|
||||
- id: publish
|
||||
name: Publish
|
||||
action: publish:github
|
||||
input:
|
||||
allowedHosts: ['github.com']
|
||||
description: 'This is {{ parameters.name }}'
|
||||
repoUrl: '{{ parameters.repoUrl }}'
|
||||
|
||||
# this action will then register the created component in Backstage
|
||||
- id: register
|
||||
name: Register
|
||||
action: catalog:register
|
||||
input:
|
||||
repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}'
|
||||
catalogInfoPath: '/catalog-info.yaml'
|
||||
```
|
||||
|
||||
### `output`
|
||||
|
||||
Steps can output values, and so can the template itself. This is good for
|
||||
returning values to the frontend, so we can make the buttons like
|
||||
`Go to catalog` and `Go to repo` work correctly. You can add the following to
|
||||
your `template.yaml` to make sure you return the right values from the steps:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
output:
|
||||
remoteUrl: '{{ steps.publish.output.remoteUrl }}'
|
||||
entityRef: '{{ steps.register.output.entityRef }}'
|
||||
```
|
||||
|
||||
Or you can return a `links` array with text and a URL explicitly:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
output:
|
||||
links:
|
||||
- url: '{{steps.publish.output.remoteUrl}}'
|
||||
text: 'Go to Repo'
|
||||
```
|
||||
|
||||
## Questions?
|
||||
|
||||
If you have any questions or feedback, please reach out to us on GitHub or
|
||||
Discord and we will do our best to help!
|
||||
@@ -1,7 +1,113 @@
|
||||
---
|
||||
id: backend-plugin
|
||||
title: Backend plugin
|
||||
description: Documentation on Backend plugin
|
||||
title: Backend plugins
|
||||
description: Creating and Developing Backend plugins
|
||||
---
|
||||
|
||||
## TODO
|
||||
This page describes the process of creating and managing backend plugins in your
|
||||
Backstage repository.
|
||||
|
||||
## Creating a Backend Plugin
|
||||
|
||||
A new, bare-bones backend plugin package can be created by issuing the following
|
||||
command in your Backstage repository root:
|
||||
|
||||
```sh
|
||||
yarn create-plugin --backend
|
||||
```
|
||||
|
||||
Please also see the `--help` flag for the `create-plugin` command for some
|
||||
further options that are available, notably the `--scope` and `--no-private`
|
||||
flags that control naming and publishing of the newly created package. Your repo
|
||||
root `package.json` will probably also have some default values already set up
|
||||
for these.
|
||||
|
||||
You will be asked to supply a name for the plugin. This is an identifier that
|
||||
will be part of the NPM package name, so make it short and containing only
|
||||
lowercase characters separated by dashes, for example `carmen`, if it's a
|
||||
package that adds an integration with a system named Carmen, for example. The
|
||||
full NPM package name would then be something like
|
||||
`@internal/plugin-carmen-backend`, depending on the other flags passed to the
|
||||
`create-plugin` command, and your settings for the `create-plugin` command in
|
||||
your root `package.json`.
|
||||
|
||||
Creating the plugin will take a little while, so be patient. It will helpfully
|
||||
run the initial installation and build commands, so that your package is ready
|
||||
to be hacked on! It will be located in a new folder in your `plugins` directory,
|
||||
in this example `plugins/carmen-backend`.
|
||||
|
||||
For simple development purposes, a backend plugin can actually be started in a
|
||||
standalone mode. You can do a first-light test of your service:
|
||||
|
||||
```sh
|
||||
cd plugins/carmen-backend
|
||||
yarn start
|
||||
```
|
||||
|
||||
This will think for a bit, and then say `Listening on :7000`. In a different
|
||||
terminal window, now run
|
||||
|
||||
```sh
|
||||
curl localhost:7000/carmen/health
|
||||
```
|
||||
|
||||
This should return `{"status":"ok"}`. Success! Press `Ctrl + c` to kill it
|
||||
again.
|
||||
|
||||
## Developing your Backend Plugin
|
||||
|
||||
A freshly created backend plugin does basically nothing, in terms of the overall
|
||||
app. It has a small set of basic dependencies and exposes an Express router in
|
||||
`src/service/router.ts`. This is where you will start adding routes and
|
||||
connecting those to actual underlying functionality. But nothing in your
|
||||
Backstage application / backend exposes it.
|
||||
|
||||
To actually attach and run the plugin router, you will make some modifications
|
||||
to your backend.
|
||||
|
||||
```sh
|
||||
# From the Backstage root directory
|
||||
cd packages/backend
|
||||
yarn add @internal/plugin-carmen-backend@^0.1.1 # Change this to match the plugin's package.json
|
||||
```
|
||||
|
||||
Create a new file named `packages/backend/src/plugins/carmen.ts`, and add the
|
||||
following to it
|
||||
|
||||
```ts
|
||||
import { createRouter } from '@internal/plugin-carmen-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
// Here is where you will add all of the required initialization code that
|
||||
// your backend plugin needs to be able to start!
|
||||
|
||||
// The env contains a lot of goodies, but our router currently only
|
||||
// needs a logger
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
And finally, wire this into the overall backend router. Edit
|
||||
`packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
import carmen from './plugins/carmen';
|
||||
// ...
|
||||
async function main() {
|
||||
// ...
|
||||
const carmenEnv = useHotMemoize(module, () => createEnv('carmen'));
|
||||
apiRouter.use('/carmen', await carmen(badgesEnv));
|
||||
```
|
||||
|
||||
After you start the backend (e.g. using `yarn start-backend` from the repo
|
||||
root), you should be able to fetch data from it.
|
||||
|
||||
```sh
|
||||
# Note the extra /api here
|
||||
curl localhost:7000/api/carmen/health
|
||||
```
|
||||
|
||||
This should return `{"status":"ok"}` like before. Success!
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Where do you start when adopting Backstage?
|
||||
author: Austin Lamon, Spotify
|
||||
authorURL: https://www.linkedin.com/in/austinlamon
|
||||
---
|
||||
|
||||

|
||||
|
||||
One of the greatest strengths of Backstage also presents a never-ending challenge: Backstage is highly customizable and allows you to easily build a unique developer portal suited to your organization’s needs. The downside of this flexibility is that it can be hard to know where to start. Backstage can do so many things — integrating every part of your tech infrastructure and developer experience — but if you set off building a developer portal without a plan, it’s easy to get overwhelmed by all the possibilities. To help you form your plan, this post will detail how Spotify came to design our internal portal and recommend potential models for you to use when designing and building your own.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Infrastructure as tech culture
|
||||
|
||||
Before providing recommendations on getting started with Backstage, it’s helpful to have a bit of context as to why Spotify made the design decisions we did. No two companies are identical — and thus, no two Backstage implementations are the same.
|
||||
|
||||
Rolling back the clock just a few years, [Spotify was challenged](https://engineering.atspotify.com/2021/05/18/a-product-story-the-lessons-of-backstage-and-spotifys-autonomous-culture/) to continue to scale our engineering team (and the number of features and components built) but retain the speed of product development. Some user research with Spotify developers highlighted a clear problem: there was simply too much non-documented institutional knowledge needed to get things done. No one could find anything and everyone was interrupting everyone else trying to figure things out.
|
||||
|
||||
Spotify’s developers were facing three big challenges on a daily basis:
|
||||
|
||||
- They not only needed to build software quickly, they also needed to pass along knowledge to new joiners about how best to create new components.
|
||||
- They needed to somehow maintain a mental model of the systems their squad owned. (Or, if they were lucky, they found a hopefully-up-to-date spreadsheet tracking this information.)
|
||||
- They needed to keep an eye on what squads around them might be building to ensure they could reuse systems when they needed to solve similar problems in the future.
|
||||
|
||||
In short, Spotify developers needed to continue building industry leading features at breakneck speed, while simultaneously maintaining a mental model for all the software at Spotify (oh, and help every new joiner develop that mental model as well!).
|
||||
<br>
|
||||
|
||||
## Three jobs: Create, manage, explore
|
||||
|
||||
Around the same time, the [jobs to be done](https://hbr.org/2016/09/know-your-customers-jobs-to-be-done) framework was becoming popular and luckily, a few Spotifiers helped guide the vision for a _sense-making tool for developers_ toward using it. After user research and many failed attempts, we landed on three jobs Spotify developers needed to do consistently:
|
||||
<br>
|
||||
|
||||
- **Create**: Spotify developers want to delight their customers with incredible features. They create new software to do that.
|
||||
- **Manage**: Spotify developers are proud of their work and treat their software and data like products. That requires maintaining all the software they own on an ongoing basis.
|
||||
- **Explore**: Spotify developers want to solve new, yet unsolved problems. They try to build on existing systems to help them do that.
|
||||
|
||||
So: make software, maintain the software you own throughout its lifecycle, and integrate with other people’s software.
|
||||
|
||||
Within today’s complex development environments, there are barriers both big and small that get in the way of those three jobs. Backstage provides the building blocks for removing those barriers, streamlining your development cycle, and letting developers do what they really want to do: build great features. Let’s take a closer look at each of these jobs.
|
||||
|
||||

|
||||
|
||||
### Create
|
||||
|
||||
**Job:** You’re an engineer, ready to start building a new microservice. Do you just pick whatever framework you want? How do you reserve capacity to run your service in production? What about managing CI/CD?
|
||||
|
||||
**Tool:** At Spotify, we use **Backstage Software Templates** to simplify all of this, reducing time–to–Hello World to just a few clicks. Instead of researching Spring Boot versus Helidon, opening a Jira ticket, rummaging through docs, and configuring CI automations, you just choose a template and your project is automatically set up in the repo for you, with the CI already running your first build.
|
||||
|
||||
**Result:** By making it easier to start new projects, your engineers get to the good part of coding features faster. And your organization’s best practices are built into the templates, encouraging standards and reducing complexity in your tech ecosystem.
|
||||
|
||||

|
||||
|
||||
### Manage
|
||||
|
||||
**Job:** You’re on a small team that owns a dozen services. Whenever you update and deploy those services, you’re switching between your CI, the AWS console, a security dashboard, and a CLI so you can try to figure out which Kubernetes cluster your service ended up on. In other words, you have a lot of open windows and tabs, and each step means switching to a new interface.
|
||||
|
||||
**Tool:** All of your team’s software components are organized together on one page in the **Backstage Service Catalog**. Go to any service’s page from there and its CI/CD status, [Kubernetes deployment status](https://backstage.io/blog/2021/01/12/new-backstage-feature-kubernetes-for-service-owners), documentation, security checks — and everything else related to that service — is grouped together in one seamless interface that shows you only the info you want.
|
||||
|
||||
**Result**: One page in Backstage has everything you need to manage the software you own. No more context-switching. No more digging through your cloud provider’s obscure admin features. Outside the repo and your IDE, everything you need to manage your service is right inside Backstage.
|
||||
|
||||

|
||||
|
||||
### Explore
|
||||
|
||||
**Job:** You're building a new mobile feature that needs to ensure a user is paying for the premium version of your product — but someone must’ve already built a library that handles that, right? A company-wide email and a few calls for help on Slack yield no response, so you resign yourself to building the capability yourself. Turns out someone did build the library you needed. They were just on vacation so they didn’t see your messages. How do you enable better discovery and collaboration across your org?
|
||||
|
||||
**Tools:** At Spotify, anyone can find everyone else’s software — because everything is centralized inside Backstage, organized by the **Backstage Service Catalog** and accessible by **search**. Go to any library or services page, and you’ll find the owners and documentation, even its API and how to extend it if need be.
|
||||
|
||||
**Result:** One place for everything, one place to search. Developers can more easily share components, build on top of each other’s work, and discover tools, libraries, frameworks, documentation, system designs, org charts, and more.
|
||||
|
||||
## Sounds great. Where do I start?
|
||||
|
||||
After talking with companies who have already adopted Backstage, we’ve seen a few common strategies for getting started. The different strategies are based on the size of your engineering org (which often also corresponds with how fast you’re growing).
|
||||
|
||||

|
||||
<br>
|
||||
|
||||
### My org has ~200 engineers (and is growing fast)
|
||||
|
||||
You’re big enough to start feeling the pain, and only getting bigger. Onboarding and discoverability are your biggest challenges.
|
||||
|
||||
**Pain points:**
|
||||
|
||||
- This size seems to be the tipping point — where complexity is taking hold, collaboration starts breaking down, and ad hoc solutions stop working.
|
||||
- Often this is also when you’re growing so fast (doubling in size every 6–12 months) that there are now more new engineers than old engineers.
|
||||
- New engineers can’t find anything, so they ask around, which pulls all your other engineers off-task with interruptions.
|
||||
- Logjams form. One company told us how it was taking 3–4 days for pull requests to get merged.
|
||||
|
||||
**Recommendation — Explore, then create:**
|
||||
|
||||
- New engineers need an easy way to find out how to do things, so you’re not just adding bodies, you’re adding happy, productive contributors.
|
||||
- To streamline onboarding, start with centralizing your documentation and making your tools and software components discoverable in Backstage.
|
||||
- At this size, you might not have a dedicated platform/infra team in place yet, but Backstage can provide the framework for centralizing and sharing knowledge — from managing compliance requirements to finding the right API documentation.
|
||||
- This allows both new and old engineers to collaborate more efficiently, easily discover best practices, and cuts down on duplicated work (e.g., a new team doesn’t end up rebuilding a database that already exists but nobody can find).
|
||||
|
||||

|
||||
|
||||
### My org has ~1,000 engineers
|
||||
|
||||
You’re officially big. Lots and lots of teams are managing lots and lots of software — and the frustration of switching between all the various tools to do that is growing exponentially.
|
||||
|
||||
**Pain points:**
|
||||
|
||||
- Fragmentation and entropy are real threats to productivity. From security requirements to cluster management to a thousand data endpoints, there’s too much to keep track of, leading to cognitive overload.
|
||||
- A death by a thousand cuts: constantly logging into new interfaces — from your cloud provider’s console to some brand new security tool then back to your CI/CD — is bogging your engineers down with too much context-switching and a lot of noise.
|
||||
- Every day, it’s getting more and more difficult for one team to manage their own microservices, data, and documentation, let alone share their knowledge with other teams.
|
||||
|
||||
**Recommendation — Manage, then explore, then create:**
|
||||
|
||||
- Backstage will allow your teams to get a handle on the software they own, since everything they need to manage it is in one place.
|
||||
- From CI/CD status to documentation to deciphering a monorepo, Backstage makes it easy to manage a service (or other software component) on a day-to-day basis.
|
||||
- The service catalog also helps your developers visualize your existing software ecosystem, beyond the software they own. And with Backstage Software Templates, every new software component is already added to the catalog.
|
||||
- Even at this scale, a small platform team should be all it takes to build and maintain your own version of Backstage. At Spotify, we have a 4-person team responsible for our internal version of Backstage, which is used by all of Spotify’s 1,600+ developers.
|
||||
|
||||

|
||||
|
||||
### My org has 1,000+ engineers
|
||||
|
||||
Integrating infrastructure of this size and complexity can seem overwhelming. It’s an even bigger challenge to bring this level of change to a well-established culture with ingrained processes.
|
||||
|
||||
**Pain points:**
|
||||
|
||||
- When you’re this large, you’ve incorporated a lot of technology and processes, as well as a lot of partners, each with their own technologies and processes.
|
||||
- You’re focussed on “replacing cruft” and bringing discoverability and order to your vast ecosystem of components and tools.
|
||||
- Getting your tools adopted by your engineers while modernizing your tech stack and coordinating with your infra teams to improve your engineering practices across the whole company… it’s a lot.
|
||||
|
||||
**Recommendation — Create, then manage, then explore:**
|
||||
|
||||
- The fastest way to bring change to your ecosystem is to start at the beginning of the chain with Backstage Software Templates.
|
||||
- With every new component created with your templates, you’re establishing best practices and rewarding your developers with a streamlined experience focused on their needs — all the while building up your new service catalog.
|
||||
- The more the templates ease the process of starting up a project, the more your engineers will adopt them, and the faster the other benefits of Backstage will build up, transforming productivity across your organization. ([That’s how we did it at Spotify](https://open.spotify.com/episode/7iuQ3ew1Wwpuiq6LbBKzCl).)
|
||||
|
||||
## More questions about adopting Backstage?
|
||||
|
||||
[Contact the Backstage team at Spotify.](https://calendly.com/spotify-backstage) We’ll share more about what we’ve learned from our experience here at Spotify — and from other companies who are already using Backstage to transform their developer experience.
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 795 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 314 KiB |
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: iLert
|
||||
author: iLert
|
||||
authorUrl: https://www.ilert.com
|
||||
category: Monitoring
|
||||
description: iLert is a platform for alerting, on-call management and uptime monitoring targeted at DevOps and IT teams.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/ilert
|
||||
iconUrl: https://avatars.githubusercontent.com/u/13230510?s=200&v=4
|
||||
npmPackageName: '@backstage/plugin-ilert'
|
||||
tags:
|
||||
- monitoring
|
||||
- errors
|
||||
- alerting
|
||||
- uptime
|
||||
- on-call
|
||||
@@ -40,6 +40,7 @@
|
||||
"features/software-catalog/references",
|
||||
"features/software-catalog/well-known-annotations",
|
||||
"features/software-catalog/well-known-relations",
|
||||
"features/software-catalog/well-known-statuses",
|
||||
"features/software-catalog/extending-the-model",
|
||||
"features/software-catalog/external-integrations",
|
||||
"features/software-catalog/software-catalog-api"
|
||||
@@ -65,7 +66,8 @@
|
||||
"features/software-templates/writing-templates",
|
||||
"features/software-templates/builtin-actions",
|
||||
"features/software-templates/writing-custom-actions",
|
||||
"features/software-templates/template-legacy"
|
||||
"features/software-templates/template-legacy",
|
||||
"features/software-templates/migrating-from-v1alpha1-to-v1beta2"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,13 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="500px" height="500px" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Export</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ship" transform="translate(249.680798, 211.000000) scale(-1, 1) translate(-249.680798, -211.000000) translate(35.680798, -39.000000)" fill-rule="nonzero">
|
||||
<g id="Flamez" transform="translate(279.925187, 455.280081) rotate(-90.000000) translate(-279.925187, -455.280081) translate(235.660848, 411.639183)">
|
||||
<path d="M77.6240648,9.79348504 C64.5660848,-3.26449501 42.8017456,-3.26449501 29.7437656,9.79348504 C11.6072319,27.9300187 0.725685786,80.8882481 8.85963511e-15,83.064682 C8.85963511e-15,84.5154302 8.85963511e-15,85.241116 0.725685786,85.9668017 C2.17643392,86.6924875 3.62780549,86.6924875 5.07855362,86.6924875 C7.25498753,85.9668017 60.213217,75.810318 78.3497506,56.9487219 C91.4077307,43.8901185 91.4077307,22.8514651 77.6240648,9.79348504 Z" id="Path" fill="#F79548"></path>
|
||||
<path d="M75.4476309,10.5185474 C68.9183292,3.98924564 58.036783,3.98924564 51.5074813,10.5185474 C42.0766833,19.9493454 36.9981297,46.0659289 36.9981297,46.7916147 C36.9981297,47.5173005 36.9981297,48.2423628 37.7238155,48.2423628 C38.4495012,48.9680486 38.4495012,48.9680486 39.1745636,48.9680486 C40.6253117,48.9680486 66.7418953,43.1644327 75.4476309,34.458697 C81.9769327,27.9300187 81.9769327,17.0478491 75.4476309,10.5185474 Z" id="Path" fill="#FCBB76"></path>
|
||||
</g>
|
||||
<path d="M216.186409,55.2394015 C218.49257,55.2394015 220.58047,56.1742348 222.091833,57.6855981 C223.534498,59.128263 224.451854,61.0962436 224.532269,63.2779325 L224.53803,63.5910224 L224.53803,91.005836 L353.369587,261.704121 C354.323776,262.968115 354.87911,264.447677 355.019478,265.965658 C355.159883,267.484039 354.88511,269.040862 354.179061,270.458806 C353.472845,271.876805 352.395871,273.033852 351.099552,273.836436 C349.895645,274.581806 348.502551,275.021453 347.041547,275.080457 L346.703242,275.087282 L224.53803,275.087282 L224.53803,303.904613 L346.684539,303.904613 C348.308638,303.904613 349.858968,304.375354 351.174692,305.21426 C352.490519,306.053232 353.571696,307.260417 354.25731,308.732969 C354.942424,310.205367 355.16974,311.809443 354.964363,313.356104 C354.77365,314.792333 354.20983,316.179047 353.29311,317.364769 L353.075304,317.634761 L309.451382,369.468132 C307.930114,371.274871 305.719044,372.349921 303.368443,372.436371 L303.061097,372.44202 L124.602244,372.44202 C122.240117,372.44202 119.994057,371.442358 118.41433,369.699238 L118.212103,369.467679 L74.5886595,317.634759 C73.5428392,316.392134 72.9047814,314.903039 72.6994228,313.356514 C72.494029,311.809724 72.7214803,310.205497 73.4066556,308.732969 C74.0917295,307.260659 75.1726391,306.053772 76.4882065,305.214939 C77.7099169,304.435952 79.1339993,303.974371 80.6313862,303.912406 L80.9781796,303.905237 L207.834788,303.905237 L207.834788,274.720698 L106.28616,274.720698 C103.979999,274.720698 101.892098,273.785865 100.380735,272.274502 C98.8693719,270.763138 97.9345387,268.675238 97.9345387,266.369077 C97.9345387,233.249109 111.496611,180.759139 130.891816,138.720038 C142.0216,114.595575 154.255888,95.1994886 167.266567,81.0803824 C183.02715,63.976526 199.471354,55.2394015 216.186409,55.2394015 Z M328.739655,320.607855 L98.9224399,320.607855 L128.488453,355.738155 L299.173642,355.738155 L328.739655,320.607855 Z M224.53803,118.732589 L224.53803,258.384663 L329.936488,258.384663 L224.53803,118.732589 Z M207.834165,73.0839035 C187.31365,78.5957312 165.169959,104.292863 146.058313,145.71761 C129.135086,182.399574 116.887708,227.020442 114.917014,258.016833 L114.917014,258.016833 L207.834165,258.016833 Z" id="Shape" stroke="#DDDDDD" stroke-width="2" fill="#E8E8E8" transform="translate(213.840829, 213.840711) rotate(45.000000) translate(-213.840829, -213.840711) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500" version="1.1" viewBox="0 0 500 500"><title>Export</title><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="ship" fill-rule="nonzero" transform="translate(249.680798, 211.000000) scale(-1, 1) translate(-249.680798, -211.000000) translate(35.680798, -39.000000)"><g id="Flamez" transform="translate(279.925187, 455.280081) rotate(-90.000000) translate(-279.925187, -455.280081) translate(235.660848, 411.639183)"><path id="Path" fill="#F79548" d="M77.6240648,9.79348504 C64.5660848,-3.26449501 42.8017456,-3.26449501 29.7437656,9.79348504 C11.6072319,27.9300187 0.725685786,80.8882481 8.85963511e-15,83.064682 C8.85963511e-15,84.5154302 8.85963511e-15,85.241116 0.725685786,85.9668017 C2.17643392,86.6924875 3.62780549,86.6924875 5.07855362,86.6924875 C7.25498753,85.9668017 60.213217,75.810318 78.3497506,56.9487219 C91.4077307,43.8901185 91.4077307,22.8514651 77.6240648,9.79348504 Z"/><path id="Path" fill="#FCBB76" d="M75.4476309,10.5185474 C68.9183292,3.98924564 58.036783,3.98924564 51.5074813,10.5185474 C42.0766833,19.9493454 36.9981297,46.0659289 36.9981297,46.7916147 C36.9981297,47.5173005 36.9981297,48.2423628 37.7238155,48.2423628 C38.4495012,48.9680486 38.4495012,48.9680486 39.1745636,48.9680486 C40.6253117,48.9680486 66.7418953,43.1644327 75.4476309,34.458697 C81.9769327,27.9300187 81.9769327,17.0478491 75.4476309,10.5185474 Z"/></g><path id="Shape" fill="#E8E8E8" stroke="#DDD" stroke-width="2" d="M216.186409,55.2394015 C218.49257,55.2394015 220.58047,56.1742348 222.091833,57.6855981 C223.534498,59.128263 224.451854,61.0962436 224.532269,63.2779325 L224.53803,63.5910224 L224.53803,91.005836 L353.369587,261.704121 C354.323776,262.968115 354.87911,264.447677 355.019478,265.965658 C355.159883,267.484039 354.88511,269.040862 354.179061,270.458806 C353.472845,271.876805 352.395871,273.033852 351.099552,273.836436 C349.895645,274.581806 348.502551,275.021453 347.041547,275.080457 L346.703242,275.087282 L224.53803,275.087282 L224.53803,303.904613 L346.684539,303.904613 C348.308638,303.904613 349.858968,304.375354 351.174692,305.21426 C352.490519,306.053232 353.571696,307.260417 354.25731,308.732969 C354.942424,310.205367 355.16974,311.809443 354.964363,313.356104 C354.77365,314.792333 354.20983,316.179047 353.29311,317.364769 L353.075304,317.634761 L309.451382,369.468132 C307.930114,371.274871 305.719044,372.349921 303.368443,372.436371 L303.061097,372.44202 L124.602244,372.44202 C122.240117,372.44202 119.994057,371.442358 118.41433,369.699238 L118.212103,369.467679 L74.5886595,317.634759 C73.5428392,316.392134 72.9047814,314.903039 72.6994228,313.356514 C72.494029,311.809724 72.7214803,310.205497 73.4066556,308.732969 C74.0917295,307.260659 75.1726391,306.053772 76.4882065,305.214939 C77.7099169,304.435952 79.1339993,303.974371 80.6313862,303.912406 L80.9781796,303.905237 L207.834788,303.905237 L207.834788,274.720698 L106.28616,274.720698 C103.979999,274.720698 101.892098,273.785865 100.380735,272.274502 C98.8693719,270.763138 97.9345387,268.675238 97.9345387,266.369077 C97.9345387,233.249109 111.496611,180.759139 130.891816,138.720038 C142.0216,114.595575 154.255888,95.1994886 167.266567,81.0803824 C183.02715,63.976526 199.471354,55.2394015 216.186409,55.2394015 Z M328.739655,320.607855 L98.9224399,320.607855 L128.488453,355.738155 L299.173642,355.738155 L328.739655,320.607855 Z M224.53803,118.732589 L224.53803,258.384663 L329.936488,258.384663 L224.53803,118.732589 Z M207.834165,73.0839035 C187.31365,78.5957312 165.169959,104.292863 146.058313,145.71761 C129.135086,182.399574 116.887708,227.020442 114.917014,258.016833 L114.917014,258.016833 L207.834165,258.016833 Z" transform="translate(213.840829, 213.840711) rotate(45.000000) translate(-213.840829, -213.840711)"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -37,6 +37,7 @@ nav:
|
||||
- Entity References: 'features/software-catalog/references.md'
|
||||
- Well-known Annotations: 'features/software-catalog/well-known-annotations.md'
|
||||
- Well-known Relations: 'features/software-catalog/well-known-relations.md'
|
||||
- Well-known Statuses: 'features/software-catalog/well-known-statuses.md'
|
||||
- Extending the model: 'features/software-catalog/extending-the-model.md'
|
||||
- External integrations: 'features/software-catalog/external-integrations.md'
|
||||
- API: 'features/software-catalog/api.md'
|
||||
@@ -53,6 +54,8 @@ nav:
|
||||
- Builtin Actions: 'features/software-templates/builtin-actions.md'
|
||||
- Writing Custom Actions: 'features/software-templates/writing-custom-actions.md'
|
||||
- Writing Templates (Legacy): 'features/software-templates/legacy.md'
|
||||
- Migrating from v1alpha1 to v1beta2 templates: 'features/software-templates/migrating-from-v1alpha1-to-v1beta2.md'
|
||||
|
||||
- Backstage Search:
|
||||
- Overview: 'features/search/README.md'
|
||||
- Search Architecture: 'features/search/architecture.md'
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"prettier:check": "prettier --check .",
|
||||
"lerna": "lerna",
|
||||
"storybook": "yarn workspace storybook start",
|
||||
"build-storybook": "yarn workspace storybook build-storybook"
|
||||
"build-storybook": "yarn workspace storybook build-storybook",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
@@ -60,18 +61,13 @@
|
||||
"command-exists": "^1.2.9",
|
||||
"concurrently": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"husky": "^4.2.3",
|
||||
"husky": "^6.0.0",
|
||||
"lerna": "^4.0.0",
|
||||
"lint-staged": "^10.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"shx": "^0.3.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7cbfcae48]
|
||||
- Updated dependencies [2bfec55a6]
|
||||
- Updated dependencies [f7f7783a3]
|
||||
- Updated dependencies [65e6c4541]
|
||||
- Updated dependencies [68fdbf014]
|
||||
- Updated dependencies [5da6a561d]
|
||||
- Updated dependencies [ca6e0ab69]
|
||||
- Updated dependencies [5914a76d5]
|
||||
- Updated dependencies [81d7b9c6f]
|
||||
- Updated dependencies [a62cfe068]
|
||||
- Updated dependencies [35e091604]
|
||||
- Updated dependencies [a53f3d603]
|
||||
- Updated dependencies [b203699e9]
|
||||
- @backstage/plugin-cost-insights@0.10.0
|
||||
- @backstage/cli@0.6.12
|
||||
- @backstage/catalog-model@0.7.10
|
||||
- @backstage/plugin-scaffolder@0.9.5
|
||||
- @backstage/core@0.7.10
|
||||
- @backstage/plugin-api-docs@0.4.14
|
||||
- @backstage/plugin-cloudbuild@0.2.15
|
||||
- @backstage/plugin-github-actions@0.4.7
|
||||
- @backstage/plugin-techdocs@0.9.3
|
||||
- @backstage/plugin-catalog-import@0.5.7
|
||||
- @backstage/plugin-catalog@0.5.8
|
||||
|
||||
## 0.2.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.28",
|
||||
"version": "0.2.29",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.9",
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/core": "^0.7.9",
|
||||
"@backstage/catalog-model": "^0.7.10",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@backstage/core": "^0.7.10",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-api-docs": "^0.4.13",
|
||||
"@backstage/plugin-api-docs": "^0.4.14",
|
||||
"@backstage/plugin-badges": "^0.2.1",
|
||||
"@backstage/plugin-catalog": "^0.5.7",
|
||||
"@backstage/plugin-catalog-import": "^0.5.6",
|
||||
"@backstage/plugin-catalog": "^0.5.8",
|
||||
"@backstage/plugin-catalog-import": "^0.5.7",
|
||||
"@backstage/plugin-catalog-react": "^0.1.6",
|
||||
"@backstage/plugin-circleci": "^0.2.14",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.14",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.15",
|
||||
"@backstage/plugin-code-coverage": "^0.1.3",
|
||||
"@backstage/plugin-cost-insights": "^0.9.1",
|
||||
"@backstage/plugin-cost-insights": "^0.10.0",
|
||||
"@backstage/plugin-explore": "^0.3.5",
|
||||
"@backstage/plugin-gcp-projects": "^0.2.6",
|
||||
"@backstage/plugin-github-actions": "^0.4.6",
|
||||
"@backstage/plugin-github-actions": "^0.4.7",
|
||||
"@backstage/plugin-graphiql": "^0.2.11",
|
||||
"@backstage/plugin-jenkins": "^0.4.3",
|
||||
"@backstage/plugin-kafka": "^0.2.7",
|
||||
@@ -29,12 +29,12 @@
|
||||
"@backstage/plugin-org": "^0.3.13",
|
||||
"@backstage/plugin-pagerduty": "0.3.4",
|
||||
"@backstage/plugin-rollbar": "^0.3.5",
|
||||
"@backstage/plugin-scaffolder": "^0.9.4",
|
||||
"@backstage/plugin-scaffolder": "^0.9.5",
|
||||
"@backstage/plugin-search": "^0.3.6",
|
||||
"@backstage/plugin-sentry": "^0.3.10",
|
||||
"@backstage/plugin-shortcuts": "^0.1.2",
|
||||
"@backstage/plugin-tech-radar": "^0.3.11",
|
||||
"@backstage/plugin-techdocs": "^0.9.2",
|
||||
"@backstage/plugin-techdocs": "^0.9.3",
|
||||
"@backstage/plugin-todo": "^0.1.1",
|
||||
"@backstage/plugin-user-settings": "^0.2.10",
|
||||
"@backstage/theme": "^0.2.7",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@roadiehq/backstage-plugin-buildkite": "^1.0.0",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^1.0.0",
|
||||
"@roadiehq/backstage-plugin-github-insights": "^1.1.10",
|
||||
"@roadiehq/backstage-plugin-github-pull-requests": "^1.0.0",
|
||||
"@roadiehq/backstage-plugin-travis-ci": "^1.0.0",
|
||||
"history": "^5.0.0",
|
||||
@@ -56,7 +56,7 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@testing-library/cypress": "^7.0.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
@@ -67,7 +67,7 @@
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^4.2.0",
|
||||
"cypress": "^7.3.0",
|
||||
"eslint-plugin-cypress": "^2.10.3",
|
||||
"start-server-and-test": "^1.10.11"
|
||||
},
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import {
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasSubcomponentsCard,
|
||||
EntityHasSystemsCard,
|
||||
@@ -37,6 +39,9 @@ import {
|
||||
EntitySwitch,
|
||||
isComponentType,
|
||||
isKind,
|
||||
EntityHasResourcesCard,
|
||||
EntityOrphanWarning,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityCircleCIContent,
|
||||
@@ -211,7 +216,15 @@ const errorsContent = (
|
||||
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isOrphan}>
|
||||
<Grid item xs={12}>
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
|
||||
@@ -223,7 +236,7 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={4} sm={6}>
|
||||
<Grid item md={4} xs={12}>
|
||||
<EntityLinksCard />
|
||||
</Grid>
|
||||
|
||||
@@ -257,7 +270,7 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={6}>
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityHasSubcomponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -288,6 +301,17 @@ const serviceEntityPage = (
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
@@ -336,6 +360,17 @@ const websiteEntityPage = (
|
||||
{errorsContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
@@ -466,6 +501,9 @@ const systemPage = (
|
||||
<Grid item md={6}>
|
||||
<EntityHasApisCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/diagram" title="Diagram">
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c7dad9218: All cache-related connection errors are now handled and logged by the cache manager. App Integrators may provide an optional error handler when instantiating the cache manager if custom error handling is needed.
|
||||
|
||||
```typescript
|
||||
// Providing an error handler
|
||||
const cacheManager = CacheManager.fromConfig(config, {
|
||||
onError: e => {
|
||||
if (isSomehowUnrecoverable(e)) {
|
||||
gracefullyShutThingsDown();
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- 65e6c4541: Remove circular dependencies
|
||||
- 5001de908: Change GitlabUrlReader to SHA timestamp compare using only commits that modify given file path, if file path given
|
||||
- Updated dependencies [65e6c4541]
|
||||
- Updated dependencies [290405276]
|
||||
- @backstage/integration@0.5.3
|
||||
- @backstage/config-loader@0.6.2
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface CacheClient {
|
||||
// @public
|
||||
export class CacheManager {
|
||||
forPlugin(pluginId: string): PluginCacheManager;
|
||||
static fromConfig(config: Config): CacheManager;
|
||||
static fromConfig(config: Config, options?: CacheManagerOptions): CacheManager;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -31,9 +31,9 @@
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/config-loader": "^0.6.1",
|
||||
"@backstage/config-loader": "^0.6.2",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/integration": "^0.5.2",
|
||||
"@backstage/integration": "^0.5.3",
|
||||
"@google-cloud/storage": "^5.8.0",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/cors": "^2.8.6",
|
||||
@@ -76,8 +76,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/concat-stream": "^1.6.0",
|
||||
|
||||
@@ -92,7 +92,8 @@ export class DefaultCacheClient implements CacheClient {
|
||||
const wellFormedKey = Buffer.from(candidateKey).toString('base64');
|
||||
|
||||
// Memcache in particular doesn't do well with keys > 250 bytes.
|
||||
if (wellFormedKey.length < 250) {
|
||||
// Padded because a plugin ID is also prepended to the key.
|
||||
if (wellFormedKey.length < 200) {
|
||||
return wellFormedKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,16 @@ describe('CacheManager', () => {
|
||||
expect(client).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('attaches error handler to client', () => {
|
||||
const pluginId = 'error-test';
|
||||
manager.forPlugin(pluginId).getClient();
|
||||
|
||||
const client = DefaultCacheClient as jest.Mock;
|
||||
const mockCalls = client.mock.calls.splice(-1);
|
||||
const realClient = mockCalls[0][0].client as Keyv;
|
||||
expect(realClient.on).toHaveBeenCalledWith('error', expect.any(Function));
|
||||
});
|
||||
|
||||
it('provides different plugins different cache clients', async () => {
|
||||
const plugin1Id = 'test1';
|
||||
const plugin2Id = 'test2';
|
||||
@@ -163,4 +173,53 @@ describe('CacheManager', () => {
|
||||
expect(mockMemcacheCalls[0][0]).toEqual(expectedHost);
|
||||
});
|
||||
});
|
||||
|
||||
describe('connection errors', () => {
|
||||
it('uses provided logger', () => {
|
||||
// Set up and inject mock logger.
|
||||
const mockLogger = { child: jest.fn(), error: jest.fn() };
|
||||
mockLogger.child.mockImplementation(() => mockLogger as any);
|
||||
const manager = CacheManager.fromConfig(defaultConfig(), {
|
||||
logger: mockLogger as any,
|
||||
});
|
||||
|
||||
// Set up a cache client using the configured manager.
|
||||
manager.forPlugin('error-logger-test').getClient();
|
||||
|
||||
// Retrieve the error handler attached to the cache client.
|
||||
const client = DefaultCacheClient as jest.Mock;
|
||||
const mockCalls = client.mock.calls.splice(-1);
|
||||
const realClient = mockCalls[0][0].client as Keyv;
|
||||
const realOnError = realClient.on as jest.Mock;
|
||||
const realHandler = realOnError.mock.calls.splice(-1)[0][1];
|
||||
|
||||
// Invoke the actual error handler.
|
||||
const expectedError = new Error('some error');
|
||||
realHandler(expectedError);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(expectedError);
|
||||
});
|
||||
|
||||
it('calls provided handler', () => {
|
||||
// Set up and inject mock logger.
|
||||
const mockHandler = jest.fn();
|
||||
const manager = CacheManager.fromConfig(defaultConfig(), {
|
||||
onError: mockHandler,
|
||||
});
|
||||
|
||||
// Set up a cache client using the configured manager.
|
||||
manager.forPlugin('error-handler-test').getClient();
|
||||
|
||||
// Retrieve the error handler attached to the cache client.
|
||||
const client = DefaultCacheClient as jest.Mock;
|
||||
const mockCalls = client.mock.calls.splice(-1);
|
||||
const realClient = mockCalls[0][0].client as Keyv;
|
||||
const realOnError = realClient.on as jest.Mock;
|
||||
const realHandler = realOnError.mock.calls.splice(-1)[0][1];
|
||||
|
||||
// Invoke the actual error handler.
|
||||
const expectedError = new Error('some error');
|
||||
realHandler(expectedError);
|
||||
expect(mockHandler).toHaveBeenCalledWith(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,9 +18,15 @@ import { Config } from '@backstage/config';
|
||||
import Keyv from 'keyv';
|
||||
// @ts-expect-error
|
||||
import KeyvMemcache from 'keyv-memcache';
|
||||
import { Logger } from 'winston';
|
||||
import { getRootLogger } from '../logging';
|
||||
import { DefaultCacheClient, CacheClient } from './CacheClient';
|
||||
import { NoStore } from './NoStore';
|
||||
import { PluginCacheManager } from './types';
|
||||
import {
|
||||
CacheManagerOptions,
|
||||
OptionalOnError,
|
||||
PluginCacheManager,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Implements a Cache Manager which will automatically create new cache clients
|
||||
@@ -38,8 +44,10 @@ export class CacheManager {
|
||||
none: this.getNoneClient,
|
||||
};
|
||||
|
||||
private readonly logger: Logger;
|
||||
private readonly store: keyof CacheManager['storeFactories'];
|
||||
private readonly connection: string;
|
||||
private readonly errorHandler: OptionalOnError;
|
||||
|
||||
/**
|
||||
* Creates a new CacheManager instance by reading from the `backend` config
|
||||
@@ -47,25 +55,38 @@ export class CacheManager {
|
||||
*
|
||||
* @param config The loaded application configuration.
|
||||
*/
|
||||
static fromConfig(config: Config): CacheManager {
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options: CacheManagerOptions = {},
|
||||
): CacheManager {
|
||||
// If no `backend.cache` config is provided, instantiate the CacheManager
|
||||
// with a "NoStore" cache client.
|
||||
const store = config.getOptionalString('backend.cache.store') || 'none';
|
||||
const connectionString =
|
||||
config.getOptionalString('backend.cache.connection') || '';
|
||||
return new CacheManager(store, connectionString);
|
||||
const logger = (options.logger || getRootLogger()).child({
|
||||
type: 'cacheManager',
|
||||
});
|
||||
return new CacheManager(store, connectionString, logger, options.onError);
|
||||
}
|
||||
|
||||
private constructor(store: string, connectionString: string) {
|
||||
private constructor(
|
||||
store: string,
|
||||
connectionString: string,
|
||||
logger: Logger,
|
||||
errorHandler: OptionalOnError,
|
||||
) {
|
||||
if (!this.storeFactories.hasOwnProperty(store)) {
|
||||
throw new Error(`Unknown cache store: ${store}`);
|
||||
}
|
||||
this.logger = logger;
|
||||
this.store = store as keyof CacheManager['storeFactories'];
|
||||
this.connection = connectionString;
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CacheManagerInstance for consumption by plugins.
|
||||
* Generates a PluginCacheManager for consumption by plugins.
|
||||
*
|
||||
* @param pluginId The plugin that the cache manager should be created for. Plugin names should be unique.
|
||||
*/
|
||||
@@ -73,6 +94,18 @@ export class CacheManager {
|
||||
return {
|
||||
getClient: (opts = {}): CacheClient => {
|
||||
const concreteClient = this.getClientWithTtl(pluginId, opts.defaultTtl);
|
||||
|
||||
// Always provide an error handler to avoid killing the process.
|
||||
concreteClient.on('error', (err: Error) => {
|
||||
// In all cases, just log the error.
|
||||
this.logger.error(err);
|
||||
|
||||
// Invoke any custom error handler if provided.
|
||||
if (typeof this.errorHandler === 'function') {
|
||||
this.errorHandler(err);
|
||||
}
|
||||
});
|
||||
|
||||
return new DefaultCacheClient({
|
||||
client: concreteClient,
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { CacheClient } from './CacheClient';
|
||||
|
||||
type ClientOptions = {
|
||||
@@ -25,6 +26,21 @@ type ClientOptions = {
|
||||
defaultTtl?: number;
|
||||
};
|
||||
|
||||
export type OptionalOnError = ((err: Error) => void) | undefined;
|
||||
|
||||
export type CacheManagerOptions = {
|
||||
/**
|
||||
* An optional logger for use by the PluginCacheManager.
|
||||
*/
|
||||
logger?: Logger;
|
||||
|
||||
/**
|
||||
* An optional handler for connection errors emitted from the underlying data
|
||||
* store.
|
||||
*/
|
||||
onError?: OptionalOnError;
|
||||
};
|
||||
|
||||
/**
|
||||
* The PluginCacheManager manages access to cache stores that Plugins get.
|
||||
*/
|
||||
|
||||
@@ -29,11 +29,11 @@ import path from 'path';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { AzureUrlReader } from './AzureUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReadTreeResponseFactory,
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
|
||||
@@ -28,9 +28,9 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import { NotModifiedError } from '@backstage/errors';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReadTreeResponseFactory,
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { msw } from '@backstage/test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { FetchUrlReader } from './FetchUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
|
||||
describe('FetchUrlReader', () => {
|
||||
const worker = setupServer();
|
||||
@@ -45,7 +45,7 @@ describe('FetchUrlReader', () => {
|
||||
},
|
||||
}),
|
||||
logger: getVoidLogger(),
|
||||
treeResponseFactory: ReadTreeResponseFactory.create({
|
||||
treeResponseFactory: DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -35,9 +35,9 @@ import {
|
||||
GhTreeResponse,
|
||||
GithubUrlReader,
|
||||
} from './GithubUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import {
|
||||
ReadTreeResponseFactory,
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
|
||||
@@ -24,7 +24,7 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { GitlabUrlReader } from './GitlabUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import { NotModifiedError, NotFoundError } from '@backstage/errors';
|
||||
import {
|
||||
GitLabIntegration,
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
@@ -190,11 +190,17 @@ describe('GitlabUrlReader', () => {
|
||||
default_branch: 'main',
|
||||
};
|
||||
|
||||
const branchGitlabApiResponse = {
|
||||
commit: {
|
||||
const commitsGitlabApiResponse = [
|
||||
{
|
||||
id: 'sha123abc',
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
const specificPathCommitsGitlabApiResponse = [
|
||||
{
|
||||
id: 'sha456def',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
@@ -221,17 +227,29 @@ describe('GitlabUrlReader', () => {
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(branchGitlabApiResponse),
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/branches/branchDoesNotExist',
|
||||
(_, res, ctx) => res(ctx.status(404)),
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/commits',
|
||||
(req, res, ctx) => {
|
||||
const refName = req.url.searchParams.get('ref_name');
|
||||
if (refName === 'main') {
|
||||
const filepath = req.url.searchParams.get('path');
|
||||
if (filepath === 'testFilepath') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(specificPathCommitsGitlabApiResponse),
|
||||
);
|
||||
}
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(commitsGitlabApiResponse),
|
||||
);
|
||||
}
|
||||
if (refName === 'branchDoesNotExist') {
|
||||
return res(ctx.status(404));
|
||||
}
|
||||
return res();
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock',
|
||||
@@ -243,13 +261,26 @@ describe('GitlabUrlReader', () => {
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(branchGitlabApiResponse),
|
||||
),
|
||||
'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/commits',
|
||||
(req, res, ctx) => {
|
||||
const refName = req.url.searchParams.get('ref_name');
|
||||
if (refName === 'main') {
|
||||
const filepath = req.url.searchParams.get('path');
|
||||
if (filepath === 'testFilepath') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(specificPathCommitsGitlabApiResponse),
|
||||
);
|
||||
}
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(commitsGitlabApiResponse),
|
||||
);
|
||||
}
|
||||
return res();
|
||||
},
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main',
|
||||
@@ -351,7 +382,7 @@ describe('GitlabUrlReader', () => {
|
||||
).resolves.toBe('# Test\n');
|
||||
});
|
||||
|
||||
it('throws a NotModifiedError when given a etag in options', async () => {
|
||||
it('throws a NotModifiedError when given a etag in options matching last commit', async () => {
|
||||
const fnGitlab = async () => {
|
||||
await gitlabProcessor.readTree('https://gitlab.com/backstage/mock', {
|
||||
etag: 'sha123abc',
|
||||
@@ -371,6 +402,29 @@ describe('GitlabUrlReader', () => {
|
||||
await expect(fnHostedGitlab).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('throws a NotModifiedError when given a etag in options matching last commit affecting specified filepath', async () => {
|
||||
const fnGitlab = async () => {
|
||||
await gitlabProcessor.readTree(
|
||||
'https://gitlab.com/backstage/mock/blob/main/testFilepath',
|
||||
{
|
||||
etag: 'sha456def',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const fnHostedGitlab = async () => {
|
||||
await hostedGitlabProcessor.readTree(
|
||||
'https://gitlab.mycompany.com/backstage/mock/blob/main/testFilepath',
|
||||
{
|
||||
etag: 'sha456def',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
await expect(fnGitlab).rejects.toThrow(NotModifiedError);
|
||||
await expect(fnHostedGitlab).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
|
||||
it('should not throw error when given an outdated etag in options', async () => {
|
||||
const response = await gitlabProcessor.readTree(
|
||||
'https://gitlab.com/backstage/mock/tree/main',
|
||||
@@ -389,12 +443,12 @@ describe('GitlabUrlReader', () => {
|
||||
});
|
||||
|
||||
it('should throw error on missing branch', async () => {
|
||||
const fnGithub = async () => {
|
||||
const fnGitlab = async () => {
|
||||
await gitlabProcessor.readTree(
|
||||
'https://gitlab.com/backstage/mock/tree/branchDoesNotExist',
|
||||
);
|
||||
};
|
||||
await expect(fnGithub).rejects.toThrow(NotFoundError);
|
||||
await expect(fnGitlab).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -408,11 +462,11 @@ describe('GitlabUrlReader', () => {
|
||||
default_branch: 'main',
|
||||
};
|
||||
|
||||
const branchGitlabApiResponse = {
|
||||
commit: {
|
||||
const commitsGitlabApiResponse = [
|
||||
{
|
||||
id: 'sha123abc',
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
@@ -439,13 +493,18 @@ describe('GitlabUrlReader', () => {
|
||||
),
|
||||
),
|
||||
rest.get(
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/branches/main',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(branchGitlabApiResponse),
|
||||
),
|
||||
'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/commits',
|
||||
(req, res, ctx) => {
|
||||
const refName = req.url.searchParams.get('ref_name');
|
||||
if (refName === 'main') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json(commitsGitlabApiResponse),
|
||||
);
|
||||
}
|
||||
return res();
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,9 +25,9 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { stripFirstDirectoryFromPath } from './tree/util';
|
||||
import {
|
||||
ReadTreeResponseFactory,
|
||||
ReaderFactory,
|
||||
ReadTreeOptions,
|
||||
ReadTreeResponse,
|
||||
@@ -106,25 +106,30 @@ export class GitlabUrlReader implements UrlReader {
|
||||
// ref is an empty string if no branch is set in provided url to readTree.
|
||||
const branch = ref || projectGitlabResponseJson.default_branch;
|
||||
|
||||
// Fetch the latest commit in the provided or default branch to compare against
|
||||
// the provided sha.
|
||||
const branchGitlabResponse = await fetch(
|
||||
// Fetch the latest commit that modifies the the filepath in the provided or default branch
|
||||
// to compare against the provided sha.
|
||||
const commitsReqParams = new URLSearchParams();
|
||||
commitsReqParams.set('ref_name', branch);
|
||||
if (!!filepath) {
|
||||
commitsReqParams.set('path', filepath);
|
||||
}
|
||||
const commitsGitlabResponse = await fetch(
|
||||
new URL(
|
||||
`${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(
|
||||
full_name,
|
||||
)}/repository/branches/${branch}`,
|
||||
)}/repository/commits?${commitsReqParams.toString()}`,
|
||||
).toString(),
|
||||
getGitLabRequestOptions(this.integration.config),
|
||||
);
|
||||
if (!branchGitlabResponse.ok) {
|
||||
const message = `Failed to read tree (branch) from ${url}, ${branchGitlabResponse.status} ${branchGitlabResponse.statusText}`;
|
||||
if (branchGitlabResponse.status === 404) {
|
||||
if (!commitsGitlabResponse.ok) {
|
||||
const message = `Failed to read tree (branch) from ${url}, ${commitsGitlabResponse.status} ${commitsGitlabResponse.statusText}`;
|
||||
if (commitsGitlabResponse.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const commitSha = (await branchGitlabResponse.json()).commit.id;
|
||||
const commitSha = (await commitsGitlabResponse.json())[0].id;
|
||||
|
||||
if (options?.etag && options.etag === commitSha) {
|
||||
throw new NotModifiedError();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ConfigReader, JsonObject } from '@backstage/config';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
import { UrlReaderPredicateTuple } from './types';
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('GcsUrlReader', () => {
|
||||
return GoogleGcsUrlReader.factory({
|
||||
config: new ConfigReader(config),
|
||||
logger: getVoidLogger(),
|
||||
treeResponseFactory: ReadTreeResponseFactory.create({
|
||||
treeResponseFactory: DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ import { AzureUrlReader } from './AzureUrlReader';
|
||||
import { BitbucketUrlReader } from './BitbucketUrlReader';
|
||||
import { GithubUrlReader } from './GithubUrlReader';
|
||||
import { GitlabUrlReader } from './GitlabUrlReader';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import { FetchUrlReader } from './FetchUrlReader';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
|
||||
@@ -44,7 +44,9 @@ export class UrlReaders {
|
||||
*/
|
||||
static create({ logger, config, factories }: CreateOptions): UrlReader {
|
||||
const mux = new UrlReaderPredicateMux();
|
||||
const treeResponseFactory = ReadTreeResponseFactory.create({ config });
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config,
|
||||
});
|
||||
|
||||
for (const factory of factories ?? []) {
|
||||
const tuples = factory({ config, logger: logger, treeResponseFactory });
|
||||
|
||||
@@ -15,27 +15,18 @@
|
||||
*/
|
||||
|
||||
import os from 'os';
|
||||
import { Readable } from 'stream';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ReadTreeResponse } from '../types';
|
||||
import {
|
||||
ReadTreeResponse,
|
||||
FromArchiveOptions,
|
||||
ReadTreeResponseFactory,
|
||||
} from '../types';
|
||||
import { TarArchiveResponse } from './TarArchiveResponse';
|
||||
import { ZipArchiveResponse } from './ZipArchiveResponse';
|
||||
|
||||
type FromArchiveOptions = {
|
||||
// A binary stream of a tar archive.
|
||||
stream: Readable;
|
||||
// If unset, the files at the root of the tree will be read.
|
||||
// subpath must not contain the name of the top level directory.
|
||||
subpath?: string;
|
||||
// etag of the blob
|
||||
etag: string;
|
||||
// Filter passed on from the ReadTreeOptions
|
||||
filter?: (path: string) => boolean;
|
||||
};
|
||||
|
||||
export class ReadTreeResponseFactory {
|
||||
static create(options: { config: Config }): ReadTreeResponseFactory {
|
||||
return new ReadTreeResponseFactory(
|
||||
export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
|
||||
static create(options: { config: Config }): DefaultReadTreeResponseFactory {
|
||||
return new DefaultReadTreeResponseFactory(
|
||||
options.config.getOptionalString('backend.workingDirectory') ??
|
||||
os.tmpdir(),
|
||||
);
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { ReadTreeResponseFactory } from './ReadTreeResponseFactory';
|
||||
export { DefaultReadTreeResponseFactory } from './ReadTreeResponseFactory';
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ReadTreeResponseFactory } from './tree';
|
||||
|
||||
/**
|
||||
* A generic interface for fetching plain data from URLs.
|
||||
@@ -105,6 +105,23 @@ export type ReadTreeResponseFile = {
|
||||
content(): Promise<Buffer>;
|
||||
};
|
||||
|
||||
export type FromArchiveOptions = {
|
||||
// A binary stream of a tar archive.
|
||||
stream: Readable;
|
||||
// If unset, the files at the root of the tree will be read.
|
||||
// subpath must not contain the name of the top level directory.
|
||||
subpath?: string;
|
||||
// etag of the blob
|
||||
etag: string;
|
||||
// Filter passed on from the ReadTreeOptions
|
||||
filter?: (path: string) => boolean;
|
||||
};
|
||||
|
||||
export interface ReadTreeResponseFactory {
|
||||
fromTarArchive(options: FromArchiveOptions): Promise<ReadTreeResponse>;
|
||||
fromZipArchive(options: FromArchiveOptions): Promise<ReadTreeResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An options object for search operations.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.7.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f7f7783a3: Add Owner field in template card and new data distribution
|
||||
Add spec.owner as optional field into TemplateV1Alpha and TemplateV1Beta Schema
|
||||
Add relations ownedBy and ownerOf into Template entity
|
||||
Template documentation updated
|
||||
- 68fdbf014: Add the `status` field to the Entity envelope
|
||||
|
||||
## 0.7.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -112,6 +112,7 @@ export type Entity = {
|
||||
metadata: EntityMeta;
|
||||
spec?: JsonObject;
|
||||
relations?: EntityRelation[];
|
||||
status?: Record<string, JsonObject>;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -486,6 +487,7 @@ interface TemplateEntityV1alpha1 extends Entity {
|
||||
templater: string;
|
||||
path?: string;
|
||||
schema: JSONSchema;
|
||||
owner?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -519,6 +521,7 @@ export interface TemplateEntityV1beta2 extends Entity {
|
||||
output?: {
|
||||
[name: string]: string;
|
||||
};
|
||||
owner?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.7.9",
|
||||
"version": "0.7.10",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -48,6 +48,14 @@ export type Entity = {
|
||||
* The relations that this entity has with other entities.
|
||||
*/
|
||||
relations?: EntityRelation[];
|
||||
|
||||
/**
|
||||
* The current status of the entity, as claimed by various sources.
|
||||
*
|
||||
* The keys are implementation defined and the values can be any JSON object
|
||||
* with semantics that match that implementation.
|
||||
*/
|
||||
status?: Record<string, JsonObject>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,11 +42,9 @@ export function generateEntityEtag(): string {
|
||||
* the next version of this entity.
|
||||
*
|
||||
* Significance, in this case, means that we do not compare generated fields
|
||||
* such as uid, etag and generation, and we only check that no new annotations
|
||||
* are added or existing annotations were changed (since they are effectively
|
||||
* merged when doing updates).
|
||||
* such as uid, etag and generation.
|
||||
*
|
||||
* Note that this comparison does NOT take state, relations or similar into
|
||||
* Note that this comparison does NOT take status, relations or similar into
|
||||
* account. It only compares the actual input entity data, i.e. metadata and
|
||||
* spec.
|
||||
*
|
||||
@@ -86,7 +84,9 @@ export function entityHasChanges(previous: Entity, next: Entity): boolean {
|
||||
|
||||
// Remove things that we explicitly do not compare
|
||||
delete e1.relations;
|
||||
delete e1.status;
|
||||
delete e2.relations;
|
||||
delete e2.status;
|
||||
|
||||
return !lodash.isEqual(e1, e2);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ describe('templateEntityV1alpha1Validator', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
owner: 'team-a@example.com',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -90,4 +91,16 @@ describe('templateEntityV1alpha1Validator', () => {
|
||||
(entity as any).spec.templater = '';
|
||||
await expect(validator.check(entity)).rejects.toThrow(/templater/);
|
||||
});
|
||||
it('accepts missing owner', async () => {
|
||||
delete (entity as any).spec.owner;
|
||||
await expect(validator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
it('rejects empty owner', async () => {
|
||||
(entity as any).spec.owner = '';
|
||||
await expect(validator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
it('rejects wrong type owner', async () => {
|
||||
(entity as any).spec.owner = 5;
|
||||
await expect(validator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface TemplateEntityV1alpha1 extends Entity {
|
||||
templater: string;
|
||||
path?: string;
|
||||
schema: JSONSchema;
|
||||
owner?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ describe('templateEntityV1beta2Validator', () => {
|
||||
output: {
|
||||
fetchUrl: '{{ steps.fetch.output.targetUrl }}',
|
||||
},
|
||||
owner: 'team-b@example.com',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -121,4 +122,16 @@ describe('templateEntityV1beta2Validator', () => {
|
||||
delete (entity as any).spec.steps[0].action;
|
||||
await expect(validator.check(entity)).rejects.toThrow(/action/);
|
||||
});
|
||||
it('accepts missing owner', async () => {
|
||||
delete (entity as any).spec.owner;
|
||||
await expect(validator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
it('rejects empty owner', async () => {
|
||||
(entity as any).spec.owner = '';
|
||||
await expect(validator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
it('rejects wrong type owner', async () => {
|
||||
(entity as any).spec.owner = 5;
|
||||
await expect(validator.check(entity)).rejects.toThrow(/owner/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface TemplateEntityV1beta2 extends Entity {
|
||||
input?: JsonObject;
|
||||
}>;
|
||||
output?: { [name: string]: string };
|
||||
owner?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,15 @@
|
||||
"items": {
|
||||
"$ref": "common#relation"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"description": "The current status of the entity, as claimed by various sources.",
|
||||
"patternProperties": {
|
||||
"^.+$": {
|
||||
"$ref": "common#status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,11 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "The JSONSchema describing the inputs for the template."
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "The user (or group) owner of the template",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,11 @@
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "The user (or group) owner of the template",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"$id": "#relation",
|
||||
"type": "object",
|
||||
"description": "A directed relation from one entity to another.",
|
||||
"required": ["type", "source", "target"],
|
||||
"required": ["type", "target"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -38,13 +38,16 @@
|
||||
"pattern": "^\\w+$",
|
||||
"description": "The type of relation."
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#reference"
|
||||
},
|
||||
"target": {
|
||||
"$ref": "#reference"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"$id": "#status",
|
||||
"type": "object",
|
||||
"description": "A specific status of an entity.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.6.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2bfec55a6: Update `fork-ts-checker-webpack-plugin`
|
||||
- Updated dependencies [290405276]
|
||||
- @backstage/config-loader@0.6.2
|
||||
|
||||
## 0.6.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.6.11",
|
||||
"version": "0.6.12",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -32,7 +32,7 @@
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
|
||||
"@backstage/cli-common": "^0.1.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/config-loader": "^0.6.1",
|
||||
"@backstage/config-loader": "^0.6.2",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^4.0.0",
|
||||
"@lerna/project": "^4.0.0",
|
||||
@@ -75,7 +75,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"express": "^4.17.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.9",
|
||||
"fs-extra": "^9.0.0",
|
||||
"handlebars": "^4.7.3",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
@@ -117,11 +117,11 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.8.0",
|
||||
"@backstage/backend-common": "^0.8.1",
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core": "^0.7.9",
|
||||
"@backstage/dev-utils": "^0.1.14",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/core": "^0.7.10",
|
||||
"@backstage/dev-utils": "^0.1.15",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@backstage/theme": "^0.2.7",
|
||||
"@types/diff": "^5.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
|
||||
@@ -97,15 +97,18 @@ export async function createConfig(
|
||||
if (checksEnabled) {
|
||||
plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
tsconfig: paths.targetTsConfig,
|
||||
eslint: true,
|
||||
eslintOptions: {
|
||||
parserOptions: {
|
||||
project: paths.targetTsConfig,
|
||||
tsconfigRootDir: paths.targetPath,
|
||||
typescript: {
|
||||
configFile: paths.targetTsConfig,
|
||||
},
|
||||
eslint: {
|
||||
files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
options: {
|
||||
parserOptions: {
|
||||
project: paths.targetTsConfig,
|
||||
tsconfigRootDir: paths.targetPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -291,15 +294,18 @@ export async function createBackendConfig(
|
||||
...(checksEnabled
|
||||
? [
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
tsconfig: paths.targetTsConfig,
|
||||
eslint: true,
|
||||
eslintOptions: {
|
||||
parserOptions: {
|
||||
project: paths.targetTsConfig,
|
||||
tsconfigRootDir: paths.targetPath,
|
||||
typescript: {
|
||||
configFile: paths.targetTsConfig,
|
||||
},
|
||||
eslint: {
|
||||
files: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
options: {
|
||||
parserOptions: {
|
||||
project: paths.targetTsConfig,
|
||||
tsconfigRootDir: paths.targetPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
reportFiles: ['**', '!**/__tests__/**', '!**/?(*.)(spec|test).*'],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/config-loader
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 290405276: Updated dependencies
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -36,7 +36,7 @@
|
||||
"ajv": "^7.0.3",
|
||||
"fs-extra": "^9.0.0",
|
||||
"json-schema": "^0.3.0",
|
||||
"json-schema-merge-allof": "^0.7.0",
|
||||
"json-schema-merge-allof": "^0.8.1",
|
||||
"typescript-json-schema": "^0.49.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.29.3"
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/core-api
|
||||
|
||||
## 0.2.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 61c3f927c: Updated the `Observable` type to provide interoperability with `Symbol.observable`, making it compatible with at least `zen-observable` and `RxJS 7`.
|
||||
|
||||
In cases where this change breaks tests that mocked the `Observable` type, the following addition to the mock should fix the breakage:
|
||||
|
||||
```ts
|
||||
[Symbol.observable]() {
|
||||
return this;
|
||||
},
|
||||
```
|
||||
|
||||
- 65e6c4541: Remove circular dependencies
|
||||
|
||||
## 0.2.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.2.18",
|
||||
"version": "0.2.19",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -42,8 +42,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@backstage/test-utils-core": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconComponent } from '../../icons';
|
||||
import { IconComponent } from '../../icons/types';
|
||||
import { Observable } from '../../types';
|
||||
import { ApiRef, createApiRef } from '../system';
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { PublishSubject } from '../../../lib';
|
||||
import { PublishSubject } from '../../../lib/subjects';
|
||||
import { Observable } from '../../../types';
|
||||
import { AlertApi, AlertMessage } from '../../definitions';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { AppThemeApi, AppTheme } from '../../definitions';
|
||||
import { BehaviorSubject } from '../../../lib';
|
||||
import { BehaviorSubject } from '../../../lib/subjects';
|
||||
import { Observable } from '../../../types';
|
||||
|
||||
const STORAGE_KEY = 'theme';
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { PublishSubject } from '../../../lib';
|
||||
import { PublishSubject } from '../../../lib/subjects';
|
||||
import { Observable } from '../../../types';
|
||||
import { ErrorApi, ErrorContext } from '../../definitions';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from '../../../lib';
|
||||
import { BehaviorSubject } from '../../../lib/subjects';
|
||||
import { Observable } from '../../../types';
|
||||
|
||||
type RequestQueueEntry<ResultType> = {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
AuthRequesterOptions,
|
||||
} from '../../definitions';
|
||||
import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests';
|
||||
import { BehaviorSubject } from '../../../lib';
|
||||
import { BehaviorSubject } from '../../../lib/subjects';
|
||||
import { Observable } from '../../../types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
*/
|
||||
|
||||
import { ComponentType } from 'react';
|
||||
import { IconComponent, IconComponentMap, IconKey } from '../icons';
|
||||
import { IconComponent, IconComponentMap, IconKey } from '../icons/types';
|
||||
import { AnyExternalRoutes, BackstagePlugin } from '../plugin/types';
|
||||
import { ExternalRouteRef, RouteRef } from '../routing';
|
||||
import { AnyApiFactory } from '../apis';
|
||||
import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing/types';
|
||||
import { AnyApiFactory } from '../apis/system';
|
||||
import { AppTheme, ProfileInfo } from '../apis/definitions';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
import { SubRouteRef } from '../routing/types';
|
||||
|
||||
export type BootErrorPageProps = {
|
||||
step: 'load-config' | 'load-chunk';
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthRequester } from '../../apis';
|
||||
import {
|
||||
AuthRequester,
|
||||
OAuthRequestApi,
|
||||
AuthProvider,
|
||||
DiscoveryApi,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SessionState } from '../../apis';
|
||||
import { SessionState } from '../../apis/definitions';
|
||||
import { Observable } from '../../types';
|
||||
import { BehaviorSubject } from '../subjects';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Observable } from '../../types';
|
||||
import { SessionState } from '../../apis';
|
||||
import { SessionState } from '../../apis/definitions';
|
||||
|
||||
export type GetSessionOptions = {
|
||||
optional?: boolean;
|
||||
|
||||
@@ -53,6 +53,10 @@ export class PublishSubject<T>
|
||||
ZenObservable.SubscriptionObserver<T>
|
||||
>();
|
||||
|
||||
[Symbol.observable]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
get closed() {
|
||||
return this.isClosed;
|
||||
}
|
||||
@@ -148,6 +152,10 @@ export class BehaviorSubject<T>
|
||||
ZenObservable.SubscriptionObserver<T>
|
||||
>();
|
||||
|
||||
[Symbol.observable]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
get closed() {
|
||||
return this.isClosed;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
ParamKeys,
|
||||
OptionalParams,
|
||||
} from './types';
|
||||
import { IconComponent } from '../icons';
|
||||
import { IconComponent } from '../icons/types';
|
||||
|
||||
// TODO(Rugvip): Remove this in the next breaking release, it's exported but unused
|
||||
export type RouteRefConfig<Params extends AnyParams> = {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IconComponent } from '../icons';
|
||||
import { IconComponent } from '../icons/types';
|
||||
import { getOrCreateGlobalSingleton } from '../lib/globalObject';
|
||||
|
||||
export type AnyParams = { [param in string]: string } | undefined;
|
||||
|
||||
@@ -39,9 +39,17 @@ export type Subscription = {
|
||||
/**
|
||||
* Value indicating whether the subscription is closed.
|
||||
*/
|
||||
readonly closed: Boolean;
|
||||
readonly closed: boolean;
|
||||
};
|
||||
|
||||
// Declares the global well-known Symbol.observable
|
||||
// We get the actual runtime polyfill from zen-observable
|
||||
declare global {
|
||||
interface SymbolConstructor {
|
||||
readonly observable: symbol;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observable sequence of values and errors, see TC39.
|
||||
*
|
||||
@@ -51,12 +59,14 @@ export type Subscription = {
|
||||
* using many different observable implementations, such as zen-observable or RxJS 5.
|
||||
*/
|
||||
export type Observable<T> = {
|
||||
[Symbol.observable](): Observable<T>;
|
||||
|
||||
/**
|
||||
* Subscribes to this observable to start receiving new values.
|
||||
*/
|
||||
subscribe(observer: Observer<T>): Subscription;
|
||||
subscribe(
|
||||
onNext: (value: T) => void,
|
||||
onNext?: (value: T) => void,
|
||||
onError?: (error: Error) => void,
|
||||
onComplete?: () => void,
|
||||
): Subscription;
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/core
|
||||
|
||||
## 0.7.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 65e6c4541: Remove circular dependencies
|
||||
- 5da6a561d: Fix a bug where users are asked to log-in on every page refresh. This is specific to people with only one sign-in provider in their SignInPage component.
|
||||
- Updated dependencies [61c3f927c]
|
||||
- Updated dependencies [65e6c4541]
|
||||
- @backstage/core-api@0.2.19
|
||||
|
||||
## 0.7.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.7.9",
|
||||
"version": "0.7.10",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.5",
|
||||
"@backstage/core-api": "^0.2.18",
|
||||
"@backstage/core-api": "^0.2.19",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/theme": "^0.2.7",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
@@ -71,8 +71,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import React, { useContext, ReactNode, PropsWithChildren } from 'react';
|
||||
import { Button, makeStyles } from '@material-ui/core';
|
||||
import { StepActions } from './SimpleStepperStep';
|
||||
import { StepActions } from './types';
|
||||
import { VerticalStepperContext } from './SimpleStepper';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import { SimpleStepperFooter } from './SimpleStepperFooter';
|
||||
import { StepProps } from './types';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
end: {
|
||||
@@ -29,30 +30,6 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export type StepActions = {
|
||||
showNext?: boolean;
|
||||
canNext?: () => boolean;
|
||||
onNext?: () => void;
|
||||
nextStep?: (current: number, last: number) => number;
|
||||
nextText?: string;
|
||||
|
||||
showBack?: boolean;
|
||||
backText?: string;
|
||||
onBack?: () => void;
|
||||
|
||||
showRestart?: boolean;
|
||||
canRestart?: () => boolean;
|
||||
onRestart?: () => void;
|
||||
restartText?: string;
|
||||
};
|
||||
|
||||
export type StepProps = {
|
||||
title: string;
|
||||
children: React.ReactElement;
|
||||
end?: boolean;
|
||||
actions?: StepActions;
|
||||
};
|
||||
|
||||
export const SimpleStepperStep = ({
|
||||
title,
|
||||
children,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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';
|
||||
|
||||
export type StepActions = {
|
||||
showNext?: boolean;
|
||||
canNext?: () => boolean;
|
||||
onNext?: () => void;
|
||||
nextStep?: (current: number, last: number) => number;
|
||||
nextText?: string;
|
||||
|
||||
showBack?: boolean;
|
||||
backText?: string;
|
||||
onBack?: () => void;
|
||||
|
||||
showRestart?: boolean;
|
||||
canRestart?: () => boolean;
|
||||
onRestart?: () => void;
|
||||
restartText?: string;
|
||||
};
|
||||
|
||||
export type StepProps = {
|
||||
title: string;
|
||||
children: React.ReactElement;
|
||||
end?: boolean;
|
||||
actions?: StepActions;
|
||||
};
|
||||
@@ -16,7 +16,8 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { matchRoutes, useNavigate, useParams, useRoutes } from 'react-router';
|
||||
import { Content, HeaderTabs } from '../../layout';
|
||||
import { Content } from '../../layout/Content';
|
||||
import { HeaderTabs } from '../../layout/HeaderTabs';
|
||||
import { SubRoute } from './types';
|
||||
|
||||
export function useSelectedSubRoute(
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configApiRef, SignInPageProps, useApi } from '@backstage/core-api';
|
||||
import { Button, Grid, Typography } from '@material-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { Header } from '../Header';
|
||||
import { Progress } from '../../components/Progress';
|
||||
import { Content } from '../Content/Content';
|
||||
import { ContentHeader } from '../ContentHeader/ContentHeader';
|
||||
import { Grid, Button, Typography } from '@material-ui/core';
|
||||
import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api';
|
||||
import { useSignInProviders, getSignInProviders } from './providers';
|
||||
import { IdentityProviders, SignInProviderConfig } from './types';
|
||||
import { Progress } from '../../components/Progress';
|
||||
import { GridItem, useStyles } from './styles';
|
||||
import { Header } from '../Header';
|
||||
import { InfoCard } from '../InfoCard';
|
||||
import { Page } from '../Page';
|
||||
import { getSignInProviders, useSignInProviders } from './providers';
|
||||
import { GridItem, useStyles } from './styles';
|
||||
import { IdentityProviders, SignInProviderConfig } from './types';
|
||||
|
||||
type MultiSignInPageProps = SignInPageProps & {
|
||||
providers: IdentityProviders;
|
||||
@@ -87,34 +87,63 @@ export const SingleSignInPage = ({
|
||||
const authApi = useApi(provider.apiRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
const [retry, setRetry] = useState<{} | boolean | undefined>(auto);
|
||||
const [autoShowPopup, setAutoShowPopup] = useState<boolean>(auto ?? false);
|
||||
// Defaults to true so that an initial check for existing user session is made
|
||||
const [retry, setRetry] = useState<{} | boolean | undefined>(true);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
// The SignIn component takes some time to decide whether the user is logged-in or not.
|
||||
// showLoginPage is used to prevent a glitch-like experience where the sign-in page is
|
||||
// displayed for a split second when the user is already logged-in.
|
||||
const [showLoginPage, setShowLoginPage] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const login = async () => {
|
||||
const identity = await authApi.getBackstageIdentity({
|
||||
instantPopup: true,
|
||||
});
|
||||
try {
|
||||
let identity;
|
||||
// Do an initial check if any logged-in session exists
|
||||
identity = await authApi.getBackstageIdentity({
|
||||
optional: true,
|
||||
});
|
||||
|
||||
const profile = await authApi.getProfile();
|
||||
onResult({
|
||||
userId: identity!.id,
|
||||
profile: profile!,
|
||||
getIdToken: () => {
|
||||
return authApi.getBackstageIdentity().then(i => i!.idToken);
|
||||
},
|
||||
signOut: async () => {
|
||||
await authApi.signOut();
|
||||
},
|
||||
});
|
||||
// If no session exists, show the sign-in page
|
||||
if (!identity && autoShowPopup) {
|
||||
// Unless auto is set to true, this step should not happen.
|
||||
// When user intentionally clicks the Sign In button, autoShowPopup is set to true
|
||||
identity = await authApi.getBackstageIdentity({
|
||||
instantPopup: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (!identity) {
|
||||
setShowLoginPage(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = await authApi.getProfile();
|
||||
onResult({
|
||||
userId: identity!.id,
|
||||
profile: profile!,
|
||||
getIdToken: () => {
|
||||
return authApi.getBackstageIdentity().then(i => i!.idToken);
|
||||
},
|
||||
signOut: async () => {
|
||||
await authApi.signOut();
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// User closed the sign-in modal
|
||||
setError(err);
|
||||
setShowLoginPage(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (retry) {
|
||||
login().catch(setError);
|
||||
login();
|
||||
}
|
||||
}, [onResult, authApi, retry]);
|
||||
}, [onResult, authApi, retry, autoShowPopup]);
|
||||
|
||||
return (
|
||||
return showLoginPage ? (
|
||||
<Page themeId="home">
|
||||
<Header title={configApi.getString('app.title')} />
|
||||
<Content>
|
||||
@@ -133,7 +162,10 @@ export const SingleSignInPage = ({
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => setRetry({})}
|
||||
onClick={() => {
|
||||
setRetry({});
|
||||
setAutoShowPopup(true);
|
||||
}}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
@@ -150,6 +182,8 @@ export const SingleSignInPage = ({
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
) : (
|
||||
<Progress />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^4.2.0",
|
||||
"cypress": "^7.3.0",
|
||||
"eslint-plugin-cypress": "^2.10.3",
|
||||
"start-server-and-test": "^1.10.11"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5e7b36e74: Fix a bug where an instance of the SCM integrations API wasn't being provided properly to the dev app.
|
||||
- Updated dependencies [f7f7783a3]
|
||||
- Updated dependencies [65e6c4541]
|
||||
- Updated dependencies [68fdbf014]
|
||||
- Updated dependencies [5da6a561d]
|
||||
- Updated dependencies [61c3f927c]
|
||||
- @backstage/catalog-model@0.7.10
|
||||
- @backstage/core@0.7.10
|
||||
- @backstage/test-utils@0.1.12
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"version": "0.1.14",
|
||||
"version": "0.1.15",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,11 +29,11 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^0.7.9",
|
||||
"@backstage/catalog-model": "^0.7.9",
|
||||
"@backstage/core": "^0.7.10",
|
||||
"@backstage/catalog-model": "^0.7.10",
|
||||
"@backstage/integration-react": "^0.1.2",
|
||||
"@backstage/plugin-catalog-react": "^0.1.6",
|
||||
"@backstage/test-utils": "^0.1.11",
|
||||
"@backstage/test-utils": "^0.1.12",
|
||||
"@backstage/theme": "^0.2.3",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -48,7 +48,7 @@
|
||||
"react-router-dom": "6.0.0-beta.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.6.11",
|
||||
"@backstage/cli": "^0.6.12",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32"
|
||||
},
|
||||
|
||||
@@ -154,7 +154,7 @@ class DevAppBuilder {
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
apis: this.apis,
|
||||
apis,
|
||||
plugins: this.plugins,
|
||||
bindRoutes: ({ bind }) => {
|
||||
for (const plugin of this.plugins ?? []) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/integration
|
||||
|
||||
## 0.5.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 65e6c4541: Remove circular dependencies
|
||||
|
||||
## 0.5.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||