diff --git a/.changeset/angry-ghosts-report.md b/.changeset/angry-ghosts-report.md new file mode 100644 index 0000000000..dfa83663a9 --- /dev/null +++ b/.changeset/angry-ghosts-report.md @@ -0,0 +1,16 @@ +--- +'@backstage/backend-common': patch +--- + +Support a `ensureExists` config option to skip ensuring a configured database exists. This allows deployment scenarios where +limited permissions are given for provisioned databases without privileges to create new databases. If set to `false`, the +database connection will not be validated prior to use which means the backend will not attempt to create the database if it +doesn't exist. You can configure this in your app-config.yaml: + +```yaml +backend: + database: + ensureExists: false +``` + +This defaults to `true` if unspecified. You can also configure this per plugin connection and will override the base option. diff --git a/.changeset/angry-rules-fail.md b/.changeset/angry-rules-fail.md new file mode 100644 index 0000000000..cc52d4478e --- /dev/null +++ b/.changeset/angry-rules-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add unstable prop for disabling unregister entity menu diff --git a/.changeset/backstage-changelog.js b/.changeset/backstage-changelog.js new file mode 100644 index 0000000000..ea8ca2300b --- /dev/null +++ b/.changeset/backstage-changelog.js @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + default: defaultChangelogFunctions, +} = require('@changesets/cli/changelog'); + +// Custom CHANGELOG generation for changesets, stolen from here with one minor change: +// https://github.com/atlassian/changesets/blob/main/packages/cli/src/changelog/index.ts +async function getDependencyReleaseLine(changesets, dependenciesUpdated) { + if (dependenciesUpdated.length === 0) return ''; + + const updatedDepenenciesList = dependenciesUpdated.map( + dependency => ` - ${dependency.name}@${dependency.newVersion}`, + ); + + // Return one `Updated dependencies` bullet instead of repeating for each changeset; this + // sacrifices the commit shas for brevity. + return ['- Updated dependencies', ...updatedDepenenciesList].join('\n'); +} + +module.exports = { + getReleaseLine: defaultChangelogFunctions.getReleaseLine, + getDependencyReleaseLine, +}; diff --git a/.changeset/config.json b/.changeset/config.json index 44a8523265..067dcb5f9f 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,10 +1,13 @@ { "$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json", - "changelog": "@changesets/cli/changelog", + "changelog": "./backstage-changelog.js", "commit": false, "linked": [["*"]], "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": [], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } } diff --git a/.changeset/curly-badgers-sit.md b/.changeset/curly-badgers-sit.md new file mode 100644 index 0000000000..ca740e907a --- /dev/null +++ b/.changeset/curly-badgers-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Store filter values set in `EntityListProvider` in query parameters. This allows selected filters to be restored when returning to pages that list catalog entities. diff --git a/.changeset/funny-toys-talk.md b/.changeset/funny-toys-talk.md deleted file mode 100644 index 6e6a779ab8..0000000000 --- a/.changeset/funny-toys-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/codemods': patch ---- - -Fix execution of `jscodeshift` on windows. diff --git a/.changeset/green-vans-peel.md b/.changeset/green-vans-peel.md new file mode 100644 index 0000000000..cd4d30139a --- /dev/null +++ b/.changeset/green-vans-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix `EntityTypeFilter` so it produces unique case-insensitive set of available types diff --git a/.changeset/grumpy-dolls-call.md b/.changeset/grumpy-dolls-call.md new file mode 100644 index 0000000000..13d96ecb1f --- /dev/null +++ b/.changeset/grumpy-dolls-call.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-scaffolder': patch +--- + +Updated the software templates list page (`ScaffolderPage`) to use the `useEntityListProvider` hook from #5643. This reduces the code footprint, making it easier to customize the display of this page, and consolidates duplicate approaches to querying the catalog with filters. + +- The `useEntityTypeFilter` hook has been updated along with the underlying `EntityTypeFilter` to work with multiple values, to allow more flexibility for different user interfaces. It's unlikely that this change affects you; however, if you're using either of these directly, you'll need to update your usage. +- `SearchToolbar` was renamed to `EntitySearchBar` and moved to `catalog-react` to be usable by other entity list pages +- `UserListPicker` now has an `availableTypes` prop to restrict which user-related options to present diff --git a/.changeset/lemon-crabs-confess.md b/.changeset/lemon-crabs-confess.md new file mode 100644 index 0000000000..0d87843df5 --- /dev/null +++ b/.changeset/lemon-crabs-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Updating fs-extra to 10.0.0 to handle broken symbolic links correctly diff --git a/.changeset/lemon-dancers-taste.md b/.changeset/lemon-dancers-taste.md new file mode 100644 index 0000000000..0542fe4a54 --- /dev/null +++ b/.changeset/lemon-dancers-taste.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +The codeowners processor extracts the username of the primary owner and uses this as the owner field. +Given the kind isn't specified this is assumed to be a group and so the link to the owner in the about card +doesn't work. This change specifies the kind where the entity is a user. e.g: + +`@iain-b` -> `user:iain-b` diff --git a/.changeset/nice-bugs-beg.md b/.changeset/nice-bugs-beg.md new file mode 100644 index 0000000000..c41b54b220 --- /dev/null +++ b/.changeset/nice-bugs-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': minor +--- + +Exported and renamed components from the `@backstage/plugin-user-settings` plugin , to be able to use it in the consumer side and customize the `SettingPage` diff --git a/.changeset/polite-spies-judge.md b/.changeset/polite-spies-judge.md new file mode 100644 index 0000000000..075bcf1598 --- /dev/null +++ b/.changeset/polite-spies-judge.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-rails': patch +--- + +updated paths to consider differences between platform (windows corrected) diff --git a/.changeset/poor-otters-buy.md b/.changeset/poor-otters-buy.md new file mode 100644 index 0000000000..b60cbddc1b --- /dev/null +++ b/.changeset/poor-otters-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Add new `fetch:template` action which handles the same responsibilities as `fetch:cookiecutter` without the external dependency on `cookiecutter`. For information on migrating from `fetch:cookiecutter` to `fetch:template`, see the [migration guide](https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetch-cookiecutter-to-fetch-template) in the docs. diff --git a/.changeset/pretty-drinks-serve.md b/.changeset/pretty-drinks-serve.md new file mode 100644 index 0000000000..a012aba11f --- /dev/null +++ b/.changeset/pretty-drinks-serve.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Scaffolder: Added an 'eq' handlebars helper for use in software template YAML files. This can be used to execute a step depending on the value of an input, e.g.: + +```yaml +steps: + id: 'conditional-step' + action: 'custom-action' + if: '{{ eq parameters.myvalue "custom" }}', +``` diff --git a/.changeset/purple-papayas-exist.md b/.changeset/purple-papayas-exist.md deleted file mode 100644 index 4c77558476..0000000000 --- a/.changeset/purple-papayas-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Fix a bug that prevented changing themes on the user settings page when the theme `id` didn't match exactly the theme `variant`. diff --git a/.changeset/real-plums-vanish.md b/.changeset/real-plums-vanish.md new file mode 100644 index 0000000000..a653a6bdf2 --- /dev/null +++ b/.changeset/real-plums-vanish.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Add Sign In and Handler resolver for Microsoft provider diff --git a/.changeset/search-mighty-mice-collect.md b/.changeset/search-mighty-mice-collect.md new file mode 100644 index 0000000000..bad91ff853 --- /dev/null +++ b/.changeset/search-mighty-mice-collect.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-node': minor +--- + +Change return value of `SearchEngine.index` to `Promise` to support +implementation of external search engines. diff --git a/.changeset/short-eggs-confess.md b/.changeset/short-eggs-confess.md new file mode 100644 index 0000000000..3fa240a347 --- /dev/null +++ b/.changeset/short-eggs-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Expose missing types used by the custom transformers diff --git a/.changeset/shy-rules-design.md b/.changeset/shy-rules-design.md new file mode 100644 index 0000000000..4f009e5aa8 --- /dev/null +++ b/.changeset/shy-rules-design.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Updated inputs for the `publish:github:pull-request` action. + +Now requires a `repoUrl` instead of separate `owner` and `repo` inputs. This aligns with the output of the `RepoUrlPicker` ui field used by the pull-request sample template. diff --git a/.changeset/techdocs-metal-clouds-work.md b/.changeset/techdocs-metal-clouds-work.md deleted file mode 100644 index 64d18dcc02..0000000000 --- a/.changeset/techdocs-metal-clouds-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Adding support for user owned document filter for TechDocs custom Homepage diff --git a/.changeset/techdocs-typescript-isnt-fun.md b/.changeset/techdocs-typescript-isnt-fun.md new file mode 100644 index 0000000000..9341c1c14a --- /dev/null +++ b/.changeset/techdocs-typescript-isnt-fun.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +It is no longer required to provide a generator and a preparer to the TechDocs +router factory when running TechDocs in the "recommended" (e.g. externally +prepared and generated docs) configuration. diff --git a/.changeset/wise-rockets-smoke.md b/.changeset/wise-rockets-smoke.md new file mode 100644 index 0000000000..c81d20519d --- /dev/null +++ b/.changeset/wise-rockets-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Document the default behavior of `statusCheck` option in `createStatusCheckRouter`. diff --git a/.changeset/young-tables-reply.md b/.changeset/young-tables-reply.md new file mode 100644 index 0000000000..e08a875c14 --- /dev/null +++ b/.changeset/young-tables-reply.md @@ -0,0 +1,16 @@ +--- +'@backstage/backend-common': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/create-app': patch +'@backstage/techdocs-common': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-rollbar-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Revert the upgrade to `fs-extra@10.0.0` as that seemed to have broken all installs inexplicably. diff --git a/.eslintrc.js b/.eslintrc.js index 4dfcf5317f..6c8b62e4aa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,12 @@ module.exports = { { // eslint-disable-next-line no-restricted-syntax templateFile: path.resolve(__dirname, './scripts/copyright-header.txt'), + templateVars: { + NAME: 'The Backstage Authors', + }, + varRegexps: { + NAME: /(The Backstage Authors)|(Spotify AB)/, + }, onNonMatchingHeader: 'replace', }, ], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 206c1a7883..fda500ed02 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # The last matching pattern takes precedence. # https://help.github.com/articles/about-codeowners/ -* @backstage/maintainers +* @backstage/reviewers /docs/features/techdocs @backstage/techdocs-core /docs/features/search @backstage/techdocs-core /docs/assets/search @backstage/techdocs-core diff --git a/.github/codecov.yml b/.github/codecov.yml index a15de165d0..6450a704bb 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -14,11 +14,15 @@ coverage: # Since Backstage is a mono repo, flags here help in getting the code coverage of individual packages. # Documentation: https://docs.codecov.io/docs/flags flags: - core: + core-app-api: paths: - - packages/core/ + - packages/core-app-api/ carryforward: true - core-api: + core-components: paths: - - packages/core-api/ + - packages/core-components/ + carryforward: true + core-plugin-api: + paths: + - packages/core-plugin-api/ carryforward: true diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 4ff766e5e2..e6af881f1e 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -52,10 +52,12 @@ cookiecutter css Datadog dataflow +dayjs deadnaming debounce Debounce declaratively +deduplicated deps destructured dev @@ -120,6 +122,7 @@ Knex kubectl kubernetes kubernetes +ldap learnings Leasot lerna @@ -134,6 +137,7 @@ maintainership makefile md memcache +memoized microservice microservices microsite @@ -149,6 +153,7 @@ Mkdocs monorepo Monorepo monorepos +msgraph msw mysql namespace @@ -164,6 +169,7 @@ nohoist nonces noop npm +nunjucks nvarchar nvm OAuth @@ -172,6 +178,7 @@ oidc Okta onboarding Onboarding +orgs pagerduty pageview parallelization @@ -203,6 +210,8 @@ repo Repo repos rerender +Reusability +reusability rollbar Rollbar Rollup @@ -220,6 +229,7 @@ seb semlas semver Serverless +siloed Sinon Snyk sourcemaps @@ -265,13 +275,16 @@ touchpoints transpilation transpiled truthy +typeahead ui +unbreak unmanaged unregister unregistration untracked upvote url +URLs utils validator validators diff --git a/.github/workflows/chromatic-storybook-test.yml b/.github/workflows/chromatic-storybook-test.yml index 89df848c76..d189236987 100644 --- a/.github/workflows/chromatic-storybook-test.yml +++ b/.github/workflows/chromatic-storybook-test.yml @@ -4,8 +4,7 @@ on: paths: - '.github/workflows/chromatic-storybook-test.yml' - 'packages/storybook/**' - - 'packages/core/src/components/**' - - 'packages/core/src/layout/**' + - 'packages/core-components/src/**' jobs: chromatic: diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 75e26d52d3..daac9a535d 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -119,8 +119,9 @@ jobs: yarn lerna -- run test -- --coverage bash <(curl -s https://codecov.io/bash) # Upload code coverage for some specific flags. Also see .codecov.yml - bash <(curl -s https://codecov.io/bash) -f packages/core/coverage/* -F core - bash <(curl -s https://codecov.io/bash) -f packages/core-api/coverage/* -F core-api + bash <(curl -s https://codecov.io/bash) -f packages/core-app-api/coverage/* -F core-app-api + bash <(curl -s https://codecov.io/bash) -f packages/core-components/coverage/* -F core-components + bash <(curl -s https://codecov.io/bash) -f packages/core-plugin-api/coverage/* -F core-plugin-api env: BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }} diff --git a/.github/workflows/microsite-with-storybook-deploy.yml b/.github/workflows/microsite-with-storybook-deploy.yml index cf571ef4e8..e8886b7d6b 100644 --- a/.github/workflows/microsite-with-storybook-deploy.yml +++ b/.github/workflows/microsite-with-storybook-deploy.yml @@ -7,7 +7,7 @@ on: paths: - '.github/workflows/microsite-with-storybook-deploy.yml' - 'packages/storybook/**' - - 'packages/core/src/**' + - 'packages/core-components/src/**' - 'microsite/**' - 'docs/**' diff --git a/ADOPTERS.md b/ADOPTERS.md index 31f384e4e6..1dcd4a774f 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,32 +1,39 @@ -| 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. | -| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| 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) | [@sagacity](https://github.com/sagacity) | 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. | +| [PagerDuty](https://www.pagerduty.com/) | [Mark Shaw](https://github.com/markshawtoronto) | Developer portal, initially focused on software templates and tech-docs. | +| [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | +| [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | +| [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie), [Alex Hollerith](mailto:alex.hollerith@dazn.com) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | +| [FactSet](https://www.factset.com/) | [@kuangp](https://github.com/kuangp) | Developer portal to provide discoverability to all internal components, APIs, documentation, and scaffold templates with integrations to our internal infrastructure tools. | +| [Workrise](https://www.workrise.com/) | [Michael Rode](https://github.com/michaelrode) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [RedVentures](https://www.redventures.com/) | [Chris Diaz](https://github.com/codingdiaz) | Developer portal that brings everything an engineer needs to provide value into a single pane of glass. | diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 0ab8d8390d..936c27c7cf 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -36,9 +36,33 @@ To become a maintainer you need to demonstrate the following: If a maintainer is no longer interested or cannot perform the maintainer duties listed above, they should volunteer to be moved to emeritus status. In extreme cases this can also occur by a vote of the sponsors and maintainers per the voting process below. +# Reviewers + +The project also contains a team called [@backstage/reviewers](https://github.com/orgs/backstage/teams/reviewers). This is the team of people who are the fallback in [`CODEOWNERS`](./.github/CODEOWNERS). This team will typically contain the maintainers, and a small number of additional people who are permitted to approve and merge pull requests. The purpose of this group is to offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. + +This responsibility is distinct from the maintainer role. A reviewer must not approve and merge changes that have a level of impact that a maintainer should oversee; see below for clarification. For that class of changes, a reviewer can still review the pull request thoroughly without approving it (e.g. with a comment on the pull request), and is expected to notify `@backstage/maintainers` for final approval. Note that it is best to not use the GitHub review approve functionality for this, since that would let Hall of Fame members self-merge the pull request before maintainers get the chance to look at it. + +The following is a non-exhaustive list of types of change, for which a reviewer should defer final decision and merge to a maintainer: + +- A larger refactoring that significantly affects the structure of/between packages +- Changes that settle or alter the trajectory of contested ongoing topics in issues or elsewhere +- Changes that affect the [Architecture Decision Records](./docs/architecture-decisions) +- Changes to APIs that have large customer impact, such as the core APIs in `@backstage/core-*` packages, or significant `@backstage/cli` changes. +- Pull requests whose build checks are not passing fully +- Additions and removals of entire packages +- Releases (e.g. pull requests titled `Version Packages`) + +A maintainer may suggest an addition to the reviewers team by opening a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. Prospective reviewers are not expected to do this themselves, but should rather ask a maintainer to sponsor their addition. All of the maintainers and sponsors are called to vote on the addition (see the section below about voting). If the vote passes, the pull request can be approved and merged, and the corresponding addition to the GitHub team can be made. + +A reviewer can elect to remove themselves from the reviewers group by opening, or asking a maintainer to open, a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. A maintainer will approve and merge the pull request, and the corresponding removal from the GitHub team can be made. + +A maintainer can call on the other maintainers and sponsors for a vote to remove a reviewer (see the section below about conflict resolution and voting). If the vote passes, a maintainer creates a pull request that modifies [`OWNERS.md`](./OWNERS.md) accordingly. After approval by another maintainer, the pull request can be merged, and the corresponding removal from the GitHub team can be made. + # Conflict resolution and voting -In general, we prefer that technical issues and maintainer membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. The voting process is a simple majority in which each sponsor receives two votes and each maintainer receives one vote. +In general, we prefer that technical issues and membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. + +In all cases in this document where voting is mentioned, the voting process is a simple majority in which each sponsor receives two votes and each maintainer receives one vote. If such a majority is reached, the vote is said to have _passed_. # Adding new projects to the Backstage GitHub organization diff --git a/LICENSE b/LICENSE index 224306fe45..72ea3a85b0 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright 2020 Spotify AB +Copyright 2020 The Backstage Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index 0967e9bd8d..38e5cdef85 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Backstage -Copyright 2020 Spotify AB +Copyright 2020 The Backstage Authors Portions of this software were developed by third-party software vendors: - Tech Radar Plugin (https://opensource.zalando.com/tech-radar/), Copyright (c) 2017 Zalando SE diff --git a/OWNERS.md b/OWNERS.md index cdbc234183..6615046540 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -16,6 +16,18 @@ This page lists all active sponsors and maintainers. - Ben Lambert ([benjdlambert](https://github.com/benjdlambert)) (Discord: @blam) - Johan Haals ([jhaals](https://github.com/jhaals)) (Discord: @jhaals) +# Reviewers + +See [`GOVERNANCE.md`](./GOVERNANCE.md) for details about how the reviewers team +works. + +- Patrik Oldsberg ([rugvip](https://github.com/rugvip)) (Discord: @Rugvip) +- Fredrik Adelöw ([freben](https://github.com/freben)) (Discord: @freben) +- Ben Lambert ([benjdlambert](https://github.com/benjdlambert)) (Discord: @blam) +- Johan Haals ([jhaals](https://github.com/jhaals)) (Discord: @jhaals) +- Himanshu Mishra ([OrkoHunter](https://github.com/OrkoHunter)) (Discord: @OrkoHunter) +- Tim Hansen ([timbonicus](https://github.com/timbonicus)) (Discord: @timbonicus) + # Emeritus maintainers - Stefan Ålund ([stefanalund](https://github.com/stefanalund)) (Discord: @stalund) diff --git a/README.md b/README.md index 6dd490e43b..98da194c1b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # [Backstage](https://backstage.io) +_During the month of July the majority of the maintainers will be on summer vacation 🏖️ Development will continue as usual, but expect a slower pace for discussions and PR reviews. Why not take this opportunity to [build a plugin](https://backstage.io/docs/plugins/)?_ + [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-sandbox-blue.svg)](https://www.cncf.io/projects) [![Main CI Build](https://github.com/backstage/backstage/workflows/Main%20Master%20Build/badge.svg)](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22) @@ -12,15 +14,15 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) Out of the box, Backstage includes: -- [Backstage Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) +- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) - [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices - [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach - Plus, a growing ecosystem of [open source plugins](https://github.com/backstage/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality @@ -38,7 +40,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## Documentation - [Main documentation](https://backstage.io/docs) -- [Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) +- [Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) - [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview)) - [Designing for Backstage](https://backstage.io/docs/dls/design) - [Storybook - UI components](https://backstage.io/storybook) @@ -58,6 +60,6 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## License -Copyright 2020-2021 © Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage +Copyright 2020-2021 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/app-config.yaml b/app-config.yaml index c951594677..b50de963ed 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -72,7 +72,7 @@ proxy: '/pagerduty': target: https://api.pagerduty.com headers: - Authorization: ${PAGERDUTY_TOKEN} + Authorization: Token token=${PAGERDUTY_TOKEN} '/buildkite/api': target: https://api.buildkite.com/v2/ @@ -250,6 +250,10 @@ catalog: target: ../catalog-model/examples/acme-corp.yaml scaffolder: + # Use to customize default commit author info used when new components are created + # defaultAuthor: + # name: Scaffolder + # email: scaffolder@backstage.io github: token: ${GITHUB_TOKEN} visibility: public # or 'internal' or 'private' diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 7422fa03ec..6fc39811b5 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -4,7 +4,7 @@ The Backstage backend APIs are by default available without authentication. To a API requests from frontend plugins include an authorization header with a Backstage identity token acquired when the user logs in. By adding a middleware that verifies said token to be valid and signed by Backstage, non-authenticated requests can be blocked with a 401 Unauthorized response. -Note that this means Backstage will stop working for guests, as no token is issued for them. +**NOTE**: Enabling this means that Backstage will stop working for guests, as no token is issued for them. As techdocs HTML pages load assets without an Authorization header the code below also sets a token cookie when the user logs in (and when the token is about to expire). @@ -99,7 +99,7 @@ async function main() { ```typescript // packages/app/src/App.tsx from a create-app deployment -import { discoveryApiRef, useApi } from '@backstage/core'; +import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; // ... @@ -181,3 +181,83 @@ const app = createApp({ // ... ``` + +**NOTE**: Most Backstage frontend plugins come with the support for the `IdentityApi`. +In case you already have a dozen of internal ones, you may need to update those too. +Assuming you follow the common plugin structure, the changes to your front-end may look like: + +```diff +// plugins/internal-plugin/src/api.ts +- import {createApiRef} from '@backstage/core'; ++ import {createApiRef, IdentityApi} from '@backstage/core'; +import {Config} from '@backstage/config'; +// ... + +type MyApiOptions = { + configApi: Config; ++ identityApi: IdentityApi; + // ... +} + +interface MyInterface { + getData(): Promise; +} + +export class MyApi implements MyInterface { + private configApi: Config; ++ private identityApi: IdentityApi; + // ... + + constructor(options: MyApiOptions) { + this.configApi = options.configApi; ++ this.identityApi = options.identityApi; + } + + async getMyData() { + const backendUrl = this.configApi.getString('backend.baseUrl'); + ++ const token = await this.identityApi.getIdToken(); + const requestUrl = `${backendUrl}/api/data/`; +- const response = await fetch(requestUrl); ++ const response = await fetch( + requestUrl, + { headers: { Authorization: `Bearer ${token}` } }, + ); + // ... + } +``` + +and + +```diff +// plugins/internal-plugin/src/plugin.ts + +import { + configApiRef, + createApiFactory, + createPlugin, ++ identityApiRef, +} from '@backstage/core'; +import {mypluginPageRouteRef} from './routeRefs'; +import {MyApi, myApiRef} from './api'; + +export const plugin = createPlugin({ + id: 'my-plugin', + routes: { + mainPage: mypluginPageRouteRef, + }, + apis: [ + createApiFactory({ + api: myApiRef, + deps: { + configApi: configApiRef, ++ identityApi: identityApiRef, + }, +- factory: ({configApi}) => +- new MyApi({ configApi }), ++ factory: ({configApi, identityApi}) => ++ new MyApi({ configApi, identityApi }), + }), + ], +}); +``` diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 0567a6bbb6..b121bab25f 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -51,7 +51,7 @@ The Backstage App needs a SignInPage when authentication is required. When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token. - edit `packages/app/src/App.tsx` -- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB +- import the following two additional definitions from `@backstage/core-plugin-api`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB - add the following definition just before the app is created (`const app = createApp`): ```ts diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md index 841e13dd8b..8a4d6fb273 100644 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md @@ -26,6 +26,7 @@ import 'global-agent/bootstrap'; ```sh export GLOBAL_AGENT_HTTP_PROXY=$HTTP_PROXY +export GLOBAL_AGENT_NO_PROXY=$NO_PROXY yarn start ``` diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md index 77b820d921..fe56adddd8 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleComponent.md @@ -5,6 +5,7 @@ ExampleComponent.tsx reference ```tsx import React from 'react'; import { Typography, Grid } from '@material-ui/core'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; import { InfoCard, Header, @@ -13,12 +14,10 @@ import { ContentHeader, HeaderLabel, SupportButton, - identityApiRef, - useApi, -} from '@backstage/core'; -import ExampleFetchComponent from '../ExampleFetchComponent'; +} from '@backstage/core-components'; +import { ExampleFetchComponent } from '../ExampleFetchComponent'; -const ExampleComponent = () => { +export const ExampleComponent = () => { const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const profile = identityApi.getProfile(); @@ -52,6 +51,4 @@ const ExampleComponent = () => { ); }; - -export default ExampleComponent; ``` diff --git a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md b/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md index 6992d05866..086175a0ce 100644 --- a/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md +++ b/contrib/docs/tutorials/quickstart-app-plugin/ExampleFetchComponent.md @@ -6,13 +6,8 @@ ExampleFetchComponent.tsx reference import React from 'react'; import { useAsync } from 'react-use'; import Alert from '@material-ui/lab/Alert'; -import { - Table, - TableColumn, - Progress, - githubAuthApiRef, - useApi, -} from '@backstage/core'; +import { githubAuthApiRef, useApi } from '@backstage/core-plugin-api'; +import { Table, TableColumn, Progress } from '@backstage/core-components'; import { graphql } from '@octokit/graphql'; const query = `{ @@ -76,7 +71,7 @@ export const DenseTable = ({ viewer }: DenseTableProps) => { ); }; -const ExampleFetchComponent = () => { +export const ExampleFetchComponent = () => { const auth = useApi(githubAuthApiRef); const { value, loading, error } = useAsync(async (): Promise => { @@ -106,6 +101,4 @@ const ExampleFetchComponent = () => { /> ); }; - -export default ExampleFetchComponent; ``` diff --git a/cypress/src/integration/catalog.ts b/cypress/src/integration/catalog.ts index 4016c15470..20dcdd9b8a 100644 --- a/cypress/src/integration/catalog.ts +++ b/cypress/src/integration/catalog.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ describe('Catalog', () => { cy.visit('/catalog'); - cy.contains('Owned (8)').should('be.visible'); + cy.contains('Owned (10)').should('be.visible'); }); }); }); diff --git a/cypress/src/integration/integrations.ts b/cypress/src/integration/integrations.ts index 5ee077502a..4703c18881 100644 --- a/cypress/src/integration/integrations.ts +++ b/cypress/src/integration/integrations.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,10 @@ describe('Integrations', () => { type: 'url', }); + cy.wait(5000); + cy.visit('/catalog'); - cy.contains('All').click(); + cy.get('[data-testid="user-picker-all"]').click(); cy.get('table').should('contain', 'github-repo'); cy.get('table').should('contain', 'github-repo-nested'); }); @@ -52,8 +54,10 @@ describe('Integrations', () => { type: 'url', }); + cy.wait(5000); + cy.visit('/catalog'); - cy.contains('All').click(); + cy.get('[data-testid="user-picker-all"]').click(); cy.get('table').should('contain', 'gitlab-repo'); cy.get('table').should('contain', 'gitlab-repo-nested'); }); @@ -67,8 +71,10 @@ describe('Integrations', () => { type: 'url', }); + cy.wait(5000); + cy.visit('/catalog'); - cy.contains('All').click(); + cy.get('[data-testid="user-picker-all"]').click(); cy.get('table').should('contain', 'bitbucket-repo'); cy.get('table').should('contain', 'bitbucket-repo-nested'); }); diff --git a/cypress/src/plugins/index.ts b/cypress/src/plugins/index.ts index b90c276d71..4c8a35f0d1 100644 --- a/cypress/src/plugins/index.ts +++ b/cypress/src/plugins/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/cypress/src/support/index.ts b/cypress/src/support/index.ts index e17081831e..ebfcb04d03 100644 --- a/cypress/src/support/index.ts +++ b/cypress/src/support/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/cypress/src/types.d.ts b/cypress/src/types.d.ts index 361aaba9f3..fff4fe29e4 100644 --- a/cypress/src/types.d.ts +++ b/cypress/src/types.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 308544e0f1..f04202b79b 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -23,18 +23,18 @@ during their entire life cycle. Each Utility API is tied to an `ApiRef` instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. `ApiRef`s are created using `createApiRef`, which is -exported by `@backstage/core`. There are many +exported by `@backstage/core-plugin-api`. There are many [predefined Utility APIs](../reference/utility-apis/README.md) defined in -`@backstage/core`, and they're all exported with a name of the pattern -`*ApiRef`, for example `errorApiRef`. +`@backstage/core-plugin-api`, and they're all exported with a name of the +pattern `*ApiRef`, for example `errorApiRef`. To access one of the Utility APIs inside a React component, use the `useApi` -hook exported by `@backstage/core`, or the `withApis` HOC if you prefer class -components. For example, the `ErrorApi` can be accessed like this: +hook exported by `@backstage/core-plugin-api`, or the `withApis` HOC if you +prefer class components. For example, the `ErrorApi` can be accessed like this: ```tsx import React from 'react'; -import { useApi, errorApiRef } from '@backstage/core'; +import { useApi, errorApiRef } from '@backstage/core-plugin-api'; export const MyComponent = () => { const errorApi = useApi(errorApiRef); @@ -52,9 +52,9 @@ Note that there is no explicit type given for `ErrorApi`. This is because the `errorApiRef` has the type embedded, and `useApi` is able to infer the type. Also note that consuming Utility APIs is not limited to plugins, it can be done -from any component inside Backstage, including the ones in `@backstage/core`. -The only requirement is that they are beneath the `AppProvider` in the react -tree. +from any component inside Backstage, including the ones in +`@backstage/core-plugin-api`. The only requirement is that they are beneath the +`AppProvider` in the react tree. ## Supplying APIs @@ -71,8 +71,11 @@ For example, this is the default `ApiFactory` for the `ErrorApi`: createApiFactory({ api: errorApiRef, deps: { alertApi: alertApiRef }, - factory: ({ alertApi }) => - new ErrorAlerter(alertApi, new ErrorApiForwarder()), + factory: ({ alertApi }) => { + const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); + UnhandledErrorForwarder.forward(errorApi, { hidden: false }); + return errorApi; + }, }); ``` @@ -98,13 +101,13 @@ app, and the app itself. ### Core APIs Starting with the Backstage core library, it provides implementations for all of -the core APIs. The core APIs are the ones exported by `@backstage/core`, such as -the `errorApiRef` and `configApiRef`. You can find a full list of them -[here](../reference/utility-apis/README.md). +the core APIs. The core APIs are the ones exported by +`@backstage/core-plugin-api`, such as the `errorApiRef` and `configApiRef`. You +can find a full list of them [here](../reference/utility-apis/README.md). The core APIs are loaded for any app created with `createApp` from -`@backstage/core`, which means that there is no step that needs to be taken to -include these APIs in an app. +`@backstage/core-plugin-api`, which means that there is no step that needs to be +taken to include these APIs in an app. ### Plugin APIs @@ -210,8 +213,9 @@ implement the `ErrorApi`, as it is checked by the type embedded in the Plugins are free to define their own Utility APIs. Simply define the TypeScript interface for the API, and create an `ApiRef` using `createApiRef` exported from -`@backstage/core`. Also be sure to provide at least one implementation of the -API, and to declare a default factory for the API in `createPlugin`. +`@backstage/core-plugin-api`. Also be sure to provide at least one +implementation of the API, and to declare a default factory for the API in +`createPlugin`. Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md index 846ca8feae..e6e378da9d 100644 --- a/docs/architecture-decisions/adr004-module-export-structure.md +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -6,8 +6,8 @@ description: Architecture Decision Record (ADR) log on Module Export Structure ## Context -With a growing number of exports of packages like `@backstage/core`, it is -becoming more and more difficult to answer questions such as +With a growing number of exports of packages like `@backstage/core-components`, +it is becoming more and more difficult to answer questions such as > Is the export in this module also exported by the package? @@ -86,7 +86,7 @@ import { helperFunc } from '../../lib/UtilityX/helper'; ## Consequences We will actively work to rework the export structure in our codebase, -prioritizing the library packages such as `@backstage/core` and +prioritizing the library packages such as `@backstage/core-components` and `@backstage/backend-common`. If possible, we will add tools, such as lint rules, to help enforce the export diff --git a/docs/architecture-decisions/adr011-plugin-package-structure.md b/docs/architecture-decisions/adr011-plugin-package-structure.md index 9133e5b7c2..374a3ed546 100644 --- a/docs/architecture-decisions/adr011-plugin-package-structure.md +++ b/docs/architecture-decisions/adr011-plugin-package-structure.md @@ -35,6 +35,8 @@ example `catalog` or `techdocs`): - `x`: Contains the main frontend code of the plugin. - `x-backend`: Contains the main backend code of the plugin. +- `x-backend-module-`: Contains optional modules related to the backend + plugin. - `x-react`: Contains shared widgets, hooks and similar that both the plugin itself (`x`) and third-party frontend plugins can depend on. - `x-node`: Contains utilities for backends that both the plugin backend itself @@ -61,6 +63,10 @@ We will actively migrate existing packages that are part of a plugin to the `plugins/catalog-common` we might want to do an exception here, as it's a very central package. +We will actively migrate optional features of backend plugins into separate +`x-backend-module-` packages, for example the more specialized processors +in the catalog backend. + The limited set of rules might not be sufficient in the future. If additional packages are required, we will revisit this decision and extend the pattern. diff --git a/docs/assets/software-catalog/service-catalog-home.png b/docs/assets/software-catalog/software-catalog-home.png similarity index 100% rename from docs/assets/software-catalog/service-catalog-home.png rename to docs/assets/software-catalog/software-catalog-home.png diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md new file mode 100644 index 0000000000..814ff63729 --- /dev/null +++ b/docs/auth/identity-resolver.md @@ -0,0 +1,150 @@ +--- +id: identity-resolver +title: Identity resolver +description: Identity resolvers of Backstage users after they sign-in +--- + +This guide explains how the identity of a Backstage user is stored inside their +Backstage Identity Token and how you can customize the Sign-In resolvers to +include identity and group membership information of the user from other +external systems. This ultimately helps with determining the ownership of a +Backstage entity by a user. The ideas here were originally proposed in the RFC +[#4089](https://github.com/backstage/backstage/issues/4089). + +When a user signs in to Backstage, inside the `claims` field of their Backstage +Token (which are standard JWT tokens) a special `ent` claim is set. `ent` +contains a list of +[entity references](../features/software-catalog/references.md), each of which +denotes an identity or a membership that is relevant to the user. There is no +guarantee that these correspond to actual existing catalog entities. + +Let's take an example sign-in resolver for the Google auth provider and explore +how the `ent` field inside `claims` can be set. + +Inside your `packages/backend/src/plugins/auth.ts` file, you can provide custom +sign-in resolvers and set them for any of the Authentication providers inside +`providerFactories` of the `createRouter` imported from the +`@backstage/plugin-auth-backend` plugin. + +```ts +export default async function createPlugin({ + ... +}: PluginEnvironment): Promise { + return await createRouter({ + ... + providerFactories: { + google: createGoogleProvider({ + signIn: { + resolver: async ({ profile: { email } }, ctx) => { + // Call a custom validator function that checks that the email is + // valid and on our own company's domain, and throws an Error if it + // isn't + validateEmail(email); + + // List of entity references that denote the identity and + // membership of the user + const ent = []; + + // Let's use the username in the email ID as the user's default + // unique identifier inside Backstage. + const [id] = email.split('@'); + ent.push(`User:default/${id}`) + + // Let's call the internal LDAP provider to get a list of groups + // that the user belongs to, and add those to the list as well + const ldapGroups = await getLdapGroups(email); + ldapGroups.forEach(group => ent.push(`Group:default/${group}`)) + + // Issue the token containing the entity claims + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub: id, ent }, + }); + return { id, token }; + }, + }, + }), + }, + }); +} +``` + +As you can see, the generated Backstage Token now contains all the claims about +the identity and membership of the user. Once the sign-in process is complete, +and we need to find out if a user owns an Entity in the Software Catalog, these +`ent` claims can be used to determine the ownership. + +According to the RFC, the definition of the ownership of an entity E, for a user +U, is as follows: + +- Get all the `ownedBy` relations of E, and call them O +- Get all the claims of the user U and call them C +- If any C matches any O, return `true` +- Get all Group entities that U is a member of, using the regular + `memberOf`/`hasMember` relation mechanism, and call them G +- If any G matches any O, return `true` +- Otherwise, return `false` + +## Default sign-in resolvers + +Of course you don't have to customize the sign-in resolver if you don't need to. +The Auth backend plugin comes with a set of default sign-in resolvers which you +can use. For example - the Google provider has a default email-based sign-in +resolver, which will search the catalog for a single user entity that has a +matching `google.com/email` annotation. + +It can be enabled like this + +```tsx +// File: packages/backend/src/plugins/auth.ts +import { googleEmailSignInResolver } from '@backstage/plugin-auth-backend'; + +export default async function createPlugin({ + ... +}: PluginEnvironment): Promise { + return await createRouter({ + ... + providerFactories: { + google: createGoogleProvider({ + signIn: { + resolver: googleEmailSignInResolver + } +... +``` + +## AuthHandler + +Similar to a custom sign-in resolver, you can also write a custom auth handler +function which is used to verify and convert the auth response into the profile +that will be presented to the user. This is where you can customize things like +display name and profile picture. + +This is also the place where you can do authorization and validation of the user +and throw errors if the user should not be allowed access in Backstage. + +```tsx +// File: packages/backend/src/plugins/auth.ts +export default async function createPlugin({ + ... +}: PluginEnvironment): Promise { + return await createRouter({ + ... + providerFactories: { + google: createGoogleProvider({ + authHandler: async ({ + fullProfile // Type: passport.Profile, + idToken // Type: (Optional) string, + }) => { + // Custom validation code goes here + return { + profile: { + email, + picture, + displayName, + } + }; + } + }) + } + }) +} +``` diff --git a/docs/auth/index.md b/docs/auth/index.md index 000c57a50c..76cd79b0fd 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -66,7 +66,8 @@ built-in providers: ```diff # packages/app/src/App.tsx -+ import { githubAuthApiRef, SignInProviderConfig, SignInPage } from '@backstage/core'; ++ import { githubAuthApiRef } from '@backstage/core-plugin-api'; ++ import { SignInProviderConfig, SignInPage } from '@backstage/core-components'; + const githubProvider: SignInProviderConfig = { + id: 'github-auth-provider', diff --git a/docs/auth/oauth.md b/docs/auth/oauth.md index 4209b81279..fbd87d653b 100644 --- a/docs/auth/oauth.md +++ b/docs/auth/oauth.md @@ -14,7 +14,7 @@ to various third party APIs. There are occasions when the user wants to perform actions towards third party services that require authorization via OAuth. Backstage provides standardized [Utility APIs](../api/utility-apis.md) such as the -[GoogleAuthApi](https://github.com/backstage/backstage/blob/master/packages/core-api/src/apis/definitions/auth.ts) +[GoogleAuthApi](https://github.com/backstage/backstage/blob/master/packages/core-plugin-api/src/apis/definitions/auth.ts) for that use-case. Backstage also includes a set of implementations of these APIs that integrate with the [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend) @@ -38,7 +38,7 @@ choose an account to log in with, and accept or reject the request. If the user accepts the login request, a token is issued, and any holder of the token can use it to make authenticated requests towards the third party service. -## OAuth in @backstage/core-api and auth-backend +## OAuth in @backstage/core-app-api and auth-backend The default OAuth implementation in Backstage is based on an OAuth server-side offline access flow, which means that it uses the backend as a helper in order @@ -59,8 +59,8 @@ easier to make authenticated requests inside a plugin. The following describes the OAuth flow implemented by the [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend) and -[DefaultAuthConnector](https://github.com/backstage/backstage/blob/master/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts) -in `@backstage/core-api`. +[DefaultAuthConnector](https://github.com/backstage/backstage/blob/master/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts) +in `@backstage/core-app-api`. Component and APIs can request Access or ID Tokens from any available Auth provider. If there already exists a cached fresh token that covers (at least) diff --git a/docs/auth/using-auth.md b/docs/auth/using-auth.md index fbc6017272..e4f87a12f9 100644 --- a/docs/auth/using-auth.md +++ b/docs/auth/using-auth.md @@ -45,8 +45,8 @@ pieces in place that can be used. #### Identity for Plugin Developers As a plugin developer, there are two main touchpoints for identities: the -`IdentityApi` exported by `@backstage/core` via the `identityApiRef`, and a not -yet existing middleware exported by `@backstage/backend-common`. +`IdentityApi` exported by `@backstage/core-plugin-api` via the `identityApiRef`, +and a not yet existing middleware exported by `@backstage/backend-common`. The `IdentityApi` gives access to the signed-in user's identity in the frontend. It provides access to the user's ID, lightweight profile information, and an ID @@ -61,8 +61,9 @@ https://github.com/backstage/backstage/issues/1435. If you're setting up your own Backstage app, or want to add a new identity provider, there are three touchpoints: the frontend auth APIs in -`@backstage/core-api`, the backend auth providers in `auth-backend`, and the -`SignInPage` component configured in the Backstage app via `createApp`. +`@backstage/core-app-api` and `@backstage/core-plugin-api`, the backend auth +providers in `auth-backend`, and the `SignInPage` component configured in the +Backstage app via `createApp`. The frontend APIs and backend providers are tightly coupled together for each auth provider, and together they implement an e2e auth flow. Only some auth @@ -81,10 +82,10 @@ The final piece of the puzzle is the `SignInPage` component that can be configured as part of the app. Without a sign-in page, Backstage will fall back to a `guest` identity for all users, without any ID token. To enable sign-in, a `SignInPage` needs to be configured, which in turn has to supply a user to the -app. The `@backstage/core` package provides a basic sign-in page that allows -both the user and the app developer to choose between a couple of different -sign-in methods, or to designate a single provider that may also be logged in to -automatically. +app. The `@backstage/core-components` package provides a basic sign-in page that +allows both the user and the app developer to choose between a couple of +different sign-in methods, or to designate a single provider that may also be +logged in to automatically. ## Further Reading diff --git a/docs/cli/commands.md b/docs/cli/commands.md index a99d4cd9d0..ff61e51c75 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -554,9 +554,7 @@ Options: Scope: `root` Validate `@backstage` dependencies within the repo, making sure that there are -no duplicates of packages that might lead to breakages. For example, -`@backstage/core` must not be loaded in twice, so having two different versions -of it installed will cause this command to exit with an error. +no duplicates of packages that might lead to breakages. By supplying the `--fix` flag the command will attempt to fix any conflict that can be resolved by editing `yarn.lock`, but will not attempt to search for diff --git a/docs/conf/defining.md b/docs/conf/defining.md index 34b9b11977..f13beb9f77 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -93,6 +93,21 @@ declare the visibility of a leaf node of `type: "string"`. | `backend` | (Default) Only in backend | | `secret` | Only in backend and may be excluded from logs for security reasons | +You can set visibility with an `@visibility` comment in the `Config` Typescript +interface. + +```ts +export interface Config { + app: { + /** + * Frontend root URL + * @visibility frontend + */ + baseUrl: string; + }; +} +``` + ## Validation Schemas can be validated using the `backstage-cli config:check` command. If you diff --git a/docs/conf/reading.md b/docs/conf/reading.md index 6d4b61224d..6ac10a8c8b 100644 --- a/docs/conf/reading.md +++ b/docs/conf/reading.md @@ -112,7 +112,7 @@ example `getString`. These will throw an error if there is no value available. The [ConfigApi](../reference/utility-apis/Config.md) in the frontend is a [UtilityApi](../api/utility-apis.md). It's accessible as usual via the -`configApiRef` exported from `@backstage/core`. +`configApiRef` exported from `@backstage/core-plugin-api`. Depending on the config api in another API is slightly different though, as the `ConfigApi` implementation is supplied via the App itself and not instantiated @@ -123,7 +123,7 @@ for an example of how this wiring is done. For standalone plugin setups in `dev/index.ts`, register a factory with a statically mocked implementation of the config API. Use the `ConfigReader` from `@backstage/config` to create an instance and register it for the `configApiRef` -from `@backstage/core`. +from `@backstage/core-plugin-api`. ## Accessing ConfigApi in Backend Plugins diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 095a4989d8..e542b02ebf 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -59,16 +59,17 @@ Once the host build is complete, we are ready to build our image. The following FROM node:14-buster-slim WORKDIR /app - # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. -ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ +COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` diff --git a/docs/dls/contributing-to-storybook.md b/docs/dls/contributing-to-storybook.md index fff9e51e18..84282289f8 100644 --- a/docs/dls/contributing-to-storybook.md +++ b/docs/dls/contributing-to-storybook.md @@ -25,7 +25,7 @@ component, which are then displayed both visually and with sample code to be copied. When custom Backstage components are created, they are placed in the -`@backstage/core` package and added to the Storybook. +`@backstage/core-components` package and added to the Storybook. There may be times where an existing Material-UI component (in `@material-ui/core`) is sufficient and doesn't need to be wrapped or duplicated. diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 45ef28ea2f..068a17249c 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -68,7 +68,7 @@ The base URL to the Kubernetes control plane. Can be found by using the ##### `clusters.\*.name` A name to represent this cluster, this must be unique within the `clusters` -array. Users will see this value in the Service Catalog Kubernetes plugin. +array. Users will see this value in the Software Catalog Kubernetes plugin. ##### `clusters.\*.authProvider` @@ -195,7 +195,7 @@ annotations: #### Labeling Kubernetes components -In order for Kubernetes components to show up in the service catalog as a part +In order for Kubernetes components to show up in the software catalog as a part of an entity, Kubernetes components themselves can have the following label: ```yaml diff --git a/docs/features/kubernetes/index.md b/docs/features/kubernetes/index.md index 26794eca10..159370fbf4 100644 --- a/docs/features/kubernetes/index.md +++ b/docs/features/kubernetes/index.md @@ -2,7 +2,7 @@ id: overview title: Kubernetes sidebar_label: Overview -description: Monitoring Kubernetes based services with the service catalog +description: Monitoring Kubernetes based services with the software catalog --- Kubernetes in Backstage is a tool that's designed around the needs of service diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index 7408d69193..2bd26faf8b 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -29,7 +29,7 @@ Backstage app with the following contents: ```tsx import React from 'react'; -import { Content, Header, Page } from '@backstage/core'; +import { Content, Header, Page } from '@backstage/core-components'; import { Grid, List, Card, CardContent } from '@material-ui/core'; import { SearchBar, @@ -142,7 +142,6 @@ export default async function createPlugin({ const indexBuilder = new IndexBuilder({ logger, searchEngine }); indexBuilder.addCollator({ - type: 'software-catalog', defaultRefreshIntervalSeconds: 600, collator: new DefaultCatalogCollator({ discovery }), }); @@ -262,21 +261,21 @@ const indexBuilder = new IndexBuilder({ logger, searchEngine }); ``` Backstage Search can be used to power search of anything! Plugins like the -Catalog offer default [collators](./concepts.md#collators) which are responsible -for providing documents [to be indexed](./concepts.md#documents-and-indices). -You can register any number of collators with the `IndexBuilder` like this: +Catalog offer default [collators](./concepts.md#collators) (e.g. +[DefaultCatalogCollator](https://github.com/backstage/backstage/blob/df12cc25aa4934a98bc42ed03c07f64a1a0a9d72/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts)) +which are responsible for providing documents +[to be indexed](./concepts.md#documents-and-indices). You can register any +number of collators with the `IndexBuilder` like this: ```typescript const indexBuilder = new IndexBuilder({ logger, searchEngine }); indexBuilder.addCollator({ - type: 'software-catalog', defaultRefreshIntervalSeconds: 600, collator: new DefaultCatalogCollator({ discovery }), }); indexBuilder.addCollator({ - type: 'my-custom-stuff', defaultRefreshIntervalSeconds: 3600, collator: new MyCustomCollator(), }); @@ -290,7 +289,6 @@ its `defaultRefreshIntervalSeconds` value, like this: ```typescript {3} indexBuilder.addCollator({ - type: 'software-catalog', defaultRefreshIntervalSeconds: 600, collator: new DefaultCatalogCollator({ discovery }), }); diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 988af89e47..fb17f4a434 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -654,7 +654,7 @@ spec: steps: - id: fetch-base name: Fetch Base - action: fetch:cookiecutter + action: fetch:template input: url: ./template values: diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index 70541b85db..a502c2aee5 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -1,29 +1,29 @@ --- id: software-catalog-overview -title: Backstage Service Catalog (alpha) +title: Backstage Software Catalog (alpha) sidebar_label: Overview # prettier-ignore -description: The Backstage Service Catalog — actually, a software catalog, since it includes more than just services +description: The Backstage Software Catalog --- -## What is a Service Catalog? +## What is a Software Catalog? -The Backstage Service Catalog — actually, a software catalog, since it includes +The Backstage Software Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata YAML files](descriptor-format.md) stored together with the code, which are then harvested and visualized in Backstage. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) ## How it works -Backstage and the Backstage Service Catalog make it easy for one team to manage +Backstage and the Backstage Software Catalog make it easy for one team to manage 10 services — and makes it possible for your company to manage thousands of them. -More specifically, the Service Catalog enables two main use-cases: +More specifically, the Software Catalog enables two main use-cases: 1. Helping teams manage and maintain the software they own. Teams get a uniform view of all their software; services, libraries, websites, ML models — you @@ -34,15 +34,14 @@ More specifically, the Service Catalog enables two main use-cases: ## Getting Started The Software Catalog is available to browse at `/catalog`. If you've followed -[Installing in your Backstage App](./installation.md) in your separate App or -[Getting Started with Backstage](../../getting-started) for this repo, you -should be able to browse the catalog at `http://localhost:3000`. +[Getting Started with Backstage](../../getting-started), you should be able to +browse the catalog at `http://localhost:3000`. -![](../../assets/software-catalog/service-catalog-home.png) +![](../../assets/software-catalog/software-catalog-home.png) ## Adding components to the catalog -The source of truth for the components in your service catalog are +The source of truth for the components in your software catalog are [metadata YAML files](descriptor-format.md) stored in source control (GitHub, GitHub Enterprise, GitLab, ...). @@ -105,11 +104,11 @@ them, and do so using their normal Git workflow. ![](../../assets/software-catalog/bsc-edit.png) Once the change has been merged, Backstage will automatically show the updated -metadata in the service catalog after a short while. +metadata in the software catalog after a short while. ## Finding software in the catalog -By default the service catalog shows components owned by the team of the logged +By default the software catalog shows components owned by the team of the logged in user. But you can also switch to _All_ to see all the components across your company's software ecosystem. Basic inline _search_ and _column filtering_ makes it easy to browse a big set of components. @@ -125,7 +124,7 @@ _starring_ of components: ## Integrated tooling through plugins -The service catalog is a great way to organize the infrastructure tools you use +The software catalog is a great way to organize the infrastructure tools you use to manage the software. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UIs (and incurring additional cognitive overhead each time they diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md deleted file mode 100644 index 0b622fb093..0000000000 --- a/docs/features/software-catalog/installation.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -id: installation -title: Installing in your Backstage App -description: Documentation on How to install Backstage Plugin ---- - -The catalog plugin comes in two packages, `@backstage/plugin-catalog` and -`@backstage/plugin-catalog-backend`. Each has their own installation steps, -outlined below. - -## Installing @backstage/plugin-catalog - -> **Note that if you used `npx @backstage/create-app`, the plugin is already -> installed and you can skip to -> [adding entries to the catalog](#adding-entries-to-the-catalog)** - -The catalog frontend plugin should be installed in your `app` package, which is -created as a part of `@backstage/create-app`. To install the package, run: - -```bash -# From your Backstage root directory -cd packages/app -yarn add @backstage/plugin-catalog -``` - -### Adding the Plugin to your `packages/app` - -Add the two pages that the catalog plugin provides to your app. You can choose -any name for these routes, but we recommend the following: - -```tsx -// packages/app/src/App.tsx -import { - catalogPlugin, - CatalogIndexPage, - CatalogEntityPage, -} from '@backstage/plugin-catalog'; - -// Add to the top-level routes, directly within -} /> -}> - {/* - This is the root of the custom entity pages for your app, refer to the example app - in the main repo or the output of @backstage/create-app for an example - */} - - -``` - -The catalog plugin also has one external route that needs to be bound for it to -function: the `createComponent` route which should link to the page where the -user can create components. In a typical setup the create component route will -be linked to the Scaffolder plugin's template index page: - -```ts -// packages/app/src/App.tsx -import { catalogPlugin } from '@backstage/plugin-catalog'; -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; - -const app = createApp({ - // ... - bindRoutes({ bind }) { - bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.root, - }); - }, -}); -``` - -You may also want to add a link to the catalog index page to your sidebar: - -```tsx -// packages/app/src/components/Root.tsx -import HomeIcon from '@material-ui/icons/Home'; - -// Somewhere within the -; -``` - -This is all that is needed for the frontend part of the Catalog plugin to work! - -## Gotchas that we will fix - -Since the catalog plugin currently ships with a sentry plugin `InfoCard` -installed by default, you'll need to set `sentry.organization` in your -`app-config.yaml`. For example: - -```yaml -sentry: - organization: Acme Corporation -``` - -If you've created an app with an older version of `@backstage/create-app` or -`@backstage/cli create-app`, be sure to remove the Welcome plugin from the app, -as that will conflict with the catalog routes. - -## Installing @backstage/plugin-catalog-backend - -> **Note that if you used `npx @backstage/create-app`, the plugin is already -> installed and you can skip to -> [adding entries to the catalog](#adding-entries-to-the-catalog)** - -The catalog backend should be installed in your `backend` package, which is -created as a part of `@backstage/create-app`. To install the package, run: - -```bash -# From your Backstage root directory -cd packages/backend -yarn add @backstage/plugin-catalog-backend -``` - -### Adding the Plugin to your `packages/backend` - -You'll need to add the plugin to the `backend`'s router. You can do this by -creating a file called `packages/backend/src/plugins/catalog.ts` with contents -matching -[catalog.ts in the create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts). - -Once the `catalog.ts` router setup file is in place, add the router to -`packages/backend/src/index.ts`: - -```ts -import catalog from './plugins/catalog'; - -const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); - -const apiRouter = Router(); -/** several different routers */ -apiRouter.use('/catalog', await catalog(catalogEnv)); -``` - -### Adding Entries to the Catalog - -At this point the catalog backend is installed in your backend package, but you -will not have any entities loaded. - -To get up and running and try out some templates quickly, you can add some of -our example templates through static configuration. Add the following to the -`catalog.locations` section in your `app-config.yaml`: - -```yaml -catalog: - locations: - # Backstage Example Components - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/artist-lookup-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/playback-order-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/podcast-api-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/queue-proxy-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/searcher-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/playback-lib-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/www-artist-component.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/components/shuffle-api-component.yaml -``` - -### Running the Backend - -Finally, start up Backstage with the new configuration: - -```bash -# Run from the root to start both backend and frontend -yarn dev - -# Alternatively, run only the backend from its own package -cd packages/backend -yarn start -``` - -If you've also set up the frontend plugin, you should be ready to go browse the -catalog at [localhost:3000](http://localhost:3000) now! diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index ecb7d11be9..2cdf6f1b35 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -51,7 +51,7 @@ spec: steps: - id: fetch-base name: Fetch Base - action: fetch:cookiecutter + action: fetch:template input: url: ./template values: @@ -83,7 +83,7 @@ spec: [Template Entity](../software-catalog/descriptor-format.md#kind-template) contains more information about the required fields. -Once we have a `template.yaml` ready, we can then add it to the service catalog +Once we have a `template.yaml` ready, we can then add it to the software catalog for use by the scaffolder. You can add the template files to the catalog through diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index 14dc7ef861..af3ae97b21 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -14,3 +14,54 @@ Azure, GitLab and Bitbucket. A list of all registered actions can be found under `/create/actions`. For local development you should be able to reach them at `http://localhost:3000/create/actions`. + +### Migrating from `fetch:cookiecutter` to `fetch:template` + +The `fetch:template` action is a new action with a similar API to +`fetch:cookiecutter` but no dependency on `cookiecutter`. There are two options +for migrating templates that use `fetch:cookiecutter` to use `fetch:template`: + +#### Using `cookiecutterCompat` mode + +The new `fetch:template` action has a `cookiecutterCompat` flag which should +allow most templates built for `fetch:cookiecutter` to work without any changes. + +1. Update action name in `template.yaml`. The name should be changed from + `fetch:cookiecutter` to `fetch:template`. +2. Set `cookiecutterCompat` to `true` in the `fetch:template` step input in + `template.yaml`. + +```diff + steps: + - id: fetch-base + name: Fetch Base +- action: fetch:cookiecutter ++ action: fetch:template + input: + url: ./skeleton ++ cookiecutterCompat: true + values: +``` + +#### Manual migration + +If you prefer, you can manually migrate your templates to avoid the need for +enabling cookiecutter compatibility mode, which will result in slightly less +verbose template variables expressions. + +1. Update action name in `template.yaml`. The name should be changed from + `fetch:cookiecutter` to `fetch:template`. +2. Update variable syntax in file names and content. `fetch:cookiecutter` + expects variables to be enclosed in `{{` `}}` and prefixed with + `cookiecutter.`, while `fetch:template` expects variables to be enclosed in + `${{` `}}` and prefixed with `values.`. For example, a reference to variable + `myInputVariable` would need to be migrated from + `{{ cookiecutter.myInputVariable }}` to `${{ values.myInputVariable }}`. +3. Replace uses of `jsonify` with `dump`. The + [`jsonify` filter](https://cookiecutter.readthedocs.io/en/latest/advanced/template_extensions.html#jsonify-extension) + is built in to `cookiecutter`, and is not available by default when using + `fetch:template`. The + [`dump` filter](https://mozilla.github.io/nunjucks/templating.html#dump) is + the equivalent filter in nunjucks, so an expression like + `{{ cookiecutter.myAwesomeList | jsonify }}` should be migrated to + `${{ values.myAwesomeList | dump }}`. diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md new file mode 100644 index 0000000000..cdd8166889 --- /dev/null +++ b/docs/features/software-templates/configuration.md @@ -0,0 +1,52 @@ +--- +id: configuration +title: Software Template Configuration +sidebar_label: Configuration +description: Configuration options for Backstage Software Templates +--- + +Backstage software templates create source code, so your Backstage application +needs to be set up to allow repository creation. + +This is done in your `app-config.yaml` by adding +[Backstage integrations](https://backstage.io/docs/integrations/) for the +appropriate source code repository for your organization. + +> Note: Integrations may already be set up as part of your `app-config.yaml`. + +The next step is to add +[add templates](http://backstage.io/docs/features/software-templates/adding-templates) +to your Backstage app. + +### GitHub + +For GitHub, you can configure who can see the new repositories that are created +by specifying `visibility` option. Valid options are `public`, `private` and +`internal`. The `internal` option is for GitHub Enterprise clients, which means +public within the enterprise. + +```yaml +scaffolder: + github: + visibility: public # or 'internal' or 'private' +``` + +### Disabling Docker in Docker situation (Optional) + +Software Templates use +[Cookiecutter](https://github.com/cookiecutter/cookiecutter) as a templating +library. By default it will use the +[scaffolder-backend/Cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile) +docker image. + +If you are running Backstage from a Docker container and you want to avoid +calling a container inside a container, you can set up Cookiecutter in your own +image, this will use the local installation instead. + +You can do so by including the following lines in the last step of your +`Dockerfile`: + +```Dockerfile +RUN apt-get update && apt-get install -y python3 python3-pip +RUN pip3 install cookiecutter +``` diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 12ce6e3ed3..1434d62a29 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -17,10 +17,8 @@ locations like GitHub or GitLab. ### Getting Started -> Be sure to have covered [Installing in your Backstage App](./installation.md) -> for your separate App or -> [Getting Started with Backstage](../../getting-started) for this repo before -> proceeding. +> Be sure to have covered +> [Getting Started with Backstage](../../getting-started) before proceeding. The Software Templates are available under `/create`. For local development you should be able to reach them at `http://localhost:3000/create`. diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md deleted file mode 100644 index d5643ca955..0000000000 --- a/docs/features/software-templates/installation.md +++ /dev/null @@ -1,280 +0,0 @@ ---- -id: installation -title: Installing in your Backstage App -description: Documentation on How to install Backstage App ---- - -The scaffolder plugin comes in two packages, `@backstage/plugin-scaffolder` and -`@backstage/plugin-scaffolder-backend`. Each has their own installation steps, -outlined below. - -The Scaffolder plugin also depends on the Software Catalog. Instructions for how -to set that up can be found [here](../software-catalog/installation.md). - -## Installing @backstage/plugin-scaffolder - -> **Note that if you used `npx @backstage/create-app`, the plugin may already be -> present** - -The scaffolder frontend plugin should be installed in your `app` package, which -is created as a part of `@backstage/create-app`. To install the package, run: - -```bash -# From your Backstage root directory -cd packages/app -yarn add @backstage/plugin-scaffolder -``` - -### Adding the Plugin to your `packages/app` - -Add the root page that the Scaffolder plugin provides to your app. You can -choose any path for the route, but we recommend the following: - -```tsx -import { ScaffolderPage } from '@backstage/plugin-scaffolder'; - -// Add to the top-level routes, directly within -} />; -``` - -You may also want to add a link to the template index page to your sidebar: - -```tsx -import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; - -// Somewhere within the -; -``` - -This is all that is needed for the frontend part of the Scaffolder plugin to -work! - -## Installing @backstage/plugin-scaffolder-backend - -> **Note that if you used `npx @backstage/create-app`, the plugin may already be -> present** - -The scaffolder backend should be installed in your `backend` package, which is -created as a part of `@backstage/create-app`. To install the package, run: - -```bash -# From your Backstage root directory -cd packages/backend -yarn add @backstage/plugin-scaffolder-backend -``` - -### Adding the Plugin to your `packages/backend` - -You'll need to add the plugin to the `backend`'s router. You can do this by -creating a file called `packages/backend/src/plugins/scaffolder.ts` with the -following contents to get you up and running quickly. - -```ts -import { - DockerContainerRunner, - SingleHostDiscovery, -} from '@backstage/backend-common'; -import { - CookieCutter, - createRouter, - Preparers, - Publishers, - CreateReactAppTemplater, - Templaters, -} from '@backstage/plugin-scaffolder-backend'; -import type { PluginEnvironment } from '../types'; -import Docker from 'dockerode'; -import { CatalogClient } from '@backstage/catalog-client'; - -export default async function createPlugin({ - logger, - config, - database, - reader, -}: PluginEnvironment) { - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); - - const cookiecutterTemplater = new CookieCutter({ containerRunner }); - const craTemplater = new CreateReactAppTemplater({ containerRunner }); - const templaters = new Templaters(); - - templaters.register('cookiecutter', cookiecutterTemplater); - templaters.register('cra', craTemplater); - - const preparers = await Preparers.fromConfig(config, { logger }); - const publishers = await Publishers.fromConfig(config, { logger }); - - const discovery = SingleHostDiscovery.fromConfig(config); - const catalogClient = new CatalogClient({ discoveryApi: discovery }); - - return await createRouter({ - preparers, - templaters, - publishers, - logger, - config, - database, - catalogClient, - reader, - }); -} -``` - -Once the `scaffolder.ts` router setup file is in place, add the router to -`packages/backend/src/index.ts`: - -```ts -import scaffolder from './plugins/scaffolder'; - -const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); - -const apiRouter = Router(); -/* several router .use calls */ - -/* add this line */ -apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); -``` - -### Adding Templates - -At this point the scaffolder backend is installed in your backend package, but -you will not have any templates available to use. These need to be added to the -software catalog, as they are represented as entities of kind -[Template](../software-catalog/descriptor-format.md#kind-template). You can find -out more about adding templates [here](./adding-templates.md). - -To get up and running and try out some templates quickly, you can add some of -our example templates through static configuration. Add the following to the -`catalog.locations` section in your `app-config.yaml`: - -```yaml -catalog: - locations: - # Backstage Example Templates - - type: url - target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml - - type: url - target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml - - type: url - target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml -``` - -### Runtime Dependencies / Configuration - -For the scaffolder backend plugin to function, you'll need to setup the -integrations config in your `app-config.yaml`. - -You can find help for different providers below. - -> Note: Some of this configuration may already be set up as part of your -> `app-config.yaml`. We're moving away from the duplicated config for -> authentication in the `scaffolder` section and using `integrations` instead. - -#### GitHub - -The GitHub access token is retrieved from environment variables via the config. -The config file needs to specify what environment variable the token is -retrieved from. Your config should have the following objects. - -You can configure who can see the new repositories that the scaffolder creates -by specifying `visibility` option. Valid options are `public`, `private` and -`internal`. The `internal` option is for GitHub Enterprise clients, which means -public within the enterprise. - -```yaml -integrations: - github: - - host: github.com - token: ${GITHUB_TOKEN} - -scaffolder: - github: - visibility: public # or 'internal' or 'private' -``` - -#### GitLab - -For GitLab, we currently support the configuration of the GitLab publisher and -allows to configure the private access token and the base URL of a GitLab -instance: - -```yaml -integrations: - gitlab: - - host: gitlab.com - token: ${GITLAB_TOKEN} -``` - -#### Bitbucket - -For Bitbucket there are two authentication methods supported. Either `token` or -a combination of `appPassword` and `username`. It looks like either of the -following: - -```yaml -integrations: - bitbucket: - - host: bitbucket.org - token: ${BITBUCKET_TOKEN} -``` - -or - -```yaml -integrations: - bitbucket: - - host: bitbucket.org - appPassword: ${BITBUCKET_APP_PASSWORD} - username: ${BITBUCKET_USERNAME} -``` - -#### Azure DevOps - -For Azure DevOps we support both the preparer and publisher stage with the -configuration of a private access token (PAT). For the publisher it's also -required to define the base URL for the client to connect to the service. This -will hopefully support on-prem installations as well but that has not been -verified. - -```yaml -integrations: - azure: - - host: dev.azure.com - token: ${AZURE_TOKEN} -``` - -### Running the Backend - -Finally, make sure you have a local Docker daemon running, and start up the -backend with the new configuration: - -```bash -cd packages/backend -GITHUB_TOKEN= yarn start -``` - -If you've also set up the frontend plugin, so you should be ready to go browse -the templates at [localhost:3000/create](http://localhost:3000/create) now! - -### Disabling Docker in Docker situation (Optional) - -Software Templates use -[Cookiecutter](https://github.com/cookiecutter/cookiecutter) as templating -library. By default it will use the -[spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b910afc7d1270855aed0ec4718aba366c91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile) -docker image. - -If you are running Backstage from a Docker container and you want to avoid -calling a container inside a container, you can set up Cookiecutter in your own -image, this will use the local installation instead. - -You can do so by including the following lines in the last step of your -`Dockerfile`: - -```Dockerfile -RUN apt-get update && apt-get install -y python3 python3-pip -RUN pip3 install cookiecutter -``` diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 90dd603c8f..53a90e2954 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -101,9 +101,7 @@ should have something similar to the below in ```ts return await createRouter({ - preparers, - templaters, - publishers, + containerRunner, logger, config, database, @@ -118,9 +116,7 @@ will set the available actions that the scaffolder has access to. ```ts const actions = [createNewFileAction()]; return await createRouter({ - preparers, - templaters, - publishers, + containerRunner, logger, config, database, @@ -137,18 +133,17 @@ want to have those as well as your new one, you'll need to do the following: import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend'; const builtInActions = createBuiltinActions({ + containerRunner, integrations, + config, catalogClient, - templaters, reader, }); const actions = [...builtInActions, createNewFileAction()]; return await createRouter({ - preparers, - templaters, - publishers, + containerRunner, logger, config, database, diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 8481e64eac..7b475f2fa1 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -4,8 +4,8 @@ title: Writing Templates description: Details around creating your own custom Software Templates --- -Templates are stored in the **Service Catalog** under a kind `Template`. You can -create your own templates with a small `yaml` definition which describes the +Templates are stored in the **Software Catalog** under a kind `Template`. You +can create your own templates with a small `yaml` definition which describes the template and it's metadata, along with some input variables that your template will need, and then a list of actions which are then executed by the scaffolding service. @@ -62,7 +62,7 @@ spec: steps: - id: fetch-base name: Fetch Base - action: fetch:cookiecutter + action: fetch:template input: url: ./template values: @@ -289,8 +289,8 @@ template. These follow the same standard format: - id: fetch-base # A unique id for the step name: Fetch Base # A title displayed in the frontend if: '{{ parameters.name }}' # Optional condition, skip the step if not truthy - action: fetch:cookiecutter # an action to call - input: # input that is passed as arguments to the action handler + action: fetch:template # An action to call + input: # Input that is passed as arguments to the action handler url: ./template values: name: '{{ parameters.name }}' @@ -317,20 +317,20 @@ output: ### The templating syntax -You might have noticed in the examples that there are `{{ }}`, and these are a -`handlebars` templates for linking and glueing all these different parts of -`yaml` together. All the form inputs from the `parameters` section, when passed -to the steps will be available by using the template syntax -`{{ parameters.something }}`. This is great for passing the values from the form -into different steps and reusing these input variables. To pass arrays or -objects use the syntax `{{ json paramaters.something }}` where -`paramaters.something` is of type `object` or `array` in the `jsonSchema`, such -as the `nicknames` parameter in the previous example. +You might have noticed variables wrapped in `{{ }}` in the examples. These are +`handlebars` template strings for linking and gluing the different parts of the +template together. All the form inputs from the `parameters` section will be +available by using this template syntax (for example, +`{{ parameters.firstName }}` inserts the value of `firstName` from the +parameters). This is great for passing the values from the form into different +steps and reusing these input variables. To pass arrays or objects use the +`json` custom [helper](https://handlebarsjs.com/guide/expressions.html#helpers). +For example, `{{ json parameters.nicknames }}` will insert the result of calling +`JSON.stringify` on the value of the `nicknames` parameter. As you can see above in the `Outputs` section, `actions` and `steps` can also -output things. So you can grab that output by using -`steps.$stepId.output.$property`. +output things. You can grab that output using `steps.$stepId.output.$property`. You can read more about all the `inputs` and `outputs` defined in the actions in -code part of the `JSONSchema` or you can read more about our built in ones +code part of the `JSONSchema`, or you can read more about our built in ones [here](./builtin-actions.md). diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index b9340330a8..3fdc942ac4 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -28,10 +28,10 @@ scratch. ### Use the documentation template Your working Backstage instance should by default have a documentation template -added. If not, follow these -[instructions](../software-templates/installation.md#adding-templates) to add -the documentation template. The template creates a component with only TechDocs -configuration and default markdown files as below mentioned in manual +added. If not, copy the catalog locations from the +[create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs) +to add the documentation template. The template creates a component with only +TechDocs configuration and default markdown files as below mentioned in manual documentation setup, and is otherwise empty. ![Documentation Template](../../assets/techdocs/documentation-template.png) diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 6c64e83a76..3f3c92d0de 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -78,11 +78,6 @@ the repository. The archive does not have any git history attached to it. Also it is a compressed file. Hence the file size is significantly smaller than how much data git clone has to transfer. -Caveat: Currently TechDocs sites built using URL Reader will be cached for 30 -minutes which means they will not be re-built if new changes are made within 30 -minutes. This cache invalidation will be replaced by commit timestamp based -implementation very soon. - ## How to use a custom TechDocs home page? ### 1st way: TechDocsCustomHome with a custom configuration diff --git a/docs/features/techdocs/troubleshooting.md b/docs/features/techdocs/troubleshooting.md index e6efefc578..d8b413be5c 100644 --- a/docs/features/techdocs/troubleshooting.md +++ b/docs/features/techdocs/troubleshooting.md @@ -55,3 +55,20 @@ INFO - Start watching changes [I 210115 19:00:45 handlers:64] Start detecting changes INFO - Start detecting changes ``` + +## PlantUML with `svg_object` doesn't render + +The [plantuml-markdown](https://pypi.org/project/plantuml-markdown/) MkDocs +plugin available in +[`mkdocs-techdocs-core`](https://github.com/backstage/mkdocs-techdocs-core) +supports different formats for rendering diagrams. TechDocs does however not +support all of them. + +The `svg_object` format renders a diagram as an HTML `` tag but this is +not allowed as it enables bad actors to inject malicious content into +documentation pages. See +[CVE-2021-32661](https://github.com/advisories/GHSA-gg96-f8wr-p89f) for more +details. + +Instead use `svg_inline` which renders as an `` tag and provides the same +benefits as `svg_object`. diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 62ee4867c8..119629915e 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -370,7 +370,7 @@ techdocs: openStackSwift: containerName: 'name-of-techdocs-storage-bucket' credentials: - userName: ${OPENSTACK_SWIFT_STORAGE_USERNAME} + username: ${OPENSTACK_SWIFT_STORAGE_USERNAME} password: ${OPENSTACK_SWIFT_STORAGE_PASSWORD} authUrl: ${OPENSTACK_SWIFT_STORAGE_AUTH_URL} keystoneAuthVersion: ${OPENSTACK_SWIFT_STORAGE_AUTH_VERSION} diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 4dbc6bfa48..aa3851463f 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -56,7 +56,7 @@ For example, adding the theme that we created in the previous section can be done like this: ```ts -import { createApp } from '@backstage/core'; +import { createApp } from '@backstage/core-app-api'; const app = createApp({ apis: ..., diff --git a/docs/getting-started/contributors.md b/docs/getting-started/contributors.md index e33d7a8479..e12c0c5f17 100644 --- a/docs/getting-started/contributors.md +++ b/docs/getting-started/contributors.md @@ -94,7 +94,7 @@ here are some useful ones: ```python yarn start # Start serving the example app, use --check to include type checks and linting -yarn storybook # Start local storybook, useful for working on components in @backstage/core +yarn storybook # Start local storybook, useful for working on components in @backstage/core-components yarn workspace @backstage/plugin-welcome start # Serve welcome plugin only, also supports --check diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 2e568664d9..ac78685a82 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -137,7 +137,7 @@ frontend with `yarn start` in one window, and the backend with It can often be useful to try out changes to the packages in the main Backstage repo within your own app. For example if you want to make modifications to -`@backstage/core` and try them out in your app. +`@backstage/core-plugin-api` and try them out in your app. To link in external packages, add them to your `package.json` and `lerna.json` workspace paths. These can be either relative or absolute paths with or without @@ -147,7 +147,7 @@ globs. For example: "packages": [ "packages/*", "plugins/*", - "../backstage/packages/core", // New path added to work on @backstage/core + "../backstage/packages/core-plugin-api", // New path added to work on @backstage/core-plugin-api ], ``` @@ -157,9 +157,10 @@ Then reinstall packages to make yarn set up symlinks: yarn install ``` -With this in place you can now modify the `@backstage/core` package within the -main repo, and have those changes be reflected and tested in your app. Simply -run your app using `yarn dev` (or `yarn start` for just frontend) as normal. +With this in place you can now modify the `@backstage/core-plugin-api` package +within the main repo, and have those changes be reflected and tested in your +app. Simply run your app using `yarn dev` (or `yarn start` for just frontend) as +normal. Note that for backend packages you need to make sure that linked packages are not dependencies of any non-linked package. If you for example want to work on diff --git a/docs/support/project-structure.md b/docs/getting-started/project-structure.md similarity index 95% rename from docs/support/project-structure.md rename to docs/getting-started/project-structure.md index 55a92da571..1d5d58c2c9 100644 --- a/docs/support/project-structure.md +++ b/docs/getting-started/project-structure.md @@ -127,18 +127,17 @@ are separated out into their own folder, see further down. used by the backend, we chose to separate `config` and `config-loader` into two different packages. -- [`core/`](https://github.com/backstage/backstage/tree/master/packages/core) - +- [`core-app-api/`](https://github.com/backstage/backstage/tree/master/packages/core-app-api) - + This package contains the core APIs that are used to wire together Backstage + apps. + +- [`core-components/`](https://github.com/backstage/backstage/tree/master/packages/core-components) - This package contains our visual React components, some of which you can find in [plugin examples](https://backstage.io/storybook/?path=/story/plugins-examples--plugin-with-data). - Apart from that it re-exports everything from [`core-api`] so that users only - need to rely on one package. -- [`core-api/`](https://github.com/backstage/backstage/tree/master/packages/core-api) - - This package contains APIs and definitions of such. It is it's own package - because we needed to split our `test-utils` package. It's an implementation - detail that we try to hide from our users, and no one should have to depend on - it directly. +- [`core-plugin-api/`](https://github.com/backstage/backstage/tree/master/packages/core-plugin-api) - + This package contains the core APIs that are used to build Backstage plugins. - [`create-app/`](https://github.com/backstage/backstage/tree/master/packages/create-app) - An CLI to specifically scaffold a new Backstage App. It does so by using a diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md index 163674f00a..3b3e3dd5ed 100644 --- a/docs/integrations/azure/locations.md +++ b/docs/integrations/azure/locations.md @@ -6,8 +6,8 @@ sidebar_label: Locations description: Integrating source code stored in Azure DevOps into the Backstage catalog --- -The Azure integration supports loading catalog entities from Azure DevOps. -Entities can be added to +The Azure DevOps integration supports loading catalog entities from Azure +DevOps. Entities can be added to [static catalog configuration](../../features/software-catalog/configuration.md), or registered with the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md new file mode 100644 index 0000000000..c359960f4d --- /dev/null +++ b/docs/integrations/azure/org.md @@ -0,0 +1,14 @@ +--- +id: org +title: Microsoft Azure Active Directory Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Importing users and groups from a Microsoft Azure Active Directory into Backstage +--- + +The Backstage catalog can be set up to ingest organizational data - users and +teams - directly from an tenant in Microsoft Azure Active Directory via the +Microsoft Graph API. + +More details on this are available in the +[README of the `@backstage/plugin-catalog-backend-module-msgraph` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md). diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 24a4335b8b..d1438aba8b 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -14,19 +14,25 @@ entities that mirror your org setup. ## Installation -The processor that performs the import, `LdapOrgReaderProcessor`, comes -installed with the default setup of Backstage. +1. The processor is not installed by default, therefore you have to add a + dependency to `@backstage/plugin-catalog-backend-module-ldap` to your backend + package. -If you replace the set of processors in your installation using that facility of -the catalog builder class, you can import and add it as follows. +```bash +# From your Backstage root directory +cd packages/backend +yarn add @backstage/plugin-catalog-backend-module-ldap +``` -```ts -// Typically in packages/backend/src/plugins/catalog.ts -import { LdapOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; +2. The `LdapOrgReaderProcessor` is not registered by default, so you have to + register it in the catalog plugin: -builder.replaceProcessors( - LdapOrgReaderProcessor.fromConfig(config, { logger }), - // ... +```typescript +// packages/backend/src/plugins/catalog.ts +builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(config, { + logger, + }), ); ``` @@ -116,8 +122,8 @@ The DN under which users are stored, e.g. #### users.options The search options to use when sending the query to the server, when reading all -users. All of the options are shown below, with their default values, but they -are all optional. +users. All the options are shown below, with their default values, but they are +all optional. ```yaml options: @@ -152,8 +158,8 @@ set: Mappings from well known entity fields, to LDAP attribute names. This is where you are able to define how to interpret the attributes of each LDAP result item, -and to move them into the corresponding entity fields. All of the options are -shown below, with their default values, but they are all optional. +and to move them into the corresponding entity fields. All the options are shown +below, with their default values, but they are all optional. If you leave out an optional mapping, it will still be copied using that default value. For example, even if you do not put in the field `displayName` in your @@ -198,8 +204,8 @@ The DN under which groups are stored, e.g. #### groups.options The search options to use when sending the query to the server, when reading all -groups. All of the options are shown below, with their default values, but they -are all optional. +groups. All the options are shown below, with their default values, but they are +all optional. ```yaml options: @@ -276,3 +282,35 @@ map: # the spec.children field of the entity. members: member ``` + +## Customize the Processor + +In case you want to customize the ingested entities, the +`LdapOrgReaderProcessor` allows to pass transformers for users and groups. + +1. Create a transformer: + +```ts +export async function myGroupTransformer( + vendor: LdapVendor, + config: GroupConfig, + group: SearchEntry, +): Promise { + // Transformations may change namespace, change entity naming pattern, fill + // profile with more or other details... + + // Create the group entity on your own, or wrap the default transformer + return await defaultGroupTransformer(vendor, config, group); +} +``` + +2. Configure the processor with the transformer: + +```ts +builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(config, { + logger, + groupTransformer: myGroupTransformer, + }), +); +``` diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 00d83b43ed..7ea892840c 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -25,7 +25,7 @@ different ways. The following diagram shows how Backstage might look when deployed inside a company which uses the Tech Radar plugin, the Lighthouse plugin, the CircleCI -plugin and the service catalog. +plugin and the software catalog. There are 3 main components in this architecture: @@ -142,7 +142,7 @@ Its architecture looks like this: ![lighthouse plugin backed to microservice and database](../assets/architecture-overview/lighthouse-plugin-architecture.png) -The service catalog in Backstage is another example of a service backed plugin. +The software catalog in Backstage is another example of a service backed plugin. It retrieves a list of services, or "entities", from the Backstage Backend service and renders them in a table for the user. diff --git a/docs/overview/background.md b/docs/overview/background.md index 0c22396e72..00fb5107ea 100644 --- a/docs/overview/background.md +++ b/docs/overview/background.md @@ -22,8 +22,8 @@ Our idea was to centralize and simplify end-to-end software development with an abstraction layer that sits on top of all of our infrastructure and developer tooling. That’s Backstage. -It’s a developer portal powered by a centralized service catalog — with a plugin -architecture that makes it endlessly extensible and customizable. +It’s a developer portal powered by a centralized software catalog — with a +plugin architecture that makes it endlessly extensible and customizable. Manage all your services, software, tooling, and testing in Backstage. Start building a new microservice using an automated template in Backstage. Create, diff --git a/docs/glossary.md b/docs/overview/glossary.md similarity index 96% rename from docs/glossary.md rename to docs/overview/glossary.md index 745cf5f95b..97a2823365 100644 --- a/docs/glossary.md +++ b/docs/overview/glossary.md @@ -11,7 +11,7 @@ terminology below for clarity and consistency when discussing Backstage. ### Authentication Glossary -This [page](./auth/glossary.md) directs to the terms and phrases related to +This [page](../auth/glossary.md) directs to the terms and phrases related to authentication and identity section of Backstage. ### Backstage User Profiles diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index a736126324..0013c99c6b 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -22,7 +22,7 @@ We have divided the project into three high-level _phases_: [UX patterns and components](https://backstage.io/storybook) help ensure a consistent experience between tools. -- 🐢 **Phase 2:** Service Catalog +- 🐢 **Phase 2:** Software Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. @@ -120,13 +120,13 @@ Chances are that someone will jump in and help build it. - [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) - [Plugin marketplace](https://backstage.io/plugins) - [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) -- [Backstage Service Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) +- [Backstage Software Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) - [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates) - [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) - [TechDocs v0](https://github.com/backstage/backstage/milestone/15) - CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI - [Service API documentation](https://github.com/backstage/backstage/pull/1737) -- Backstage Service Catalog can read from: GitHub, GitLab, +- Backstage Software Catalog can read from: GitHub, GitLab, [Bitbucket](https://github.com/backstage/backstage/pull/1938) - Support auth providers: Google, Okta, GitHub, GitLab, [auth0](https://github.com/backstage/backstage/pull/1611), diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 29b9be14b6..2b730024fd 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -106,16 +106,6 @@ Used to load in static configuration, mainly for use by the CLI and Stability: `1`. Mainly intended for internal use. -### `core` [GitHub](https://github.com/backstage/backstage/tree/master/packages/core/) - -The `@backstage/core` and `@backstage/core-api` packages are being phased out -and replaced by other `@backstage/core-*` packages. They are still in use but -will not receive any breaking changes. - -### `core-api` [GitHub](https://github.com/backstage/backstage/tree/master/packages/core-api/) - -Stability: See `@backstage/core` above - ### `core-app-api` [GitHub](https://github.com/backstage/backstage/tree/master/packages/core-app-api/) The APIs used exclusively in the app, such as `createApp` and the system icons. @@ -194,8 +184,9 @@ Stability: `2` ### `test-utils-core` [GitHub](https://github.com/backstage/backstage/tree/master/packages/test-utils-core/) Internal testing utilities that are separated out for usage in -@backstage/core-api. All exports are re-exported by @backstage/test-utils. This -package should not be depended on directly. +@backstage/core-app-api and @backstage/core-plugin-api. All exports are +re-exported by @backstage/test-utils. This package should not be depended on +directly. Stability: See @backstage/test-utils @@ -218,9 +209,6 @@ Stability: `1` ## Plugins -Plugins are rarely marked as stable as the `@backstage/core` plugin API is under -heavy development. - Many backend plugins are split into "REST API" and "TypeScript Interface" sections. The "TypeScript Interface" refers to the API used to integrate the plugin into the backend. @@ -344,8 +332,7 @@ Stability: `1` The backend scaffolder plugin that provides an implementation for templates in the catalog. -Stability: `1`. There is planned work to rework the scaffolder in -https://github.com/backstage/backstage/issues/2771. +Stability: `2`. ### `tech-radar` [GitHub](https://github.com/backstage/backstage/tree/master/plugins/tech-radar/) diff --git a/docs/support/support.md b/docs/overview/support.md similarity index 100% rename from docs/support/support.md rename to docs/overview/support.md diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index ec824a1e11..1d9310541a 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -2,13 +2,13 @@ id: what-is-backstage title: What is Backstage? # prettier-ignore -description: Backstage is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure +description: Backstage is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure --- -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) [Backstage](https://backstage.io/) is an open platform for building developer -portals. Powered by a centralized service catalog, Backstage restores order to +portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -17,7 +17,7 @@ to create a streamlined development environment from end to end. Out of the box, Backstage includes: -- [Backstage Service Catalog](../features/software-catalog/index.md) for +- [Backstage Software Catalog](../features/software-catalog/index.md) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 4480cd69e3..1d728b76aa 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -99,7 +99,7 @@ import carmen from './plugins/carmen'; async function main() { // ... const carmenEnv = useHotMemoize(module, () => createEnv('carmen')); - apiRouter.use('/carmen', await carmen(badgesEnv)); + apiRouter.use('/carmen', await carmen(carmenEnv)); ``` After you start the backend (e.g. using `yarn start-backend` from the repo @@ -111,3 +111,42 @@ curl localhost:7000/api/carmen/health ``` This should return `{"status":"ok"}` like before. Success! + +## Making Use of a Database + +The Backstage backend comes with a builtin facility for SQL database access. +Most plugins that have persistence needs will choose to make use of this +facility, so that Backstage operators can manage database needs uniformly. + +As part of the environment object that is passed to your `createPlugin` +function, there is a `database` field. You can use that to get a +[Knex](http://knexjs.org/) connection object. + +```ts +// in packages/backend/src/plugins/carmen.ts +export default async function createPlugin(env: PluginEnvironment) { + const db: Knex = await env.database.getClient(); + + // You will then pass this client into your actual plugin implementation + // code, maybe similar to the following: + const model = new CarmenDatabaseModel(db); + return await createRouter({ + model: model, + logger: env.logger, + }); +} +``` + +You may note that the `getClient` call has no parameters. This is because all +plugin database needs are configured under the `backend.database` config key of +your `app-config.yaml`. The framework may even make sure behind the scenes that +the logical database is created automatically if it doesn't exist, based on +rules that the Backstage operator decides on. + +The framework does not handle database schema migrations for you, however. The +builtin plugins in the main repo have chosen to use the Knex library to manage +schema migrations as well, but you can do so in any manner that you see fit. + +See the [Knex library documentation](http://knexjs.org/) for examples and +details on how to write schema migrations and perform SQL queries against your +database.. diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 52d8b69f7b..44c180fc5e 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -511,10 +511,11 @@ clarify intent. Refer to the following table to formulate the new name: ## Porting Existing Apps The first step of porting any app is to replace the root `Routes` component with -`FlatRoutes` from `@backstage/core`. As opposed to the `Routes` component, -`FlatRoutes` only considers the first level of `Route` components in its -children, and provides any additional children to the outlet of the route. It -also removes the need to append `"/*"` to paths, as it is added automatically. +`FlatRoutes` from `@backstage/core-app-api`. As opposed to the `Routes` +component, `FlatRoutes` only considers the first level of `Route` components in +its children, and provides any additional children to the outlet of the route. +It also removes the need to append `"/*"` to paths, as it is added +automatically. ```diff const AppRoutes = () => ( diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md index 4ccd48f17e..c35f4b91fa 100644 --- a/docs/plugins/create-a-plugin.md +++ b/docs/plugins/create-a-plugin.md @@ -22,9 +22,9 @@ yarn create-plugin This will create a new Backstage Plugin based on the ID that was provided. It will be built and added to the Backstage App automatically. -> If `yarn start` is already running you should be able to see the default page -> for your new plugin directly by navigating to -> `http://localhost:3000/my-plugin`. +> If the Backstage App is already running (with `yarn start` or `yarn dev`) you +> should be able to see the default page for your new plugin directly by +> navigating to `http://localhost:3000/my-plugin`. ![](../assets/my-plugin_screenshot.png) @@ -32,7 +32,7 @@ You can also serve the plugin in isolation by running `yarn start` in the plugin directory. Or by using the yarn workspace command, for example: ```bash -yarn workspace @backstage/plugin-welcome start # Also supports --check +yarn workspace @backstage/my-plugin start # Also supports --check ``` This method of serving the plugin provides quicker iteration speed and a faster diff --git a/docs/plugins/github-apps.md b/docs/plugins/github-apps.md index 386320cbb3..81fc870593 100644 --- a/docs/plugins/github-apps.md +++ b/docs/plugins/github-apps.md @@ -43,6 +43,10 @@ root of the project which you can then use as an `include` in your `app-config.yaml`. You can go ahead and [skip ahead](#including-in-integrations-config) if you've already got an app. +Note that the created app will have a webhook that is disabled by default and +points to `smee.io`, which is intended for local development. There's also +currently no part of Backstage that makes use of the webhook. + ### GitHub Enterprise You have to create the GitHub Application manually using these @@ -104,3 +108,12 @@ privateKey: | This will result in backstage preventing the use of any installation that is not within the allow list. + +### Permissions for pull requests + +These are the minimum permissions required for creating a pull request with +Backstage software templates: + +- Read and Write permissions for `Contents`. +- Read and write permissions for `Pull Requests` and `Issues`. +- Read permissions on `Metadata`. diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 32d523b2f5..a582556311 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -28,9 +28,9 @@ This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work. -## Integrate into the Service Catalog +## Integrate into the Software Catalog If your plugin isn't supposed to live as a standalone page, but rather needs to -be presented as a part of a Service Catalog (e.g. a separate tab or a card on an -"Overview" tab), then check out -[the instruction](integrating-plugin-into-service-catalog.md) on how to do it. +be presented as a part of a Software Catalog (e.g. a separate tab or a card on +an "Overview" tab), then check out +[the instruction](integrating-plugin-into-software-catalog.md) on how to do it. diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md similarity index 95% rename from docs/plugins/integrating-plugin-into-service-catalog.md rename to docs/plugins/integrating-plugin-into-software-catalog.md index 047b6f14e6..ae7c3189da 100644 --- a/docs/plugins/integrating-plugin-into-service-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -1,7 +1,7 @@ --- -id: integrating-plugin-into-service-catalog -title: Integrate into the Service Catalog -description: How to integrate a plugin into service catalog +id: integrating-plugin-into-software-catalog +title: Integrate into the Software Catalog +description: How to integrate a plugin into software catalog --- > This is an advanced use case and currently is an experimental feature. Expect diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md index bfeed58c8f..f61fae0968 100644 --- a/docs/plugins/plugin-development.md +++ b/docs/plugins/plugin-development.md @@ -36,7 +36,7 @@ to avoid import cycles, for example like this: ```tsx /* src/routes.ts */ -import { createRouteRef } from '@backstage/core'; +import { createRouteRef } from '@backstage/core-plugin-api'; // Note: This route ref is for internal use only, don't export it from the plugin export const rootRouteRef = createRouteRef({ @@ -46,11 +46,11 @@ export const rootRouteRef = createRouteRef({ Now that we have a `RouteRef`, we import it into `src/plugin.ts`, create our plugin instance with `createPlugin`, as well as create and wrap our routable -extension using `createRoutableExtension` from `@backstage/core`: +extension using `createRoutableExtension` from `@backstage/core-plugin-api`: ```tsx /* src/plugin.ts */ -import { createPlugin, createRouteRef } from '@backstage/core'; +import { createPlugin, createRouteRef } from '@backstage/core-plugin-api'; import ExampleComponent from './components/ExampleComponent'; // Create a plugin instance and export this from your plugin package diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index f81e3794e5..beffcdb313 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -57,7 +57,10 @@ package.json to declare the plugin dependencies, metadata and scripts. In the `src` folder we get to the interesting bits. Check out the `plugin.ts`: ```jsx -import { createPlugin, createRoutableExtension } from '@backstage/core'; +import { + createPlugin, + createRoutableExtension, +} from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; diff --git a/docs/plugins/url-reader.md b/docs/plugins/url-reader.md new file mode 100644 index 0000000000..41aba98a66 --- /dev/null +++ b/docs/plugins/url-reader.md @@ -0,0 +1,274 @@ +--- +id: url-reader +title: URL Reader +sidebar_label: URL Reader +# prettier-ignore +description: URL Reader is a backend core API responsible for reading files from external locations. +--- + +## Concept + +Some of the core plugins of Backstage have to read files from an external +location. [Software Catalog](../features/software-catalog/index.md) has to read +the [`catalog-info.yaml`](../features/software-catalog/descriptor-format.md) +entity descriptor files to register and track an entity. +[Software Templates](../features/software-templates/index.md) have to download +the template skeleton files before creating a new component. +[TechDocs](../features/techdocs/README.md) has to download the markdown source +files before generating a documentation site. + +Since, the requirement for reading files is so essential for Backstage plugins, +the +[`@backstage/backend-common`](https://github.com/backstage/backstage/tree/master/packages/backend-common) +package provides a dedicated API for reading from such URL based remote +locations like GitHub, GitLab, Bitbucket, Google Cloud Storage, etc. This is +commonly referred to as "URL Reader". It takes care of making authenticated +requests to the remote host so that private files can be read securely. If users +have [GitHub App based authentication](github-apps.md) set up, URL Reader even +refreshes the token, to avoid reaching the GitHub API rate limit. + +As a result, plugin authors do not have to worry about any of these problems +when trying to read files. + +## Interface + +When the Backstage backend starts, a new instance of URL Reader is created. You +can see this in the index file of your Backstage backend +i.e.`packages/backend/src/index.ts`. +[Example](https://github.com/backstage/backstage/blob/ebbe91dbe79038a61d35cf6ed2d96e0e0d5a15f3/packages/backend/src/index.ts#L57) + +```ts +// File: packages/backend/src/index.ts + +import { URLReaders } from '@backstage/backend-common'; + +function makeCreateEnv(config: Config) { + // .... + const reader = UrlReaders.default({ logger, config }); + // +} +``` + +This instance contains all +[the default URL Reader providers](https://github.com/backstage/backstage/blob/master/packages/backend-common/src/reading/UrlReaders.ts) +in the backend-common package including GitHub, GitLab, Bitbucket, Azure, Google +GCS. As the need arises, more URL Readers are being written to support different +providers. + +The generic interface of a URL Reader instance looks like this. + +```ts +export type UrlReader = { + /* Used to read a single file and return its content. */ + read(url: string): Promise; + /** + * A replacement for the read method that supports options and complex responses. + * + * Use this whenever it is available, as the read method will be deprecated and + * eventually removed in the future. + */ + readUrl?(url: string, options?: ReadUrlOptions): Promise; + + /* Used to read a file tree and download as a directory. */ + readTree(url: string, options?: ReadTreeOptions): Promise; + /* Used to search a file in a tree. */ + search(url: string, options?: SearchOptions): Promise; +}; +``` + +## Using a URL Reader inside a plugin + +The `reader` instance is available in the backend plugin environment and passed +on to all the backend plugins. You can see an +[example](https://github.com/backstage/backstage/blob/b0be185369ebaad22255b7cdf18535d1d4ffd0e7/packages/backend/src/plugins/techdocs.ts#L31). +When any of the methods on this instance is called with a URL, URL Reader +extracts the host for that URL (e.g. `github.com`, `ghe.mycompany.com`, etc.). +Using the +[`@backstage/integration`](https://github.com/backstage/backstage/tree/master/packages/integration) +package, it looks inside the +[`integrations:`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/app-config.yaml#L134-L158) +config of the `app-config.yaml` to find out how to work with the host based on +the configs provided like authentication token, API base URL, etc. + +Make sure your plugin-specific backend file at +`packages/backend/src/plugins/.ts` is forwarding the `reader` instance +passed on as the `PluginEnvironment` to the actual plugin's `createRouter` +function. See how this is done in +[Catalog](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend/src/plugins/catalog.ts#L25-L27) +and +[TechDocs](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend/src/plugins/techdocs.ts#L31-L36) +backend plugins. + +Once the reader instance is available inside the plugin, one of its methods can +directly be used with a URL. Some example usages - + +- [`read`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/plugins/catalog-backend/src/ingestion/processors/codeowners/read.ts#L24-L33) - + Catalog using the `read` method to read the CODEOWNERS file in a repository. +- [`readTree`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/techdocs-common/src/helpers.ts#L198-L220) - + TechDocs using the `readTree` method to download markdown files in order to + generate the documentation site. +- [`readTree`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/techdocs-common/src/stages/prepare/url.ts#L33-L54) - + TechDocs using `NotModifiedError` to maintain cache and speed up and limit the + number of requests. +- [`search`](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts#L88-L108) - + Catalog using the `search` method to find files for a location URL containing + a glob pattern. + +## Writing a new URL Reader + +If the available URL Readers are not sufficient for your use case and you want +to add a new URL Reader for any other provider, you are most welcome to +contribute one! + +Feel free to use the +[GitHub URL Reader](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/GithubUrlReader.ts) +as a source of inspiration. + +### 1. Add an integration + +The provider for your new URL Reader can also be called an "integration" in +Backstage. The `integrations:` section of your Backstage `app-config.yaml` +config file is supposed to be the place where a Backstage integrator defines the +host URL for the integration, authentication details and other integration +related configurations. + +The `@backstage/integration` package is where most of the integration specific +code lives, so that it is shareable across Backstage. Functions like "read the +integrations config and process it", "construct headers for authenticated +requests to the host" or "convert a plain file URL into its API URL for +downloading the file" would live in this package. + +### 2. Create the URL Reader + +Create a new class which implements the +[`UrlReader` type](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/types.ts#L21-L28) +inside `@backstage/backend-common`. Create and export a static `factory` method +which reads the integration config and returns a map of host URLs the new reader +should be used for. See the +[GitHub URL Reader](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/GithubUrlReader.ts#L50-L63) +for example. + +### 3. Implement the methods + +We want to make sure all URL Readers behave in the same way. Hence if possible, +all the methods of the `UrlReader` interface should be implemented. However it +is okay to start by implementing just one of them and create issues for the +remaining. + +#### read + +NOTE: Use `readUrl` instead of `read`. + +`read` method expects a user-friendly URL, something which can be copied from +the browser naturally when a person is browsing the provider in their browser. + +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/ADOPTERS.md` +- ❌ Not a valid URL : + `https://raw.githubusercontent.com/backstage/backstage/master/ADOPTERS.md` +- ❌ Not a valid URL : `https://github.com/backstage/backstage/ADOPTERS.md` + +Upon receiving the URL, `read` converts the user-friendly URL into an API URL +which can be used to request the provider's API. + +`read` then makes an authenticated request to the provider API and returns the +file's content. + +#### readUrl + +`readUrl` is a new interface that allows complex response objects and is +intended to replace the `read` method. This new method is currently optional to +implement which allows for a soft migration to `readUrl` instead of `read` in +the future. + +#### readTree + +`readTree` method also expects user-friendly URLs similar to `read` but the URL +should point to a tree (could be the root of a repository or even a +sub-directory). + +- ✅ Valid URL : `https://github.com/backstage/backstage` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/docs` + +Using the provider's API documentation, find out an API endpoint which can be +used to download either a zip or a tarball. You can download the entire tree +(e.g. a repository) and filter out in case the user is expecting only a +sub-tree. But some APIs are smart enough to accept a path and return only a +sub-tree in the downloaded archive. + +#### search + +`search` method expects a glob pattern of a URL and returns a list of files +matching the query. + +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/**/catalog-info.yaml` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/**/*.md` +- ✅ Valid URL : + `https://github.com/backstage/backstage/blob/master/*/package.json` +- ✅ Valid URL : `https://github.com/backstage/backstage/blob/master/READM` + +The core logic of `readTree` can be used here to extract all the files inside +the tree and return the files matching the pattern in the `url`. + +### 4. Add to available URL Readers + +There are two ways to make your new URL Reader available for use. + +You can choose to make it open source, by updating the +[`default` factory](https://github.com/backstage/backstage/blob/d5c83bb889b8142e343ebc4e4c0b90a02d1c1a3d/packages/backend-common/src/reading/UrlReaders.ts#L62-L81) +method of URL Readers. + +But for something internal which you don't want to make open source, you can +update your `packages/backend/src/index.ts` file and update how the `reader` +instance is created. + +```ts +// File: packages/backend/src/index.ts +const reader = UrlReaders.default({ + logger: root, + config, + // This is where your internal URL Readers would go. + factories: [myCustomReader.factory], +}); +``` + +### 5. Caching + +All of the methods above support an ETag based caching. If the method is called +without an `etag`, the response contains an ETag of the resource (should ideally +forward the ETag returned by the provider). If the method is called with an +`etag`, it first compares the ETag and returns a `NotModifiedError` in case the +resource has not been modified. This approach is very similar to the actual +[ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and +[If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) +HTTP headers. + +### 6. Debugging + +When debugging one of the URL Readers, you can straightforward use the +[`reader` instance created](https://github.com/backstage/backstage/blob/ebbe91dbe79038a61d35cf6ed2d96e0e0d5a15f3/packages/backend/src/index.ts#L57) +when the backend starts and call one of the methods with your debugging URL. + +```ts +// File: packages/backend/src/index.ts + +async function main() { + // ... + const createEnv = makeCreateEnv(config); + + const testReader = createEnv('test-url-reader').reader; + const response = await testReader.readUrl( + 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + ); + console.log((await response.buffer()).toString()); + // ... +} +``` + +This will be run every time you restart the backend. Note that after any change +in the URL Reader code, you need to kill the backend and restart, since the +`reader` instance is memoized and does not update on hot module reloading. Also, +there are a lot of unit tests written for the URL Readers, which you can make +use of. diff --git a/docs/prettier.config.js b/docs/prettier.config.js index 6fab539fb2..4285cde950 100644 --- a/docs/prettier.config.js +++ b/docs/prettier.config.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/docs/reference/createPlugin-feature-flags.md b/docs/reference/createPlugin-feature-flags.md index 5bfb4fe096..d550ba10a9 100644 --- a/docs/reference/createPlugin-feature-flags.md +++ b/docs/reference/createPlugin-feature-flags.md @@ -11,7 +11,7 @@ can use this to split out logic in your code for manual A/B testing, etc. Here's a code sample: ```typescript -import { createPlugin } from '@backstage/core'; +import { createPlugin } from '@backstage/core-plugin-api'; export default createPlugin({ id: 'plugin-name', @@ -29,7 +29,7 @@ To inspect the state of a feature flag inside your plugin, you can use the ```tsx import React from 'react'; import { Button } from '@material-ui/core'; -import { featureFlagsApiRef, useApi } from '@backstage/core'; +import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; const ExamplePage = () => { const featureFlags = useApi(featureFlagsApiRef); diff --git a/docs/reference/createPlugin.md b/docs/reference/createPlugin.md index e3df66fa12..6602b0b2cc 100644 --- a/docs/reference/createPlugin.md +++ b/docs/reference/createPlugin.md @@ -30,7 +30,7 @@ type PluginHooks = { Showcasing adding a feature flag. ```jsx -import { createPlugin } from '@backstage/core'; +import { createPlugin } from '@backstage/core-plugin-api'; export default createPlugin({ id: 'new-plugin', diff --git a/docs/tutorials/configuring-plugin-databases.md b/docs/tutorials/configuring-plugin-databases.md new file mode 100644 index 0000000000..5c5f0ffb68 --- /dev/null +++ b/docs/tutorials/configuring-plugin-databases.md @@ -0,0 +1,204 @@ +--- +id: configuring-plugin-databases +title: Configuring Plugin Databases +# prettier-ignore +description: Guide on how to configure Backstage databases. +--- + +This guide covers a variety of production persistence use cases which are +supported out of the box by Backstage. The database manager allows the developer +to set the client and database connection details on a per plugin basis in +addition to the base client and connection configuration. This means that you +can use a SQLite 3 in-memory database for a specific plugin whilst using +PostgreSQL for everything else and so on. + +By default, Backstage uses automatically created databases for each plugin whose +names follow the `backstage_plugin_` pattern, e.g. +`backstage_plugin_auth`. You can configure a different database name prefix for +use cases where you have multiple deployments running on a shared database +instance or cluster. + +With infrastructure defined as code or data (Terraform, AWS CloudFormation, +etc.), you may have database credentials which lack permissions to create new +databases or you do not have control over the database names. In these +instances, you can set the database name and connection information on a per +plugin basis as mentioned earlier. + +Backstage supports all of these use cases with the `DatabaseManager` provided by +`@backstage/backend-common`. We will now cover how to use and configure +Backstage's databases. + +## Prerequisites + +### Dependencies + +Please ensure the appropriate database drivers are installed in your `backend` +package. If you intend to use both `postgres` and `sqlite3`, you can install +both of them. + +```sh +cd packages/backend + +# install pg if you need postgres +yarn add pg + +# install sqlite3 if you intend to set it as the client +yarn add sqlite3 +``` + +From an operational perspective, you only need to install drivers for clients +that are actively used. + +### Database Manager + +Existing Backstage instances should be updated to use `DatabaseManager` from +`@backstage/backend-common` in your `packages/backend/src/index.ts` file, the +`SingleConnectionDatabaseManager` has been deprecated. Import the manager and +update the references as shown below if this is not the case: + +```diff +import { +- SingleConnectionDatabaseManager, ++ DatabaseManager, +} from '@backstage/backend-common'; + +// ... + +function makeCreateEnv(config: Config) { + // ... +- const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config); + // ... +} +``` + +## Configuration + +You should set the base database client and connection information in your +`app-config.yaml` (or equivalent) file. The base client and configuration is +used as the default which is extended for each plugin with the same or unset +client type. If a client type is specified for a specific plugin which does not +match the base client, the configuration set for the plugin will be used as is +without extending the base configuration. + +Client type and configuration for plugins need to be defined under +**`backend.database.plugin.`**. As an example, `catalog` is the +`pluginId` for the catalog plugin and any configuration defined under that block +is specific to that plugin. We will now explore more detailed example +configurations below. + +### Minimal In-Memory Configuration + +In the example below, we are using `sqlite3` in-memory databases for all +plugins. You may want to use this configuration for testing or other non-durable +use cases. + +```yaml +backend: + database: + client: sqlite3 + connection: ':memory:' +``` + +### PostgreSQL + +The example below uses PostgreSQL (`pg`) as the database client for all plugins. +The `auth` plugin uses a user defined database name instead of the automatically +generated one which would have been `backstage_plugin_auth`. + +```yaml +backend: + database: + client: pg + connection: + host: some.example-pg-instance.tld + user: postgres + password: password + port: 5432 + plugin: + auth: + connection: + database: pg_auth_set_by_user +``` + +### Custom Database Name Prefix + +The configuration below uses `example_prefix_` as the database name prefix +instead of `backstage_plugin_`. Plugins such as `auth` and `catalog` will use +databases named `example_prefix_auth` and `example_prefix_catalog` respectively. + +```yaml +backend: + database: + client: pg + connection: + host: some.example-pg-instance.tld + user: postgres + password: password + port: 5432 + prefix: 'example_prefix_' +``` + +### Connection Configuration Per Plugin + +Both `auth` and `catalog` use connection configuration with different +credentials and database names. This type of configuration can be useful for +environments with infrastructure as code or data which may provide randomly +generated credentials and/or database names. + +```yaml +backend: + database: + client: pg + connection: 'postgresql://some.example-pg-instance.tld:5432' + plugin: + auth: + connection: 'postgresql://fort:knox@some.example-pg-instance.tld:5432/unwitting_fox_jumps' + catalog: + connection: 'postgresql://bank:reserve@some.example-pg-instance.tld:5432/shuffle_ransack_playback' +``` + +### PostgreSQL and SQLite 3 + +The example below uses PostgreSQL (`pg`) as the database client for all plugins +except the `auth` plugin which uses `sqlite3`. As the `auth` plugin's client +type is different from the base client type, the connection configuration for +`auth` is used verbatim without extending the base configuration for PostgreSQL. + +```yaml +backend: + database: + client: pg + connection: 'postgresql://foo:bar@some.example-pg-instance.tld:5432' + plugin: + auth: + client: sqlite3 + connection: ':memory:' +``` + +## Check Your Databases + +The `DatabaseManager` will attempt to create the databases if they do not exist. +If you have set credentials per plugin because the credentials in the base +configuration do not have permissions to create databases, you must ensure they +exist before starting the service. The service will not be able to create them, +it can only use them. + +### Privileges + +As Backstage attempts to check if the database exists, you may need to grant +privileges to list or show databases for a given user. For PostgreSQL, you would +grant the following: + +```postgres +GRANT SELECT ON pg_database TO some_user; +``` + +MySQL: + +```mysql +GRANT SHOW DATABASES ON *.* TO some_user; +``` + +The mechanisms in this guide should help you tackle different database +deployment situations. Good luck! diff --git a/docs/tutorials/journey.md b/docs/tutorials/journey.md index adefdfa73f..249507ddf0 100644 --- a/docs/tutorials/journey.md +++ b/docs/tutorials/journey.md @@ -55,10 +55,10 @@ const spotifyAuthApiRef = createApiRef({ Sam realizes that Spotify auth might be useful to others, and that it would be more convenient if it was a part of the Backstage Core. After submitting and merging a Pull Request with the additions to the -`@backstage/plugin-auth-backend` and `@backstage/core` packages, Spotify auth is -now available for everyone to use. Since the Backstage Core team also adds it to -the public demo server, Sam can now get rid of it in the local setup and rely on -the shared development auth providers instead. +`@backstage/plugin-auth-backend` and `@backstage/core-plugin-api` packages, +Spotify auth is now available for everyone to use. Since the Backstage Core team +also adds it to the public demo server, Sam can now get rid of it in the local +setup and rely on the shared development auth providers instead. The only thing left now is making sure that users of the plugin provide Spotify auth in the app. Sam ensures this by adding `spotifyAuthApiRef` to the plugin's @@ -70,7 +70,7 @@ README. This plugin requires the following APIs to function: -- `spotifyAuthApiRef` from `@backstage/core@^1.1.0` +- `spotifyAuthApiRef` from `@@backstage/core-plugin-api@^1.1.0` ``` # 3. The Catalog Awakens diff --git a/docs/tutorials/migrating-away-from-core.md b/docs/tutorials/migrating-away-from-core.md new file mode 100644 index 0000000000..da9d177ccb --- /dev/null +++ b/docs/tutorials/migrating-away-from-core.md @@ -0,0 +1,135 @@ +--- +id: migrating-away-from-core +title: Migrating away from @backstage/core +description: Guide on how to migrate to the new Backstage core libraries. +--- + +The `@backstage/core` package has been split into three separate packages, +`@backstage/core-app-api`, `@backstage/core-plugin-api`, and +`@backstage/core-components`. For more information about the reasoning behind +this change and the naming of the packages, see the +[original RFC](https://github.com/backstage/backstage/issues/4872) and +[initial PR](https://github.com/backstage/backstage/pull/5825). + +The main purpose of the split is to make plugins more decoupled from the app, +and open up for the possibility of combining plugins using many different +versions of the core libraries. This should significantly reduce the maintenance +burden on plugin authors, as well as reduce the impact of breaking changes in +the core APIs. + +## Migration + +At a high level the migration is done by simply replacing usages of +`@backstage/core` with one or more of the three new core libraries. There are a +few breaking changes in the new packages that are listed below, but for most +plugins the migration is a simple replacement. In order to make the migration as +smooth as possible we provide a collection of tools to automate the majority of +the migration effort. + +Below is a list of steps that should get most projects completely migrated, the +order of the steps is a recommendation but not required, so don't worry if you +need to go back to previous steps to fix things. + +### Step 1 - Run codemod + +The first step is to run +[`@backstage/codemods`](https://www.npmjs.com/package/@backstage/codemods) +across your project. This will automatically convert all module imports in your +source code to use one of the three new core packages instead. For example, the +following change might occur: + +```diff +-import { useApi, configApiRef, InfoCard } from '@backstage/core'; ++import { useApi, configApiRef } from '@backstage/core-plugin-api'; ++import { InfoCard } from '@backstage/core-components'; +``` + +In a typical app created with `@backstage/create-app`, you would run the +following: + +```shell +npx @backstage/codemods apply core-imports packages plugins +``` + +The last two arguments, `packages` and `plugins`, are the folders that the +codemod should be applied to. Add or remove folders as needed for your project. + +The codemod might fail for some files because of the missing `IconKey` type in +any of the new packages. This is one of the few breaking changes. To fix, remove +any `IconKey` imports and replace usages of it with the `string` type, see the +breaking changes section below for details. Once usages of `IconKey` type have +been removed, you can re-run the codemod for those files. + +Note that while the codemod tries to stick to using the existing formatting in +your project, it doesn't always manage to do that. If you're using `prettier` to +format the code in your project, it's best to run `prettier --write` on any +files that were changed by the codemod. + +### Step 2 - Update dependencies + +The next step is to update dependencies in your `package.json` files. Any +package that currently depends on `@backstage/core` will need to have it +replaced by one or more of the new packages. The app package should have all +three packages added to `dependencies`, while for plugins and additional non-app +packages, the `@backstage/core-plugin-api` and `@backstage/core-components` +packages should be added to the set of regular `dependencies`, and +`@backstage/core-app-api` should be added to `devDependencies` for usage in +tests. + +A tool that can help out with step is the `plugin:diff` command from the +`@backstage/cli`, it will compare your plugin to the base plugin template and +suggest changes where the plugin deviates. A quick way to get this step done if +you have up-to-date project is to run the following in the project root: + +```bash +# The --yes flag causes all suggested changes to be accepted automatically +yarn diff --yes +``` + +If you do not have the `diff` command set up in `package.json`, you can also +manually execute the following in each plugin folder: + +```bash +yarn backstage-cli plugin:diff --yes +``` + +### Step 3 - Manual review + +At this point your app is either completely or very close to being migrated. Run +type checks with `yarn tsc` to check if you hit any of the breaking changes +below or if there are any other things to fix. It can also be worthwhile +searching for occurrences of `@backstage/core` in the codebase, as that might +find usages in for example `jest` mock calls, which aren't handled by the +codemod. + +As a final step you'll want to boot up the app and take it through any regular +verification step that you have set up for your project. Don't hesitate to open +a GitHub issue, PR, or reach out on Discord if you hit any snags, or if there +are any additional steps or hints that you think should be added to this guide! + +## Breaking Changes + +The following is a list of breaking changes between `@backstage/core` and the +three new core packages. Not that this list may not be exhaustive depending on +when you migrate your app, as new releases of the new core packages may bring +further changes. + +### Removed `IconKey` type + +The `IconKey` type used to be a string union of all known keys used for the app +icons available through `useApp().getSystemIcon(key)`. The type has been removed +since the set of allowed icon keys is no longer constrained, and there is +instead only a guarantee that the app provides a minimum set of icons, but can +provide any icons it wants beyond that. Migration is done by simply replacing +old usages by the `string` type. + +### Constrained `IconComponent` type + +The `IconComponent` type used to allow all of the props from the MUI `SvgIcon`. +This encouraged some bad patterns in open source plugins such as applying colors +to the icons, which in turn hurt the ability to replace the icons with custom +ones. The `IconComponent` type, which is now exported from +`@backstage/core-plugin-api`, now only accepts a `fontSize` prop used to set the +size of the icon. The type is compatible with the MUI `SvgIcon`, but there may +be situations where an icon needs an explicit cast to `IconComponent` in order +to narrow the type. diff --git a/docs/tutorials/quickstart-app-plugin.md b/docs/tutorials/quickstart-app-plugin.md index 7dca90c14b..e123410fe0 100644 --- a/docs/tutorials/quickstart-app-plugin.md +++ b/docs/tutorials/quickstart-app-plugin.md @@ -72,7 +72,7 @@ Our first modification will be to extract information from the Identity API. ```tsx // Add identityApiRef to the list of imported from core -import { identityApiRef, useApi } from '@backstage/core'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; ``` 3. Adjust the ExampleComponent from inline to block @@ -137,20 +137,13 @@ changes, let's start by wiping this component clean. import React from 'react'; import { useAsync } from 'react-use'; import Alert from '@material-ui/lab/Alert'; -import { - Table, - TableColumn, - Progress, - githubAuthApiRef, - useApi, -} from '@backstage/core'; +import { Table, TableColumn, Progress } from '@backstage/core-components'; +import { githubAuthApiRef, useApi } from '@backstage/core-plugin-api'; import { graphql } from '@octokit/graphql'; -const ExampleFetchComponent = () => { +export const ExampleFetchComponent = () => { return
Nothing to see yet
; }; - -export default ExampleFetchComponent; ``` 3. Save that and ensure you see no errors. Comment out the unused imports if diff --git a/microsite/blog/2021-05-20-adopting-backstage.md b/microsite/blog/2021-05-20-adopting-backstage.md index 191422004b..290436c875 100644 --- a/microsite/blog/2021-05-20-adopting-backstage.md +++ b/microsite/blog/2021-05-20-adopting-backstage.md @@ -132,4 +132,4 @@ Integrating infrastructure of this size and complexity can seem overwhelming. It ## 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. +[Contact the Backstage team at Spotify.](https://backstage.spotify.com/) 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. diff --git a/microsite/blog/2021-06-22-spotify-backstage-is-growing.md b/microsite/blog/2021-06-22-spotify-backstage-is-growing.md new file mode 100644 index 0000000000..2507192a4a --- /dev/null +++ b/microsite/blog/2021-06-22-spotify-backstage-is-growing.md @@ -0,0 +1,75 @@ +--- +title: How Spotify is helping more companies adopt Backstage +author: Austin Lamon, Spotify +authorURL: https://www.linkedin.com/in/austinlamon +--- + +[![The Backstage community is growing! In just over a year, Backstage has gone from a few open source building blocks to a thriving platform used by engineering orgs with thousands of developers. But even with 30+ adopting companies and 400+ contributors, we are still in the very early stages of reaching the platform’s potential.](assets/21-06-22/spotify-backstage-header.gif)](https://backstage.spotify.com/) +_[backstage.spotify.com](https://backstage.spotify.com)_ + +The Backstage community is growing! In just over [a year](https://engineering.atspotify.com/2021/03/16/happy-birthday-backstage-spotifys-biggest-open-source-project-grows-up-fast/), Backstage has gone from a few open source building blocks to a thriving platform used by engineering orgs with thousands of developers. But even with 30+ [adopting companies](https://github.com/backstage/backstage/blob/master/ADOPTERS.md) and 400+ contributors, we are still in the very early stages of reaching the platform’s potential. + +In order to grow Backstage further, Spotify is increasing the support we provide both adopters (the people integrating Backstage into their organizations) and contributors (the people building features and improving the code). The more companies that adopt Backstage, the more support the project gets, the stronger the platform becomes for everyone. + +And while Spotify remains committed to maturing the Backstage platform — both as original creator and active maintainer — we also want to make room for the community to take greater ownership. Backstage may have started inside Spotify, but it belongs to all of you. So, we hope you join us in what’s next. + + + +## What’s next: More support for adopters and contributors + +Alongside the code contributions, technical support, and community leadership provided by our dedicated (and still growing) Backstage team, Spotify is introducing three additional ways to help lower the barriers to adopting the platform: + +1. **New consulting support.** In addition to investing in the Backstage getting started experience and the technical support we already provide, we’re adding [consulting support](https://backstage.spotify.com) for companies who are looking to adopt (or are already in the middle of adopting) Backstage. +2. **Double the community sessions.** We are creating separate meetups for Backstage adopters and Backstage contributors for more focused discussions. (Come to both!) +3. **Adding reviewers and maintainers.** We recently introduced [reviewers](https://github.com/backstage/backstage/pull/5137) to the Backstage project to speed up PR reviews and approvals, with the hope of also adding more maintainers in the future. + +## Why Spotify is increasing its investment in open source (and why now) + +Before we talk in more detail about these new efforts, why is Spotify doing this? The short answer is the same answer as when we released the very first open source version of Backstage: we envision Backstage as the standard developer portal platform across the industry. + +### Setting the standard, both inside and outside Spotify + +We believe in Backstage — we believe in the developer experience it provides, the developer-centric culture it encourages, and the immense value that the open source community brings to it. It is no exaggeration to say that we depend on Backstage every day at Spotify. It’s the central hub for our internal R&D community, and it’s both mission-critical to our daily operations and our future growth. + +### We’re an adopter, too + +We (that includes Spotify’s leadership, as well as our platform teams and dedicated Backstage team) also believe that Backstage’s continued success here — inside Spotify — depends on its success out here — in the wider open source community, where Backstage can reach its full potential. Like other adopters, we’re fully invested in the platform’s growth. + +### An open platform is the strongest platform + +We genuinely believe that the best platform for developers can only be shaped by the most diverse group of developers. Each new adopter and every new contributor brings unique perspectives and experiences to the challenges of improving developer experience and effectiveness. As the project scales — and progresses toward CNCF graduation — we need to make more room in the community for both adopters and contributors. + +So, let’s get to it. + +## Consulting support (and a new website) for adopters + +[![Who else is using Backstage? Netflix, Zalando, TELUS, DoorDash, more](assets/21-06-22/spotify-backstage-adopters.png)](https://backstage.spotify.com/) + +We’ve launched a new website at: [backstage.spotify.com](https://backstage.spotify.com). It’s a hub for new and potential adopters to receive support from Spotify and our Preferred Partners. The site is focused on helping organizations get up and running with Backstage by addressing their unique needs and use cases. + +You’ll find a high-level introduction to the platform, tips and tricks tested by Spotify to accelerate developer effectiveness, and access to a group of partners that have scaled Backstage for numerous adopters. You can also use the site to book product overviews, demos, and technical deep dives with members of the Spotify team. + +We will continue to post important product announcements, technical documentation, feature demos, and community news here on Backstage.io. ([Subscribe to the newsletter](https://mailchi.mp/spotify/backstage-community) to stay up to date.) And both contributors and adopting companies can continue to find around-the-clock/around-the-world technical support on [GitHub](https://github.com/backstage/backstage) and [Discord](https://discord.gg/MUpMjP2). + +## Separate community sessions for adopters and contributors + +[![Backstage Community Sessions, hosted by Spotify](assets/21-06-22/backstage-community-sessions.png)](https://github.com/backstage/community/#backstage-community) + +Earlier this year, we began hosting [Backstage Community Sessions](https://github.com/backstage/community/#backstage-community) — official meetups for anyone who wanted to join them. Since [the very first one](https://youtu.be/4-VX9tDdJYY), the Backstage team has been inspired and humbled by the community’s participation in these sessions — from hearing the [Expedia Group team share their journey adopting Backstage](https://youtu.be/rRphwXeq33Q?t=1509) to discussions about TypeScript and Material-UI. It’s great collaborating through code — but it’s also a lot of fun when you can see each other’s faces and have a conversation. + +And while these sessions have been a success, the feedback we’ve gotten from the community has been very clear: more frequent and more focused conversations. So, later this summer, we’ll be launching standalone Backstage Adopter Sessions and Backstage Contributor Sessions. We hope this will lead to more useful sessions for everyone — and, of course, you are welcome to attend either or both: + +- **For the adopter sessions:** we invite you to share the challenges, learnings, and use cases you’re facing with companies similar to yourselves. +- **For the contributor sessions:** we invite you to share thoughts, suggestions, and gaps in the Backstage core with the maintainers and reviewers. + +Speaking of reviewers and maintainers… + +## Adding reviewers and maintainers + +[![GitHub logo](assets/21-06-22/gh-reviewers.png)](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) + +We have introduced [reviewers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. + +Of course, with these new efforts, we expect even more companies to adopt Backstage, which means the platform will continue to grow, and the number of PRs will continue to grow with it. As that happens, we hope to add to both the maintainer and reviewer teams in the future. + +So, I’ll end this post as it began: the Backstage community is growing! And we look forward to growing even bigger, even faster, together. diff --git a/microsite/blog/2021-06-24-announcing-backstage-search-platform.md b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md new file mode 100644 index 0000000000..0a4ae572c2 --- /dev/null +++ b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md @@ -0,0 +1,101 @@ +--- +title: Announcing the Backstage Search platform: a customizable search tool built just for you +author: Emma Indal, Spotify +authorURL: https://www.linkedin.com/in/emma-indal +--- + +![Backstage Search platform](assets/21-06-24/backstage-search-platform.png) + +**TLDR;** The new Backstage Search is now available in alpha, ready for you to start building on. A total rethinking of the core search feature in Backstage, it’s more than just a box you type into — it’s a mini platform all by itself. With its composable frontend and extensible backend, you can design and build the search tool that suits your organization’s needs. + +So, you don’t just get an improved out-of-the-box experience for searching whatever is in your software catalog. You can also add support for searching other sources, too. Customize it the way you want and you can search your catalog, your plugins and docs — and even external sources, like Stack Overflow and Confluence — all at once, all right inside Backstage. + +With one query, your teams can find exactly what they’re looking for: anything and everything. + + + +## Search and explore + +Being able to easily explore your ecosystem — to discover software, tools, documentation, and other valuable knowledge — is one of [the three main jobs of Backstage](https://backstage.io/blog/2021/05/20/adopting-backstage#three-jobs-create-manage-explore). Teams should be able to find what other teams have already built, so they can reuse and contribute to components instead of unknowingly duplicating them. Data endpoints should be shared, not siloed away. Services and their APIs should be easily discoverable. Best practices and technical documentation should be easily found. + +Along with the [Backstage Service Catalog](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha), Backstage Search is essential to enabling this discoverability — allowing new hires and old hands alike to explore your infrastructure instead of getting lost inside it. + +We also quickly realized that search looks different from organization to organization. Therefore, we built a search platform that lets you plug in your own search engine, index any information you like, or build a customized search page experience that fits your users’ needs. + +Since finding what you are looking for in Backstage is critical for success, we started by identifying the needs and goals of search. + +## Rethinking search, inside and out + +Spotify’s internal version of Backstage has had some of the features of Backstage Search for a while, and open sourcing them has been top of mind since day one. But we didn’t want to just port our internal version to the open source version. We wanted to take the opportunity to apply what we’ve learned inside Spotify over the last year, address the needs we’ve observed in the community, and ultimately open source not just a search feature but a search platform. We started the process by looking at the [jobs to be done](https://hbr.org/2016/09/know-your-customers-jobs-to-be-done). + +![Backstage Search platform](assets/21-06-24/jobs-to-be-done.png) +_A high-level overview of the process, identifying all the jobs of search._ + +First, we looked at which jobs to be done belonged to the search plugin itself (e.g., “collect documents to index”) and which belonged to the other plugins (e.g., “format documents for indexing”), and then whether those jobs belonged to the frontend (“display results”) or the backend (“schedule indexing”). + +Looking at all these various jobs, we defined four goals for the platform: + +- **Flexibility:** Be search engine–agnostic +- **Simplicity:** Make it easy for content owners to make their content searchable/discoverable +- **Control:** Allow plugin developers to customize their search results components +- **Reusability:** Offer reusable components/APIs that other devs can leverage + +Beginning our journey this way — by identifying the jobs to be done first, then defining the product goals from there — we could make sure that the search platform addressed real needs and improved the search experience for both users and plugin developers. + +This approach not only created a better search tool for the open source community, but for Spotify, as well. So, instead of just open sourcing our internal version of search, we ended up with an even better solution — one that we can all use and build on together, both inside and outside Spotify. + +## Say hello to the Backstage Search platform + +![Backstage Search platform](assets/21-06-24/search-results.png) + +We are now happy and proud to announce our alpha version of the [Backstage Search Platform](https://backstage.io/docs/features/search/architecture), featuring: + +- Bring your own search engine (Flexibility) +- Collators for easily indexing content from plugins and other sources (Simplicity) +- Composable search page experiences (Control, Reusability) +- Customize the look and feel of each search result (Control, Reusability) + +### Bring your own search engine + +By introducing a Search Integration Layer, we have been able to keep the query translation of the search term and filters close to the search engine itself. This makes our search backend less focused on how a set of terms and filters should be translated to fit a certain search engine interface and more focused on querying and retrieving results as well as collecting results to index. + +With the Search Integration Layer, your organization can bring your search engine of choice to Backstage — instead of relying on Backstage to support a specific search engine that might not fit the needs of your organization, either today or in the future. + +But that doesn’t mean “batteries not included”. The current version of Backstage Search ships with Lunr support built-in — and support for ElasticSearch is not very far off. And we hope the number of supported search engines will continue to grow with the community’s help. + +### Collators for easily indexing content from plugins and other sources + +Since Backstage’s functionality comes from its plugins, we wanted the process of making plugin content searchable to be as frictionless as possible. Therefore we decided on a concept we call collators. Collators are responsible for collecting documents to index from a plugin. Your collators live inside your own plugin, but are registered in the Backstage app’s search backend. + +Collators can also be used to index external sources, like Stack Overflow and Confluence. You can watch a demo of how easy it is to extend search with collators [here](https://youtu.be/Z78FFaObTfk?t=339). + +### Composable search page experiences + +Every engineering org has different needs — that is something we have definitely learned over the last year. Your software catalog might be set up differently than ours and therefore your needs for how search results look and how the search filters work will also differ. + +That's why we have put effort into making your search page experience composable to your organization's needs. What do we mean by that? When you adopt Backstage and set up your app, you can set up — or, compose — your search page by using existing components or by creating your own custom ones. + +### Customize the look and feel of each search result + +A good example of the level of customization the platform allows is how list items are displayed in search results. A search result component can be a list, this list can consist of different list items (search results returned from the search engine) — but these list items could look different depending on what the search result returns in terms of its fields. + +Let’s say that for an entity returned from the software catalog maybe the most important information to show is the name, while a result returned from the TechDocs plugin should maybe show the text content as the most important information. This can be customized by creating components (like TechDocsResultListItem or CatalogResultListItem or whatever list item component you want) and configuring them in the app. + +If there is no need to customize your search result list items, the component is there for you to reuse. + +## Getting started with Backstage Search + +We put together [a getting started guide](https://backstage.io/docs/features/search/getting-started) that provides two different ways to set up Backstage Search: + +- Create a new app and get the most out of the search setup right out of the box, or +- Add the new Backstage Search setup to your existing Backstage app. + +Whichever situation you’re in, we have you covered. + +## What’s next? + +We’ve built the foundation for the Backstage Search platform, and we can't wait to see the exciting engines, collators, and components the community builds on the platform. + +You can check out our [project roadmap](https://backstage.io/docs/features/search/search-overview#project-roadmap) in our search documentation or track the progress of our [Beta milestone](https://github.com/backstage/backstage/milestone/27) and [GA milestone](https://github.com/backstage/backstage/milestone/28). + +For any questions, feedback or ideas about the Backstage Search platform, join us in the #search channel on [Discord](https://discord.gg/MUpMjP2)! diff --git a/microsite/blog/assets/21-06-22/backstage-community-sessions.png b/microsite/blog/assets/21-06-22/backstage-community-sessions.png new file mode 100644 index 0000000000..ccfaf6498e Binary files /dev/null and b/microsite/blog/assets/21-06-22/backstage-community-sessions.png differ diff --git a/microsite/blog/assets/21-06-22/gh-reviewers.png b/microsite/blog/assets/21-06-22/gh-reviewers.png new file mode 100644 index 0000000000..c7402525a8 Binary files /dev/null and b/microsite/blog/assets/21-06-22/gh-reviewers.png differ diff --git a/microsite/blog/assets/21-06-22/spotify-backstage-adopters.png b/microsite/blog/assets/21-06-22/spotify-backstage-adopters.png new file mode 100644 index 0000000000..2bd963f91a Binary files /dev/null and b/microsite/blog/assets/21-06-22/spotify-backstage-adopters.png differ diff --git a/microsite/blog/assets/21-06-22/spotify-backstage-header.gif b/microsite/blog/assets/21-06-22/spotify-backstage-header.gif new file mode 100644 index 0000000000..93c4b5857b Binary files /dev/null and b/microsite/blog/assets/21-06-22/spotify-backstage-header.gif differ diff --git a/microsite/blog/assets/21-06-24/backstage-search-platform.png b/microsite/blog/assets/21-06-24/backstage-search-platform.png new file mode 100644 index 0000000000..b83b3aa0f4 Binary files /dev/null and b/microsite/blog/assets/21-06-24/backstage-search-platform.png differ diff --git a/microsite/blog/assets/21-06-24/jobs-to-be-done.png b/microsite/blog/assets/21-06-24/jobs-to-be-done.png new file mode 100644 index 0000000000..74585d0945 Binary files /dev/null and b/microsite/blog/assets/21-06-24/jobs-to-be-done.png differ diff --git a/microsite/blog/assets/21-06-24/search-results.png b/microsite/blog/assets/21-06-24/search-results.png new file mode 100644 index 0000000000..5524d89d7e Binary files /dev/null and b/microsite/blog/assets/21-06-24/search-results.png differ diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index ec08216367..8e69b7ff6f 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -33,7 +33,7 @@ class Footer extends React.Component { - Service Catalog + Software Catalog Create a Plugin Designing for Backstage diff --git a/microsite/data/plugins/backstage-service-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml similarity index 79% rename from microsite/data/plugins/backstage-service-catalog.yaml rename to microsite/data/plugins/backstage-software-catalog.yaml index 1a788078a5..ae16550297 100644 --- a/microsite/data/plugins/backstage-service-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -1,10 +1,10 @@ --- -title: Backstage Service Catalog +title: Backstage Software Catalog author: Spotify authorUrl: https://github.com/spotify category: Core Feature description: Manage all your services and software components, all in one place. documentation: https://backstage.io/docs/features/software-catalog/software-catalog-overview -iconUrl: img/backstage-service-catalog.svg +iconUrl: img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 diff --git a/microsite/data/plugins/gke-usage.yaml b/microsite/data/plugins/gke-usage.yaml new file mode 100644 index 0000000000..42d3e3a287 --- /dev/null +++ b/microsite/data/plugins/gke-usage.yaml @@ -0,0 +1,15 @@ +--- +title: GKE Usage +author: BESTSELLER +authorUrl: bestsellerit.com +category: Discovery +description: This plugin will show you the cost and resource usage of your application within GKE. +documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md +iconUrl: https://bestsellerit.com/img/google-container-engine_avatar.svg +npmPackageName: '@bestsellerit/backstage-plugin-gkeusage' +tags: + - gke + - cost + - google + - usage + - metering diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml new file mode 100644 index 0000000000..a851f99545 --- /dev/null +++ b/microsite/data/plugins/harbor.yaml @@ -0,0 +1,13 @@ +--- +title: Harbor +author: BESTSELLER +authorUrl: bestsellerit.com +category: Discovery +description: This plugin will show you information about docker images within harbor. +documentation: https://github.com/BESTSELLER/backstage-plugin-harbor/blob/master/README.md +iconUrl: https://bestsellerit.com/img/terraform-harbor/goharbor.jpeg +npmPackageName: '@bestsellerit/backstage-plugin-harbor' +tags: + - goharbor + - harbor + - docker diff --git a/microsite/data/plugins/scaffolder-backend-module-rails.yaml b/microsite/data/plugins/scaffolder-backend-module-rails.yaml new file mode 100644 index 0000000000..5f5a29cf3c --- /dev/null +++ b/microsite/data/plugins/scaffolder-backend-module-rails.yaml @@ -0,0 +1,9 @@ +--- +title: Scaffolder Backend Module Rails +author: Rogerio Angeliski +authorUrl: https://angeliski.com.br/ +category: Scaffolder +description: Here you can find all Rails related features to improve your scaffolder. +documentation: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-rails/README.md +iconUrl: img/rails-icon.png +npmPackageName: '@backstage/plugin-scaffolder-backend-module-rails' diff --git a/microsite/package.json b/microsite/package.json index 3112bbfe17..eee549fb70 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -19,8 +19,8 @@ "@spotify/prettier-config": "^10.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.3.1", - "yarn-lock-check": "^1.0.4" + "prettier": "^2.3.2", + "yarn-lock-check": "^1.0.5" }, "prettier": "@spotify/prettier-config" } diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js index e3943d2905..7d97641815 100644 --- a/microsite/pages/en/demos.js +++ b/microsite/pages/en/demos.js @@ -42,7 +42,7 @@ const Background = props => { To explore the UI and basic features of Backstage firsthand, go to: demo.backstage.io. (Tip: click “All” to view all the example components in the - service catalog.) + software catalog.) diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 45d87daa1d..d7325bb234 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -27,7 +27,7 @@ class Index extends React.Component { An open platform for building developer portals - Powered by a centralized service catalog, Backstage restores + Powered by a centralized software catalog, Backstage restores order to your infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -102,10 +102,10 @@ class Index extends React.Component { {' '} - Backstage Service Catalog{' '} + Backstage Software Catalog{' '} - Learn more about the service catalog + Learn more about the software catalog \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/backstage-service-catalog.svg b/microsite/static/img/backstage-service-catalog.svg deleted file mode 100644 index 188b52a43b..0000000000 --- a/microsite/static/img/backstage-service-catalog.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/microsite/static/img/backstage-software-catalog.svg b/microsite/static/img/backstage-software-catalog.svg new file mode 100644 index 0000000000..101439497b --- /dev/null +++ b/microsite/static/img/backstage-software-catalog.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/static/img/backstage-software-templates.svg b/microsite/static/img/backstage-software-templates.svg index 41d51ff8e1..3984cd2870 100644 --- a/microsite/static/img/backstage-software-templates.svg +++ b/microsite/static/img/backstage-software-templates.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/backstage-techdocs.svg b/microsite/static/img/backstage-techdocs.svg index 48085df83c..aa9362b1bc 100644 --- a/microsite/static/img/backstage-techdocs.svg +++ b/microsite/static/img/backstage-techdocs.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/microsite/static/img/cortex.png b/microsite/static/img/cortex.png index 6a45d0ca06..d16b3f9c32 100644 Binary files a/microsite/static/img/cortex.png and b/microsite/static/img/cortex.png differ diff --git a/microsite/static/img/rails-icon.png b/microsite/static/img/rails-icon.png new file mode 100644 index 0000000000..f7b8b69bd9 Binary files /dev/null and b/microsite/static/img/rails-icon.png differ diff --git a/microsite/static/img/twitter-summary.png b/microsite/static/img/twitter-summary.png index 4a31eec062..7330b071ee 100644 Binary files a/microsite/static/img/twitter-summary.png and b/microsite/static/img/twitter-summary.png differ diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 6f71394265..1da9288808 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9,13 +9,6 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -227,11 +220,6 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/helper-validator-identifier@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" - integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== - "@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11": version "7.12.11" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f" @@ -265,15 +253,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.12.13": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" - integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.0" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7": version "7.12.11" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" @@ -942,29 +921,11 @@ dependencies: "@types/node" "*" -"@types/glob@^7.1.3": - version "7.1.3" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.4" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" - integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== - "@types/node@*": version "14.14.20" resolved "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340" integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A== -"@types/node@^15.6.1": - version "15.9.0" - resolved "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8" - integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA== - "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" @@ -1449,11 +1410,6 @@ buffer@^5.2.1: base64-js "^1.3.1" ieee754 "^1.1.13" -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - bytes@1: version "1.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -1567,7 +1523,7 @@ caw@^2.0.0, caw@^2.0.1: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1758,7 +1714,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.12.1, commander@^2.8.1: +commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -2230,11 +2186,6 @@ diacritics-map@^0.1.0: resolved "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68= -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - dir-glob@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" @@ -3091,19 +3042,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.1, glob@^7.1.7: +glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@^7.1.7, glob@~7.1.1: version "7.1.7" resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -3638,13 +3577,6 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4457,7 +4389,7 @@ mixin-deep@^1.1.3, mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -5275,10 +5207,10 @@ prepend-http@^2.0.0: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" - integrity sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA== +prettier@^2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== prismjs@^1.22.0: version "1.23.0" @@ -5681,14 +5613,6 @@ resolve@^1.1.6, resolve@^1.10.0: is-core-module "^2.1.0" path-parse "^1.0.6" -resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - responselike@1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -6437,37 +6361,11 @@ truncate-html@^1.0.3: "@types/cheerio" "^0.22.8" cheerio "0.22.0" -tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslint@^6.1.3: - version "6.1.3" - resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904" - integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== - dependencies: - "@babel/code-frame" "^7.0.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^4.0.1" - glob "^7.1.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" - mkdirp "^0.5.3" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.13.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6493,11 +6391,6 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" - integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== - unbzip2-stream@^1.0.9: version "1.4.3" resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -6767,18 +6660,14 @@ yargs@^2.3.0: dependencies: wordwrap "0.0.2" -yarn-lock-check@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4" - integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg== +yarn-lock-check@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.5.tgz#69d9516385f3ff010d0e2b0e87fbbd9bb1ffecaf" + integrity sha512-dxmV4LpIBrRAPbPg+klyGvqdVo3Y6PgJs6ERJYXf0HSEst7klDmvKKqQUtk+wrIRCoMDIVIzSUTZsuC4zr/tFQ== dependencies: - "@types/glob" "^7.1.3" - "@types/node" "^15.6.1" "@yarnpkg/lockfile" "^1.1.0" glob "^7.1.7" ini "^2.0.0" - tslint "^6.1.3" - typescript "^4.3.2" yauzl@^2.4.2: version "2.10.0" diff --git a/mkdocs.yml b/mkdocs.yml index 67b97b6ffb..4d984e63e3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,8 @@ nav: - The Spotify Story: 'overview/background.md' - Strategies for adopting: 'overview/adopting.md' - Stability Index: 'overview/stability-index.md' + - Support and community: 'overview/support.md' + - Glossary: 'overview/glossary.md' - Logo assets: 'overview/logos.md' - Getting Started: - Getting Started: 'getting-started/index.md' @@ -24,13 +26,13 @@ nav: - Keeping Backstage Updated: 'getting-started/keeping-backstage-updated.md' - Key Concepts: 'getting-started/concepts.md' - Contributors: 'getting-started/contributors.md' + - Project Structure: 'getting-started/project-structure.md' - CLI: - Overview: 'cli/index.md' - Commands: 'cli/commands.md' - Core Features: - Software Catalog: - Overview: 'features/software-catalog/index.md' - - Installing in your Backstage App: 'features/software-catalog/installation.md' - Catalog Configuration: 'features/software-catalog/configuration.md' - System Model: 'features/software-catalog/system-model.md' - YAML File Format: 'features/software-catalog/descriptor-format.md' @@ -49,7 +51,7 @@ nav: - Troubleshooting: 'features/kubernetes/troubleshooting.md' - Software Templates: - Overview: 'features/software-templates/index.md' - - Installing in your Backstage App: 'features/software-templates/installation.md' + - Configuration: 'features/software-templates/configuration.md' - Adding your own Templates: 'features/software-templates/adding-templates.md' - Writing Templates: 'features/software-templates/writing-templates.md' - Builtin Actions: 'features/software-templates/builtin-actions.md' @@ -76,8 +78,9 @@ nav: - FAQ: 'features/techdocs/FAQ.md' - Integrations: - Overview: 'integrations/index.md' - - Azure DevOps: + - Azure: - Locations: 'integrations/azure/locations.md' + - Org Data: 'integrations/azure/org.md' - Bitbucket: - Locations: 'integrations/bitbucket/locations.md' - Discovery: 'integrations/bitbucket/discovery.md' @@ -102,13 +105,14 @@ nav: - Plugin Development: 'plugins/plugin-development.md' - Structure of a plugin: 'plugins/structure-of-a-plugin.md' - Plugin Development: 'plugins/plugin-development.md' - - Integrate into the Service Catalog: 'plugins/integrating-plugin-into-service-catalog.md' + - Integrate into the Software Catalog: 'plugins/integrating-plugin-into-software-catalog.md' - Composability System Migration: 'plugins/composability.md' - Backends and APIs: - Proxying: 'plugins/proxying.md' - Backend plugin: 'plugins/backend-plugin.md' - Call existing API: 'plugins/call-existing-api.md' - GitHub Apps for Backend Authentication: 'plugins/github-apps.md' + - URL Reader: 'plugins/url-reader.md' - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: @@ -133,6 +137,7 @@ nav: - OneLogin: 'auth/onelogin/provider.md' - Adding authentication providers: 'auth/add-auth-provider.md' - Using authentication and identity: 'auth/using-auth.md' + - Sign in resolvers: 'auth/identity-resolver.md' - Auth backend: 'auth/auth-backend.md' - OAuth and OpenID Connect: 'auth/oauth.md' - Auth backend classes: 'auth/auth-backend-classes.md' @@ -157,6 +162,7 @@ nav: - Backend: 'api/backend.md' - Tutorials: - Future developer journey: 'tutorials/journey.md' + - Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md' - Adding Custom Plugin to Existing Monorepo App: 'tutorials/quickstart-app-plugin.md' - Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md' - Architecture Decision Records (ADRs): @@ -173,7 +179,6 @@ nav: - ADR010 - Luxon Date Library: 'architecture-decisions/adr010-luxon-date-library.md' - ADR011 - Plugin Package Structure: 'architecture-decisions/adr011-plugin-package-structure.md' - Support: - - Support and community: 'support/support.md' - Backstage Project Structure: 'support/project-structure.md' - Glossary: glossary.md - FAQ: FAQ.md diff --git a/package.json b/package.json index c0887d7f05..93517d3374 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "yarn workspace example-app start", "start-backend": "yarn workspace example-backend start", "build": "lerna run build", - "build:api-reports": "tsc && yarn build:api-reports:only", + "build:api-reports": "yarn tsc:full && yarn build:api-reports:only", "build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts", "build:api-docs": "yarn build:api-reports --docs", "tsc": "tsc", @@ -44,15 +44,14 @@ "**/@roadiehq/**/@backstage/core": "*", "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*", - "**/@microsoft/api-extractor/typescript": "^4.0.3", "graphql-language-service-interface": "2.8.2", "graphql-language-service-parser": "1.9.0" }, "version": "1.0.0", "dependencies": { - "@microsoft/api-documenter": "^7.12.16", - "@microsoft/api-extractor": "7.13.2-pr1916.0", - "@microsoft/api-extractor-model": "^7.12.5" + "@microsoft/api-documenter": "^7.13.30", + "@microsoft/api-extractor": "^7.18.1", + "@microsoft/api-extractor-model": "^7.13.3" }, "devDependencies": { "@changesets/cli": "^2.14.0", @@ -61,7 +60,7 @@ "command-exists": "^1.2.9", "concurrently": "^6.0.0", "eslint-plugin-notice": "^0.9.10", - "fs-extra": "^9.0.0", + "fs-extra": "9.1.0", "husky": "^6.0.0", "lerna": "^4.0.0", "lint-staged": "^10.1.0", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index c200104775..cc67b78f44 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,93 @@ # example-app +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.1.5 + - @backstage/plugin-pagerduty@0.3.7 + - @backstage/plugin-catalog-import@0.5.12 + - @backstage/plugin-explore@0.3.9 + - @backstage/catalog-model@0.9.0 + - @backstage/plugin-scaffolder@0.10.0 + - @backstage/plugin-catalog@0.6.6 + - @backstage/plugin-org@0.3.16 + - @backstage/plugin-techdocs@0.9.9 + - @backstage/plugin-api-docs@0.6.1 + - @backstage/plugin-badges@0.2.4 + - @backstage/plugin-catalog-react@0.2.6 + - @backstage/plugin-circleci@0.2.18 + - @backstage/plugin-cloudbuild@0.2.18 + - @backstage/plugin-code-coverage@0.1.6 + - @backstage/plugin-github-actions@0.4.12 + - @backstage/plugin-jenkins@0.4.7 + - @backstage/plugin-kafka@0.2.10 + - @backstage/plugin-kubernetes@0.4.7 + - @backstage/plugin-lighthouse@0.2.19 + - @backstage/plugin-rollbar@0.3.8 + - @backstage/plugin-search@0.4.2 + - @backstage/plugin-sentry@0.3.14 + - @backstage/plugin-todo@0.1.4 + +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.6.4 + - @backstage/plugin-search@0.4.1 + - @backstage/plugin-explore@0.3.7 + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + - @backstage/plugin-scaffolder@0.9.9 + - @backstage/cli@0.7.2 + - @backstage/plugin-api-docs@0.6.0 + - @backstage/plugin-cost-insights@0.11.0 + - @backstage/plugin-gcp-projects@0.3.0 + - @backstage/plugin-newrelic@0.3.0 + - @backstage/plugin-techdocs@0.9.7 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-badges@0.2.3 + - @backstage/plugin-catalog-import@0.5.11 + - @backstage/plugin-catalog-react@0.2.4 + - @backstage/plugin-circleci@0.2.17 + - @backstage/plugin-cloudbuild@0.2.17 + - @backstage/plugin-code-coverage@0.1.5 + - @backstage/plugin-github-actions@0.4.10 + - @backstage/plugin-graphiql@0.2.12 + - @backstage/plugin-jenkins@0.4.6 + - @backstage/plugin-kafka@0.2.9 + - @backstage/plugin-kubernetes@0.4.6 + - @backstage/plugin-lighthouse@0.2.18 + - @backstage/plugin-org@0.3.15 + - @backstage/plugin-pagerduty@0.3.6 + - @backstage/plugin-rollbar@0.3.7 + - @backstage/plugin-sentry@0.3.13 + - @backstage/plugin-shortcuts@0.1.4 + - @backstage/plugin-tech-radar@0.4.1 + - @backstage/plugin-todo@0.1.3 + - @backstage/plugin-user-settings@0.2.12 + +## 0.2.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.2.3 + - @backstage/plugin-catalog@0.6.3 + - @backstage/cli@0.7.1 + - @backstage/plugin-api-docs@0.5.0 + - @backstage/plugin-jenkins@0.4.5 + - @backstage/plugin-techdocs@0.9.6 + - @backstage/plugin-circleci@0.2.16 + - @backstage/plugin-catalog-import@0.5.10 + - @backstage/plugin-sentry@0.3.12 + - @backstage/plugin-user-settings@0.2.11 + - @backstage/catalog-model@0.8.3 + - @backstage/core@0.7.13 + ## 0.2.32 ### Patch Changes diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js index a7462588a0..7b874907be 100644 --- a/packages/app/cypress/integration/app.js +++ b/packages/app/cypress/integration/app.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/cypress/integration/components/search/SearchPage.js b/packages/app/cypress/integration/components/search/SearchPage.js index 4e13fc8a0f..4db7acf4fd 100644 --- a/packages/app/cypress/integration/components/search/SearchPage.js +++ b/packages/app/cypress/integration/components/search/SearchPage.js @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/cypress/support/commands.js b/packages/app/cypress/support/commands.js index dd2b26634d..f26d2c999b 100644 --- a/packages/app/cypress/support/commands.js +++ b/packages/app/cypress/support/commands.js @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/cypress/support/index.js b/packages/app/cypress/support/index.js index c1f930027a..fb62f6359f 100644 --- a/packages/app/cypress/support/index.js +++ b/packages/app/cypress/support/index.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/package.json b/packages/app/package.json index 1b0b3aa22c..b26e94fc26 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,50 +1,52 @@ { "name": "example-app", - "version": "0.2.32", + "version": "0.2.36", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.8.2", - "@backstage/cli": "^0.7.0", - "@backstage/core": "^0.7.12", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-api-docs": "^0.4.15", - "@backstage/plugin-badges": "^0.2.2", - "@backstage/plugin-catalog": "^0.6.2", - "@backstage/plugin-catalog-import": "^0.5.9", - "@backstage/plugin-catalog-react": "^0.2.2", - "@backstage/plugin-circleci": "^0.2.15", - "@backstage/plugin-cloudbuild": "^0.2.16", - "@backstage/plugin-code-coverage": "^0.1.4", - "@backstage/plugin-cost-insights": "^0.10.2", - "@backstage/plugin-explore": "^0.3.6", - "@backstage/plugin-gcp-projects": "^0.2.6", - "@backstage/plugin-github-actions": "^0.4.9", - "@backstage/plugin-graphiql": "^0.2.11", - "@backstage/plugin-jenkins": "^0.4.4", - "@backstage/plugin-kafka": "^0.2.8", - "@backstage/plugin-kubernetes": "^0.4.5", - "@backstage/plugin-lighthouse": "^0.2.17", - "@backstage/plugin-newrelic": "^0.2.7", - "@backstage/plugin-org": "^0.3.14", - "@backstage/plugin-pagerduty": "0.3.5", - "@backstage/plugin-rollbar": "^0.3.6", - "@backstage/plugin-scaffolder": "^0.9.8", - "@backstage/plugin-search": "^0.4.0", - "@backstage/plugin-sentry": "^0.3.11", - "@backstage/plugin-shortcuts": "^0.1.2", - "@backstage/plugin-tech-radar": "^0.4.0", - "@backstage/plugin-techdocs": "^0.9.5", - "@backstage/plugin-todo": "^0.1.2", - "@backstage/plugin-user-settings": "^0.2.10", + "@backstage/catalog-model": "^0.9.0", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/core-components": "^0.1.5", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-api-docs": "^0.6.1", + "@backstage/plugin-badges": "^0.2.4", + "@backstage/plugin-catalog": "^0.6.6", + "@backstage/plugin-catalog-import": "^0.5.12", + "@backstage/plugin-catalog-react": "^0.2.6", + "@backstage/plugin-circleci": "^0.2.18", + "@backstage/plugin-cloudbuild": "^0.2.18", + "@backstage/plugin-code-coverage": "^0.1.6", + "@backstage/plugin-cost-insights": "^0.11.0", + "@backstage/plugin-explore": "^0.3.9", + "@backstage/plugin-gcp-projects": "^0.3.0", + "@backstage/plugin-github-actions": "^0.4.12", + "@backstage/plugin-graphiql": "^0.2.12", + "@backstage/plugin-jenkins": "^0.4.7", + "@backstage/plugin-kafka": "^0.2.10", + "@backstage/plugin-kubernetes": "^0.4.7", + "@backstage/plugin-lighthouse": "^0.2.19", + "@backstage/plugin-newrelic": "^0.3.0", + "@backstage/plugin-org": "^0.3.16", + "@backstage/plugin-pagerduty": "0.3.7", + "@backstage/plugin-rollbar": "^0.3.8", + "@backstage/plugin-scaffolder": "^0.10.0", + "@backstage/plugin-search": "^0.4.2", + "@backstage/plugin-sentry": "^0.3.14", + "@backstage/plugin-shortcuts": "^0.1.4", + "@backstage/plugin-tech-radar": "^0.4.1", + "@backstage/plugin-techdocs": "^0.9.9", + "@backstage/plugin-todo": "^0.1.4", + "@backstage/plugin-user-settings": "^0.2.12", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.5.3", - "@roadiehq/backstage-plugin-buildkite": "^1.0.3", - "@roadiehq/backstage-plugin-github-insights": "^1.1.11", - "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.5", - "@roadiehq/backstage-plugin-travis-ci": "^1.0.0", + "@roadiehq/backstage-plugin-buildkite": "^1.0.4", + "@roadiehq/backstage-plugin-github-insights": "^1.1.15", + "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.8", + "@roadiehq/backstage-plugin-travis-ci": "^1.0.4", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -56,7 +58,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.13", + "@backstage/test-utils": "^0.1.14", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx index cdb1e8db4a..aa6781ce61 100644 --- a/packages/app/src/App.test.tsx +++ b/packages/app/src/App.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index b14f9a3163..de62971f96 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,12 @@ * limitations under the License. */ +import { createApp, FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, - createApp, - FlatRoutes, OAuthRequestDialog, SignInPage, -} from '@backstage/core'; +} from '@backstage/core-components'; import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; import { CatalogEntityPage, @@ -65,6 +64,7 @@ const app = createApp({ // Custom icon example alert: AlarmIcon, }, + components: { SignInPage: props => { return ( @@ -116,6 +116,7 @@ const routes = ( /> } /> } /> + } /> } /> } /> diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index ccc576e727..08ff781319 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,6 @@ * limitations under the License. */ -import { - AnyApiFactory, - configApiRef, - createApiFactory, - errorApiRef, - githubAuthApiRef, -} from '@backstage/core'; import { ScmIntegrationsApi, scmIntegrationsApiRef, @@ -33,6 +26,13 @@ import { graphQlBrowseApiRef, GraphQLEndpoints, } from '@backstage/plugin-graphiql'; +import { + AnyApiFactory, + configApiRef, + createApiFactory, + errorApiRef, + githubAuthApiRef, +} from '@backstage/core-plugin-api'; export const apis: AnyApiFactory[] = [ createApiFactory({ diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx index 2fb767465b..c7b1c846c4 100644 --- a/packages/app/src/components/Root/LogoFull.tsx +++ b/packages/app/src/components/Root/LogoFull.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx index 507e47ddb9..073cf6edad 100644 --- a/packages/app/src/components/Root/LogoIcon.tsx +++ b/packages/app/src/components/Root/LogoIcon.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 9c62ec0b4d..48141e4f53 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,11 @@ import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import MoneyIcon from '@material-ui/icons/MonetizationOn'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; +import { NavLink } from 'react-router-dom'; +import { GraphiQLIcon } from '@backstage/plugin-graphiql'; +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +import { SidebarSearch } from '@backstage/plugin-search'; +import { Shortcuts } from '@backstage/plugin-shortcuts'; import { Sidebar, SidebarPage, @@ -34,12 +39,8 @@ import { SidebarItem, SidebarDivider, SidebarSpace, -} from '@backstage/core'; -import { NavLink } from 'react-router-dom'; -import { GraphiQLIcon } from '@backstage/plugin-graphiql'; -import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; -import { SidebarSearch } from '@backstage/plugin-search'; -import { Shortcuts } from '@backstage/plugin-shortcuts'; + SidebarScrollWrapper, +} from '@backstage/core-components'; const useSidebarLogoStyles = makeStyles({ root: { @@ -88,10 +89,12 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( {/* End global nav */} - - - - + + + + + + diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts index ab65cb2451..dff706f08f 100644 --- a/packages/app/src/components/Root/index.ts +++ b/packages/app/src/components/Root/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx index c33adf6c4a..955e477f3c 100644 --- a/packages/app/src/components/catalog/EntityPage.test.tsx +++ b/packages/app/src/components/catalog/EntityPage.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,12 @@ */ import React from 'react'; -import { ApiProvider, ApiRegistry } from '@backstage/core'; import { EntityLayout } from '@backstage/plugin-catalog'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import { cicdContent } from './EntityPage'; import { githubActionsApiRef } from '@backstage/plugin-github-actions'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; describe('EntityPage Test', () => { const entity = { diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index f646992c97..16107a6cc0 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ import React, { ReactNode, useMemo, useState } from 'react'; import BadgeIcon from '@material-ui/icons/CallToAction'; -import { EmptyState } from '@backstage/core'; import { EntityApiDefinitionCard, EntityConsumingComponentsCard, @@ -108,6 +107,7 @@ import { isTravisciAvailable, } from '@roadiehq/backstage-plugin-travis-ci'; import { EntityCodeCoverageContent } from '@backstage/plugin-code-coverage'; +import { EmptyState } from '@backstage/core-components'; const EntityLayoutWrapper = (props: { children?: ReactNode }) => { const [badgesDialogOpen, setBadgesDialogOpen] = useState(false); diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 7b94e876e6..35c4a4ac1c 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import React from 'react'; import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; -import { Content, Header, Lifecycle, Page } from '@backstage/core'; import { CatalogResultListItem } from '@backstage/plugin-catalog'; import { SearchBar, @@ -25,6 +24,7 @@ import { SearchResult, DefaultResultListItem, } from '@backstage/plugin-search'; +import { Content, Header, Lifecycle, Page } from '@backstage/core-components'; const useStyles = makeStyles((theme: Theme) => ({ bar: { diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 6f45f9ba32..4cc7bab91a 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import { oneloginAuthApiRef, oauth2ApiRef, oidcAuthApiRef, -} from '@backstage/core'; +} from '@backstage/core-plugin-api'; export const providers = [ { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 71fd40dd7e..b15bc4c102 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index e02e30e5d9..a1e952a07c 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/react-app-env.d.ts b/packages/app/src/react-app-env.d.ts index f3b69cc361..b1e99a84e6 100644 --- a/packages/app/src/react-app-env.d.ts +++ b/packages/app/src/react-app-env.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/app/src/setupTests.ts b/packages/app/src/setupTests.ts index c717b2753b..23fcbe9676 100644 --- a/packages/app/src/setupTests.ts +++ b/packages/app/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 6256697036..93016bd13c 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/backend-common +## 0.8.5 + +### Patch Changes + +- 09d3eb684: Added a `readUrl` method to the `UrlReader` interface that allows for complex response objects and is intended to replace the `read` method. This new method is currently optional to implement which allows for a soft migration to `readUrl` instead of `read` in the future. + + The main use case for `readUrl` returning an object instead of solely a read buffer is to allow for additional metadata such as ETag, which is a requirement for more efficient catalog processing. + + The `GithubUrlReader` and `GitlabUrlReader` readers fully implement `readUrl`. The other existing readers implement the new method but do not propagate or return ETags. + + While the `readUrl` method is not yet required, it will be in the future, and we already log deprecation warnings when custom `UrlReader` implementations that do not implement `readUrl` are used. We therefore recommend that any existing custom implementations are migrated to implement `readUrl`. + + The old `read` and the new `readUrl` methods can easily be implemented using one another, but we recommend moving the chunk of the implementation to the new `readUrl` method as `read` is being removed, for example this: + + ```ts + class CustomUrlReader implements UrlReader { + read(url: string): Promise { + const res = await fetch(url); + + if (!res.ok) { + // error handling ... + } + + return Buffer.from(await res.text()); + } + } + ``` + + Can be migrated to something like this: + + ```ts + class CustomUrlReader implements UrlReader { + read(url: string): Promise { + const res = await this.readUrl(url); + return res.buffer(); + } + + async readUrl( + url: string, + _options?: ReadUrlOptions, + ): Promise { + const res = await fetch(url); + + if (!res.ok) { + // error handling ... + } + + const buffer = Buffer.from(await res.text()); + return { buffer: async () => buffer }; + } + } + ``` + + While there is no usage of the ETag capability yet in the main Backstage packages, you can already add it to your custom implementations. To do so, refer to the documentation of the `readUrl` method and surrounding types, and the existing implementation in `packages/backend-common/src/reading/GithubUrlReader.ts`. + +- 6841e0113: fix minor version of git-url-parse as 11.5.x introduced a bug for Bitbucket Server +- c2db794f5: add defaultBranch property for publish GitHub action +- Updated dependencies + - @backstage/integration@0.5.8 + +## 0.8.4 + +### Patch Changes + +- 88d742eb8: Download archives as compressed tar files for GitLab to fix the `readTree` bug in TODO Plugin. +- ab5cc376f: Add new `isChildPath` and `resolveSafeChildPath` exports +- Updated dependencies + - @backstage/cli-common@0.1.2 + - @backstage/integration@0.5.7 + +## 0.8.3 + +### Patch Changes + +- e5cdf0560: Provide a more clear error message when database connection fails. +- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database + connection manager, `DatabaseManager`, which allows developers to configure database + connections on a per plugin basis. + + The `backend.database` config path allows you to set `prefix` to use an + alternate prefix for automatically generated database names, the default is + `backstage_plugin_`. Use `backend.database.plugin.` to set plugin + specific database connection configuration, e.g. + + ```yaml + backend: + database: + client: 'pg', + prefix: 'custom_prefix_' + connection: + host: 'localhost' + user: 'foo' + password: 'bar' + plugin: + catalog: + connection: + database: 'database_name_overriden' + scaffolder: + client: 'sqlite3' + connection: ':memory:' + ``` + + Migrate existing backstage installations by swapping out the database manager in the + `packages/backend/src/index.ts` file as shown below: + + ```diff + import { + - SingleConnectionDatabaseManager, + + DatabaseManager, + } from '@backstage/backend-common'; + + // ... + + function makeCreateEnv(config: Config) { + // ... + - const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + + const databaseManager = DatabaseManager.fromConfig(config); + // ... + } + ``` + +- Updated dependencies + - @backstage/config-loader@0.6.4 + ## 0.8.2 ### Patch Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 068220cad3..94f1147b5a 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// +/// import { AzureIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; @@ -16,9 +18,10 @@ import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; import * as http from 'http'; +import { isChildPath } from '@backstage/cli-common'; import { JsonValue } from '@backstage/config'; import { Knex } from 'knex'; -import { Logger } from 'winston'; +import { Logger as Logger_2 } from 'winston'; import { MergeResult } from 'isomorphic-git'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; @@ -31,95 +34,133 @@ import { Writable } from 'stream'; // @public (undocumented) export class AzureUrlReader implements UrlReader { - constructor(integration: AzureIntegration, deps: { - treeResponseFactory: ReadTreeResponseFactory; - }); - // (undocumented) - static factory: ReaderFactory; - // (undocumented) - read(url: string): Promise; - // (undocumented) - readTree(url: string, options?: ReadTreeOptions): Promise; - // (undocumented) - search(url: string, options?: SearchOptions): Promise; - // (undocumented) - toString(): string; + constructor( + integration: AzureIntegration, + deps: { + treeResponseFactory: ReadTreeResponseFactory; + }, + ); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(url: string, options?: ReadTreeOptions): Promise; + // (undocumented) + readUrl(url: string, _options?: ReadUrlOptions): Promise; + // (undocumented) + search(url: string, options?: SearchOptions): Promise; + // (undocumented) + toString(): string; } // @public export class BitbucketUrlReader implements UrlReader { - constructor(integration: BitbucketIntegration, deps: { - treeResponseFactory: ReadTreeResponseFactory; - }); - // (undocumented) - static factory: ReaderFactory; - // (undocumented) - read(url: string): Promise; - // (undocumented) - readTree(url: string, options?: ReadTreeOptions): Promise; - // (undocumented) - search(url: string, options?: SearchOptions): Promise; - // (undocumented) - toString(): string; + constructor( + integration: BitbucketIntegration, + deps: { + treeResponseFactory: ReadTreeResponseFactory; + }, + ); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(url: string, options?: ReadTreeOptions): Promise; + // (undocumented) + readUrl(url: string, _options?: ReadUrlOptions): Promise; + // (undocumented) + search(url: string, options?: SearchOptions): Promise; + // (undocumented) + toString(): string; } // @public export interface CacheClient { - delete(key: string): Promise; - get(key: string): Promise; - set(key: string, value: JsonValue, options?: CacheSetOptions): Promise; + delete(key: string): Promise; + get(key: string): Promise; + set(key: string, value: JsonValue, options?: CacheSetOptions): Promise; } // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; - static fromConfig(config: Config, options?: CacheManagerOptions): CacheManager; - } + forPlugin(pluginId: string): PluginCacheManager; + static fromConfig( + config: Config, + options?: CacheManagerOptions, + ): CacheManager; +} // @public (undocumented) export const coloredFormat: winston.Logform.Format; // @public (undocumented) export interface ContainerRunner { - // (undocumented) - runContainer(opts: RunContainerOptions): Promise; + // (undocumented) + runContainer(opts: RunContainerOptions): Promise; } // @public @deprecated export const createDatabase: typeof createDatabaseClient; // @public -export function createDatabaseClient(dbConfig: Config, overrides?: Partial): Knex; +export function createDatabaseClient( + dbConfig: Config, + overrides?: Partial, +): Knex; // @public (undocumented) -export function createRootLogger(options?: winston.LoggerOptions, env?: NodeJS.ProcessEnv): winston.Logger; +export function createRootLogger( + options?: winston.LoggerOptions, + env?: NodeJS.ProcessEnv, +): winston.Logger; // @public export function createServiceBuilder(_module: NodeModule): ServiceBuilderImpl; // @public (undocumented) -export function createStatusCheckRouter(options: StatusCheckRouterOptions): Promise; +export function createStatusCheckRouter( + options: StatusCheckRouterOptions, +): Promise; + +// @public (undocumented) +export class DatabaseManager { + forPlugin(pluginId: string): PluginDatabaseManager; + static fromConfig(config: Config): DatabaseManager; +} // @public (undocumented) export class DockerContainerRunner implements ContainerRunner { - constructor({ dockerClient }: { - dockerClient: Docker; - }); - // (undocumented) - runContainer({ imageName, command, args, logStream, mountDirs, workingDir, envVars, }: RunContainerOptions): Promise; + constructor({ dockerClient }: { dockerClient: Docker }); + // (undocumented) + runContainer({ + imageName, + command, + args, + logStream, + mountDirs, + workingDir, + envVars, + }: RunContainerOptions): Promise; } // @public -export function ensureDatabaseExists(dbConfig: Config, ...databases: Array): Promise; +export function ensureDatabaseExists( + dbConfig: Config, + ...databases: Array +): Promise; // @public -export function errorHandler(options?: ErrorHandlerOptions): ErrorRequestHandler; +export function errorHandler( + options?: ErrorHandlerOptions, +): ErrorRequestHandler; // @public (undocumented) export type ErrorHandlerOptions = { - showStackTraces?: boolean; - logger?: Logger; - logClientErrors?: boolean; + showStackTraces?: boolean; + logger?: Logger_2; + logClientErrors?: boolean; }; // @public (undocumented) @@ -130,122 +171,155 @@ export function getVoidLogger(): winston.Logger; // @public (undocumented) export class Git { - // (undocumented) - add({ dir, filepath, }: { - dir: string; - filepath: string; - }): Promise; - // (undocumented) - addRemote({ dir, url, remote, }: { - dir: string; - remote: string; - url: string; - }): Promise; - // (undocumented) - clone({ url, dir, ref, }: { - url: string; - dir: string; - ref?: string; - }): Promise; - // (undocumented) - commit({ dir, message, author, committer, }: { - dir: string; - message: string; - author: { - name: string; - email: string; - }; - committer: { - name: string; - email: string; - }; - }): Promise; - // (undocumented) - currentBranch({ dir, fullName, }: { - dir: string; - fullName?: boolean; - }): Promise; - // (undocumented) - fetch({ dir, remote, }: { - dir: string; - remote?: string; - }): Promise; - // (undocumented) - static fromAuth: ({ username, password, logger, }: { - username?: string | undefined; - password?: string | undefined; - logger?: Logger | undefined; - }) => Git; - // (undocumented) - init({ dir }: { - dir: string; - }): Promise; - // (undocumented) - merge({ dir, theirs, ours, author, committer, }: { - dir: string; - theirs: string; - ours?: string; - author: { - name: string; - email: string; - }; - committer: { - name: string; - email: string; - }; - }): Promise; - // (undocumented) - push({ dir, remote }: { - dir: string; - remote: string; - }): Promise; - // (undocumented) - readCommit({ dir, sha, }: { - dir: string; - sha: string; - }): Promise; - // (undocumented) - resolveRef({ dir, ref, }: { - dir: string; - ref: string; - }): Promise; + // (undocumented) + add({ dir, filepath }: { dir: string; filepath: string }): Promise; + // (undocumented) + addRemote({ + dir, + url, + remote, + }: { + dir: string; + remote: string; + url: string; + }): Promise; + // (undocumented) + clone({ + url, + dir, + ref, + }: { + url: string; + dir: string; + ref?: string; + }): Promise; + // (undocumented) + commit({ + dir, + message, + author, + committer, + }: { + dir: string; + message: string; + author: { + name: string; + email: string; + }; + committer: { + name: string; + email: string; + }; + }): Promise; + // (undocumented) + currentBranch({ + dir, + fullName, + }: { + dir: string; + fullName?: boolean; + }): Promise; + // (undocumented) + fetch({ dir, remote }: { dir: string; remote?: string }): Promise; + // (undocumented) + static fromAuth: ({ + username, + password, + logger, + }: { + username?: string | undefined; + password?: string | undefined; + logger?: Logger_2 | undefined; + }) => Git; + // (undocumented) + init({ + dir, + defaultBranch, + }: { + dir: string; + defaultBranch?: string; + }): Promise; + // (undocumented) + merge({ + dir, + theirs, + ours, + author, + committer, + }: { + dir: string; + theirs: string; + ours?: string; + author: { + name: string; + email: string; + }; + committer: { + name: string; + email: string; + }; + }): Promise; + // (undocumented) + push({ dir, remote }: { dir: string; remote: string }): Promise; + // (undocumented) + readCommit({ + dir, + sha, + }: { + dir: string; + sha: string; + }): Promise; + // (undocumented) + resolveRef({ dir, ref }: { dir: string; ref: string }): Promise; } // @public export class GithubUrlReader implements UrlReader { - constructor(integration: GitHubIntegration, deps: { - treeResponseFactory: ReadTreeResponseFactory; - credentialsProvider: GithubCredentialsProvider; - }); - // (undocumented) - static factory: ReaderFactory; - // (undocumented) - read(url: string): Promise; - // (undocumented) - readTree(url: string, options?: ReadTreeOptions): Promise; - // (undocumented) - search(url: string, options?: SearchOptions): Promise; - // (undocumented) - toString(): string; + constructor( + integration: GitHubIntegration, + deps: { + treeResponseFactory: ReadTreeResponseFactory; + credentialsProvider: GithubCredentialsProvider; + }, + ); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(url: string, options?: ReadTreeOptions): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(url: string, options?: SearchOptions): Promise; + // (undocumented) + toString(): string; } // @public (undocumented) export class GitlabUrlReader implements UrlReader { - constructor(integration: GitLabIntegration, deps: { - treeResponseFactory: ReadTreeResponseFactory; - }); - // (undocumented) - static factory: ReaderFactory; - // (undocumented) - read(url: string): Promise; - // (undocumented) - readTree(url: string, options?: ReadTreeOptions): Promise; - // (undocumented) - search(url: string, options?: SearchOptions): Promise; - // (undocumented) - toString(): string; + constructor( + integration: GitLabIntegration, + deps: { + treeResponseFactory: ReadTreeResponseFactory; + }, + ); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(url: string, options?: ReadTreeOptions): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(url: string, options?: SearchOptions): Promise; + // (undocumented) + toString(): string; } +export { isChildPath }; + // @public export function loadBackendConfig(options: Options): Promise; @@ -254,126 +328,133 @@ export function notFoundHandler(): RequestHandler; // @public export type PluginCacheManager = { - getClient: (options?: ClientOptions) => CacheClient; + getClient: (options?: ClientOptions) => CacheClient; }; // @public export interface PluginDatabaseManager { - getClient(): Promise; + getClient(): Promise; } // @public export type PluginEndpointDiscovery = { - getBaseUrl(pluginId: string): Promise; - getExternalBaseUrl(pluginId: string): Promise; + getBaseUrl(pluginId: string): Promise; + getExternalBaseUrl(pluginId: string): Promise; }; -// @public (undocumented) +// @public export type ReadTreeResponse = { - files(): Promise; - archive(): Promise; - dir(options?: ReadTreeResponseDirOptions): Promise; - etag: string; + files(): Promise; + archive(): Promise; + dir(options?: ReadTreeResponseDirOptions): Promise; + etag: string; }; // @public export type ReadTreeResponseFile = { - path: string; - content(): Promise; + path: string; + content(): Promise; }; // @public -export function requestLoggingHandler(logger?: Logger): RequestHandler; +export function requestLoggingHandler(logger?: Logger_2): RequestHandler; // @public export function resolvePackagePath(name: string, ...paths: string[]): string; +// @public +export function resolveSafeChildPath(base: string, path: string): string; + // @public (undocumented) export type RunContainerOptions = { - imageName: string; - command?: string | string[]; - args: string[]; - logStream?: Writable; - mountDirs?: Record; - workingDir?: string; - envVars?: Record; + imageName: string; + command?: string | string[]; + args: string[]; + logStream?: Writable; + mountDirs?: Record; + workingDir?: string; + envVars?: Record; }; // @public export type SearchResponse = { - files: SearchResponseFile[]; - etag: string; + files: SearchResponseFile[]; + etag: string; }; // @public export type SearchResponseFile = { - url: string; - content(): Promise; + url: string; + content(): Promise; }; // @public (undocumented) export type ServiceBuilder = { - loadConfig(config: ConfigReader): ServiceBuilder; - setPort(port: number): ServiceBuilder; - setHost(host: string): ServiceBuilder; - setLogger(logger: Logger): ServiceBuilder; - enableCors(options: cors.CorsOptions): ServiceBuilder; - setHttpsSettings(settings: HttpsSettings): ServiceBuilder; - addRouter(root: string, router: Router | RequestHandler): ServiceBuilder; - start(): Promise; + loadConfig(config: ConfigReader): ServiceBuilder; + setPort(port: number): ServiceBuilder; + setHost(host: string): ServiceBuilder; + setLogger(logger: Logger_2): ServiceBuilder; + enableCors(options: cors.CorsOptions): ServiceBuilder; + setHttpsSettings(settings: HttpsSettings): ServiceBuilder; + addRouter(root: string, router: Router | RequestHandler): ServiceBuilder; + start(): Promise; }; // @public (undocumented) export function setRootLogger(newLogger: winston.Logger): void; -// @public -export class SingleConnectionDatabaseManager { - forPlugin(pluginId: string): PluginDatabaseManager; - static fromConfig(config: Config): SingleConnectionDatabaseManager; - } +// @public @deprecated +export const SingleConnectionDatabaseManager: typeof DatabaseManager; // @public export class SingleHostDiscovery implements PluginEndpointDiscovery { - static fromConfig(config: Config, options?: { - basePath?: string; - }): SingleHostDiscovery; - // (undocumented) - getBaseUrl(pluginId: string): Promise; - // (undocumented) - getExternalBaseUrl(pluginId: string): Promise; - } + static fromConfig( + config: Config, + options?: { + basePath?: string; + }, + ): SingleHostDiscovery; + // (undocumented) + getBaseUrl(pluginId: string): Promise; + // (undocumented) + getExternalBaseUrl(pluginId: string): Promise; +} // @public (undocumented) export type StatusCheck = () => Promise; // @public -export function statusCheckHandler(options?: StatusCheckHandlerOptions): Promise; +export function statusCheckHandler( + options?: StatusCheckHandlerOptions, +): Promise; // @public (undocumented) export interface StatusCheckHandlerOptions { - statusCheck?: StatusCheck; + statusCheck?: StatusCheck; } // @public export type UrlReader = { - read(url: string): Promise; - readTree(url: string, options?: ReadTreeOptions): Promise; - search(url: string, options?: SearchOptions): Promise; + read(url: string): Promise; + readUrl?(url: string, options?: ReadUrlOptions): Promise; + readTree(url: string, options?: ReadTreeOptions): Promise; + search(url: string, options?: SearchOptions): Promise; }; // @public export class UrlReaders { - static create({ logger, config, factories }: CreateOptions): UrlReader; - static default({ logger, config, factories }: CreateOptions): UrlReader; + static create({ logger, config, factories }: CreateOptions): UrlReader; + static default({ logger, config, factories }: CreateOptions): UrlReader; } // @public -export function useHotCleanup(_module: NodeModule, cancelEffect: () => void): void; +export function useHotCleanup( + _module: NodeModule, + cancelEffect: () => void, +): void; // @public export function useHotMemoize(_module: NodeModule, valueFactory: () => T): T; - // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 875660a594..bc5e026375 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,20 +53,40 @@ export interface Config { }; }; - /** Database connection configuration, select database type using the `client` field */ - database: - | { - client: 'sqlite3'; - connection: ':memory:' | string | { filename: string }; - } - | { - client: 'pg'; + /** Database connection configuration, select base database type using the `client` field */ + database: { + /** Default database client to use */ + client: 'sqlite3' | 'pg'; + /** + * Base database connection string or Knex object + * @secret + */ + connection: string | object; + /** Database name prefix override */ + prefix?: string; + /** + * Whether to ensure the given database exists by creating it if it does not. + * Defaults to true if unspecified. + */ + ensureExists?: boolean; + /** Plugin specific database configuration and client override */ + plugin?: { + [pluginId: string]: { + /** Database client override */ + client?: 'sqlite3' | 'pg'; /** - * PostgreSQL connection string or knex configuration object. + * Database connection string or Knex object override * @secret */ - connection: string | object; + connection?: string | object; + /** + * Whether to ensure the given database exists by creating it if it does not. + * Defaults to base config if unspecified. + */ + ensureExists?: boolean; }; + }; + }; /** Cache connection configuration, select cache type using the `store` field */ cache?: diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index fc57ee59a8..6cf20b8234 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.8.2", + "version": "0.8.5", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli-common": "^0.1.1", + "@backstage/cli-common": "^0.1.2", "@backstage/config": "^0.1.5", - "@backstage/config-loader": "^0.6.2", + "@backstage/config-loader": "^0.6.4", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.6", + "@backstage/integration": "^0.5.8", "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", @@ -47,8 +47,8 @@ "dockerode": "^3.2.1", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "fs-extra": "^9.0.1", - "git-url-parse": "^11.4.4", + "fs-extra": "9.1.0", + "git-url-parse": "~11.4.4", "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", "keyv": "^4.0.3", @@ -76,7 +76,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.12", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", @@ -96,7 +96,7 @@ "http-errors": "^1.7.3", "jest": "^26.0.1", "mock-fs": "^4.13.0", - "msw": "^0.21.2", + "msw": "^0.29.0", "mysql2": "^2.2.5", "recursive-readdir": "^2.2.2", "supertest": "^6.1.3" diff --git a/packages/backend-common/src/cache/CacheClient.test.ts b/packages/backend-common/src/cache/CacheClient.test.ts index 81e8351b58..8a5779550a 100644 --- a/packages/backend-common/src/cache/CacheClient.test.ts +++ b/packages/backend-common/src/cache/CacheClient.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts index 3b654e248b..860754aae1 100644 --- a/packages/backend-common/src/cache/CacheClient.ts +++ b/packages/backend-common/src/cache/CacheClient.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts index 97c1714c53..b55b63f469 100644 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index e6699e628e..9d1afdaaeb 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/NoStore.ts b/packages/backend-common/src/cache/NoStore.ts index c89e814c93..3bb28afc71 100644 --- a/packages/backend-common/src/cache/NoStore.ts +++ b/packages/backend-common/src/cache/NoStore.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/index.ts b/packages/backend-common/src/cache/index.ts index 0cc8178b1a..45eb431ed1 100644 --- a/packages/backend-common/src/cache/index.ts +++ b/packages/backend-common/src/cache/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 30db53d420..ac5bb91b21 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 6989f3567b..1316642ae1 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts new file mode 100644 index 0000000000..e839123908 --- /dev/null +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -0,0 +1,318 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConfigReader } from '@backstage/config'; +import { omit } from 'lodash'; +import { createDatabaseClient, ensureDatabaseExists } from './connection'; +import { DatabaseManager } from './DatabaseManager'; + +jest.mock('./connection', () => ({ + ...jest.requireActual('./connection'), + createDatabaseClient: jest.fn(), + ensureDatabaseExists: jest.fn(), +})); + +describe('DatabaseManager', () => { + // This is similar to the ts-jest `mocked` helper. + const mocked = (f: Function) => f as jest.Mock; + + afterEach(() => jest.resetAllMocks()); + + describe('DatabaseManager.fromConfig', () => { + it('accesses the backend.database key', () => { + const config = new ConfigReader({ + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + user: 'foo', + password: 'bar', + database: 'foodb', + }, + }, + }, + }); + const getConfigSpy = jest.spyOn(config, 'getConfig'); + DatabaseManager.fromConfig(config); + + expect(getConfigSpy).toHaveBeenCalledWith('backend.database'); + }); + }); + + describe('DatabaseManager.forPlugin', () => { + const config = { + backend: { + database: { + client: 'pg', + prefix: 'test_prefix_', + connection: { + host: 'localhost', + user: 'foo', + password: 'bar', + database: 'foodb', + }, + plugin: { + testdbname: { + connection: { + database: 'database_name_overriden', + }, + }, + differentclient: { + client: 'sqlite3', + connection: { + filename: 'plugin_with_different_client', + }, + }, + differentclientconnstring: { + client: 'sqlite3', + connection: ':memory:', + }, + stringoverride: { + connection: 'postgresql://testuser:testpass@acme:5432/userdbname', + }, + }, + }, + }, + }; + const manager = DatabaseManager.fromConfig(new ConfigReader(config)); + + it('connects to a plugin database using default config', async () => { + const pluginId = 'pluginwithoutconfig'; + + await manager.forPlugin(pluginId).getClient(); + expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, overrides] = mockCalls[0]; + + // default config should be passed through to underlying connector + expect(baseConfig.get()).toMatchObject({ + client: 'pg', + connection: omit(config.backend.database.connection, ['database']), + }); + + // override using database name generated from pluginId and prefix + expect(overrides).toMatchObject({ + connection: { + database: `${config.backend.database.prefix}${pluginId}`, + }, + }); + }); + + it('provides a plugin db which uses components from top level connection string', async () => { + const testManager = DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'pg', + connection: 'postgresql://foo:bar@acme:5432/foodb', + }, + }, + }), + ); + + await testManager.forPlugin('pluginwithoutconfig').getClient(); + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, overrides] = mockCalls[0]; + + // parsed connection string **without** db name should be passed through + expect(baseConfig.get()).toMatchObject({ + connection: { + host: 'acme', + user: 'foo', + password: 'bar', + port: '5432', + }, + }); + + // we expect a pg database name override with ${prefix} followed by pluginId + expect(overrides).toHaveProperty( + 'connection.database', + expect.stringContaining('pluginwithoutconfig'), + ); + }); + + it('uses top level sqlite database filename if plugin config is not present', async () => { + const testManager = DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: 'some-file-path', + }, + }, + }), + ); + + await testManager.forPlugin('pluginwithoutconfig').getClient(); + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [_, overrides] = mockCalls[0]; + + expect(overrides).toHaveProperty( + 'connection.filename', + expect.stringContaining('some-file-path'), + ); + }); + + it('provides an inmemory sqlite database if top level is also inmemory and plugin config is not present', async () => { + const testManager = DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }), + ); + + await testManager.forPlugin('pluginwithoutconfig').getClient(); + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [_, overrides] = mockCalls[0]; + + expect(overrides).toHaveProperty( + 'connection.filename', + expect.stringContaining(':memory:'), + ); + }); + + it('connects to a plugin database using a specific database name', async () => { + // testdbname.connection.database is set in config + await manager.forPlugin('testdbname').getClient(); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [_baseConfig, overrides] = mockCalls[0]; + + // simple case where only database name is overriden + expect(overrides).toMatchObject({ + connection: { + database: 'database_name_overriden', + }, + }); + }); + + it('ensure plugin specific database is created', async () => { + const pluginId = 'testdbname'; + // testdbname.connection.database is set in config + await manager.forPlugin(pluginId).getClient(); + + const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1); + const [_, dbname] = mockCalls[0]; + + expect(dbname).toEqual( + config.backend.database.plugin[pluginId].connection.database, + ); + }); + + it('provides different plugins with their own databases', async () => { + await manager.forPlugin('plugin1').getClient(); + await manager.forPlugin('plugin2').getClient(); + + expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(2); + + const mockCalls = mocked(createDatabaseClient).mock.calls; + const [plugin1CallArgs, plugin2CallArgs] = mockCalls; + + // database name overrides should be different + expect(plugin1CallArgs[1].connection.database).not.toEqual( + plugin2CallArgs[1].connection.database, + ); + }); + + it('uses plugin connection as base if default client is different from plugin client', async () => { + const pluginId = 'differentclient'; + await manager.forPlugin(pluginId).getClient(); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, _overrides] = mockCalls[0]; + + // plugin connection should be used as base config, client is different + expect(baseConfig.get()).toMatchObject({ + client: 'sqlite3', + connection: config.backend.database.plugin[pluginId].connection, + }); + }); + + it('provides database client specific base and override when client set under plugin', async () => { + const pluginId = 'differentclient'; + await manager.forPlugin(pluginId).getClient(); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, overrides] = mockCalls[0]; + + // plugin client should be sqlite3 + expect(baseConfig.get().client).toEqual('sqlite3'); + + // sqlite3 uses 'filename' instead of 'database' + expect(overrides).toHaveProperty('connection.filename'); + }); + + it('provides database client specific base from plugin connection string when client set under plugin', async () => { + const pluginId = 'differentclientconnstring'; + await manager.forPlugin(pluginId).getClient(); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, overrides] = mockCalls[0]; + + expect(baseConfig.get().client).toEqual('sqlite3'); + + expect(overrides).toHaveProperty('connection.filename', ':memory:'); + }); + + it('generates a database name override when prefix is not explicitly set', async () => { + const testManager = DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'pg', + connection: { + host: 'localhost', + user: 'foo', + password: 'bar', + database: 'foodb', + }, + }, + }, + }), + ); + + await testManager.forPlugin('testplugin').getClient(); + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [_baseConfig, overrides] = mockCalls[0]; + + expect(overrides).toHaveProperty( + 'connection.database', + expect.stringContaining('backstage_plugin_'), + ); + }); + + it('uses values from plugin connection string if top level client should be used', async () => { + const pluginId = 'stringoverride'; + await manager.forPlugin(pluginId).getClient(); + + const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); + const [baseConfig, overrides] = mockCalls[0]; + + // plugin client should be pg + expect(baseConfig.get().client).toEqual('pg'); + + expect(overrides).toHaveProperty( + 'connection.database', + expect.stringContaining('userdbname'), + ); + }); + }); +}); diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts new file mode 100644 index 0000000000..f3a3f218b0 --- /dev/null +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -0,0 +1,230 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Knex } from 'knex'; +import { omit } from 'lodash'; +import { Config, ConfigReader, JsonObject } from '@backstage/config'; +import { + createDatabaseClient, + ensureDatabaseExists, + createNameOverride, + normalizeConnection, +} from './connection'; +import { PluginDatabaseManager } from './types'; + +/** + * Provides a config lookup path for a plugin's config block. + */ +function pluginPath(pluginId: string): string { + return `plugin.${pluginId}`; +} + +export class DatabaseManager { + /** + * Creates a DatabaseManager from `backend.database` config. + * + * The database manager allows the user to set connection and client settings on a per pluginId + * basis by defining a database config block under `plugin.` in addition to top level + * defaults. Optionally, a user may set `prefix` which is used to prefix generated database + * names if config is not provided. + * + * @param config The loaded application configuration. + */ + static fromConfig(config: Config): DatabaseManager { + const databaseConfig = config.getConfig('backend.database'); + + return new DatabaseManager( + databaseConfig, + databaseConfig.getOptionalString('prefix'), + ); + } + + private constructor( + private readonly config: Config, + private readonly prefix: string = 'backstage_plugin_', + ) {} + + /** + * Generates a PluginDatabaseManager for consumption by plugins. + * + * @param pluginId The plugin that the database manager should be created for. Plugin names + * should be unique as they are used to look up database config overrides under + * `backend.database.plugin`. + */ + forPlugin(pluginId: string): PluginDatabaseManager { + const _this = this; + + return { + getClient(): Promise { + return _this.getDatabase(pluginId); + }, + }; + } + + /** + * Provides the canonical database name for a given plugin. + * + * This method provides the effective database name which is determined using global + * and plugin specific database config. If no explicit database name is configured, + * this method will provide a generated name which is the pluginId prefixed with + * 'backstage_plugin_'. + * + * @param pluginId Lookup the database name for given plugin + * @returns String representing the plugin's database name + */ + private getDatabaseName(pluginId: string): string { + const connection = this.getConnectionConfig(pluginId); + + if (this.getClientType(pluginId).client === 'sqlite3') { + // sqlite database name should fallback to ':memory:' as a special case + return ( + (connection as Knex.Sqlite3ConnectionConfig)?.filename ?? ':memory:' + ); + } + // all other supported databases should fallback to an auto-prefixed name + return ( + (connection as Knex.ConnectionConfig)?.database ?? + `${this.prefix}${pluginId}` + ); + } + + /** + * Provides the client type which should be used for a given plugin. + * + * The client type is determined by plugin specific config if present. Otherwise the base + * client is used as the fallback. + * + * @param pluginId Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean representing whether + * or not the client was overridden as `overridden` + */ + private getClientType( + pluginId: string, + ): { + client: string; + overridden: boolean; + } { + const pluginClient = this.config.getOptionalString( + `${pluginPath(pluginId)}.client`, + ); + + const baseClient = this.config.getString('client'); + const client = pluginClient ?? baseClient; + return { + client, + overridden: client !== baseClient, + }; + } + + private getEnsureExistsConfig(pluginId: string): boolean { + const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + + /** + * Provides a Knex connection plugin config by combining base and plugin config. + * + * This method provides a baseConfig for a plugin database connector. If the client type + * has not been overridden, the global connection config will be included with plugin + * specific config as the base. Values from the plugin connection take precedence over the + * base. Base database name is omitted for all supported databases excluding SQLite. + */ + private getConnectionConfig( + pluginId: string, + ): Partial { + const { client, overridden } = this.getClientType(pluginId); + + let baseConnection = normalizeConnection( + this.config.get('connection'), + this.config.getString('client'), + ); + // As databases cannot be shared, the `database` property from the base connection + // is omitted. SQLite3's `filename` property is an exception as this is used as a + // directory elsewhere so we preserve `filename`. + baseConnection = omit(baseConnection, 'database'); + + // get and normalize optional plugin specific database connection + const connection = normalizeConnection( + this.config.getOptional(`${pluginPath(pluginId)}.connection`), + client, + ); + + return { + // include base connection if client type has not been overriden + ...(overridden ? {} : baseConnection), + ...connection, + }; + } + + /** + * Provides a Knex database config for a given plugin. + * + * This method provides a Knex configuration object along with the plugin's client type. + * + * @param pluginId The plugin that the database config should correspond with + */ + private getConfigForPlugin(pluginId: string): Knex.Config { + const { client } = this.getClientType(pluginId); + + return { + client, + connection: this.getConnectionConfig(pluginId), + }; + } + + /** + * Provides a partial Knex.Config database name override for a given plugin. + * + * @param pluginId Target plugin to get database name override + * @returns Partial Knex.Config with database name override + */ + private getDatabaseOverrides(pluginId: string): Knex.Config { + return createNameOverride( + this.getClientType(pluginId).client, + this.getDatabaseName(pluginId), + ); + } + + /** + * Provides a scoped Knex client for a plugin as per application config. + * + * @param pluginId Plugin to get a Knex client for + * @returns Promise which resolves to a scoped Knex database client for a plugin + */ + private async getDatabase(pluginId: string): Promise { + const pluginConfig = new ConfigReader( + this.getConfigForPlugin(pluginId) as JsonObject, + ); + + if (this.getEnsureExistsConfig(pluginId)) { + const databaseName = this.getDatabaseName(pluginId); + try { + await ensureDatabaseExists(pluginConfig, databaseName); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, + ); + } + } + + return createDatabaseClient( + pluginConfig, + this.getDatabaseOverrides(pluginId), + ); + } +} diff --git a/packages/backend-common/src/database/SingleConnection.test.ts b/packages/backend-common/src/database/SingleConnection.test.ts index 3466b0f79d..46d7376d25 100644 --- a/packages/backend-common/src/database/SingleConnection.test.ts +++ b/packages/backend-common/src/database/SingleConnection.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,14 @@ */ import { ConfigReader } from '@backstage/config'; -import { createDatabaseClient } from './connection'; +import { createDatabaseClient, ensureDatabaseExists } from './connection'; import { SingleConnectionDatabaseManager } from './SingleConnection'; -jest.mock('./connection'); +jest.mock('./connection', () => ({ + ...jest.requireActual('./connection'), + createDatabaseClient: jest.fn(), + ensureDatabaseExists: jest.fn(), +})); describe('SingleConnectionDatabaseManager', () => { const defaultConfigOptions = { @@ -43,9 +47,8 @@ describe('SingleConnectionDatabaseManager', () => { describe('SingleConnectionDatabaseManager.fromConfig', () => { it('accesses the backend.database key', () => { - const getConfig = jest.fn(); const config = defaultConfig(); - config.getConfig = getConfig; + const getConfig = jest.spyOn(config, 'getConfig'); SingleConnectionDatabaseManager.fromConfig(config); @@ -64,7 +67,6 @@ describe('SingleConnectionDatabaseManager', () => { const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); const callArgs = mockCalls[0]; - expect(callArgs[0].get()).toEqual(defaultConfigOptions.backend.database); expect(callArgs[1].connection.database).toEqual( `backstage_plugin_${pluginId}`, ); @@ -85,5 +87,13 @@ describe('SingleConnectionDatabaseManager', () => { plugin2CallArgs[1].connection.database, ); }); + + it('ensure plugin database is created', async () => { + await manager.forPlugin('test').getClient(); + const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1); + const [_, database] = mockCalls[0]; + + expect(database).toEqual('backstage_plugin_test'); + }); }); }); diff --git a/packages/backend-common/src/database/SingleConnection.ts b/packages/backend-common/src/database/SingleConnection.ts index 1c5931a662..153aea7f38 100644 --- a/packages/backend-common/src/database/SingleConnection.ts +++ b/packages/backend-common/src/database/SingleConnection.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,69 +14,14 @@ * limitations under the License. */ -import { Knex } from 'knex'; -import { Config } from '@backstage/config'; -import { createDatabaseClient, ensureDatabaseExists } from './connection'; -import { PluginDatabaseManager } from './types'; +import { DatabaseManager } from './DatabaseManager'; /** * Implements a Database Manager which will automatically create new databases * for plugins when requested. All requested databases are created with the * credentials provided; if the database already exists no attempt to create * the database will be made. + * + * @deprecated Use `DatabaseManager` from `@backend-common` instead. */ -export class SingleConnectionDatabaseManager { - /** - * Creates a new SingleConnectionDatabaseManager instance by reading from the `backend` - * config section, specifically the `.database` key for discovering the management - * database configuration. - * - * @param config The loaded application configuration. - */ - static fromConfig(config: Config): SingleConnectionDatabaseManager { - return new SingleConnectionDatabaseManager( - config.getConfig('backend.database'), - ); - } - - private constructor(private readonly config: Config) {} - - /** - * Generates a PluginDatabaseManager for consumption by plugins. - * - * @param pluginId The plugin that the database manager should be created for. Plugin names should be unique. - */ - forPlugin(pluginId: string): PluginDatabaseManager { - const _this = this; - - return { - getClient(): Promise { - return _this.getDatabase(pluginId); - }, - }; - } - - private async getDatabase(pluginId: string): Promise { - const config = this.config; - const overrides = SingleConnectionDatabaseManager.getDatabaseOverrides( - pluginId, - ); - const overrideConfig = overrides.connection as Knex.ConnectionConfig; - await this.ensureDatabase(overrideConfig.database); - - return createDatabaseClient(config, overrides); - } - - private static getDatabaseOverrides(pluginId: string): Knex.Config { - return { - connection: { - database: `backstage_plugin_${pluginId}`, - }, - }; - } - - private async ensureDatabase(database: string) { - const config = this.config; - await ensureDatabaseExists(config, database); - } -} +export const SingleConnectionDatabaseManager = DatabaseManager; diff --git a/packages/backend-common/src/database/config.test.ts b/packages/backend-common/src/database/config.test.ts index eb26013b4b..01ec599cf4 100644 --- a/packages/backend-common/src/database/config.test.ts +++ b/packages/backend-common/src/database/config.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/config.ts b/packages/backend-common/src/database/config.ts index af32556c28..b771811e8c 100644 --- a/packages/backend-common/src/database/config.ts +++ b/packages/backend-common/src/database/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts index 6ab163ff7f..869722ddf7 100644 --- a/packages/backend-common/src/database/connection.test.ts +++ b/packages/backend-common/src/database/connection.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { createDatabaseClient } from './connection'; +import { + createDatabaseClient, + createNameOverride, + parseConnectionString, +} from './connection'; describe('database connection', () => { describe('createDatabaseClient', () => { @@ -103,4 +107,49 @@ describe('database connection', () => { ).toThrowError(); }); }); + + describe('createNameOverride', () => { + it('returns Knex config for postgres', () => { + expect(createNameOverride('pg', 'testpg')).toHaveProperty( + 'connection.database', + 'testpg', + ); + }); + + it('returns Knex config for sqlite', () => { + expect(createNameOverride('sqlite3', 'testsqlite')).toHaveProperty( + 'connection.filename', + 'testsqlite', + ); + }); + + it('returns Knex config for mysql', () => { + expect(createNameOverride('mysql', 'testmysql')).toHaveProperty( + 'connection.database', + 'testmysql', + ); + }); + + it('throws an error for unknown connection', () => { + expect(() => createNameOverride('unknown', 'testname')).toThrowError(); + }); + }); + + describe('parseConnectionString', () => { + it('returns parsed Knex.StaticConnectionConfig for postgres', () => { + expect( + parseConnectionString('postgresql://foo:bar@acme:5432/foodb', 'pg'), + ).toHaveProperty('database', 'foodb'); + }); + + it('returns parsed Knex.StaticConnectionConfig for mysql2', () => { + expect( + parseConnectionString('mysql://foo:bar@acme:3306/foodb', 'mysql2'), + ).toHaveProperty('database', 'foodb'); + }); + + it('throws an error if client hint is not provided', () => { + expect(() => parseConnectionString('sqlite://')).toThrow(); + }); + }); }); diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index fb16915cf4..6fdb5554bd 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,28 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; +import { Config, JsonObject } from '@backstage/config'; +import { InputError } from '@backstage/errors'; import knexFactory, { Knex } from 'knex'; import { mergeDatabaseConfig } from './config'; -import { createMysqlDatabaseClient, ensureMysqlDatabaseExists } from './mysql'; -import { createPgDatabaseClient, ensurePgDatabaseExists } from './postgres'; -import { createSqliteDatabaseClient } from './sqlite3'; +import { DatabaseConnector } from './types'; -type DatabaseClient = 'pg' | 'sqlite3' | string; +import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors'; + +type DatabaseClient = 'pg' | 'sqlite3' | 'mysql' | 'mysql2' | string; + +/** + * Mapping of client type to supported database connectors + * + * Database connectors can be aliased here, for example mysql2 uses + * the same connector as mysql. + */ +const ConnectorMapping: Record = { + pg: pgConnector, + sqlite3: sqlite3Connector, + mysql: mysqlConnector, + mysql2: mysqlConnector, +}; /** * Creates a knex database connection @@ -35,15 +49,10 @@ export function createDatabaseClient( ) { const client: DatabaseClient = dbConfig.getString('client'); - if (client === 'pg') { - return createPgDatabaseClient(dbConfig, overrides); - } else if (client === 'mysql' || client === 'mysql2') { - return createMysqlDatabaseClient(dbConfig, overrides); - } else if (client === 'sqlite3') { - return createSqliteDatabaseClient(dbConfig, overrides); - } - - return knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)); + return ( + ConnectorMapping[client]?.createClient(dbConfig, overrides) ?? + knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)) + ); } /** @@ -58,14 +67,66 @@ export const createDatabase = createDatabaseClient; export async function ensureDatabaseExists( dbConfig: Config, ...databases: Array -) { +): Promise { const client: DatabaseClient = dbConfig.getString('client'); - if (client === 'pg') { - return ensurePgDatabaseExists(dbConfig, ...databases); - } else if (client === 'mysql' || client === 'mysql2') { - return ensureMysqlDatabaseExists(dbConfig, ...databases); + return ConnectorMapping[client]?.ensureDatabaseExists?.( + dbConfig, + ...databases, + ); +} + +/** + * Provides a Knex.Config object with the provided database name for a given client. + */ +export function createNameOverride( + client: string, + name: string, +): Partial { + try { + return ConnectorMapping[client].createNameOverride(name); + } catch (e) { + throw new InputError( + `Unable to create database name override for '${client}' connector`, + e, + ); + } +} + +/** + * Parses a connection string for a given client and provides a connection config. + */ +export function parseConnectionString( + connectionString: string, + client?: string, +): Knex.StaticConnectionConfig { + if (typeof client === 'undefined' || client === null) { + throw new InputError( + 'Database connection string client type auto-detection is not yet supported.', + ); } - return undefined; + try { + return ConnectorMapping[client].parseConnectionString(connectionString); + } catch (e) { + throw new InputError( + `Unable to parse connection string for '${client}' connector`, + ); + } +} + +/** + * Normalizes a connection config or string into an object which can be passed to Knex. + */ +export function normalizeConnection( + connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, + client: string, +): Partial { + if (typeof connection === 'undefined' || connection === null) { + return {}; + } + + return typeof connection === 'string' || connection instanceof String + ? parseConnectionString(connection as string, client) + : connection; } diff --git a/packages/core/src/components/Avatar/Avatar.test.tsx b/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts similarity index 59% rename from packages/core/src/components/Avatar/Avatar.test.tsx rename to packages/backend-common/src/database/connectors/defaultNameOverride.test.ts index da6ca8f42e..1da8e6c11e 100644 --- a/packages/core/src/components/Avatar/Avatar.test.tsx +++ b/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import defaultNameOverride from './defaultNameOverride'; -import { render } from '@testing-library/react'; -import React from 'react'; -import { Avatar } from './Avatar'; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText } = render(); - - expect(getByText('JD')).toBeInTheDocument(); +describe('defaultNameOverride()', () => { + it('returns a partial knex static connection config with database name', () => { + const testDatabaseName = 'testdatabase'; + expect(defaultNameOverride(testDatabaseName)).toHaveProperty( + 'connection.database', + testDatabaseName, + ); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/util.ts b/packages/backend-common/src/database/connectors/defaultNameOverride.ts similarity index 54% rename from plugins/catalog-backend/src/ingestion/processors/ldap/util.ts rename to packages/backend-common/src/database/connectors/defaultNameOverride.ts index f5751b1e9f..d48cedd0ff 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/util.ts +++ b/packages/backend-common/src/database/connectors/defaultNameOverride.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Error as LDAPError } from 'ldapjs'; +import { Knex } from 'knex'; /** - * Builds a string form of an LDAP Error structure. + * Provides a partial knex config with database name override. * - * @param error The error + * Default override for knex database drivers which accept ConnectionConfig + * with `connection.database` as the database name field. + * + * @param name database name to get config override for */ -export function errorString(error: LDAPError) { - return `${error.code} ${error.name}: ${error.message}`; +export default function defaultNameOverride( + name: string, +): Partial { + return { + connection: { + database: name, + }, + }; } diff --git a/packages/backend-common/src/database/connectors/index.ts b/packages/backend-common/src/database/connectors/index.ts new file mode 100644 index 0000000000..df84ec66ba --- /dev/null +++ b/packages/backend-common/src/database/connectors/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './mysql'; +export * from './postgres'; +export * from './sqlite3'; diff --git a/packages/backend-common/src/database/mysql.test.ts b/packages/backend-common/src/database/connectors/mysql.test.ts similarity index 99% rename from packages/backend-common/src/database/mysql.test.ts rename to packages/backend-common/src/database/connectors/mysql.test.ts index 9e23585d5b..93847881f1 100644 --- a/packages/backend-common/src/database/mysql.test.ts +++ b/packages/backend-common/src/database/connectors/mysql.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts similarity index 88% rename from packages/backend-common/src/database/mysql.ts rename to packages/backend-common/src/database/connectors/mysql.ts index be4632baf6..f2f2298559 100644 --- a/packages/backend-common/src/database/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,14 @@ * limitations under the License. */ +import knexFactory, { Knex } from 'knex'; +import yn from 'yn'; + import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import knexFactory, { Knex } from 'knex'; -import { mergeDatabaseConfig } from './config'; -import yn from 'yn'; +import { mergeDatabaseConfig } from '../config'; +import { DatabaseConnector } from '../types'; +import defaultNameOverride from './defaultNameOverride'; /** * Creates a knex mysql database connection @@ -159,3 +162,15 @@ export async function ensureMysqlDatabaseExists( await admin.destroy(); } } + +/** + * MySQL database connector. + * + * Exposes database connector functionality via an immutable object. + */ +export const mysqlConnector: DatabaseConnector = Object.freeze({ + createClient: createMysqlDatabaseClient, + ensureDatabaseExists: ensureMysqlDatabaseExists, + createNameOverride: defaultNameOverride, + parseConnectionString: parseMysqlConnectionString, +}); diff --git a/packages/backend-common/src/database/postgres.test.ts b/packages/backend-common/src/database/connectors/postgres.test.ts similarity index 99% rename from packages/backend-common/src/database/postgres.test.ts rename to packages/backend-common/src/database/connectors/postgres.test.ts index 59c135f309..ac988a2880 100644 --- a/packages/backend-common/src/database/postgres.test.ts +++ b/packages/backend-common/src/database/connectors/postgres.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts similarity index 86% rename from packages/backend-common/src/database/postgres.ts rename to packages/backend-common/src/database/connectors/postgres.ts index e05eab86e5..f6e42d1945 100644 --- a/packages/backend-common/src/database/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,11 @@ */ import knexFactory, { Knex } from 'knex'; + import { Config } from '@backstage/config'; -import { mergeDatabaseConfig } from './config'; +import { mergeDatabaseConfig } from '../config'; +import { DatabaseConnector } from '../types'; +import defaultNameOverride from './defaultNameOverride'; /** * Creates a knex postgres database connection @@ -131,3 +134,15 @@ export async function ensurePgDatabaseExists( await admin.destroy(); } } + +/** + * PostgreSQL database connector. + * + * Exposes database connector functionality via an immutable object. + */ +export const pgConnector: DatabaseConnector = Object.freeze({ + createClient: createPgDatabaseClient, + ensureDatabaseExists: ensurePgDatabaseExists, + createNameOverride: defaultNameOverride, + parseConnectionString: parsePgConnectionString, +}); diff --git a/packages/backend-common/src/database/sqlite3.test.ts b/packages/backend-common/src/database/connectors/sqlite3.test.ts similarity index 98% rename from packages/backend-common/src/database/sqlite3.test.ts rename to packages/backend-common/src/database/connectors/sqlite3.test.ts index 86f3a6968b..b9da19d247 100644 --- a/packages/backend-common/src/database/sqlite3.test.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/database/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts similarity index 74% rename from packages/backend-common/src/database/sqlite3.ts rename to packages/backend-common/src/database/connectors/sqlite3.ts index d4169e3899..3dfecd7e6b 100644 --- a/packages/backend-common/src/database/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import path from 'path'; -import { Config } from '@backstage/config'; import { ensureDirSync } from 'fs-extra'; import knexFactory, { Knex } from 'knex'; -import path from 'path'; -import { mergeDatabaseConfig } from './config'; + +import { Config } from '@backstage/config'; +import { mergeDatabaseConfig } from '../config'; +import { DatabaseConnector } from '../types'; /** - * Creates a knex sqlite3 database connection + * Creates a knex SQLite3 database connection * * @param dbConfig The database config * @param overrides Additional options to merge with the config @@ -54,7 +56,7 @@ export function createSqliteDatabaseClient( } /** - * Builds a knex sqlite3 connection config + * Builds a knex SQLite3 connection config * * @param dbConfig The database config * @param overrides Additional options to merge with the config @@ -99,3 +101,34 @@ export function buildSqliteDatabaseConfig( return config; } + +/** + * Provides a partial knex SQLite3 config to override database name. + */ +export function createSqliteNameOverride(name: string): Partial { + return { + connection: parseSqliteConnectionString(name), + }; +} + +/** + * Produces a partial knex SQLite3 connection config with database name. + */ +export function parseSqliteConnectionString( + name: string, +): Knex.Sqlite3ConnectionConfig { + return { + filename: name, + }; +} + +/** + * SQLite3 database connector. + * + * Exposes database connector functionality via an immutable object. + */ +export const sqlite3Connector: DatabaseConnector = Object.freeze({ + createClient: createSqliteDatabaseClient, + createNameOverride: createSqliteNameOverride, + parseConnectionString: parseSqliteConnectionString, +}); diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index c81153aa62..7fcb8bf930 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,17 @@ * limitations under the License. */ -export * from './connection'; -export * from './types'; export * from './SingleConnection'; +export * from './DatabaseManager'; + +/* + * Undocumented API surface from connection is being reduced for future deprecation. + * Avoid exporting additional symbols. + */ +export { + createDatabaseClient, + createDatabase, + ensureDatabaseExists, +} from './connection'; + +export type { PluginDatabaseManager } from './types'; diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index bf3ceb2786..c1647862af 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Config } from '@backstage/config'; import { Knex } from 'knex'; /** @@ -28,3 +29,37 @@ export interface PluginDatabaseManager { */ getClient(): Promise; } + +/** + * DatabaseConnector manages an underlying Knex database driver. + */ +export interface DatabaseConnector { + /** + * createClient provides an instance of a knex database connector. + */ + createClient(dbConfig: Config, overrides?: Partial): Knex; + /** + * createNameOverride provides a partial knex config sufficient to override a + * database name. + */ + createNameOverride(name: string): Partial; + /** + * parseConnectionString produces a knex connection config object representing + * a database connection string. + */ + parseConnectionString( + connectionString: string, + client?: string, + ): Knex.StaticConnectionConfig; + /** + * ensureDatabaseExists performs a side-effect to ensure database names passed in are + * present. + * + * Calling this function on databases which already exist should do nothing. + * Missing databases should be created if needed. + */ + ensureDatabaseExists?( + dbConfig: Config, + ...databases: Array + ): Promise; +} diff --git a/packages/backend-common/src/discovery/SingleHostDiscovery.ts b/packages/backend-common/src/discovery/SingleHostDiscovery.ts index 184746b924..7d19284d9a 100644 --- a/packages/backend-common/src/discovery/SingleHostDiscovery.ts +++ b/packages/backend-common/src/discovery/SingleHostDiscovery.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/discovery/index.ts b/packages/backend-common/src/discovery/index.ts index 7fe320c1e5..5b62d6f4e4 100644 --- a/packages/backend-common/src/discovery/index.ts +++ b/packages/backend-common/src/discovery/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/discovery/types.ts b/packages/backend-common/src/discovery/types.ts index 22eb4b23d4..a5915be773 100644 --- a/packages/backend-common/src/discovery/types.ts +++ b/packages/backend-common/src/discovery/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/hot.ts b/packages/backend-common/src/hot.ts index c725096c75..951e29da6a 100644 --- a/packages/backend-common/src/hot.ts +++ b/packages/backend-common/src/hot.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index dab981a824..f2f38d9aab 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/logging/formats.ts b/packages/backend-common/src/logging/formats.ts index d24b9509dd..870eeafa2a 100644 --- a/packages/backend-common/src/logging/formats.ts +++ b/packages/backend-common/src/logging/formats.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/logging/index.ts b/packages/backend-common/src/logging/index.ts index ef2e96f6c0..71e9618f0c 100644 --- a/packages/backend-common/src/logging/index.ts +++ b/packages/backend-common/src/logging/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/logging/rootLogger.test.ts b/packages/backend-common/src/logging/rootLogger.test.ts index 5506d195a9..50cf867c85 100644 --- a/packages/backend-common/src/logging/rootLogger.test.ts +++ b/packages/backend-common/src/logging/rootLogger.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index a8cf4721d6..58b675d9cf 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/logging/voidLogger.ts b/packages/backend-common/src/logging/voidLogger.ts index 762b3c0dcb..0afc1fc8c7 100644 --- a/packages/backend-common/src/logging/voidLogger.ts +++ b/packages/backend-common/src/logging/voidLogger.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts index 3fb7aedc37..e9808ec633 100644 --- a/packages/backend-common/src/middleware/errorHandler.test.ts +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index bee3f5557d..ee7995f2c6 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/index.ts b/packages/backend-common/src/middleware/index.ts index 76c52d8830..f4f4fcde0b 100644 --- a/packages/backend-common/src/middleware/index.ts +++ b/packages/backend-common/src/middleware/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/notFoundHandler.test.ts b/packages/backend-common/src/middleware/notFoundHandler.test.ts index 65858e8cc1..b0dea8ef62 100644 --- a/packages/backend-common/src/middleware/notFoundHandler.test.ts +++ b/packages/backend-common/src/middleware/notFoundHandler.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/notFoundHandler.ts b/packages/backend-common/src/middleware/notFoundHandler.ts index 19dd130c64..59ca957cc1 100644 --- a/packages/backend-common/src/middleware/notFoundHandler.ts +++ b/packages/backend-common/src/middleware/notFoundHandler.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts index 6aed54540f..c95e59f80b 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.ts b/packages/backend-common/src/middleware/requestLoggingHandler.ts index 061dfbbd25..f2f5cbda27 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/statusCheckHandler.test.ts b/packages/backend-common/src/middleware/statusCheckHandler.test.ts index 7ed1a65b58..7393276045 100644 --- a/packages/backend-common/src/middleware/statusCheckHandler.test.ts +++ b/packages/backend-common/src/middleware/statusCheckHandler.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/middleware/statusCheckHandler.ts b/packages/backend-common/src/middleware/statusCheckHandler.ts index 3f62f04f59..243d6533c8 100644 --- a/packages/backend-common/src/middleware/statusCheckHandler.ts +++ b/packages/backend-common/src/middleware/statusCheckHandler.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/paths.ts b/packages/backend-common/src/paths.ts index 262be366f6..ffe99fe762 100644 --- a/packages/backend-common/src/paths.ts +++ b/packages/backend-common/src/paths.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ * limitations under the License. */ +import { isChildPath } from '@backstage/cli-common'; +import { NotAllowedError } from '@backstage/errors'; import { resolve as resolvePath } from 'path'; /** @@ -32,3 +34,27 @@ export function resolvePackagePath(name: string, ...paths: string[]) { return resolvePath(req.resolve(`${name}/package.json`), '..', ...paths); } + +/** + * Resolves a target path from a base path while guaranteeing that the result is + * a path that point to or within the base path. This is useful for resolving + * paths from user input, as it otherwise opens up for vulnerabilities. + * + * @param base The base directory to resolve the path from. + * @param path The target path, relative or absolute + * @returns A path that is guaranteed to point to or within the base path. + */ +export function resolveSafeChildPath(base: string, path: string): string { + const targetPath = resolvePath(base, path); + + if (!isChildPath(base, targetPath)) { + throw new NotAllowedError( + 'Relative path is not allowed to refer to a directory outside its parent', + ); + } + + return targetPath; +} + +// Re-export isChildPath so that backend packages don't need to depend on cli-common +export { isChildPath }; diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index c4a0fe0466..2b7c21d4ab 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ describe('AzureUrlReader', () => { ctx.status(200), ctx.json({ url: req.url.toString(), - headers: req.headers.getAllHeaders(), + headers: req.headers.all(), }), ), ), diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index e2b230a05f..bc22854d4e 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import { SearchOptions, SearchResponse, UrlReader, + ReadUrlOptions, + ReadUrlResponse, } from './types'; export class AzureUrlReader implements UrlReader { @@ -78,6 +80,15 @@ export class AzureUrlReader implements UrlReader { throw new Error(message); } + async readUrl( + url: string, + _options?: ReadUrlOptions, + ): Promise { + // TODO etag is not implemented yet. + const buffer = await this.read(url); + return { buffer: async () => buffer }; + } + async readTree( url: string, options?: ReadTreeOptions, diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts index 24216d45b5..b032322627 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,24 @@ describe('BitbucketUrlReader', () => { const worker = setupServer(); msw.setupDefaultHandlers(worker); - describe('implementation', () => { + describe('readUrl', () => { + worker.use( + rest.get( + 'https://api.bitbucket.org/2.0/repositories/backstage-verification/test-template/src/master/template.yaml', + (_, res, ctx) => res(ctx.status(200), ctx.body('foo')), + ), + ); + + it('should be able to readUrl', async () => { + const result = await bitbucketProcessor.readUrl( + 'https://bitbucket.org/backstage-verification/test-template/src/master/template.yaml', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe('foo'); + }); + }); + + describe('read', () => { it('rejects unknown targets', async () => { await expect( bitbucketProcessor.read('https://not.bitbucket.com/apa'), @@ -119,14 +136,14 @@ describe('BitbucketUrlReader', () => { ), ), rest.get( - 'https://bitbucket.org/backstage/mock/get/master.tgz', + 'https://bitbucket.org/backstage/mock/get/master.tar.gz', (_, res, ctx) => res( ctx.status(200), ctx.set('Content-Type', 'application/zip'), ctx.set( 'content-disposition', - 'attachment; filename=backstage-mock-12ab34cd56ef.tgz', + 'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz', ), ctx.body(repoBuffer), ), @@ -142,7 +159,7 @@ describe('BitbucketUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&prefix=mock&path=docs', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive', (_, res, ctx) => res( ctx.status(200), @@ -304,14 +321,14 @@ describe('BitbucketUrlReader', () => { ), ), rest.get( - 'https://bitbucket.org/backstage/mock/get/master.tgz', + 'https://bitbucket.org/backstage/mock/get/master.tar.gz', (_, res, ctx) => res( ctx.status(200), ctx.set('Content-Type', 'application/zip'), ctx.set( 'content-disposition', - 'attachment; filename=backstage-mock-12ab34cd56ef.tgz', + 'attachment; filename=backstage-mock-12ab34cd56ef.tar.gz', ), ctx.body(repoBuffer), ), @@ -366,7 +383,7 @@ describe('BitbucketUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive?format=tgz&prefix=mock&path=docs', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/archive', (_, res, ctx) => res( ctx.status(200), diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index 009b81bc28..2849bc3cd9 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import { SearchOptions, SearchResponse, UrlReader, + ReadUrlResponse, + ReadUrlOptions, } from './types'; /** @@ -99,6 +101,15 @@ export class BitbucketUrlReader implements UrlReader { throw new Error(message); } + async readUrl( + url: string, + _options?: ReadUrlOptions, + ): Promise { + // TODO etag is not implemented yet. + const buffer = await this.read(url); + return { buffer: async () => buffer }; + } + async readTree( url: string, options?: ReadTreeOptions, diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 2363a16d1a..169cbfbf66 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 57bab5e58d..30468158aa 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import { NotFoundError } from '@backstage/errors'; import { ReaderFactory, ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, SearchResponse, UrlReader, } from './types'; @@ -73,6 +75,15 @@ export class FetchUrlReader implements UrlReader { throw new Error(message); } + async readUrl( + url: string, + _options?: ReadUrlOptions, + ): Promise { + // TODO etag is not implemented yet. + const buffer = await this.read(url); + return { buffer: async () => buffer }; + } + async readTree(): Promise { throw new Error('FetchUrlReader does not implement readTree'); } diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index cbe61f2e66..c8002cd3ce 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ describe('GithubUrlReader', () => { worker.use( rest.get( - 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/?ref=main', + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', (req, res, ctx) => { expect(req.headers.get('authorization')).toBe( mockHeaders.Authorization, @@ -141,6 +141,113 @@ describe('GithubUrlReader', () => { }); }); + /* + * readUrl + */ + describe('readUrl', () => { + it('should use the headers from the credentials provider to the fetch request when doing readUrl', async () => { + expect.assertions(2); + + const mockHeaders = { + Authorization: 'bearer blah', + otherheader: 'something', + }; + + (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + headers: mockHeaders, + }); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + mockHeaders.Authorization, + ); + expect(req.headers.get('otherheader')).toBe( + mockHeaders.otherheader, + ); + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/x-gzip'), + ctx.body('foo'), + ); + }, + ), + ); + + await gheProcessor.readUrl( + 'https://github.com/backstage/mock/tree/blob/main', + ); + }); + + it('should throw NotModified if GitHub responds with 304', async () => { + expect.assertions(4); + + const mockHeaders = { + Authorization: 'bearer blah', + otherheader: 'something', + }; + + (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + headers: mockHeaders, + }); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + mockHeaders.Authorization, + ); + expect(req.headers.get('otherheader')).toBe( + mockHeaders.otherheader, + ); + expect(req.headers.get('if-none-match')).toBe('foo'); + return res( + ctx.status(304), + ctx.set('Content-Type', 'application/x-gzip'), + ctx.body('foo'), + ); + }, + ), + ); + + await expect( + gheProcessor.readUrl( + 'https://github.com/backstage/mock/tree/blob/main', + { etag: 'foo' }, + ), + ).rejects.toThrow(NotModifiedError); + }); + + it('should return etag from the response', async () => { + (mockCredentialsProvider.getCredentials as jest.Mock).mockResolvedValue({ + headers: { + Authorization: 'bearer blah', + }, + }); + + worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/tree/contents/', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Etag', 'foo'), + ctx.body('bar'), + ); + }, + ), + ); + + const response = await gheProcessor.readUrl( + 'https://github.com/backstage/mock/tree/blob/main', + ); + expect(response.etag).toBe('foo'); + }); + }); + /* * readTree */ diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 2b1247fe77..bccddc6838 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ import { SearchResponse, SearchResponseFile, UrlReader, + ReadUrlOptions, + ReadUrlResponse, } from './types'; export type GhRepoResponse = RestEndpointMethodTypes['repos']['get']['response']['data']; @@ -77,6 +79,14 @@ export class GithubUrlReader implements UrlReader { } async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { const ghUrl = getGitHubFileFetchUrl(url, this.integration.config); const { headers } = await this.deps.credentialsProvider.getCredentials({ url, @@ -86,6 +96,7 @@ export class GithubUrlReader implements UrlReader { response = await fetch(ghUrl.toString(), { headers: { ...headers, + ...(options?.etag && { 'If-None-Match': options.etag }), Accept: 'application/vnd.github.v3.raw', }, }); @@ -93,8 +104,15 @@ export class GithubUrlReader implements UrlReader { throw new Error(`Unable to read ${url}, ${e}`); } + if (response.status === 304) { + throw new NotModifiedError(); + } + if (response.ok) { - return Buffer.from(await response.text()); + return { + buffer: async () => Buffer.from(await response.text()), + etag: response.headers.get('ETag') ?? undefined, + }; } const message = `${url} could not be read as ${ghUrl}, ${response.status} ${response.statusText}`; diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index b5592c09f5..945444d8ed 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ describe('GitlabUrlReader', () => { const worker = setupServer(); msw.setupDefaultHandlers(worker); - describe('implementation', () => { + describe('read', () => { beforeEach(() => { worker.use( rest.get('*/api/v4/projects/:name', (_, res, ctx) => @@ -90,7 +90,7 @@ describe('GitlabUrlReader', () => { ctx.status(200), ctx.json({ url: req.url.toString(), - headers: req.headers.getAllHeaders(), + headers: req.headers.all(), }), ), ), @@ -180,9 +180,56 @@ describe('GitlabUrlReader', () => { }); }); + describe('readUrl', () => { + const [{ reader }] = GitlabUrlReader.factory({ + config: new ConfigReader({}), + logger, + treeResponseFactory, + }); + + it('should throw NotModified on HTTP 304', async () => { + worker.use( + rest.get('*/api/v4/projects/:name', (_, res, ctx) => + res(ctx.status(200), ctx.json({ id: 12345 })), + ), + rest.get('*', (req, res, ctx) => { + expect(req.headers.get('If-None-Match')).toBe('999'); + return res(ctx.status(304)); + }), + ); + + await expect( + reader.readUrl!( + 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + { + etag: '999', + }, + ), + ).rejects.toThrow(NotModifiedError); + }); + + it('should return etag in response', async () => { + worker.use( + rest.get('*/api/v4/projects/:name', (_, res, ctx) => + res(ctx.status(200), ctx.json({ id: 12345 })), + ), + rest.get('*', (_req, res, ctx) => { + return res(ctx.status(200), ctx.set('ETag', '999'), ctx.body('foo')); + }), + ); + + const result = await reader.readUrl!( + 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + ); + expect(result.etag).toBe('999'); + const content = await result.buffer(); + expect(content.toString()).toBe('foo'); + }); + }); + describe('readTree', () => { const archiveBuffer = fs.readFileSync( - path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'), + path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.tar.gz'), ); const projectGitlabApiResponse = { @@ -205,7 +252,7 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), @@ -283,7 +330,7 @@ describe('GitlabUrlReader', () => { }, ), rest.get( - 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), @@ -306,8 +353,8 @@ describe('GitlabUrlReader', () => { const files = await response.files(); expect(files.length).toBe(2); - const mkDocsFile = await files[0].content(); - const indexMarkdownFile = await files[1].content(); + const indexMarkdownFile = await files[0].content(); + const mkDocsFile = await files[1].content(); expect(mkDocsFile.toString()).toBe('site_name: Test\n'); expect(indexMarkdownFile.toString()).toBe('# Test\n'); @@ -318,7 +365,7 @@ describe('GitlabUrlReader', () => { 'https://gitlab.com/backstage/mock', ); - const dir = await response.dir({ targetDir: '/tmp' }); + const dir = await response.dir({ targetDir: tmpDir }); await expect( fs.readFile(path.join(dir, 'mkdocs.yml'), 'utf8'), @@ -331,7 +378,7 @@ describe('GitlabUrlReader', () => { it('returns the wanted files from hosted gitlab', async () => { worker.use( rest.get( - 'https://gitlab.mycompany.com/backstage/mock/-/archive/main.zip', + 'https://gitlab.mycompany.com/backstage/mock/-/archive/main.tar.gz', (_, res, ctx) => res( ctx.status(200), @@ -375,7 +422,7 @@ describe('GitlabUrlReader', () => { 'https://gitlab.com/backstage/mock/tree/main/docs', ); - const dir = await response.dir({ targetDir: '/tmp' }); + const dir = await response.dir({ targetDir: tmpDir }); await expect( fs.readFile(path.join(dir, 'index.md'), 'utf8'), @@ -454,7 +501,7 @@ describe('GitlabUrlReader', () => { describe('search', () => { const archiveBuffer = fs.readFileSync( - path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.zip'), + path.resolve('src', 'reading', '__fixtures__', 'gitlab-archive.tar.gz'), ); const projectGitlabApiResponse = { @@ -471,7 +518,7 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get( - 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive.zip?sha=main', + 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive', (_, res, ctx) => res( ctx.status(200), diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 635565c8a0..6c272471e2 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ import { SearchOptions, SearchResponse, UrlReader, + ReadUrlResponse, + ReadUrlOptions, } from './types'; export class GitlabUrlReader implements UrlReader { @@ -54,20 +56,37 @@ export class GitlabUrlReader implements UrlReader { ) {} async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { const builtUrl = await getGitLabFileFetchUrl(url, this.integration.config); let response: Response; try { - response = await fetch( - builtUrl, - getGitLabRequestOptions(this.integration.config), - ); + response = await fetch(builtUrl, { + headers: { + ...getGitLabRequestOptions(this.integration.config).headers, + ...(options?.etag && { 'If-None-Match': options.etag }), + }, + }); } catch (e) { throw new Error(`Unable to read ${url}, ${e}`); } + if (response.status === 304) { + throw new NotModifiedError(); + } + if (response.ok) { - return Buffer.from(await response.text()); + return { + buffer: async () => Buffer.from(await response.text()), + etag: response.headers.get('ETag') ?? undefined, + }; } const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`; @@ -139,7 +158,7 @@ export class GitlabUrlReader implements UrlReader { const archiveGitLabResponse = await fetch( `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent( full_name, - )}/repository/archive.zip?sha=${branch}`, + )}/repository/archive?sha=${branch}`, getGitLabRequestOptions(this.integration.config), ); if (!archiveGitLabResponse.ok) { @@ -150,7 +169,7 @@ export class GitlabUrlReader implements UrlReader { throw new Error(message); } - return await this.deps.treeResponseFactory.fromZipArchive({ + return await this.deps.treeResponseFactory.fromTarArchive({ stream: (archiveGitLabResponse.body as unknown) as Readable, subpath: filepath, etag: commitSha, diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts index 5d4b01da44..d8aac4886d 100644 --- a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts index 4ea24fa22f..9f3fbad302 100644 --- a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import { Storage } from '@google-cloud/storage'; import { ReaderFactory, ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, SearchResponse, UrlReader, } from './types'; @@ -90,6 +92,15 @@ export class GoogleGcsUrlReader implements UrlReader { } } + async readUrl( + url: string, + _options?: ReadUrlOptions, + ): Promise { + // TODO etag is not implemented yet. + const buffer = await this.read(url); + return { buffer: async () => buffer }; + } + async readTree(): Promise { throw new Error('GcsUrlReader does not implement readTree'); } diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts index 06ca0c5971..4a166cb517 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,35 @@ */ import { NotAllowedError } from '@backstage/errors'; +import { Logger } from 'winston'; import { ReadTreeOptions, ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, SearchOptions, SearchResponse, UrlReader, UrlReaderPredicateTuple, } from './types'; +const MIN_WARNING_INTERVAL_MS = 1000 * 60 * 15; + /** * A UrlReader implementation that selects from a set of UrlReaders * based on a predicate tied to each reader. */ export class UrlReaderPredicateMux implements UrlReader { private readonly readers: UrlReaderPredicateTuple[] = []; + private readonly readerWarnings: Map = new Map(); + + constructor(private readonly logger: Logger) {} register(tuple: UrlReaderPredicateTuple): void { this.readers.push(tuple); } - read(url: string): Promise { + async read(url: string): Promise { const parsed = new URL(url); for (const { predicate, reader } of this.readers) { @@ -47,6 +55,37 @@ export class UrlReaderPredicateMux implements UrlReader { throw new NotAllowedError(`Reading from '${url}' is not allowed`); } + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { + const parsed = new URL(url); + + for (const { predicate, reader } of this.readers) { + if (predicate(parsed)) { + if (reader.readUrl) { + return reader.readUrl(url, options); + } + const now = Date.now(); + const lastWarned = this.readerWarnings.get(reader) ?? 0; + if (now > lastWarned + MIN_WARNING_INTERVAL_MS) { + this.readerWarnings.set(reader, now); + this.logger.warn( + `No implementation of readUrl found for ${reader}, this method will be required in the ` + + `future and will replace the 'read' method. See the changelog for more details here: ` + + 'https://github.com/backstage/backstage/blob/master/packages/backend-common/CHANGELOG.md#085', + ); + } + const buffer = await reader.read(url); + return { + buffer: async () => buffer, + }; + } + } + + throw new NotAllowedError(`Reading from '${url}' is not allowed`); + } + async readTree( url: string, options?: ReadTreeOptions, diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index 8f27d058df..2b3a2f166c 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ export class UrlReaders { * Creates a UrlReader without any known types. */ static create({ logger, config, factories }: CreateOptions): UrlReader { - const mux = new UrlReaderPredicateMux(); + const mux = new UrlReaderPredicateMux(logger); const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config, }); diff --git a/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz b/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz new file mode 100644 index 0000000000..c078e07e7e Binary files /dev/null and b/packages/backend-common/src/reading/__fixtures__/gitlab-archive.tar.gz differ diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index da32f45f6f..4c601556d7 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/integration.test.ts b/packages/backend-common/src/reading/integration.test.ts index 1c7657d873..ae557be583 100644 --- a/packages/backend-common/src/reading/integration.test.ts +++ b/packages/backend-common/src/reading/integration.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts index 1ce7555a30..912fddf965 100644 --- a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts +++ b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts index 2b76bea9e3..aa904c5522 100644 --- a/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts index e61a1df645..a81add58f7 100644 --- a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts index 3bcaa5e0e3..659875286e 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 4aebff5c84..45c6880a55 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/index.ts b/packages/backend-common/src/reading/tree/index.ts index 3126907c1e..e10ae28e09 100644 --- a/packages/backend-common/src/reading/tree/index.ts +++ b/packages/backend-common/src/reading/tree/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/tree/util.ts b/packages/backend-common/src/reading/tree/util.ts index 8e908a5e60..cfb986a0b0 100644 --- a/packages/backend-common/src/reading/tree/util.ts +++ b/packages/backend-common/src/reading/tree/util.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index 7ba806197d..8efc833ead 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,20 @@ import { Config } from '@backstage/config'; * A generic interface for fetching plain data from URLs. */ export type UrlReader = { + /* Used to read a single file and return its content. */ read(url: string): Promise; + + /** + * A replacement for the read method that supports options and complex responses. + * + * Use this whenever it is available, as the read method will be deprecated and + * eventually removed in the future. + */ + readUrl?(url: string, options?: ReadUrlOptions): Promise; + + /* Used to read a file tree and download as a directory. */ readTree(url: string, options?: ReadTreeOptions): Promise; + /* Used to search a file in a tree using a glob pattern. */ search(url: string, options?: SearchOptions): Promise; }; @@ -42,6 +54,40 @@ export type ReaderFactory = (options: { treeResponseFactory: ReadTreeResponseFactory; }) => UrlReaderPredicateTuple[]; +/** + * An options object for readUrl operations. + */ +export type ReadUrlOptions = { + /** + * An etag can be provided to check whether readUrl's response has changed from a previous execution. + * + * In the readUrl() response, an etag is returned along with the data. The etag is a unique identifer + * of the data, usually the commit SHA or etag from the target. + * + * When an etag is given in ReadUrlOptions, readUrl will first compare the etag against the etag + * on the target. If they match, readUrl will throw a NotModifiedError indicating that the readUrl + * response will not differ from the previous response which included this particular etag. If they + * do not match, readUrl will return the rest of ReadUrlResponse along with a new etag. + */ + etag?: string; +}; + +/** + * A response object for readUrl operations. + */ +export type ReadUrlResponse = { + /** + * Returns the data that was read from the remote URL. + */ + buffer(): Promise; + + /** + * Etag returned by content provider. + * Can be used to compare and cache responses when doing subsequent calls. + */ + etag?: string; +}; + /** * An options object for readTree operations. */ @@ -66,14 +112,17 @@ export type ReadTreeOptions = { * In the readTree() response, an etag is returned along with the tree blob. The etag is a unique identifer * of the tree blob, usually the commit SHA or etag from the target. * - * When a etag is given in ReadTreeOptions, readTree will first compare the etag against the etag + * When an etag is given in ReadTreeOptions, readTree will first compare the etag against the etag * on the target branch. If they match, readTree will throw a NotModifiedError indicating that the readTree - * response will not differ from the previous response which included this particular etag. If they mismatch, - * readTree will return the rest of ReadTreeResponse along with a new etag. + * response will not differ from the previous response which included this particular etag. If they + * do not match, readTree will return the rest of ReadTreeResponse along with a new etag. */ etag?: string; }; +/** + * A response object for readTree operations. + */ export type ReadTreeResponse = { /** * files() returns an array of all the files inside the tree and corresponding functions to read their content. @@ -87,7 +136,8 @@ export type ReadTreeResponse = { dir(options?: ReadTreeResponseDirOptions): Promise; /** - * A unique identifer of the tree blob, usually the commit SHA or etag from the target. + * Etag returned by content provider. + * Can be used to compare and cache responses when doing subsequent calls. */ etag: string; }; diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/scm/git.test.ts index 9af080a782..0b70edc45d 100644 --- a/packages/backend-common/src/scm/git.test.ts +++ b/packages/backend-common/src/scm/git.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -201,14 +201,16 @@ describe('Git', () => { describe('init', () => { it('should call isomorphic-git with the correct arguments', async () => { const dir = '/some/mock/dir'; + const defaultBranch = 'master'; const git = Git.fromAuth({}); - await git.init({ dir }); + await git.init({ dir, defaultBranch }); expect(isomorphic.init).toHaveBeenCalledWith({ fs, dir, + defaultBranch, }); }); }); diff --git a/packages/backend-common/src/scm/git.ts b/packages/backend-common/src/scm/git.ts index 21aa9bf5d1..e786c279a1 100644 --- a/packages/backend-common/src/scm/git.ts +++ b/packages/backend-common/src/scm/git.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -150,12 +150,19 @@ export class Git { }); } - async init({ dir }: { dir: string }): Promise { + async init({ + dir, + defaultBranch = 'master', + }: { + dir: string; + defaultBranch?: string; + }): Promise { this.config.logger?.info(`Init git repository {dir=${dir}}`); return git.init({ fs, dir, + defaultBranch, }); } diff --git a/packages/backend-common/src/scm/index.ts b/packages/backend-common/src/scm/index.ts index e967fffb44..ceba752c9e 100644 --- a/packages/backend-common/src/scm/index.ts +++ b/packages/backend-common/src/scm/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/createServiceBuilder.ts b/packages/backend-common/src/service/createServiceBuilder.ts index c62921afc3..17d69e7082 100644 --- a/packages/backend-common/src/service/createServiceBuilder.ts +++ b/packages/backend-common/src/service/createServiceBuilder.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/createStatusCheckRouter.test.ts b/packages/backend-common/src/service/createStatusCheckRouter.test.ts index 6c15e7b5f9..604a1eb12f 100644 --- a/packages/backend-common/src/service/createStatusCheckRouter.test.ts +++ b/packages/backend-common/src/service/createStatusCheckRouter.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/createStatusCheckRouter.ts b/packages/backend-common/src/service/createStatusCheckRouter.ts index c6014cbfc2..fd794cc9c4 100644 --- a/packages/backend-common/src/service/createStatusCheckRouter.ts +++ b/packages/backend-common/src/service/createStatusCheckRouter.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,10 @@ import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware'; export interface StatusCheckRouterOptions { logger: Logger; path?: string; + /** + * If not implemented, the default express middleware always returns 200. + * Override this to implement your own logic for a health check. + */ statusCheck?: StatusCheck; } diff --git a/packages/backend-common/src/service/index.ts b/packages/backend-common/src/service/index.ts index 58e310032d..4eb0bf4a5a 100644 --- a/packages/backend-common/src/service/index.ts +++ b/packages/backend-common/src/service/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts index cffb0a88de..bd97f444de 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 45eba03157..380d61abc8 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/lib/config.test.ts b/packages/backend-common/src/service/lib/config.test.ts index 75252357d6..f2f0c88cfb 100644 --- a/packages/backend-common/src/service/lib/config.test.ts +++ b/packages/backend-common/src/service/lib/config.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts index 3a33675d5f..77ef925403 100644 --- a/packages/backend-common/src/service/lib/config.ts +++ b/packages/backend-common/src/service/lib/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index db202a84ab..d3795893f7 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index d389f4b5ff..70f62acfca 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/setupTests.ts b/packages/backend-common/src/setupTests.ts index ba33cf996b..d3232290a7 100644 --- a/packages/backend-common/src/setupTests.ts +++ b/packages/backend-common/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/util/ContainerRunner.ts b/packages/backend-common/src/util/ContainerRunner.ts index 7740e384e4..80ac4e3954 100644 --- a/packages/backend-common/src/util/ContainerRunner.ts +++ b/packages/backend-common/src/util/ContainerRunner.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/util/DockerContainerRunner.test.ts b/packages/backend-common/src/util/DockerContainerRunner.test.ts index 942264af84..0ed41d5cd3 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.test.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/util/DockerContainerRunner.ts b/packages/backend-common/src/util/DockerContainerRunner.ts index a96ca1351b..6ec366eb94 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-common/src/util/index.ts b/packages/backend-common/src/util/index.ts index 85c5436a2e..ba8074a11e 100644 --- a/packages/backend-common/src/util/index.ts +++ b/packages/backend-common/src/util/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index c2078fd0bc..2faa5afbc3 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/backend-test-utils +## 0.1.4 + +### Patch Changes + +- f7134c368: bump sqlite3 to 5.0.1 +- Updated dependencies + - @backstage/backend-common@0.8.5 + +## 0.1.3 + +### Patch Changes + +- 772dbdb51: Deprecates `SingleConnectionDatabaseManager` and provides an API compatible database + connection manager, `DatabaseManager`, which allows developers to configure database + connections on a per plugin basis. + + The `backend.database` config path allows you to set `prefix` to use an + alternate prefix for automatically generated database names, the default is + `backstage_plugin_`. Use `backend.database.plugin.` to set plugin + specific database connection configuration, e.g. + + ```yaml + backend: + database: + client: 'pg', + prefix: 'custom_prefix_' + connection: + host: 'localhost' + user: 'foo' + password: 'bar' + plugin: + catalog: + connection: + database: 'database_name_overriden' + scaffolder: + client: 'sqlite3' + connection: ':memory:' + ``` + + Migrate existing backstage installations by swapping out the database manager in the + `packages/backend/src/index.ts` file as shown below: + + ```diff + import { + - SingleConnectionDatabaseManager, + + DatabaseManager, + } from '@backstage/backend-common'; + + // ... + + function makeCreateEnv(config: Config) { + // ... + - const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + + const databaseManager = DatabaseManager.fromConfig(config); + // ... + } + ``` + +- Updated dependencies + - @backstage/backend-common@0.8.3 + - @backstage/cli@0.7.1 + ## 0.1.2 ### Patch Changes diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 28d4a00463..184ed74978 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -3,29 +3,30 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { Knex } from 'knex'; // @public (undocumented) export function isDockerDisabledForTests(): boolean; // @public -export type TestDatabaseId = 'POSTGRES_13' | 'POSTGRES_9' | 'MYSQL_8' | 'SQLITE_3'; +export type TestDatabaseId = + | 'POSTGRES_13' + | 'POSTGRES_9' + | 'MYSQL_8' + | 'SQLITE_3'; // @public export class TestDatabases { - static create(options?: { - ids?: TestDatabaseId[]; - disableDocker?: boolean; - }): TestDatabases; - // (undocumented) - eachSupportedId(): [TestDatabaseId][]; - init(id: TestDatabaseId): Promise; - // (undocumented) - supports(id: TestDatabaseId): boolean; + static create(options?: { + ids?: TestDatabaseId[]; + disableDocker?: boolean; + }): TestDatabases; + // (undocumented) + eachSupportedId(): [TestDatabaseId][]; + init(id: TestDatabaseId): Promise; + // (undocumented) + supports(id: TestDatabaseId): boolean; } - // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index c4ff5603f3..9ec11a58b7 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.2", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,18 +30,18 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.2", - "@backstage/cli": "^0.7.0", + "@backstage/backend-common": "^0.8.5", + "@backstage/cli": "^0.7.1", "@backstage/config": "^0.1.5", "knex": "^0.95.1", "mysql2": "^2.2.5", "pg": "^8.3.0", - "sqlite3": "^5.0.0", + "sqlite3": "^5.0.1", "testcontainers": "^7.10.0", "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.1", "jest": "^26.0.1" }, "files": [ diff --git a/packages/backend-test-utils/src/database/TestDatabases.test.ts b/packages/backend-test-utils/src/database/TestDatabases.test.ts index 7c1e6e1bdd..62612f3fcc 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.test.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_0'; + const database = 'backstage_plugin_db0'; const output = knexFactory({ client: 'pg', connection: { host, port, user, password, database }, @@ -105,7 +105,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_0'; + const database = 'backstage_plugin_db0'; const output = knexFactory({ client: 'pg', connection: { host, port, user, password, database }, @@ -139,7 +139,7 @@ describe('TestDatabases', () => { await input.insert({ x: 'y' }).into('a'); // Look for the mark - const database = 'backstage_plugin_0'; + const database = 'backstage_plugin_db0'; const output = knexFactory({ client: 'mysql2', connection: { host, port, user, password, database }, diff --git a/packages/backend-test-utils/src/database/TestDatabases.ts b/packages/backend-test-utils/src/database/TestDatabases.ts index ab5846d84d..c35202354d 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SingleConnectionDatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Knex } from 'knex'; import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; @@ -142,7 +142,7 @@ export class TestDatabases { // Ensure that a unique logical database is created in the instance const connection = await instance.databaseManager - .forPlugin(String(this.lastDatabaseIndex++)) + .forPlugin(String(`db${this.lastDatabaseIndex++}`)) .getClient(); instance.connections.push(connection); @@ -157,7 +157,7 @@ export class TestDatabases { if (envVarName) { const connectionString = process.env[envVarName]; if (connectionString) { - const databaseManager = SingleConnectionDatabaseManager.fromConfig( + const databaseManager = DatabaseManager.fromConfig( new ConfigReader({ backend: { database: { @@ -195,7 +195,7 @@ export class TestDatabases { properties.dockerImageName!, ); - const databaseManager = SingleConnectionDatabaseManager.fromConfig( + const databaseManager = DatabaseManager.fromConfig( new ConfigReader({ backend: { database: { @@ -220,7 +220,7 @@ export class TestDatabases { properties.dockerImageName!, ); - const databaseManager = SingleConnectionDatabaseManager.fromConfig( + const databaseManager = DatabaseManager.fromConfig( new ConfigReader({ backend: { database: { @@ -241,7 +241,7 @@ export class TestDatabases { private async initSqlite( _properties: TestDatabaseProperties, ): Promise { - const databaseManager = SingleConnectionDatabaseManager.fromConfig( + const databaseManager = DatabaseManager.fromConfig( new ConfigReader({ backend: { database: { diff --git a/packages/backend-test-utils/src/database/index.ts b/packages/backend-test-utils/src/database/index.ts index 6988f0b80b..69e3f41452 100644 --- a/packages/backend-test-utils/src/database/index.ts +++ b/packages/backend-test-utils/src/database/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/database/startMysqlContainer.test.ts b/packages/backend-test-utils/src/database/startMysqlContainer.test.ts index e210ccb413..282e84dabc 100644 --- a/packages/backend-test-utils/src/database/startMysqlContainer.test.ts +++ b/packages/backend-test-utils/src/database/startMysqlContainer.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/database/startMysqlContainer.ts b/packages/backend-test-utils/src/database/startMysqlContainer.ts index 739298dfe5..9601854cb3 100644 --- a/packages/backend-test-utils/src/database/startMysqlContainer.ts +++ b/packages/backend-test-utils/src/database/startMysqlContainer.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/database/startPostgresContainer.test.ts b/packages/backend-test-utils/src/database/startPostgresContainer.test.ts index 4e8acc8410..3c3ad5e15f 100644 --- a/packages/backend-test-utils/src/database/startPostgresContainer.test.ts +++ b/packages/backend-test-utils/src/database/startPostgresContainer.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/database/startPostgresContainer.ts b/packages/backend-test-utils/src/database/startPostgresContainer.ts index 1c2ecdcb60..89a0417e22 100644 --- a/packages/backend-test-utils/src/database/startPostgresContainer.ts +++ b/packages/backend-test-utils/src/database/startPostgresContainer.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/database/types.ts b/packages/backend-test-utils/src/database/types.ts index 791cf09b7b..b5516a19c0 100644 --- a/packages/backend-test-utils/src/database/types.ts +++ b/packages/backend-test-utils/src/database/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SingleConnectionDatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-common'; import { Knex } from 'knex'; /** @@ -35,10 +35,9 @@ export type TestDatabaseProperties = { export type Instance = { stopContainer?: () => Promise; - databaseManager: SingleConnectionDatabaseManager; + databaseManager: DatabaseManager; connections: Array; }; - export const allDatabases: Record< TestDatabaseId, TestDatabaseProperties diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index ad3e422f44..7a1a10fc6e 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/setupTests.ts b/packages/backend-test-utils/src/setupTests.ts index ba33cf996b..d3232290a7 100644 --- a/packages/backend-test-utils/src/setupTests.ts +++ b/packages/backend-test-utils/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/util/index.ts b/packages/backend-test-utils/src/util/index.ts index e4e0e97ac1..a6cdc621d4 100644 --- a/packages/backend-test-utils/src/util/index.ts +++ b/packages/backend-test-utils/src/util/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts index 4aeea59c50..617e9eb2a1 100644 --- a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts +++ b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 65d28457d0..47edc5107f 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,41 @@ # example-backend +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.5.8 + - @backstage/plugin-scaffolder-backend@0.13.0 + - @backstage/catalog-model@0.9.0 + - @backstage/plugin-catalog-backend@0.12.0 + - @backstage/backend-common@0.8.5 + - @backstage/plugin-search-backend-node@0.3.0 + - example-app@0.2.36 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.2 + - @backstage/catalog-client@0.3.16 + - @backstage/plugin-auth-backend@0.3.16 + - @backstage/plugin-badges-backend@0.1.8 + - @backstage/plugin-code-coverage-backend@0.1.8 + - @backstage/plugin-kafka-backend@0.2.8 + - @backstage/plugin-kubernetes-backend@0.3.9 + - @backstage/plugin-techdocs-backend@0.8.6 + - @backstage/plugin-todo-backend@0.1.8 + - @backstage/plugin-search-backend@0.2.2 + +## 0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.12.4 + - @backstage/backend-common@0.8.4 + - @backstage/plugin-auth-backend@0.3.15 + - @backstage/plugin-catalog-backend@0.11.0 + - @backstage/plugin-techdocs-backend@0.8.5 + - @backstage/catalog-client@0.3.15 + - @backstage/plugin-kafka-backend@0.2.7 + ## 0.2.32 ### Patch Changes diff --git a/packages/backend/README.md b/packages/backend/README.md index d458268846..e6f0c899ca 100644 --- a/packages/backend/README.md +++ b/packages/backend/README.md @@ -6,9 +6,7 @@ The main purpose of this package is to provide a test bed for Backstage plugins that have a backend part. Feel free to experiment locally or within your fork by adding dependencies and routes to this backend, to try things out. -Our goal is to eventually amend the create-app flow of the CLI, such that a -production ready version of a backend skeleton is made alongside the frontend -app. Until then, feel free to experiment here! +By running the `@backstage/create-app` script, you get your own separate Backstage backend. ## Development @@ -41,6 +39,16 @@ You can also, instead of using dummy values for a huge number of environment var The backend starts up on port 7000 per default. +### Debugging + +The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag to [backend:dev](https://backstage.io/docs/cli/commands#backenddev). + +To debug the backend in [Visual Studio Code](https://code.visualstudio.com/): + +- Enable Auto Attach (⌘ + Shift + P > Toggle Auto Attach > Only With Flag) +- Open a VSCode terminal (Control + `) +- Run the backend from the VSCode terminal: `yarn start-backend --inspect` + ## Populating The Catalog If you want to use the catalog functionality, you need to add so called diff --git a/packages/backend/knexfile.ts b/packages/backend/knexfile.ts index 57ccbe8528..ca05575153 100644 --- a/packages/backend/knexfile.ts +++ b/packages/backend/knexfile.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/package.json b/packages/backend/package.json index f4f304d714..4338f3e2b7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.32", + "version": "0.2.36", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,40 +27,42 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.8.2", - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.2", + "@backstage/backend-common": "^0.8.5", + "@backstage/catalog-client": "^0.3.16", + "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", + "@backstage/integration": "^0.5.8", "@backstage/plugin-app-backend": "^0.3.13", - "@backstage/plugin-auth-backend": "^0.3.12", - "@backstage/plugin-badges-backend": "^0.1.6", - "@backstage/plugin-catalog-backend": "^0.10.2", - "@backstage/plugin-code-coverage-backend": "^0.1.6", + "@backstage/plugin-auth-backend": "^0.3.16", + "@backstage/plugin-badges-backend": "^0.1.8", + "@backstage/plugin-catalog-backend": "^0.12.0", + "@backstage/plugin-code-coverage-backend": "^0.1.8", "@backstage/plugin-graphql-backend": "^0.1.8", - "@backstage/plugin-kubernetes-backend": "^0.3.8", - "@backstage/plugin-kafka-backend": "^0.2.6", + "@backstage/plugin-kubernetes-backend": "^0.3.9", + "@backstage/plugin-kafka-backend": "^0.2.8", "@backstage/plugin-proxy-backend": "^0.2.9", "@backstage/plugin-rollbar-backend": "^0.1.11", - "@backstage/plugin-scaffolder-backend": "^0.12.0", - "@backstage/plugin-search-backend": "^0.2.0", - "@backstage/plugin-search-backend-node": "^0.2.0", - "@backstage/plugin-techdocs-backend": "^0.8.2", - "@backstage/plugin-todo-backend": "^0.1.6", + "@backstage/plugin-scaffolder-backend": "^0.13.0", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.2", + "@backstage/plugin-search-backend": "^0.2.2", + "@backstage/plugin-search-backend-node": "^0.3.0", + "@backstage/plugin-techdocs-backend": "^0.8.6", + "@backstage/plugin-todo-backend": "^0.1.8", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", - "azure-devops-node-api": "^10.1.1", + "azure-devops-node-api": "^10.2.2", "dockerode": "^3.2.1", - "example-app": "^0.2.32", + "example-app": "^0.2.36", "express": "^4.17.1", "express-promise-router": "^4.1.0", "knex": "^0.95.1", "pg": "^8.3.0", "pg-connection-string": "^2.3.0", - "sqlite3": "^5.0.0", + "sqlite3": "^5.0.1", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/backend/src/index.test.ts b/packages/backend/src/index.test.ts index d18873c1f0..8b41455b27 100644 --- a/packages/backend/src/index.test.ts +++ b/packages/backend/src/index.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d43c8f0101..168f6aa2d9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import { getRootLogger, loadBackendConfig, notFoundHandler, - SingleConnectionDatabaseManager, + DatabaseManager, SingleHostDiscovery, UrlReaders, useHotMemoize, @@ -59,7 +59,7 @@ function makeCreateEnv(config: Config) { root.info(`Created UrlReader ${reader}`); - const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + const databaseManager = DatabaseManager.fromConfig(config); const cacheManager = CacheManager.fromConfig(config); return (plugin: string): PluginEnvironment => { @@ -71,9 +71,16 @@ function makeCreateEnv(config: Config) { } async function main() { + const logger = getRootLogger(); + + logger.info( + `You are running an example backend, which is supposed to be mainly used for contributing back to Backstage. ` + + `Do NOT deploy this to production. Read more here https://backstage.io/docs/getting-started/`, + ); + const config = await loadBackendConfig({ argv: process.argv, - logger: getRootLogger(), + logger, }); const createEnv = makeCreateEnv(config); @@ -118,7 +125,7 @@ async function main() { .addRouter('', await app(appEnv)); await service.start().catch(err => { - console.log(err); + logger.error(err); process.exit(1); }); } diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts index 5ee616b62f..b5f05f2255 100644 --- a/packages/backend/src/plugins/app.ts +++ b/packages/backend/src/plugins/app.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index 2b1c85f052..4e51518bc1 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/badges.ts b/packages/backend/src/plugins/badges.ts index befca76542..0f579ccf91 100644 --- a/packages/backend/src/plugins/badges.ts +++ b/packages/backend/src/plugins/badges.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 63a3e53c81..055595dcb5 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,9 @@ * limitations under the License. */ -import { useHotCleanup } from '@backstage/backend-common'; import { CatalogBuilder, createRouter, - NextCatalogBuilder, - runPeriodically, - createNextRouter, } from '@backstage/plugin-catalog-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; @@ -28,50 +24,20 @@ import { PluginEnvironment } from '../types'; export default async function createPlugin( env: PluginEnvironment, ): Promise { - /* - * ** WARNING ** - * DO NOT enable the experimental catalog, it will brick your database migrations. - * This is solely for internal backstage development. - */ - if (process.env.EXPERIMENTAL_CATALOG === '1') { - const builder = new NextCatalogBuilder(env); - const { - entitiesCatalog, - locationAnalyzer, - processingEngine, - locationService, - } = await builder.build(); - - // TODO(jhaals): run and manage in background. - await processingEngine.start(); - - return await createNextRouter({ - entitiesCatalog, - locationAnalyzer, - locationService, - logger: env.logger, - config: env.config, - }); - } - - const builder = new CatalogBuilder(env); + const builder = await CatalogBuilder.create(env); const { entitiesCatalog, - locationsCatalog, - higherOrderOperation, locationAnalyzer, + processingEngine, + locationService, } = await builder.build(); - useHotCleanup( - module, - runPeriodically(() => higherOrderOperation.refreshAllLocations(), 100000), - ); + await processingEngine.start(); return await createRouter({ entitiesCatalog, - locationsCatalog, - higherOrderOperation, locationAnalyzer, + locationService, logger: env.logger, config: env.config, }); diff --git a/packages/backend/src/plugins/codecoverage.ts b/packages/backend/src/plugins/codecoverage.ts index c06e0e516f..358cf36708 100644 --- a/packages/backend/src/plugins/codecoverage.ts +++ b/packages/backend/src/plugins/codecoverage.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts index c7f5d6e072..3c53f4a64e 100644 --- a/packages/backend/src/plugins/graphql.ts +++ b/packages/backend/src/plugins/graphql.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/healthcheck.ts b/packages/backend/src/plugins/healthcheck.ts index 8ecae6be87..897e56d381 100644 --- a/packages/backend/src/plugins/healthcheck.ts +++ b/packages/backend/src/plugins/healthcheck.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/kafka.ts b/packages/backend/src/plugins/kafka.ts index d5b5857027..baa9bb070f 100644 --- a/packages/backend/src/plugins/kafka.ts +++ b/packages/backend/src/plugins/kafka.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/kubernetes.ts b/packages/backend/src/plugins/kubernetes.ts index 7906765533..a19520312f 100644 --- a/packages/backend/src/plugins/kubernetes.ts +++ b/packages/backend/src/plugins/kubernetes.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts index 8c5e3284c0..ddffd1f018 100644 --- a/packages/backend/src/plugins/proxy.ts +++ b/packages/backend/src/plugins/proxy.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/rollbar.ts b/packages/backend/src/plugins/rollbar.ts index b510346af5..d2fbfdd43d 100644 --- a/packages/backend/src/plugins/rollbar.ts +++ b/packages/backend/src/plugins/rollbar.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 9c7c3c12f3..619134a990 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,9 @@ * limitations under the License. */ -import { - DockerContainerRunner, - SingleHostDiscovery, -} from '@backstage/backend-common'; +import { DockerContainerRunner } from '@backstage/backend-common'; import { CatalogClient } from '@backstage/catalog-client'; -import { - CookieCutter, - CreateReactAppTemplater, - createRouter, - Preparers, - Publishers, - Templaters, -} from '@backstage/plugin-scaffolder-backend'; +import { createRouter } from '@backstage/plugin-scaffolder-backend'; import Docker from 'dockerode'; import { Router } from 'express'; import type { PluginEnvironment } from '../types'; @@ -36,27 +26,15 @@ export default async function createPlugin({ config, database, reader, + discovery, }: PluginEnvironment): Promise { const dockerClient = new Docker(); const containerRunner = new DockerContainerRunner({ dockerClient }); - const cookiecutterTemplater = new CookieCutter({ containerRunner }); - const craTemplater = new CreateReactAppTemplater({ containerRunner }); - const templaters = new Templaters(); - - templaters.register('cookiecutter', cookiecutterTemplater); - templaters.register('cra', craTemplater); - - const preparers = await Preparers.fromConfig(config, { logger }); - const publishers = await Publishers.fromConfig(config, { logger }); - - const discovery = SingleHostDiscovery.fromConfig(config); const catalogClient = new CatalogClient({ discoveryApi: discovery }); return await createRouter({ - preparers, - templaters, - publishers, + containerRunner, logger, config, database, diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index e587cdb606..4a1e415c74 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts index dbdc455e09..eb1e0502db 100644 --- a/packages/backend/src/plugins/techdocs.ts +++ b/packages/backend/src/plugins/techdocs.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/plugins/todo.ts b/packages/backend/src/plugins/todo.ts index 84f2feac92..df90e5a41e 100644 --- a/packages/backend/src/plugins/todo.ts +++ b/packages/backend/src/plugins/todo.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 356dd08d5f..8290e569ef 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 847924b67b..70b79b961f 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/catalog-client +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@0.9.0 + +## 0.3.15 + +### Patch Changes + +- ca080cab8: Don't crash if the entities response doesn't include the entities name and kind + +## 0.3.14 + +### Patch Changes + +- 45ef515d0: Return entities sorted alphabetically by ref +- Updated dependencies + - @backstage/catalog-model@0.8.4 + ## 0.3.13 ### Patch Changes diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 1f911c84e9..2abdeb8c75 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -3,83 +3,130 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { Location as Location_2 } from '@backstage/catalog-model'; // @public (undocumented) export type AddLocationRequest = { - type?: string; - target: string; - dryRun?: boolean; - presence?: 'optional' | 'required'; + type?: string; + target: string; + dryRun?: boolean; + presence?: 'optional' | 'required'; }; // @public (undocumented) export type AddLocationResponse = { - location: Location_2; - entities: Entity[]; + location: Location_2; + entities: Entity[]; }; // @public (undocumented) export interface CatalogApi { - // (undocumented) - addLocation(location: AddLocationRequest, options?: CatalogRequestOptions): Promise; - // (undocumented) - getEntities(request?: CatalogEntitiesRequest, options?: CatalogRequestOptions): Promise>; - // (undocumented) - getEntityByName(name: EntityName, options?: CatalogRequestOptions): Promise; - // (undocumented) - getLocationByEntity(entity: Entity, options?: CatalogRequestOptions): Promise; - // (undocumented) - getLocationById(id: string, options?: CatalogRequestOptions): Promise; - // (undocumented) - getOriginLocationByEntity(entity: Entity, options?: CatalogRequestOptions): Promise; - // (undocumented) - removeEntityByUid(uid: string, options?: CatalogRequestOptions): Promise; - // (undocumented) - removeLocationById(id: string, options?: CatalogRequestOptions): Promise; + // (undocumented) + addLocation( + location: AddLocationRequest, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getEntities( + request?: CatalogEntitiesRequest, + options?: CatalogRequestOptions, + ): Promise>; + // (undocumented) + getEntityByName( + name: EntityName, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getLocationByEntity( + entity: Entity, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getLocationById( + id: string, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getOriginLocationByEntity( + entity: Entity, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + removeEntityByUid( + uid: string, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + removeLocationById( + id: string, + options?: CatalogRequestOptions, + ): Promise; } // @public (undocumented) export class CatalogClient implements CatalogApi { - constructor(options: { - discoveryApi: DiscoveryApi; - }); - // (undocumented) - addLocation({ type, target, dryRun, presence }: AddLocationRequest, options?: CatalogRequestOptions): Promise; - // (undocumented) - getEntities(request?: CatalogEntitiesRequest, options?: CatalogRequestOptions): Promise>; - // (undocumented) - getEntityByName(compoundName: EntityName, options?: CatalogRequestOptions): Promise; - // (undocumented) - getLocationByEntity(entity: Entity, options?: CatalogRequestOptions): Promise; - // (undocumented) - getLocationById(id: string, options?: CatalogRequestOptions): Promise; - // (undocumented) - getOriginLocationByEntity(entity: Entity, options?: CatalogRequestOptions): Promise; - // (undocumented) - removeEntityByUid(uid: string, options?: CatalogRequestOptions): Promise; - // (undocumented) - removeLocationById(id: string, options?: CatalogRequestOptions): Promise; - } + constructor(options: { discoveryApi: DiscoveryApi }); + // (undocumented) + addLocation( + { type, target, dryRun, presence }: AddLocationRequest, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getEntities( + request?: CatalogEntitiesRequest, + options?: CatalogRequestOptions, + ): Promise>; + // (undocumented) + getEntityByName( + compoundName: EntityName, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getLocationByEntity( + entity: Entity, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getLocationById( + id: string, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + getOriginLocationByEntity( + entity: Entity, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + removeEntityByUid( + uid: string, + options?: CatalogRequestOptions, + ): Promise; + // (undocumented) + removeLocationById( + id: string, + options?: CatalogRequestOptions, + ): Promise; +} // @public (undocumented) export type CatalogEntitiesRequest = { - filter?: Record[] | Record | undefined; - fields?: string[] | undefined; + filter?: + | Record[] + | Record + | undefined; + fields?: string[] | undefined; }; // @public (undocumented) export type CatalogListResponse = { - items: T[]; + items: T[]; }; // @public -export const ENTITY_STATUS_CATALOG_PROCESSING_TYPE = "backstage.io/catalog-processing"; - +export const ENTITY_STATUS_CATALOG_PROCESSING_TYPE = + 'backstage.io/catalog-processing'; // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index c366374bf5..fbb37d2449 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.13", + "version": "0.3.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,15 +29,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.3", "@types/jest": "^26.0.7", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index 98b378f5b0..4e3d95f99f 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ describe('CatalogClient', () => { apiVersion: '1', kind: 'Component', metadata: { - name: 'Test1', + name: 'Test2', namespace: 'test1', }, }, @@ -55,13 +55,13 @@ describe('CatalogClient', () => { apiVersion: '1', kind: 'Component', metadata: { - name: 'Test2', + name: 'Test1', namespace: 'test1', }, }, ]; const defaultResponse: CatalogListResponse = { - items: defaultServiceResponse, + items: defaultServiceResponse.reverse(), }; beforeEach(() => { @@ -72,7 +72,7 @@ describe('CatalogClient', () => { ); }); - it('should entities from correct endpoint', async () => { + it('should fetch entities from correct endpoint', async () => { const response = await client.getEntities({}, { token }); expect(response).toEqual(defaultResponse); }); @@ -151,6 +151,26 @@ describe('CatalogClient', () => { expect(response.items).toEqual([]); }); + + it('handles field filtered entities', async () => { + server.use( + rest.get(`${mockBaseUrl}/entities`, (_req, res, ctx) => { + return res(ctx.json([{ apiVersion: '1' }, { apiVersion: '2' }])); + }), + ); + + const response = await client.getEntities( + { + fields: ['apiVersion'], + }, + { token }, + ); + + expect(response.items).toEqual([ + { apiVersion: '1' }, + { apiVersion: '2' }, + ]); + }); }); describe('getLocationById', () => { diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 141929f3da..5dfcddde0c 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import { Location, LOCATION_ANNOTATION, ORIGIN_LOCATION_ANNOTATION, + stringifyEntityRef, stringifyLocationReference, } from '@backstage/catalog-model'; import { ResponseError } from '@backstage/errors'; @@ -89,7 +90,30 @@ export class CatalogClient implements CatalogApi { `/entities${query}`, options, ); - return { items: entities }; + + const refCompare = (a: Entity, b: Entity) => { + // in case field filtering is used, these fields might not be part of the response + if ( + a.metadata?.name === undefined || + a.kind === undefined || + b.metadata?.name === undefined || + b.kind === undefined + ) { + return 0; + } + + const aRef = stringifyEntityRef(a); + const bRef = stringifyEntityRef(b); + if (aRef < bRef) { + return -1; + } + if (aRef > bRef) { + return 1; + } + return 0; + }; + + return { items: entities.sort(refCompare) }; } async getEntityByName( diff --git a/packages/catalog-client/src/index.ts b/packages/catalog-client/src/index.ts index 59c652f9fd..42a15a27d5 100644 --- a/packages/catalog-client/src/index.ts +++ b/packages/catalog-client/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/setupTests.ts b/packages/catalog-client/src/setupTests.ts index ba33cf996b..d3232290a7 100644 --- a/packages/catalog-client/src/setupTests.ts +++ b/packages/catalog-client/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index f9ba2e9d8d..ae3fd7b514 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/types/discovery.ts b/packages/catalog-client/src/types/discovery.ts index 90eb748b2f..447998b3b8 100644 --- a/packages/catalog-client/src/types/discovery.ts +++ b/packages/catalog-client/src/types/discovery.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/types/index.ts b/packages/catalog-client/src/types/index.ts index c1670659ec..842d0393a4 100644 --- a/packages/catalog-client/src/types/index.ts +++ b/packages/catalog-client/src/types/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-client/src/types/status.ts b/packages/catalog-client/src/types/status.ts index 7990c4b121..d2935b890e 100644 --- a/packages/catalog-client/src/types/status.ts +++ b/packages/catalog-client/src/types/status.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index df350ff288..5dbbfd241f 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/catalog-model +## 0.9.0 + +### Minor Changes + +- 77db0c454: Changed the regex to validate names following the Kubernetes validation rule, this allow to be more permissive validating the name of the object in Backstage. +- 60e830222: Support for `Template` kinds with version `backstage.io/v1alpha1` has now been removed. This means that the old method of running templates with `Preparers`, `Templaters` and `Publishers` has also been removed. If you had any logic in these abstractions, they should now be moved to `actions` instead, and you can find out more about those in the [documentation](https://backstage.io/docs/features/software-templates/writing-custom-actions) + + If you need any help migrating existing templates, there's a [migration guide](https://backstage.io/docs/features/software-templates/migrating-from-v1alpha1-to-v1beta2). Reach out to us on Discord in the #support channel if you're having problems. + + The `scaffolder-backend` now no longer requires these `Preparers`, `Templaters`, and `Publishers` to be passed in, now all it needs is the `containerRunner`. + + Please update your `packages/backend/src/plugins/scaffolder.ts` like the following + + ```diff + - import { + - DockerContainerRunner, + - SingleHostDiscovery, + - } from '@backstage/backend-common'; + + import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + - import { + - CookieCutter, + - CreateReactAppTemplater, + - createRouter, + - Preparers, + - Publishers, + - Templaters, + - } from '@backstage/plugin-scaffolder-backend'; + + import { createRouter } from '@backstage/plugin-scaffolder-backend'; + import Docker from 'dockerode'; + import { Router } from 'express'; + import type { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + config, + database, + reader, + + discovery, + }: PluginEnvironment): Promise { + const dockerClient = new Docker(); + const containerRunner = new DockerContainerRunner({ dockerClient }); + + - const cookiecutterTemplater = new CookieCutter({ containerRunner }); + - const craTemplater = new CreateReactAppTemplater({ containerRunner }); + - const templaters = new Templaters(); + + - templaters.register('cookiecutter', cookiecutterTemplater); + - templaters.register('cra', craTemplater); + - + - const preparers = await Preparers.fromConfig(config, { logger }); + - const publishers = await Publishers.fromConfig(config, { logger }); + + - const discovery = SingleHostDiscovery.fromConfig(config); + const catalogClient = new CatalogClient({ discoveryApi: discovery }); + + return await createRouter({ + - preparers, + - templaters, + - publishers, + + containerRunner, + logger, + config, + database, + + ``` + +## 0.8.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. + +## 0.8.3 + +### Patch Changes + +- 1d2ed7844: Removed unused `typescript-json-schema` dependency. + ## 0.8.2 ### Patch Changes diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 0dab9670de..74027716de 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -3,212 +3,229 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { JsonObject } from '@backstage/config'; import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/config'; import { SerializedError } from '@backstage/errors'; import * as yup from 'yup'; -// @public (undocumented) -export const analyzeLocationSchema: yup.ObjectSchema<{ +// @public @deprecated (undocumented) +export const analyzeLocationSchema: yup.ObjectSchema< + { location: LocationSpec; -}, object>; + }, + object +>; // @public (undocumented) interface ApiEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'API'; - // (undocumented) - spec: { - type: string; - lifecycle: string; - owner: string; - definition: string; - system?: string; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'API'; + // (undocumented) + spec: { + type: string; + lifecycle: string; + owner: string; + definition: string; + system?: string; + }; } - -export { ApiEntityV1alpha1 as ApiEntity } - -export { ApiEntityV1alpha1 } +export { ApiEntityV1alpha1 as ApiEntity }; +export { ApiEntityV1alpha1 }; // @public (undocumented) export const apiEntityV1alpha1Validator: KindValidator; // @public export class CommonValidatorFunctions { - static isJsonSafe(value: unknown): boolean; - static isValidDnsLabel(value: unknown): boolean; - static isValidDnsSubdomain(value: unknown): boolean; - static isValidPrefixAndOrSuffix(value: unknown, separator: string, isValidPrefix: (value: string) => boolean, isValidSuffix: (value: string) => boolean): boolean; - static isValidString(value: unknown): boolean; - static isValidUrl(value: unknown): boolean; + static isJsonSafe(value: unknown): boolean; + static isValidDnsLabel(value: unknown): boolean; + static isValidDnsSubdomain(value: unknown): boolean; + static isValidPrefixAndOrSuffix( + value: unknown, + separator: string, + isValidPrefix: (value: string) => boolean, + isValidSuffix: (value: string) => boolean, + ): boolean; + static isValidString(value: unknown): boolean; + static isValidUrl(value: unknown): boolean; } // @public -export function compareEntityToRef(entity: Entity, ref: EntityRef | EntityName, context?: EntityRefContext): boolean; +export function compareEntityToRef( + entity: Entity, + ref: EntityRef | EntityName, + context?: EntityRefContext, +): boolean; // @public (undocumented) interface ComponentEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Component'; - // (undocumented) - spec: { - type: string; - lifecycle: string; - owner: string; - subcomponentOf?: string; - providesApis?: string[]; - consumesApis?: string[]; - dependsOn?: string[]; - system?: string; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'Component'; + // (undocumented) + spec: { + type: string; + lifecycle: string; + owner: string; + subcomponentOf?: string; + providesApis?: string[]; + consumesApis?: string[]; + dependsOn?: string[]; + system?: string; + }; } - -export { ComponentEntityV1alpha1 as ComponentEntity } - -export { ComponentEntityV1alpha1 } +export { ComponentEntityV1alpha1 as ComponentEntity }; +export { ComponentEntityV1alpha1 }; // @public (undocumented) export const componentEntityV1alpha1Validator: KindValidator; // @public export class DefaultNamespaceEntityPolicy implements EntityPolicy { - constructor(namespace?: string); - // (undocumented) - enforce(entity: Entity): Promise; - } + constructor(namespace?: string); + // (undocumented) + enforce(entity: Entity): Promise; +} // @public (undocumented) interface DomainEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Domain'; - // (undocumented) - spec: { - owner: string; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'Domain'; + // (undocumented) + spec: { + owner: string; + }; } - -export { DomainEntityV1alpha1 as DomainEntity } - -export { DomainEntityV1alpha1 } +export { DomainEntityV1alpha1 as DomainEntity }; +export { DomainEntityV1alpha1 }; // @public (undocumented) export const domainEntityV1alpha1Validator: KindValidator; // @public (undocumented) -export const EDIT_URL_ANNOTATION = "backstage.io/edit-url"; +export const EDIT_URL_ANNOTATION = 'backstage.io/edit-url'; // @public export type Entity = { - apiVersion: string; - kind: string; - metadata: EntityMeta; - spec?: JsonObject; - relations?: EntityRelation[]; - status?: UNSTABLE_EntityStatus; + apiVersion: string; + kind: string; + metadata: EntityMeta; + spec?: JsonObject; + relations?: EntityRelation[]; + status?: UNSTABLE_EntityStatus; }; // @public -export const ENTITY_DEFAULT_NAMESPACE = "default"; +export const ENTITY_DEFAULT_NAMESPACE = 'default'; // @public -export const ENTITY_META_GENERATED_FIELDS: readonly ["uid", "etag", "generation"]; +export const ENTITY_META_GENERATED_FIELDS: readonly [ + 'uid', + 'etag', + 'generation', +]; // @public export type EntityEnvelope = { - apiVersion: string; - kind: string; - metadata: { - name: string; - namespace?: string; - }; + apiVersion: string; + kind: string; + metadata: { + name: string; + namespace?: string; + }; }; // @public -export function entityEnvelopeSchemaValidator(schema?: unknown): (data: unknown) => T; +export function entityEnvelopeSchemaValidator< + T extends EntityEnvelope = EntityEnvelope +>(schema?: unknown): (data: unknown) => T; // @public export function entityHasChanges(previous: Entity, next: Entity): boolean; // @public -export function entityKindSchemaValidator(schema: unknown): (data: unknown) => T | false; +export function entityKindSchemaValidator( + schema: unknown, +): (data: unknown) => T | false; // @public export type EntityLink = { - url: string; - title?: string; - icon?: string; + url: string; + title?: string; + icon?: string; }; // @public export type EntityMeta = JsonObject & { - uid?: string; - etag?: string; - generation?: number; - name: string; - namespace?: string; - description?: string; - labels?: Record; - annotations?: Record; - tags?: string[]; - links?: EntityLink[]; + uid?: string; + etag?: string; + generation?: number; + name: string; + namespace?: string; + description?: string; + labels?: Record; + annotations?: Record; + tags?: string[]; + links?: EntityLink[]; }; // @public export type EntityName = { - kind: string; - namespace: string; - name: string; + kind: string; + namespace: string; + name: string; }; // @public (undocumented) export const EntityPolicies: { - allOf(policies: EntityPolicy[]): AllEntityPolicies; - oneOf(policies: EntityPolicy[]): AnyEntityPolicy; + allOf(policies: EntityPolicy[]): AllEntityPolicies; + oneOf(policies: EntityPolicy[]): AnyEntityPolicy; }; // @public export type EntityPolicy = { - enforce(entity: Entity): Promise; + enforce(entity: Entity): Promise; }; // @public -export type EntityRef = string | { - kind?: string; - namespace?: string; - name: string; -}; +export type EntityRef = + | string + | { + kind?: string; + namespace?: string; + name: string; + }; // @public export type EntityRelation = { - type: string; - target: EntityName; + type: string; + target: EntityName; }; // @public export type EntityRelationSpec = { - source: EntityName; - type: string; - target: EntityName; + source: EntityName; + type: string; + target: EntityName; }; // @public -export function entitySchemaValidator(schema?: unknown): (data: unknown) => T; +export function entitySchemaValidator( + schema?: unknown, +): (data: unknown) => T; // @public export class FieldFormatEntityPolicy implements EntityPolicy { - constructor(validators?: Validators); - // (undocumented) - enforce(entity: Entity): Promise; - } + constructor(validators?: Validators); + // (undocumented) + enforce(entity: Entity): Promise; +} // @public export function generateEntityEtag(): string; @@ -223,110 +240,108 @@ export function generateUpdatedEntity(previous: Entity, next: Entity): Entity; export function getEntityName(entity: Entity): EntityName; // @public -export function getEntitySourceLocation(entity: Entity): { - type: string; - target: string; +export function getEntitySourceLocation( + entity: Entity, +): { + type: string; + target: string; }; // @public (undocumented) interface GroupEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Group'; - // (undocumented) - spec: { - type: string; - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; - parent?: string; - children: string[]; - members?: string[]; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'Group'; + // (undocumented) + spec: { + type: string; + profile?: { + displayName?: string; + email?: string; + picture?: string; }; + parent?: string; + children: string[]; + members?: string[]; + }; } - -export { GroupEntityV1alpha1 as GroupEntity } - -export { GroupEntityV1alpha1 } +export { GroupEntityV1alpha1 as GroupEntity }; +export { GroupEntityV1alpha1 }; // @public (undocumented) export const groupEntityV1alpha1Validator: KindValidator; // @public (undocumented) -export type JSONSchema = JSONSchema7 & { +export type JSONSchema = JSONSchema7 & + { [key in string]?: JsonValue; -}; + }; // @public export type KindValidator = { - check(entity: Entity): Promise; + check(entity: Entity): Promise; }; // @public export class KubernetesValidatorFunctions { - // (undocumented) - static isValidAnnotationKey(value: unknown): boolean; - // (undocumented) - static isValidAnnotationValue(value: unknown): boolean; - // (undocumented) - static isValidApiVersion(value: unknown): boolean; - // (undocumented) - static isValidKind(value: unknown): boolean; - // (undocumented) - static isValidLabelKey(value: unknown): boolean; - // (undocumented) - static isValidLabelValue(value: unknown): boolean; - // (undocumented) - static isValidNamespace(value: unknown): boolean; - // (undocumented) - static isValidObjectName(value: unknown): boolean; + // (undocumented) + static isValidAnnotationKey(value: unknown): boolean; + // (undocumented) + static isValidAnnotationValue(value: unknown): boolean; + // (undocumented) + static isValidApiVersion(value: unknown): boolean; + // (undocumented) + static isValidKind(value: unknown): boolean; + // (undocumented) + static isValidLabelKey(value: unknown): boolean; + // (undocumented) + static isValidLabelValue(value: unknown): boolean; + // (undocumented) + static isValidNamespace(value: unknown): boolean; + // (undocumented) + static isValidObjectName(value: unknown): boolean; } // @public (undocumented) type Location_2 = { - id: string; + id: string; } & LocationSpec; - -export { Location_2 as Location } +export { Location_2 as Location }; // @public (undocumented) -export const LOCATION_ANNOTATION = "backstage.io/managed-by-location"; +export const LOCATION_ANNOTATION = 'backstage.io/managed-by-location'; // @public (undocumented) interface LocationEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Location'; - // (undocumented) - spec: { - type?: string; - target?: string; - targets?: string[]; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'Location'; + // (undocumented) + spec: { + type?: string; + target?: string; + targets?: string[]; + }; } - -export { LocationEntityV1alpha1 as LocationEntity } - -export { LocationEntityV1alpha1 } +export { LocationEntityV1alpha1 as LocationEntity }; +export { LocationEntityV1alpha1 }; // @public (undocumented) export const locationEntityV1alpha1Validator: KindValidator; -// @public (undocumented) +// @public @deprecated (undocumented) export const locationSchema: yup.ObjectSchema; // @public (undocumented) export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; + type: string; + target: string; + presence?: 'optional' | 'required'; }; -// @public (undocumented) +// @public @deprecated (undocumented) export const locationSpecSchema: yup.ObjectSchema; // @public (undocumented) @@ -334,213 +349,209 @@ export function makeValidator(overrides?: Partial): Validators; // @public export class NoForeignRootFieldsEntityPolicy implements EntityPolicy { - constructor(knownFields?: string[]); - // (undocumented) - enforce(entity: Entity): Promise; - } + constructor(knownFields?: string[]); + // (undocumented) + enforce(entity: Entity): Promise; +} // @public (undocumented) -export const ORIGIN_LOCATION_ANNOTATION = "backstage.io/managed-by-origin-location"; +export const ORIGIN_LOCATION_ANNOTATION = + 'backstage.io/managed-by-origin-location'; // @public -export function parseEntityName(ref: EntityRef, context?: EntityRefContext): EntityName; +export function parseEntityName( + ref: EntityRef, + context?: EntityRefContext, +): EntityName; // @public -export function parseEntityRef(ref: EntityRef, context?: { +export function parseEntityRef( + ref: EntityRef, + context?: { defaultKind: string; defaultNamespace: string; -}): { - kind: string; - namespace: string; - name: string; + }, +): { + kind: string; + namespace: string; + name: string; }; // @public (undocumented) -export function parseEntityRef(ref: EntityRef, context?: { +export function parseEntityRef( + ref: EntityRef, + context?: { defaultKind: string; -}): { - kind: string; - namespace?: string; - name: string; + }, +): { + kind: string; + namespace?: string; + name: string; }; // @public (undocumented) -export function parseEntityRef(ref: EntityRef, context?: { +export function parseEntityRef( + ref: EntityRef, + context?: { defaultNamespace: string; -}): { - kind?: string; - namespace: string; - name: string; + }, +): { + kind?: string; + namespace: string; + name: string; }; // @public -export function parseLocationReference(ref: string): { - type: string; - target: string; +export function parseLocationReference( + ref: string, +): { + type: string; + target: string; }; // @public (undocumented) -export const RELATION_API_CONSUMED_BY = "apiConsumedBy"; +export const RELATION_API_CONSUMED_BY = 'apiConsumedBy'; // @public (undocumented) -export const RELATION_API_PROVIDED_BY = "apiProvidedBy"; +export const RELATION_API_PROVIDED_BY = 'apiProvidedBy'; // @public (undocumented) -export const RELATION_CHILD_OF = "childOf"; +export const RELATION_CHILD_OF = 'childOf'; // @public -export const RELATION_CONSUMES_API = "consumesApi"; +export const RELATION_CONSUMES_API = 'consumesApi'; // @public (undocumented) -export const RELATION_DEPENDENCY_OF = "dependencyOf"; +export const RELATION_DEPENDENCY_OF = 'dependencyOf'; // @public -export const RELATION_DEPENDS_ON = "dependsOn"; +export const RELATION_DEPENDS_ON = 'dependsOn'; // @public (undocumented) -export const RELATION_HAS_MEMBER = "hasMember"; +export const RELATION_HAS_MEMBER = 'hasMember'; // @public (undocumented) -export const RELATION_HAS_PART = "hasPart"; +export const RELATION_HAS_PART = 'hasPart'; // @public -export const RELATION_MEMBER_OF = "memberOf"; +export const RELATION_MEMBER_OF = 'memberOf'; // @public -export const RELATION_OWNED_BY = "ownedBy"; +export const RELATION_OWNED_BY = 'ownedBy'; // @public (undocumented) -export const RELATION_OWNER_OF = "ownerOf"; +export const RELATION_OWNER_OF = 'ownerOf'; // @public -export const RELATION_PARENT_OF = "parentOf"; +export const RELATION_PARENT_OF = 'parentOf'; // @public -export const RELATION_PART_OF = "partOf"; +export const RELATION_PART_OF = 'partOf'; // @public (undocumented) -export const RELATION_PROVIDES_API = "providesApi"; +export const RELATION_PROVIDES_API = 'providesApi'; // @public (undocumented) interface ResourceEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Resource'; - // (undocumented) - spec: { - type: string; - owner: string; - dependsOn?: string[]; - system?: string; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'Resource'; + // (undocumented) + spec: { + type: string; + owner: string; + dependsOn?: string[]; + system?: string; + }; } - -export { ResourceEntityV1alpha1 as ResourceEntity } - -export { ResourceEntityV1alpha1 } +export { ResourceEntityV1alpha1 as ResourceEntity }; +export { ResourceEntityV1alpha1 }; // @public (undocumented) export const resourceEntityV1alpha1Validator: KindValidator; // @public export class SchemaValidEntityPolicy implements EntityPolicy { - // (undocumented) - enforce(entity: Entity): Promise; - } + // (undocumented) + enforce(entity: Entity): Promise; +} // @public @deprecated -export function serializeEntityRef(ref: Entity | { - kind?: string; - namespace?: string; - name: string; -}): EntityRef; +export function serializeEntityRef( + ref: + | Entity + | { + kind?: string; + namespace?: string; + name: string; + }, +): EntityRef; // @public (undocumented) -export const SOURCE_LOCATION_ANNOTATION = "backstage.io/source-location"; +export const SOURCE_LOCATION_ANNOTATION = 'backstage.io/source-location'; // @public -export function stringifyEntityRef(ref: Entity | { - kind: string; - namespace?: string; - name: string; -}): string; +export function stringifyEntityRef( + ref: + | Entity + | { + kind: string; + namespace?: string; + name: string; + }, +): string; // @public export function stringifyLocationReference(ref: { - type: string; - target: string; + type: string; + target: string; }): string; // @public (undocumented) interface SystemEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'System'; - // (undocumented) - spec: { - owner: string; - domain?: string; - }; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'System'; + // (undocumented) + spec: { + owner: string; + domain?: string; + }; } - -export { SystemEntityV1alpha1 as SystemEntity } - -export { SystemEntityV1alpha1 } +export { SystemEntityV1alpha1 as SystemEntity }; +export { SystemEntityV1alpha1 }; // @public (undocumented) export const systemEntityV1alpha1Validator: KindValidator; -// @public (undocumented) -interface TemplateEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'Template'; - // (undocumented) - spec: { - type: string; - templater: string; - path?: string; - schema: JSONSchema; - owner?: string; - }; -} - -export { TemplateEntityV1alpha1 as TemplateEntity } - -export { TemplateEntityV1alpha1 } - -// @public (undocumented) -export const templateEntityV1alpha1Validator: KindValidator; - // @public (undocumented) export interface TemplateEntityV1beta2 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1beta2'; - // (undocumented) - kind: 'Template'; - // (undocumented) - metadata: EntityMeta & { - title?: string; - }; - // (undocumented) - spec: { - type: string; - parameters?: JsonObject | JsonObject[]; - steps: Array<{ - id?: string; - name?: string; - action: string; - input?: JsonObject; - if?: string | boolean; - }>; - output?: { - [name: string]: string; - }; - owner?: string; + // (undocumented) + apiVersion: 'backstage.io/v1beta2'; + // (undocumented) + kind: 'Template'; + // (undocumented) + metadata: EntityMeta & { + title?: string; + }; + // (undocumented) + spec: { + type: string; + parameters?: JsonObject | JsonObject[]; + steps: Array<{ + id?: string; + name?: string; + action: string; + input?: JsonObject; + if?: string | boolean; + }>; + output?: { + [name: string]: string; }; + owner?: string; + }; } // @public (undocumented) @@ -548,15 +559,15 @@ export const templateEntityV1beta2Validator: KindValidator; // @alpha export type UNSTABLE_EntityStatus = { - items?: UNSTABLE_EntityStatusItem[]; + items?: UNSTABLE_EntityStatusItem[]; }; // @alpha export type UNSTABLE_EntityStatusItem = { - type: string; - level: UNSTABLE_EntityStatusLevel; - message: string; - error?: SerializedError; + type: string; + level: UNSTABLE_EntityStatusLevel; + message: string; + error?: SerializedError; }; // @alpha @@ -564,45 +575,41 @@ export type UNSTABLE_EntityStatusLevel = 'info' | 'warning' | 'error'; // @public (undocumented) interface UserEntityV1alpha1 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - // (undocumented) - kind: 'User'; - // (undocumented) - spec: { - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; - memberOf: string[]; + // (undocumented) + apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; + // (undocumented) + kind: 'User'; + // (undocumented) + spec: { + profile?: { + displayName?: string; + email?: string; + picture?: string; }; + memberOf: string[]; + }; } - -export { UserEntityV1alpha1 as UserEntity } - -export { UserEntityV1alpha1 } +export { UserEntityV1alpha1 as UserEntity }; +export { UserEntityV1alpha1 }; // @public (undocumented) export const userEntityV1alpha1Validator: KindValidator; // @public (undocumented) export type Validators = { - isValidApiVersion(value: unknown): boolean; - isValidKind(value: unknown): boolean; - isValidEntityName(value: unknown): boolean; - isValidNamespace(value: unknown): boolean; - isValidLabelKey(value: unknown): boolean; - isValidLabelValue(value: unknown): boolean; - isValidAnnotationKey(value: unknown): boolean; - isValidAnnotationValue(value: unknown): boolean; - isValidTag(value: unknown): boolean; + isValidApiVersion(value: unknown): boolean; + isValidKind(value: unknown): boolean; + isValidEntityName(value: unknown): boolean; + isValidNamespace(value: unknown): boolean; + isValidLabelKey(value: unknown): boolean; + isValidLabelValue(value: unknown): boolean; + isValidAnnotationKey(value: unknown): boolean; + isValidAnnotationValue(value: unknown): boolean; + isValidTag(value: unknown): boolean; }; // @public -export const VIEW_URL_ANNOTATION = "backstage.io/view-url"; - +export const VIEW_URL_ANNOTATION = 'backstage.io/view-url'; // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/catalog-model/examples/apis/wayback-archive-api.yaml b/packages/catalog-model/examples/apis/wayback-archive-api.yaml index dbed93dbe8..8c2a9c7d6e 100644 --- a/packages/catalog-model/examples/apis/wayback-archive-api.yaml +++ b/packages/catalog-model/examples/apis/wayback-archive-api.yaml @@ -8,4 +8,4 @@ spec: lifecycle: production owner: team-a definition: - $text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/archive.org/wayback/1.0.0/openapi.yaml + $text: https://github.com/APIs-guru/openapi-directory/blob/main/APIs/archive.org/wayback/1.0.0/openapi.yaml diff --git a/packages/catalog-model/examples/apis/wayback-search-api.yaml b/packages/catalog-model/examples/apis/wayback-search-api.yaml index b39b5df468..45fa5d1b19 100644 --- a/packages/catalog-model/examples/apis/wayback-search-api.yaml +++ b/packages/catalog-model/examples/apis/wayback-search-api.yaml @@ -8,4 +8,4 @@ spec: lifecycle: production owner: team-a definition: - $text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/archive.org/search/1.0.0/openapi.yaml + $text: https://github.com/APIs-guru/openapi-directory/blob/main/APIs/archive.org/search/1.0.0/openapi.yaml diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index e2ea7fad92..13c3214252 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.8.2", + "version": "0.9.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,13 +35,12 @@ "@types/yup": "^0.29.8", "ajv": "^7.0.3", "json-schema": "^0.3.0", - "typescript-json-schema": "^0.49.0", "lodash": "^4.17.15", "uuid": "^8.0.0", "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.2", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/catalog-model/src/EntityPolicies.test.ts b/packages/catalog-model/src/EntityPolicies.test.ts index b67180241e..c630094621 100644 --- a/packages/catalog-model/src/EntityPolicies.test.ts +++ b/packages/catalog-model/src/EntityPolicies.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/EntityPolicies.ts b/packages/catalog-model/src/EntityPolicies.ts index 2d576b1670..eca5e4b77a 100644 --- a/packages/catalog-model/src/EntityPolicies.ts +++ b/packages/catalog-model/src/EntityPolicies.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index 07b3d939aa..8843f10f5f 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/EntityEnvelope.ts b/packages/catalog-model/src/entity/EntityEnvelope.ts index 631a8873c7..ed21a7d666 100644 --- a/packages/catalog-model/src/entity/EntityEnvelope.ts +++ b/packages/catalog-model/src/entity/EntityEnvelope.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/EntityStatus.ts b/packages/catalog-model/src/entity/EntityStatus.ts index 67090f2bc2..92e1d1454f 100644 --- a/packages/catalog-model/src/entity/EntityStatus.ts +++ b/packages/catalog-model/src/entity/EntityStatus.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/constants.ts b/packages/catalog-model/src/entity/constants.ts index c8f88e3b0c..d46d839720 100644 --- a/packages/catalog-model/src/entity/constants.ts +++ b/packages/catalog-model/src/entity/constants.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index ae2c0bf503..d05045fc78 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts index c6bda864cb..55188d84c8 100644 --- a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts index 4f5bbe04f4..1750aece17 100644 --- a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts index 33ee045c1e..3e2639fff9 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts index 1ed13c972c..ee95fd16ea 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.test.ts index 03b0ed2eb5..fb1d703829 100644 --- a/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.ts b/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.ts index 7d401542ba..6700a935d5 100644 --- a/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/NoForeignRootFieldsEntityPolicy.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts index c84bdbdd38..21539aaba4 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts index 7e0a8df268..b5a4305a3c 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/index.ts b/packages/catalog-model/src/entity/policies/index.ts index 5d75ef4d84..ae14007d80 100644 --- a/packages/catalog-model/src/entity/policies/index.ts +++ b/packages/catalog-model/src/entity/policies/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/policies/types.ts b/packages/catalog-model/src/entity/policies/types.ts index 415c98bbd2..6c7f47c332 100644 --- a/packages/catalog-model/src/entity/policies/types.ts +++ b/packages/catalog-model/src/entity/policies/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/ref.test.ts b/packages/catalog-model/src/entity/ref.test.ts index 5ca511ee03..cf4bf0d869 100644 --- a/packages/catalog-model/src/entity/ref.test.ts +++ b/packages/catalog-model/src/entity/ref.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 09660811f0..91d8547c6b 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/util.test.ts b/packages/catalog-model/src/entity/util.test.ts index c7e2c036b5..1c39961c0f 100644 --- a/packages/catalog-model/src/entity/util.test.ts +++ b/packages/catalog-model/src/entity/util.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/entity/util.ts b/packages/catalog-model/src/entity/util.ts index 116913c85c..84b6845347 100644 --- a/packages/catalog-model/src/entity/util.ts +++ b/packages/catalog-model/src/entity/util.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/index.ts b/packages/catalog-model/src/index.ts index 976b5f6148..c6b9b6b956 100644 --- a/packages/catalog-model/src/index.ts +++ b/packages/catalog-model/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts index 249243bed3..6deaf2871a 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts index 37d5a4fba0..737037e6b2 100644 --- a/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ApiEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 358e7b6526..419b72d6a6 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 6ef45fda6e..4e08d4cce9 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts index 0e989f22ca..822a7984e9 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts index f23c330a87..c2f39de321 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts index 6e59637c67..284c3da7de 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 62a6edbc5e..28f1503223 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts index 2451df8e64..7efec38395 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts index dc79ff1921..4a37340bee 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts index 953ec5889a..5fda94ad2d 100644 --- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts index 4c79209c9c..c8f96b87e3 100644 --- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts index 7d744b7d0d..fd78633a89 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts index 41203083a5..7c719566ad 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts deleted file mode 100644 index bfb27b4ed6..0000000000 --- a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 { - TemplateEntityV1alpha1, - templateEntityV1alpha1Validator as validator, -} from './TemplateEntityV1alpha1'; - -describe('templateEntityV1alpha1Validator', () => { - let entity: TemplateEntityV1alpha1; - - beforeEach(() => { - entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Template', - metadata: { - name: 'test', - }, - spec: { - type: 'website', - templater: 'cookiecutter', - schema: { - $schema: 'http://json-schema.org/draft-07/schema#', - required: ['storePath', 'owner'], - properties: { - owner: { - type: 'string', - title: 'Owner', - description: 'Who is going to own this component', - }, - storePath: { - type: 'string', - title: 'Store path', - description: 'GitHub store path in org/repo format', - }, - }, - }, - owner: 'team-a@example.com', - }, - }; - }); - - it('happy path: accepts valid data', async () => { - await expect(validator.check(entity)).resolves.toBe(true); - }); - - it('silently accepts v1beta1 as well', async () => { - (entity as any).apiVersion = 'backstage.io/v1beta1'; - await expect(validator.check(entity)).resolves.toBe(true); - }); - - it('ignores unknown apiVersion', async () => { - (entity as any).apiVersion = 'backstage.io/v1beta0'; - await expect(validator.check(entity)).resolves.toBe(false); - }); - - it('ignores unknown kind', async () => { - (entity as any).kind = 'Wizard'; - await expect(validator.check(entity)).resolves.toBe(false); - }); - - it('rejects missing type', async () => { - delete (entity as any).spec.type; - await expect(validator.check(entity)).rejects.toThrow(/type/); - }); - - it('accepts any other type', async () => { - (entity as any).spec.type = 'hallo'; - await expect(validator.check(entity)).resolves.toBe(true); - }); - - it('rejects empty type', async () => { - (entity as any).spec.type = ''; - await expect(validator.check(entity)).rejects.toThrow(/type/); - }); - - it('rejects missing templater', async () => { - (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/); - }); -}); diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts deleted file mode 100644 index 0600c58278..0000000000 --- a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 type { Entity } from '../entity/Entity'; -import schema from '../schema/kinds/Template.v1alpha1.schema.json'; -import type { JSONSchema } from '../types'; -import { ajvCompiledJsonSchemaValidator } from './util'; - -export interface TemplateEntityV1alpha1 extends Entity { - apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; - kind: 'Template'; - spec: { - type: string; - templater: string; - path?: string; - schema: JSONSchema; - owner?: string; - }; -} - -export const templateEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator( - schema, -); diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts index b06a71c8da..f715f3c2b6 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts +++ b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts index 6ee6fcb1f1..2710fe1275 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts +++ b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.test.ts index 075f97e92c..3dfaea4617 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index d73fa7aaf3..267a9f07fd 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index e36575f51d..be9f7a0d6a 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,11 +50,6 @@ export type { SystemEntityV1alpha1 as SystemEntity, SystemEntityV1alpha1, } from './SystemEntityV1alpha1'; -export { templateEntityV1alpha1Validator } from './TemplateEntityV1alpha1'; -export type { - TemplateEntityV1alpha1 as TemplateEntity, - TemplateEntityV1alpha1, -} from './TemplateEntityV1alpha1'; export { templateEntityV1beta2Validator } from './TemplateEntityV1beta2'; export type { TemplateEntityV1beta2 } from './TemplateEntityV1beta2'; export type { KindValidator } from './types'; diff --git a/packages/catalog-model/src/kinds/relations.ts b/packages/catalog-model/src/kinds/relations.ts index 8ad5017fba..57977e7168 100644 --- a/packages/catalog-model/src/kinds/relations.ts +++ b/packages/catalog-model/src/kinds/relations.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/types.ts b/packages/catalog-model/src/kinds/types.ts index 4b947680c7..0ec0d313bb 100644 --- a/packages/catalog-model/src/kinds/types.ts +++ b/packages/catalog-model/src/kinds/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/kinds/util.ts b/packages/catalog-model/src/kinds/util.ts index a907df7f8a..c77b77cb97 100644 --- a/packages/catalog-model/src/kinds/util.ts +++ b/packages/catalog-model/src/kinds/util.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/annotation.ts b/packages/catalog-model/src/location/annotation.ts index ba875c3edf..137d36a0da 100644 --- a/packages/catalog-model/src/location/annotation.ts +++ b/packages/catalog-model/src/location/annotation.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/helpers.test.ts b/packages/catalog-model/src/location/helpers.test.ts index 3b5994b956..020f7be1a1 100644 --- a/packages/catalog-model/src/location/helpers.test.ts +++ b/packages/catalog-model/src/location/helpers.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index 5eff598c87..431ee71218 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index 751172c6ba..ead4a6b564 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/types.ts b/packages/catalog-model/src/location/types.ts index 33e443e04f..9837ce384d 100644 --- a/packages/catalog-model/src/location/types.ts +++ b/packages/catalog-model/src/location/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 4d2e602862..4857fc76bc 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import * as yup from 'yup'; import { LocationSpec, Location } from './types'; +/** @deprecated */ export const locationSpecSchema = yup .object({ type: yup.string().required(), @@ -26,6 +27,7 @@ export const locationSpecSchema = yup .noUnknown() .required(); +/** @deprecated */ export const locationSchema = yup .object({ id: yup.string().required(), @@ -35,6 +37,7 @@ export const locationSchema = yup .noUnknown() .required(); +/** @deprecated */ export const analyzeLocationSchema = yup .object<{ location: LocationSpec }>({ location: locationSpecSchema, diff --git a/packages/catalog-model/src/schema/kinds/Template.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Template.v1alpha1.schema.json deleted file mode 100644 index 5a0c0efa73..0000000000 --- a/packages/catalog-model/src/schema/kinds/Template.v1alpha1.schema.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "TemplateV1alpha1", - "description": "A Template describes a skeleton for use with the Scaffolder. It is used for describing what templating library is supported, and also for documenting the variables that the template requires using JSON Forms Schema.", - "examples": [ - { - "apiVersion": "backstage.io/v1alpha1", - "kind": "Template", - "metadata": { - "name": "react-ssr-template", - "title": "React SSR Template", - "description": "Next.js application skeleton for creating isomorphic web applications.", - "tags": ["recommended", "react"] - }, - "spec": { - "owner": "artist-relations-team", - "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": "Description of the component" - } - } - } - } - } - ], - "allOf": [ - { - "$ref": "Entity" - }, - { - "type": "object", - "required": ["spec"], - "properties": { - "apiVersion": { - "enum": ["backstage.io/v1alpha1", "backstage.io/v1beta1"] - }, - "kind": { - "enum": ["Template"] - }, - "metadata": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "The nice display name for the template. This field is required as is used to reference the template to the user instead of the metadata.name field.", - "examples": ["React SSR Template"], - "minLength": 1 - } - } - }, - "spec": { - "type": "object", - "required": ["type", "templater", "schema"], - "properties": { - "type": { - "type": "string", - "description": "The type of component created by the template. The software catalog accepts any type value, but an organization should take great care to establish a proper taxonomy for these. Tools including Backstage itself may read this field and behave differently depending on its value. For example, a website type component may present tooling in the Backstage interface that is specific to just websites.", - "examples": ["service", "website", "library"], - "minLength": 1 - }, - "templater": { - "type": "string", - "description": "The templating library that is supported by the template skeleton.", - "examples": ["cookiecutter"], - "minLength": 1 - }, - "path": { - "type": "string", - "description": "The string location where the templater should be run if it is not on the same level as the template.yaml definition.", - "examples": ["./cookiecutter/skeleton"], - "minLength": 1 - }, - "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 - } - } - } - } - } - ] -} diff --git a/packages/catalog-model/src/setupTests.ts b/packages/catalog-model/src/setupTests.ts index ba33cf996b..d3232290a7 100644 --- a/packages/catalog-model/src/setupTests.ts +++ b/packages/catalog-model/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/types.ts b/packages/catalog-model/src/types.ts index edac03466d..50de5a1cea 100644 --- a/packages/catalog-model/src/types.ts +++ b/packages/catalog-model/src/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/CommonValidatorFunctions.test.ts b/packages/catalog-model/src/validation/CommonValidatorFunctions.test.ts index bef997d54a..b15ac385b1 100644 --- a/packages/catalog-model/src/validation/CommonValidatorFunctions.test.ts +++ b/packages/catalog-model/src/validation/CommonValidatorFunctions.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/CommonValidatorFunctions.ts b/packages/catalog-model/src/validation/CommonValidatorFunctions.ts index 87b6ad3838..7c9736baf7 100644 --- a/packages/catalog-model/src/validation/CommonValidatorFunctions.ts +++ b/packages/catalog-model/src/validation/CommonValidatorFunctions.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/KubernetesValidatorFunctions.test.ts b/packages/catalog-model/src/validation/KubernetesValidatorFunctions.test.ts index d0673085b4..c194774906 100644 --- a/packages/catalog-model/src/validation/KubernetesValidatorFunctions.test.ts +++ b/packages/catalog-model/src/validation/KubernetesValidatorFunctions.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,9 +76,10 @@ describe('KubernetesValidatorFunctions', () => { ['a-b', true], ['-a-b', false], ['a-b-', false], - ['a--b', false], + ['a--b', true], ['a_b', true], ['a.b', true], + ['a..b', true], ])(`isValidObjectName %p ? %p`, (value, matches) => { expect(KubernetesValidatorFunctions.isValidObjectName(value)).toBe(matches); }); @@ -114,9 +115,10 @@ describe('KubernetesValidatorFunctions', () => { ['a-b', true], ['-a-b', false], ['a-b-', false], - ['a--b', false], + ['a--b', true], ['a_b', true], ['a.b', true], + ['a..b', true], ['a/a', true], ['a-b.c/a', true], ['a--b.c/a', false], @@ -150,9 +152,10 @@ describe('KubernetesValidatorFunctions', () => { ['a-b', true], ['-a-b', false], ['a-b-', false], - ['a--b', false], + ['a--b', true], ['a_b', true], ['a.b', true], + ['a..b', true], ])(`isValidLabelValue %p ? %p`, (value, matches) => { expect(KubernetesValidatorFunctions.isValidLabelValue(value)).toBe(matches); }); @@ -169,9 +172,10 @@ describe('KubernetesValidatorFunctions', () => { ['a-b', true], ['-a-b', false], ['a-b-', false], - ['a--b', false], + ['a--b', true], ['a_b', true], ['a.b', true], + ['a..b', true], ['a/a', true], ['a-b.c/a', true], ['a--b.c/a', false], diff --git a/packages/catalog-model/src/validation/KubernetesValidatorFunctions.ts b/packages/catalog-model/src/validation/KubernetesValidatorFunctions.ts index ada0aa71ff..86d27e7b13 100644 --- a/packages/catalog-model/src/validation/KubernetesValidatorFunctions.ts +++ b/packages/catalog-model/src/validation/KubernetesValidatorFunctions.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ export class KubernetesValidatorFunctions { typeof value === 'string' && value.length >= 1 && value.length <= 63 && - /^[a-z0-9A-Z]+([-_.][a-z0-9A-Z]+)*$/.test(value) + /^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$/.test(value) ); } diff --git a/packages/catalog-model/src/validation/ajv.ts b/packages/catalog-model/src/validation/ajv.ts index 02d53fcd15..c65c3df18a 100644 --- a/packages/catalog-model/src/validation/ajv.ts +++ b/packages/catalog-model/src/validation/ajv.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.test.ts b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.test.ts index 7c6613936c..e0cd08c95c 100644 --- a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.test.ts +++ b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts index 52ae00e399..2fe74ea2a4 100644 --- a/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts +++ b/packages/catalog-model/src/validation/entityEnvelopeSchemaValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entityKindSchemaValidator.test.ts b/packages/catalog-model/src/validation/entityKindSchemaValidator.test.ts index 4b258aed14..f954a2388c 100644 --- a/packages/catalog-model/src/validation/entityKindSchemaValidator.test.ts +++ b/packages/catalog-model/src/validation/entityKindSchemaValidator.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entityKindSchemaValidator.ts b/packages/catalog-model/src/validation/entityKindSchemaValidator.ts index c722687f9e..a295fac205 100644 --- a/packages/catalog-model/src/validation/entityKindSchemaValidator.ts +++ b/packages/catalog-model/src/validation/entityKindSchemaValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entitySchemaValidator.test.ts b/packages/catalog-model/src/validation/entitySchemaValidator.test.ts index 6ab2744c0f..3dfe307f92 100644 --- a/packages/catalog-model/src/validation/entitySchemaValidator.test.ts +++ b/packages/catalog-model/src/validation/entitySchemaValidator.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/entitySchemaValidator.ts b/packages/catalog-model/src/validation/entitySchemaValidator.ts index 8a30f09d31..8683ab6de6 100644 --- a/packages/catalog-model/src/validation/entitySchemaValidator.ts +++ b/packages/catalog-model/src/validation/entitySchemaValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/index.ts b/packages/catalog-model/src/validation/index.ts index bdf812b4ad..1ee14abb4a 100644 --- a/packages/catalog-model/src/validation/index.ts +++ b/packages/catalog-model/src/validation/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/makeValidator.ts b/packages/catalog-model/src/validation/makeValidator.ts index 63341f5c33..0ddd9d1088 100644 --- a/packages/catalog-model/src/validation/makeValidator.ts +++ b/packages/catalog-model/src/validation/makeValidator.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/catalog-model/src/validation/types.ts b/packages/catalog-model/src/validation/types.ts index a4475c4809..cfa9d845bc 100644 --- a/packages/catalog-model/src/validation/types.ts +++ b/packages/catalog-model/src/validation/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md new file mode 100644 index 0000000000..52a247f57a --- /dev/null +++ b/packages/cli-common/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/cli-common + +## 0.1.2 + +### Patch Changes + +- ab5cc376f: Add new `isChildPath` export diff --git a/packages/cli-common/api-report.md b/packages/cli-common/api-report.md index bcdc023c22..7141d27f3a 100644 --- a/packages/cli-common/api-report.md +++ b/packages/cli-common/api-report.md @@ -7,6 +7,9 @@ // @public export function findPaths(searchDir: string): Paths; +// @public +export function isChildPath(base: string, path: string): boolean; + // @public (undocumented) export type Paths = { ownDir: string; diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index a9fd066ef4..16f10717f9 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-common", "description": "Common functionality used by cli, backend, and create-app", - "version": "0.1.1", + "version": "0.1.2", "private": false, "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/cli-common/src/index.ts b/packages/cli-common/src/index.ts index a080f49b6b..1f4f17fe9f 100644 --- a/packages/cli-common/src/index.ts +++ b/packages/cli-common/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,4 +15,5 @@ */ export { findPaths } from './paths'; +export { isChildPath } from './isChildPath'; export type { Paths } from './paths'; diff --git a/packages/cli-common/src/isChildPath.test.ts b/packages/cli-common/src/isChildPath.test.ts new file mode 100644 index 0000000000..4c0d94f07c --- /dev/null +++ b/packages/cli-common/src/isChildPath.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { posix, win32 } from 'path'; + +describe('isChildPath', () => { + it('should check child posix paths', () => { + jest.isolateModules(() => { + jest.setMock('path', posix); + const { isChildPath } = require('./isChildPath'); + + expect(isChildPath('/', '/')).toBe(true); + expect(isChildPath('/x', '/x')).toBe(true); + expect(isChildPath('/x', '/x/y')).toBe(true); + expect(isChildPath('/x', '/x/x')).toBe(true); + expect(isChildPath('/x', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y/z', '/x/y/z')).toBe(true); + expect(isChildPath('/x/a b c/z', '/x/a b c/z')).toBe(true); + expect(isChildPath('/', '/ yz')).toBe(true); + + expect(isChildPath('/x', '/y')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + expect(isChildPath('/x', '/x y')).toBe(false); + expect(isChildPath('/x y', '/x yz')).toBe(false); + expect(isChildPath('/ yz', '/')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + + jest.dontMock('path'); + }); + }); + + it('should check child win32 paths', () => { + jest.isolateModules(() => { + jest.setMock('path', win32); + const { isChildPath } = require('./isChildPath'); + + expect(isChildPath('/x', '/x')).toBe(true); + expect(isChildPath('/x', '/x/y')).toBe(true); + expect(isChildPath('/x', '/x/x')).toBe(true); + expect(isChildPath('/x', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y', '/x/y/z')).toBe(true); + expect(isChildPath('/x/y/z', '/x/y/z')).toBe(true); + expect(isChildPath('Z:', 'Z:')).toBe(true); + expect(isChildPath('C:/', 'c:/')).toBe(true); + expect(isChildPath('C:/x', 'C:/x')).toBe(true); + expect(isChildPath('C:/x', 'c:/x')).toBe(true); + expect(isChildPath('C:/x', 'C:/x/y')).toBe(true); + expect(isChildPath('d:/x', 'D:/x/y')).toBe(true); + + expect(isChildPath('/x', '/y')).toBe(false); + expect(isChildPath('/x', '/')).toBe(false); + expect(isChildPath('C:/', 'D:/')).toBe(false); + expect(isChildPath('C:/x', 'D:/x')).toBe(false); + expect(isChildPath('D:/x', 'CD:/x')).toBe(false); + expect(isChildPath('D:/x', 'D:/y')).toBe(false); + + jest.dontMock('path'); + }); + }); +}); diff --git a/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/cli-common/src/isChildPath.ts similarity index 51% rename from packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts rename to packages/cli-common/src/isChildPath.ts index f18829d99c..f48c92db32 100644 --- a/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts +++ b/packages/cli-common/src/isChildPath.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PublishSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; -import { AlertApi, AlertMessage } from '../../definitions'; + +import { relative, isAbsolute } from 'path'; /** - * Base implementation for the AlertApi that simply forwards alerts to consumers. + * Checks if path is the same as or a child path of base. */ -export class AlertApiForwarder implements AlertApi { - private readonly subject = new PublishSubject(); - - post(alert: AlertMessage) { - this.subject.next(alert); +export function isChildPath(base: string, path: string): boolean { + const relativePath = relative(base, path); + if (relativePath === '') { + // The same directory + return true; } - alert$(): Observable { - return this.subject; - } + const outsideBase = relativePath.startsWith('..'); // not outside base + const differentDrive = isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base. + + return !outsideBase && !differentDrive; } diff --git a/packages/cli-common/src/paths.test.ts b/packages/cli-common/src/paths.test.ts index 2278ee3581..894060f2e8 100644 --- a/packages/cli-common/src/paths.test.ts +++ b/packages/cli-common/src/paths.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ describe('paths', () => { }); it('findPaths should find mocked package paths', () => { - const mockCwd = resolvePath(__dirname, '../../core'); + const mockCwd = resolvePath(__dirname, '../../config'); const mockDir = resolvePath(__dirname, '../../cli'); const root = resolvePath(__dirname, '../../..'); diff --git a/packages/cli-common/src/paths.ts b/packages/cli-common/src/paths.ts index 0db712e6cb..12a8484797 100644 --- a/packages/cli-common/src/paths.ts +++ b/packages/cli-common/src/paths.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8eae37ef56..a8cacedf22 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/cli +## 0.7.3 + +### Patch Changes + +- a93e60fdc: Updated dependencies +- 55f49fcc7: Update dependencies +- ab5cc376f: Use new `isChildPath` export from `@backstage/cli-common` +- Updated dependencies + - @backstage/cli-common@0.1.2 + +## 0.7.2 + +### Patch Changes + +- 953a7e66f: updated plugin template to generate path equals plugin id for the root page +- 04248b8f9: chore: bump `msw` dependency in `create-plugin` +- e3d31b381: Make the `create-github-app` command disable webhooks by default. +- 8f100db75: chore: bump `@typescript-eslint/eslint-plugin` from 4.26.0 to 4.27.0 +- 95e572305: chore: bump `del` from 5.1.0 to 6.0.0 +- ece2b5dd1: chore: bump `@spotify/eslint-config-typescript` from 9.0.0 to 10.0.0 +- 0ec31e596: chore: bump `@rollup/plugin-node-resolve` from 11.2.1 to 13.0.0 +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. + +## 0.7.1 + +### Patch Changes + +- 3108ff7bf: Make `yarn dev` in newly created backend plugins respect the `PLUGIN_PORT` environment variable. + + You can achieve the same in your created backend plugins by making sure to properly call the port and CORS methods on your service builder. Typically in a file named `src/service/standaloneServer.ts` inside your backend plugin package, replace the following: + + ```ts + const service = createServiceBuilder(module) + .enableCors({ origin: 'http://localhost:3000' }) + .addRouter('/my-plugin', router); + ``` + + With something like the following: + + ```ts + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/my-plugin', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + ``` + +- Updated dependencies + - @backstage/config-loader@0.6.4 + ## 0.7.0 ### Minor Changes diff --git a/packages/cli/asset-types/asset-types.d.ts b/packages/cli/asset-types/asset-types.d.ts index 9db4438fd4..879e9b0b05 100644 --- a/packages/cli/asset-types/asset-types.d.ts +++ b/packages/cli/asset-types/asset-types.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/bin/backstage-cli b/packages/cli/bin/backstage-cli index 5c127af2ec..9aa82f6c2a 100755 --- a/packages/cli/bin/backstage-cli +++ b/packages/cli/bin/backstage-cli @@ -1,6 +1,6 @@ #!/usr/bin/env node /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index e860e426d1..c619df9eee 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 6448136769..b8d3d98c8f 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 0cec82b468..e2ff338b4e 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/config/jestEsmTransform.js b/packages/cli/config/jestEsmTransform.js index 99f1a600bc..742822274d 100644 --- a/packages/cli/config/jestEsmTransform.js +++ b/packages/cli/config/jestEsmTransform.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/config/jestFileTransform.js b/packages/cli/config/jestFileTransform.js index e6ff1895f8..bdd29fc296 100644 --- a/packages/cli/config/jestFileTransform.js +++ b/packages/cli/config/jestFileTransform.js @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/package.json b/packages/cli/package.json index 7d8940f154..a7032879bc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.7.0", + "version": "0.7.3", "private": false, "publishConfig": { "access": "public" @@ -30,20 +30,20 @@ "dependencies": { "@babel/core": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.4.4", - "@backstage/cli-common": "^0.1.1", + "@backstage/cli-common": "^0.1.2", "@backstage/config": "^0.1.5", - "@backstage/config-loader": "^0.6.3", + "@backstage/config-loader": "^0.6.4", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", "@octokit/request": "^5.4.12", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.0.2", - "@rollup/plugin-node-resolve": "^11.2.0", - "@rollup/plugin-yaml": "^2.1.1", + "@rollup/plugin-node-resolve": "^13.0.0", + "@rollup/plugin-yaml": "^3.0.0", "@spotify/eslint-config-base": "^9.0.0", "@spotify/eslint-config-react": "^10.0.0", - "@spotify/eslint-config-typescript": "^9.0.0", + "@spotify/eslint-config-typescript": "^10.0.0", "@sucrase/jest-plugin": "^2.1.0", "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "5.5.x", @@ -53,15 +53,15 @@ "@types/start-server-webpack-plugin": "^2.2.0", "@types/webpack-env": "^1.15.2", "@types/webpack-node-externals": "^2.5.0", - "@typescript-eslint/eslint-plugin": "^v4.26.0", - "@typescript-eslint/parser": "^v4.14.0", + "@typescript-eslint/eslint-plugin": "^v4.27.0", + "@typescript-eslint/parser": "^v4.27.0", "@yarnpkg/lockfile": "^1.1.0", "babel-plugin-dynamic-import-node": "^2.3.3", "bfj": "^7.0.2", "chalk": "^4.0.0", "chokidar": "^3.3.1", "commander": "^6.1.0", - "css-loader": "^3.5.3", + "css-loader": "^5.2.6", "dashify": "^2.0.0", "diff": "^5.0.0", "esbuild": "^0.8.56", @@ -77,7 +77,7 @@ "express": "^4.17.1", "file-loader": "^6.2.0", "fork-ts-checker-webpack-plugin": "^6.2.9", - "fs-extra": "^9.0.0", + "fs-extra": "9.1.0", "handlebars": "^4.7.3", "html-webpack-plugin": "^4.3.0", "inquirer": "^7.0.4", @@ -94,8 +94,8 @@ "react-hot-loader": "^4.12.21", "recursive-readdir": "^2.2.2", "replace-in-file": "^6.0.0", - "rollup": "2.33.x", - "rollup-plugin-dts": "^2.0.1", + "rollup": "2.44.x", + "rollup-plugin-dts": "^3.0.1", "rollup-plugin-esbuild": "2.6.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^4.0.0", @@ -118,11 +118,13 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.8.2", + "@backstage/backend-common": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/core": "^0.7.12", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/core-components": "^0.1.4", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/core-app-api": "^0.1.4", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@backstage/theme": "^0.2.8", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", @@ -139,12 +141,12 @@ "@types/rollup-plugin-postcss": "^2.0.0", "@types/tar": "^4.0.3", "@types/webpack": "^4.41.7", - "@types/webpack-dev-server": "3.11.0", + "@types/webpack-dev-server": "^3.11.0", "@types/yarnpkg__lockfile": "^1.1.4", - "del": "^5.1.0", + "del": "^6.0.0", "mock-fs": "^4.13.0", "nodemon": "^2.0.2", - "ts-node": "^9.1.1" + "ts-node": "^10.0.0" }, "files": [ "asset-types", diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 0da4112646..22e7fb9de9 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index b542030109..85fbdd97a0 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/backend/build.ts b/packages/cli/src/commands/backend/build.ts index ceca5b0286..6cb1347b1e 100644 --- a/packages/cli/src/commands/backend/build.ts +++ b/packages/cli/src/commands/backend/build.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/backend/buildImage.ts b/packages/cli/src/commands/backend/buildImage.ts index 8434017cd9..b352e38203 100644 --- a/packages/cli/src/commands/backend/buildImage.ts +++ b/packages/cli/src/commands/backend/buildImage.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/backend/bundle.ts b/packages/cli/src/commands/backend/bundle.ts index 25322045e1..338d5cdaa0 100644 --- a/packages/cli/src/commands/backend/bundle.ts +++ b/packages/cli/src/commands/backend/bundle.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index 395fe8a4ab..a09cea0e8a 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index bd5bbc5e9f..dec0b1a293 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/buildWorkspace.ts b/packages/cli/src/commands/buildWorkspace.ts index 624f104b3f..7a7aa28ad6 100644 --- a/packages/cli/src/commands/buildWorkspace.ts +++ b/packages/cli/src/commands/buildWorkspace.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/clean/clean.ts b/packages/cli/src/commands/clean/clean.ts index bc2bcd23ac..74a1ea658b 100644 --- a/packages/cli/src/commands/clean/clean.ts +++ b/packages/cli/src/commands/clean/clean.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/config/docs.ts b/packages/cli/src/commands/config/docs.ts index e06bc42c27..198244b941 100644 --- a/packages/cli/src/commands/config/docs.ts +++ b/packages/cli/src/commands/config/docs.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 930f2c98ca..6bd575f9bf 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/config/schema.ts b/packages/cli/src/commands/config/schema.ts index 63c1524789..a36fc24f06 100644 --- a/packages/cli/src/commands/config/schema.ts +++ b/packages/cli/src/commands/config/schema.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/config/validate.ts b/packages/cli/src/commands/config/validate.ts index 37f41164af..9041272c5a 100644 --- a/packages/cli/src/commands/config/validate.ts +++ b/packages/cli/src/commands/config/validate.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts b/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts index 45671c2ead..58a3e97a9a 100644 --- a/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts +++ b/packages/cli/src/commands/create-github-app/GithubCreateAppServer.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,6 +120,7 @@ export class GithubCreateAppServer { redirect_url: `${baseUrl}/callback`, hook_attributes: { url: this.webhookUrl, + active: false, }, }; const manifestJson = JSON.stringify(manifest).replace(/\"/g, '"'); diff --git a/packages/cli/src/commands/create-github-app/index.ts b/packages/cli/src/commands/create-github-app/index.ts index cd9e8dbe09..c62234c636 100644 --- a/packages/cli/src/commands/create-github-app/index.ts +++ b/packages/cli/src/commands/create-github-app/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/create-plugin/createPlugin.test.ts b/packages/cli/src/commands/create-plugin/createPlugin.test.ts index 660caec189..012dc2be13 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.test.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 5ea8cdfbbc..c9784969af 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 431659a972..d21349a4e1 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 7b4532ffa7..41b454ffff 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/pack.ts b/packages/cli/src/commands/pack.ts index 58efece6e4..83b866aa88 100644 --- a/packages/cli/src/commands/pack.ts +++ b/packages/cli/src/commands/pack.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/plugin/build.ts b/packages/cli/src/commands/plugin/build.ts index d62ffbeebd..8a63a017a2 100644 --- a/packages/cli/src/commands/plugin/build.ts +++ b/packages/cli/src/commands/plugin/build.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index fb96250567..6a0127794f 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 27db824d2f..b9ba93bc62 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/plugin/testCommand.ts b/packages/cli/src/commands/plugin/testCommand.ts index df031d8e89..e1ff0f36f9 100644 --- a/packages/cli/src/commands/plugin/testCommand.ts +++ b/packages/cli/src/commands/plugin/testCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/remove-plugin/file-mocks.ts b/packages/cli/src/commands/remove-plugin/file-mocks.ts index 5768f5c398..1264dc8f2e 100644 --- a/packages/cli/src/commands/remove-plugin/file-mocks.ts +++ b/packages/cli/src/commands/remove-plugin/file-mocks.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts index 56cde2ca9e..0390320128 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.ts b/packages/cli/src/commands/remove-plugin/removePlugin.ts index 72d619e0bc..9e1e18bb7b 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/testCommand.ts b/packages/cli/src/commands/testCommand.ts index 2c3f9fe895..d885e1aec9 100644 --- a/packages/cli/src/commands/testCommand.ts +++ b/packages/cli/src/commands/testCommand.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index e5128fc995..96bd1ab6fd 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index d046283aab..257a83e2be 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index aa0bcd6f6a..68d0766217 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 28e46bbd1a..3e575a495b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index f8305ad6e0..60a5f0efca 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/index.ts b/packages/cli/src/lib/builder/index.ts index 17aae25ec4..c39d964ade 100644 --- a/packages/cli/src/lib/builder/index.ts +++ b/packages/cli/src/lib/builder/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/packager.test.ts b/packages/cli/src/lib/builder/packager.test.ts index f5e101cfc6..6264eb57ac 100644 --- a/packages/cli/src/lib/builder/packager.test.ts +++ b/packages/cli/src/lib/builder/packager.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 6deded5f69..3e0f1b9a46 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/plugins.test.ts b/packages/cli/src/lib/builder/plugins.test.ts index d1e1b3e2bd..dcbdc55cc0 100644 --- a/packages/cli/src/lib/builder/plugins.test.ts +++ b/packages/cli/src/lib/builder/plugins.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/plugins.ts b/packages/cli/src/lib/builder/plugins.ts index 48b6b65c61..3b1cbe4f1c 100644 --- a/packages/cli/src/lib/builder/plugins.ts +++ b/packages/cli/src/lib/builder/plugins.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/builder/types.ts b/packages/cli/src/lib/builder/types.ts index 853789df59..f75d1b1dda 100644 --- a/packages/cli/src/lib/builder/types.ts +++ b/packages/cli/src/lib/builder/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts index f3906bfd0d..d22b9b3701 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts index fed8b39a19..aa6a6b4e2e 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ import { resolve as resolvePath } from 'path'; import { ResolvePlugin } from 'webpack'; -import { isChildPath } from './paths'; +import { isChildPath } from '@backstage/cli-common'; import { LernaPackage } from './types'; // Enables proper resolution of packages when linking in external packages. diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts index 9633c1b963..bddeac0de3 100644 --- a/packages/cli/src/lib/bundler/backend.ts +++ b/packages/cli/src/lib/bundler/backend.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts index 30fef2e8b7..17a7398b2a 100644 --- a/packages/cli/src/lib/bundler/bundle.ts +++ b/packages/cli/src/lib/bundler/bundle.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 7270f7b01a..75e94af716 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,10 @@ import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; import StartServerPlugin from 'start-server-webpack-plugin'; import webpack from 'webpack'; import nodeExternals from 'webpack-node-externals'; +import { isChildPath } from '@backstage/cli-common'; import { optimization } from './optimization'; import { Config } from '@backstage/config'; -import { BundlingPaths, isChildPath } from './paths'; +import { BundlingPaths } from './paths'; import { transforms } from './transforms'; import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; import { BundlingOptions, BackendBundlingOptions, LernaPackage } from './types'; diff --git a/packages/cli/src/lib/bundler/index.ts b/packages/cli/src/lib/bundler/index.ts index 2030b4bb96..a3b584efc4 100644 --- a/packages/cli/src/lib/bundler/index.ts +++ b/packages/cli/src/lib/bundler/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index 665eb10b21..e97b2ae868 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index c8d48a7199..d465b3010f 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,25 +15,8 @@ */ import fs from 'fs-extra'; -import path from 'path'; import { paths } from '../paths'; -/** - * Checks if dir is the same as or a child of base. - */ -export function isChildPath(base: string, dir: string): boolean { - const relativePath = path.relative(base, dir); - if (relativePath === '') { - // The same directory - return true; - } - - const outsideBase = relativePath.startsWith('..'); // not outside base - const differentDrive = path.isAbsolute(relativePath); // on Windows, this means dir is on a different drive from base. - - return !outsideBase && !differentDrive; -} - export type BundlingPathsOptions = { // bundle entrypoint, e.g. 'src/index' entry: string; diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 24a24d25d2..088128c805 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index 6107485d78..6eb4920326 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 3bd941dd0f..283074a295 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/codeowners/codeowners.test.ts b/packages/cli/src/lib/codeowners/codeowners.test.ts index 89386522f5..3be2f4a2fd 100644 --- a/packages/cli/src/lib/codeowners/codeowners.test.ts +++ b/packages/cli/src/lib/codeowners/codeowners.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/codeowners/codeowners.ts b/packages/cli/src/lib/codeowners/codeowners.ts index aa24f91aac..563bd1052d 100644 --- a/packages/cli/src/lib/codeowners/codeowners.ts +++ b/packages/cli/src/lib/codeowners/codeowners.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/codeowners/index.ts b/packages/cli/src/lib/codeowners/index.ts index 97c613488c..c40619f5f9 100644 --- a/packages/cli/src/lib/codeowners/index.ts +++ b/packages/cli/src/lib/codeowners/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index de6bd6353d..db96ca7686 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 36e163d67e..5cbddde0ec 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,15 @@ import chalk from 'chalk'; import { diffLines } from 'diff'; import { FileDiff, PromptFunc, FileHandler, WriteFileFunc } from './types'; +function sortObjectKeys(obj: Record) { + const sortedKeys = Object.keys(obj).sort(); + for (const key of sortedKeys) { + const value = obj[key]; + delete obj[key]; + obj[key] = value; + } +} + class PackageJsonHandler { static async handler( { path, write, missing, targetContents, templateContents }: FileDiff, @@ -74,6 +83,7 @@ class PackageJsonHandler { obj: any = this.pkg, targetObj: any = this.targetPkg, prefix?: string, + sort?: boolean, ) { const fullFieldName = chalk.cyan( prefix ? `${prefix}[${fieldName}]` : fieldName, @@ -91,6 +101,9 @@ class PackageJsonHandler { const msg = `package.json has mismatched field, ${fullFieldName}, change from ${coloredOldValue} to ${coloredNewValue}?`; if (await this.prompt(msg)) { targetObj[fieldName] = newValue; + if (sort) { + sortObjectKeys(targetObj); + } await this.write(); } } else if (fieldName in obj) { @@ -100,6 +113,9 @@ class PackageJsonHandler { ) ) { targetObj[fieldName] = newValue; + if (sort) { + sortObjectKeys(targetObj); + } await this.write(); } } @@ -168,16 +184,22 @@ class PackageJsonHandler { return; } + // Hardcoded removal of these during migration + await this.syncField('@backstage/core', {}, targetDeps, fieldName, true); + await this.syncField( + '@backstage/core-api', + {}, + targetDeps, + fieldName, + true, + ); + for (const key of Object.keys(pkgDeps)) { if (this.variant === 'app' && key.startsWith('plugin-')) { continue; } - // Skip checking of the core packages, since we're migrating over - if (key.startsWith('@backstage/core')) { - continue; - } - await this.syncField(key, pkgDeps, targetDeps, fieldName); + await this.syncField(key, pkgDeps, targetDeps, fieldName, true); } } diff --git a/packages/cli/src/lib/diff/index.ts b/packages/cli/src/lib/diff/index.ts index 04c5ee2e45..79a55f023e 100644 --- a/packages/cli/src/lib/diff/index.ts +++ b/packages/cli/src/lib/diff/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/diff/prompts.ts b/packages/cli/src/lib/diff/prompts.ts index ac04fc3559..d2ddca5fc0 100644 --- a/packages/cli/src/lib/diff/prompts.ts +++ b/packages/cli/src/lib/diff/prompts.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/diff/read.ts b/packages/cli/src/lib/diff/read.ts index d37dfe4b03..8388d22196 100644 --- a/packages/cli/src/lib/diff/read.ts +++ b/packages/cli/src/lib/diff/read.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/diff/types.ts b/packages/cli/src/lib/diff/types.ts index 6c50d9fcf9..d22d690196 100644 --- a/packages/cli/src/lib/diff/types.ts +++ b/packages/cli/src/lib/diff/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index 110a095fe3..c0b4c45b6c 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/logging.ts b/packages/cli/src/lib/logging.ts index ef79b34097..8745585d1e 100644 --- a/packages/cli/src/lib/logging.ts +++ b/packages/cli/src/lib/logging.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index afe29fbd7c..6917795e83 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/parallel.test.ts b/packages/cli/src/lib/parallel.test.ts index 8047774a6e..c5c33473ff 100644 --- a/packages/cli/src/lib/parallel.test.ts +++ b/packages/cli/src/lib/parallel.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/parallel.ts b/packages/cli/src/lib/parallel.ts index b6926115aa..f922295a89 100644 --- a/packages/cli/src/lib/parallel.ts +++ b/packages/cli/src/lib/parallel.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/paths.ts b/packages/cli/src/lib/paths.ts index a17034344d..2c658c27b3 100644 --- a/packages/cli/src/lib/paths.ts +++ b/packages/cli/src/lib/paths.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/run.ts b/packages/cli/src/lib/run.ts index 571c75a39d..7cf22a6df9 100644 --- a/packages/cli/src/lib/run.ts +++ b/packages/cli/src/lib/run.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/svgrTemplate.ts b/packages/cli/src/lib/svgrTemplate.ts index 5f7c7f9a4c..ca2a8c38fa 100644 --- a/packages/cli/src/lib/svgrTemplate.ts +++ b/packages/cli/src/lib/svgrTemplate.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/tasks.test.ts b/packages/cli/src/lib/tasks.test.ts index 88d8050290..9ee4e93336 100644 --- a/packages/cli/src/lib/tasks.test.ts +++ b/packages/cli/src/lib/tasks.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 4b92c6b9cb..be6d052365 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index b2b793717d..61ee4003fc 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,9 @@ leaving any imports in place. import { version as backendCommon } from '@backstage/backend-common/package.json'; import { version as cli } from '@backstage/cli/package.json'; import { version as config } from '@backstage/config/package.json'; -import { version as core } from '@backstage/core/package.json'; +import { version as coreAppApi } from '@backstage/core-app-api/package.json'; +import { version as coreComponents } from '@backstage/core-components/package.json'; +import { version as corePluginApi } from '@backstage/core-plugin-api/package.json'; import { version as devUtils } from '@backstage/dev-utils/package.json'; import { version as testUtils } from '@backstage/test-utils/package.json'; import { version as theme } from '@backstage/theme/package.json'; @@ -43,7 +45,9 @@ export const packageVersions = { '@backstage/backend-common': backendCommon, '@backstage/cli': cli, '@backstage/config': config, - '@backstage/core': core, + '@backstage/core-app-api': coreAppApi, + '@backstage/core-components': coreComponents, + '@backstage/core-plugin-api': corePluginApi, '@backstage/dev-utils': devUtils, '@backstage/test-utils': testUtils, '@backstage/theme': theme, diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 89126c7ce1..f182aabdad 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index f0867c6408..7b23652c33 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/versioning/index.ts b/packages/cli/src/lib/versioning/index.ts index 71fb7647ce..fb3b8989b4 100644 --- a/packages/cli/src/lib/versioning/index.ts +++ b/packages/cli/src/lib/versioning/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index aca439a0e7..dd63e57187 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 777dc72757..6ed584a249 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 828819ad17..9088a86369 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index d041773d3c..8275223f49 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -36,7 +36,7 @@ "@backstage/cli": "^{{version '@backstage/cli'}}", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/cli/templates/default-backend-plugin/src/index.ts b/packages/cli/templates/default-backend-plugin/src/index.ts index 7612c392a2..ca73cb27ba 100644 --- a/packages/cli/templates/default-backend-plugin/src/index.ts +++ b/packages/cli/templates/default-backend-plugin/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-backend-plugin/src/run.ts.hbs b/packages/cli/templates/default-backend-plugin/src/run.ts.hbs index b96989e4b8..54d2716290 100644 --- a/packages/cli/templates/default-backend-plugin/src/run.ts.hbs +++ b/packages/cli/templates/default-backend-plugin/src/run.ts.hbs @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-backend-plugin/src/service/router.test.ts b/packages/cli/templates/default-backend-plugin/src/service/router.test.ts index 0aaeafa379..8b77a04348 100644 --- a/packages/cli/templates/default-backend-plugin/src/service/router.test.ts +++ b/packages/cli/templates/default-backend-plugin/src/service/router.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-backend-plugin/src/service/router.ts b/packages/cli/templates/default-backend-plugin/src/service/router.ts index 3ea8219365..9ceaa47627 100644 --- a/packages/cli/templates/default-backend-plugin/src/service/router.ts +++ b/packages/cli/templates/default-backend-plugin/src/service/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-backend-plugin/src/service/standaloneServer.ts.hbs b/packages/cli/templates/default-backend-plugin/src/service/standaloneServer.ts.hbs index 6e38965246..171b6da0e5 100644 --- a/packages/cli/templates/default-backend-plugin/src/service/standaloneServer.ts.hbs +++ b/packages/cli/templates/default-backend-plugin/src/service/standaloneServer.ts.hbs @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,9 +34,12 @@ export async function startStandaloneServer( logger, }); - const service = createServiceBuilder(module) - .enableCors({ origin: 'http://localhost:3000' }) + let service = createServiceBuilder(module) + .setPort(options.port) .addRouter('/{{id}}', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } return await service.start().catch(err => { logger.error(err); diff --git a/packages/cli/templates/default-backend-plugin/src/setupTests.ts b/packages/cli/templates/default-backend-plugin/src/setupTests.ts index ba33cf996b..d3232290a7 100644 --- a/packages/cli/templates/default-backend-plugin/src/setupTests.ts +++ b/packages/cli/templates/default-backend-plugin/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/cli/templates/default-plugin/dev/index.tsx.hbs b/packages/cli/templates/default-plugin/dev/index.tsx.hbs index ade00a1613..14fecf73e1 100644 --- a/packages/cli/templates/default-plugin/dev/index.tsx.hbs +++ b/packages/cli/templates/default-plugin/dev/index.tsx.hbs @@ -7,5 +7,6 @@ createDevApp() .addPage({ element: <{{ extensionName }} />, title: 'Root Page', + path: '/{{ id }}' }) .render(); diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 1d2766eab4..dbba628055 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -24,7 +24,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^{{version '@backstage/core'}}", + "@backstage/core-components": "^{{version '@backstage/core-components'}}", + "@backstage/core-plugin-api": "^{{version '@backstage/core-plugin-api'}}", "@backstage/theme": "^{{version '@backstage/theme'}}", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,6 +36,7 @@ }, "devDependencies": { "@backstage/cli": "^{{version '@backstage/cli'}}", + "@backstage/core-app-api": "^{{version '@backstage/core-app-api'}}", "@backstage/dev-utils": "^{{version '@backstage/dev-utils'}}", "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", "@testing-library/jest-dom": "^5.10.1", @@ -42,7 +44,7 @@ "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.21.2", + "msw": "^0.29.0", "cross-fetch": "^3.0.6" }, "files": [ diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index dcecebdf46..1820671fa5 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -8,7 +8,7 @@ import { ContentHeader, HeaderLabel, SupportButton, -} from '@backstage/core'; +} from '@backstage/core-components'; import { ExampleFetchComponent } from '../ExampleFetchComponent'; export const ExampleComponent = () => ( diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs index 20dd6d1f57..a7663567d9 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs @@ -1,6 +1,6 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Table, TableColumn, Progress } from '@backstage/core'; +import { Table, TableColumn, Progress } from '@backstage/core-components'; import Alert from '@material-ui/lab/Alert'; import { useAsync } from 'react-use'; diff --git a/packages/cli/templates/default-plugin/src/plugin.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.ts.hbs index 0ae5356fd1..dcf35fc5d2 100644 --- a/packages/cli/templates/default-plugin/src/plugin.ts.hbs +++ b/packages/cli/templates/default-plugin/src/plugin.ts.hbs @@ -1,4 +1,4 @@ -import { createPlugin, createRoutableExtension } from '@backstage/core'; +import { createPlugin, createRoutableExtension } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; diff --git a/packages/cli/templates/default-plugin/src/routes.ts.hbs b/packages/cli/templates/default-plugin/src/routes.ts.hbs index b2afba074d..a5a278e8a7 100644 --- a/packages/cli/templates/default-plugin/src/routes.ts.hbs +++ b/packages/cli/templates/default-plugin/src/routes.ts.hbs @@ -1,4 +1,4 @@ -import { createRouteRef } from '@backstage/core'; +import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ title: '{{ id }}', diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 9f2ae5d214..dc1f694dca 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,9 +1,45 @@ # @backstage/codemods +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.1.5 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.1.4 + - @backstage/core-components@0.1.4 + - @backstage/cli-common@0.1.2 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + +## 0.1.2 + +Fixed a publish issue, making this package available to the public. + ## 0.1.1 ### Patch Changes +- 59752e103: Fix execution of `jscodeshift` on windows. +- Updated dependencies + - @backstage/core-components@0.1.3 + +## 0.1.0 + +### Patch Changes + - Updated dependencies [9bca2a252] - Updated dependencies [e47336ea4] - Updated dependencies [75b8537ce] diff --git a/packages/codemods/bin/backstage-codemods b/packages/codemods/bin/backstage-codemods index d6d449fe31..0bc4088cae 100755 --- a/packages/codemods/bin/backstage-codemods +++ b/packages/codemods/bin/backstage-codemods @@ -1,6 +1,6 @@ #!/usr/bin/env node /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,21 @@ const path = require('path'); -require('ts-node').register({ - transpileOnly: true, - /* eslint-disable-next-line no-restricted-syntax */ - project: path.resolve(__dirname, '../../../tsconfig.json'), - compilerOptions: { - module: 'CommonJS', - }, -}); +// Figure out whether we're running inside the backstage repo or as an installed dependency +/* eslint-disable-next-line no-restricted-syntax */ +const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); -require('../src'); +if (!isLocal) { + require('..'); +} else { + require('ts-node').register({ + transpileOnly: true, + /* eslint-disable-next-line no-restricted-syntax */ + project: path.resolve(__dirname, '../../../tsconfig.json'), + compilerOptions: { + module: 'CommonJS', + }, + }); + + require('../src'); +} diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 2ce0172b75..9cf5730126 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,8 +1,12 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.1", - "private": true, + "version": "0.1.5", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -16,14 +20,18 @@ "main": "src/index.ts", "scripts": { "start": "nodemon --", + "build": "backstage-cli build --outputs cjs", "lint": "backstage-cli lint", - "test": "backstage-cli test" + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" }, "bin": { "backstage-codemods": "bin/backstage-codemods" }, "dependencies": { - "@backstage/cli-common": "0.1.1", + "@backstage/cli-common": "0.1.2", "@backstage/core-app-api": "*", "@backstage/core-components": "*", "@backstage/core-plugin-api": "*", @@ -35,7 +43,7 @@ "@types/jscodeshift": "^0.11.0", "@types/node": "^14.14.32", "commander": "^6.1.0", - "ts-node": "^9.1.1" + "ts-node": "^10.0.0" }, "nodemonConfig": { "watch": "./src", @@ -43,6 +51,7 @@ "ext": "ts" }, "files": [ + "bin", "dist", "transforms" ] diff --git a/packages/codemods/src/action.ts b/packages/codemods/src/action.ts index c3eba8aecc..bc9104425e 100644 --- a/packages/codemods/src/action.ts +++ b/packages/codemods/src/action.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/codemods/src/codemods.ts b/packages/codemods/src/codemods.ts index 23e02daabd..6cdd9d8977 100644 --- a/packages/codemods/src/codemods.ts +++ b/packages/codemods/src/codemods.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/codemods/src/errors.ts b/packages/codemods/src/errors.ts index a1eab4c9e5..2f67b94ae1 100644 --- a/packages/codemods/src/errors.ts +++ b/packages/codemods/src/errors.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/codemods/src/index.ts b/packages/codemods/src/index.ts index 967c906477..0458712522 100644 --- a/packages/codemods/src/index.ts +++ b/packages/codemods/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/codemods/src/tests/core-imports.test.ts b/packages/codemods/src/tests/core-imports.test.ts index e5924156b6..d8dea7eb90 100644 --- a/packages/codemods/src/tests/core-imports.test.ts +++ b/packages/codemods/src/tests/core-imports.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/codemods/transforms/core-imports.js b/packages/codemods/transforms/core-imports.js index 5ad38e02df..f744a9c412 100644 --- a/packages/codemods/transforms/core-imports.js +++ b/packages/codemods/transforms/core-imports.js @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 67ed7472b5..e0be1eebc8 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/config-loader +## 0.6.4 + +### Patch Changes + +- f00493739: Removed workaround for breaking change in typescript 4.3 and bump `typescript-json-schema` instead. This should again allow the usage of `@items.visibility ` to set the visibility of array items. + ## 0.6.3 ### Patch Changes diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index a09311b786..ffd15b0c9a 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -3,15 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { AppConfig } from '@backstage/config'; import { JsonObject } from '@backstage/config'; import { JSONSchema7 } from 'json-schema'; // @public export type ConfigSchema = { - process(appConfigs: AppConfig[], options?: ConfigProcessingOptions): AppConfig[]; - serialize(): JsonObject; + process( + appConfigs: AppConfig[], + options?: ConfigProcessingOptions, + ): AppConfig[]; + serialize(): JsonObject; }; // @public @@ -22,10 +24,10 @@ export function loadConfig(options: LoadConfigOptions): Promise; // @public (undocumented) export type LoadConfigOptions = { - configRoot: string; - configPaths: string[]; - env?: string; - experimentalEnvFunc?: EnvFunc; + configRoot: string; + configPaths: string[]; + env?: string; + experimentalEnvFunc?: EnvFunc; }; // @public @@ -36,10 +38,8 @@ export function mergeConfigSchemas(schemas: JSONSchema7[]): JSONSchema7; // @public export function readEnvConfig(env: { - [name: string]: string | undefined; + [name: string]: string | undefined; }): AppConfig[]; - // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index de0adb4a4a..cff324484a 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.6.3", + "version": "0.6.4", "private": false, "publishConfig": { "access": "public", @@ -34,10 +34,10 @@ "@backstage/config": "^0.1.5", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", - "fs-extra": "^9.0.0", + "fs-extra": "9.1.0", "json-schema": "^0.3.0", "json-schema-merge-allof": "^0.8.1", - "typescript-json-schema": "^0.49.0", + "typescript-json-schema": "^0.50.1", "yaml": "^1.9.2", "yup": "^0.29.3" }, diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index d9e5ae1350..f605d53115 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/env.test.ts b/packages/config-loader/src/lib/env.test.ts index 6b1e49365a..6908f0adb0 100644 --- a/packages/config-loader/src/lib/env.test.ts +++ b/packages/config-loader/src/lib/env.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/env.ts b/packages/config-loader/src/lib/env.ts index 84d39263e3..7d28e6b6ef 100644 --- a/packages/config-loader/src/lib/env.ts +++ b/packages/config-loader/src/lib/env.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/index.ts b/packages/config-loader/src/lib/index.ts index 192ac81f5d..32a0191cae 100644 --- a/packages/config-loader/src/lib/index.ts +++ b/packages/config-loader/src/lib/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 94fe2154c1..479e63c94c 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,15 @@ const mockSchema = { }, }; +// We need to load in actual TS libraries when using mock-fs. +// This lookup is to allow the `typescript` dependency to exist either +// at top level or inside node_modules of typescript-json-schema +const typescriptModuleDir = path.dirname( + require.resolve('typescript/package.json', { + paths: [require.resolve('typescript-json-schema')], + }), +); + describe('collectConfigSchemas', () => { afterEach(() => { mockFs.restore(); @@ -157,9 +166,7 @@ describe('collectConfigSchemas', () => { }, }, // TypeScript compilation needs to load some real files inside the typescript dir - '../../node_modules/typescript': (mockFs as any).load( - '../../node_modules/typescript', - ), + [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([ @@ -218,9 +225,7 @@ describe('collectConfigSchemas', () => { }, }, // TypeScript compilation needs to load some real files inside the typescript dir - '../../node_modules/typescript': (mockFs as any).load( - '../../node_modules/typescript', - ), + [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); await expect(collectConfigSchemas(['a'])).rejects.toThrow( diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts index 5baada2d6f..b53389d499 100644 --- a/packages/config-loader/src/lib/schema/collect.ts +++ b/packages/config-loader/src/lib/schema/collect.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -168,23 +168,6 @@ function compileTsSchemas(paths: string[]) { }, [path.split(sep).join('/')], // Unix paths are expected for all OSes here ) as JsonObject | null; - - // This is a workaround for an API change in TypeScript 4.3 where doc comments no - // longer are represented by a single string, but instead an array of objects. - // This isn't handled by typescript-json-schema so we do the conversion here instead. - value = JSON.parse(JSON.stringify(value), (key, prop) => { - if (key === 'visibility' && Array.isArray(prop)) { - const text = prop[0]?.text; - if (!text) { - const propStr = JSON.stringify(prop); - throw new Error( - `Failed conversion of visibility schema, got ${propStr}`, - ); - } - return text; - } - return prop; - }); } catch (error) { if (error.message !== 'type Config not found') { throw error; diff --git a/packages/config-loader/src/lib/schema/compile.test.ts b/packages/config-loader/src/lib/schema/compile.test.ts index e1d8ee5999..c9330a0440 100644 --- a/packages/config-loader/src/lib/schema/compile.test.ts +++ b/packages/config-loader/src/lib/schema/compile.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/compile.ts b/packages/config-loader/src/lib/schema/compile.ts index e85b6023cc..4236fd2d17 100644 --- a/packages/config-loader/src/lib/schema/compile.ts +++ b/packages/config-loader/src/lib/schema/compile.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/filtering.test.ts b/packages/config-loader/src/lib/schema/filtering.test.ts index feb31ebab0..5079afa876 100644 --- a/packages/config-loader/src/lib/schema/filtering.test.ts +++ b/packages/config-loader/src/lib/schema/filtering.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/filtering.ts b/packages/config-loader/src/lib/schema/filtering.ts index 93e899c9d8..74d367a30a 100644 --- a/packages/config-loader/src/lib/schema/filtering.ts +++ b/packages/config-loader/src/lib/schema/filtering.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/index.ts b/packages/config-loader/src/lib/schema/index.ts index 00bb5c7d10..851be36f84 100644 --- a/packages/config-loader/src/lib/schema/index.ts +++ b/packages/config-loader/src/lib/schema/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/load.test.ts b/packages/config-loader/src/lib/schema/load.test.ts index 7d9f7cc803..4a2b719590 100644 --- a/packages/config-loader/src/lib/schema/load.test.ts +++ b/packages/config-loader/src/lib/schema/load.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index 67b9762f51..ae5823c8cc 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts index db3d964aa0..30e47917bc 100644 --- a/packages/config-loader/src/lib/schema/types.ts +++ b/packages/config-loader/src/lib/schema/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/apply.test.ts b/packages/config-loader/src/lib/transform/apply.test.ts index 4cdd0e97f5..ab79f3805a 100644 --- a/packages/config-loader/src/lib/transform/apply.test.ts +++ b/packages/config-loader/src/lib/transform/apply.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/apply.ts b/packages/config-loader/src/lib/transform/apply.ts index 72c440a922..690d280266 100644 --- a/packages/config-loader/src/lib/transform/apply.ts +++ b/packages/config-loader/src/lib/transform/apply.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/include.test.ts b/packages/config-loader/src/lib/transform/include.test.ts index 83f35d53d0..44bf07ab4d 100644 --- a/packages/config-loader/src/lib/transform/include.test.ts +++ b/packages/config-loader/src/lib/transform/include.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/include.ts b/packages/config-loader/src/lib/transform/include.ts index 2b5ccf23b6..5ea7161e5f 100644 --- a/packages/config-loader/src/lib/transform/include.ts +++ b/packages/config-loader/src/lib/transform/include.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/index.ts b/packages/config-loader/src/lib/transform/index.ts index cb9f077d43..5053cd7443 100644 --- a/packages/config-loader/src/lib/transform/index.ts +++ b/packages/config-loader/src/lib/transform/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/substitution.test.ts b/packages/config-loader/src/lib/transform/substitution.test.ts index d07e88cfdd..dc6ed47597 100644 --- a/packages/config-loader/src/lib/transform/substitution.test.ts +++ b/packages/config-loader/src/lib/transform/substitution.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/substitution.ts b/packages/config-loader/src/lib/transform/substitution.ts index 56695fa431..21edfb7e57 100644 --- a/packages/config-loader/src/lib/transform/substitution.ts +++ b/packages/config-loader/src/lib/transform/substitution.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/types.ts b/packages/config-loader/src/lib/transform/types.ts index c13f01d016..20e5f88718 100644 --- a/packages/config-loader/src/lib/transform/types.ts +++ b/packages/config-loader/src/lib/transform/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/lib/transform/utils.ts b/packages/config-loader/src/lib/transform/utils.ts index 9a72bc3c0e..a49e9b14bd 100644 --- a/packages/config-loader/src/lib/transform/utils.ts +++ b/packages/config-loader/src/lib/transform/utils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 27b1e20789..b0e236f9c9 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 95e5b570ca..3846d6474b 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config/api-report.md b/packages/config/api-report.md index be250778ec..0c243aab48 100644 --- a/packages/config/api-report.md +++ b/packages/config/api-report.md @@ -3,79 +3,82 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - // @public (undocumented) export type AppConfig = { - context: string; - data: JsonObject; + context: string; + data: JsonObject; }; // @public (undocumented) export type Config = { - has(key: string): boolean; - keys(): string[]; - get(key?: string): T; - getOptional(key?: string): T | undefined; - getConfig(key: string): Config; - getOptionalConfig(key: string): Config | undefined; - getConfigArray(key: string): Config[]; - getOptionalConfigArray(key: string): Config[] | undefined; - getNumber(key: string): number; - getOptionalNumber(key: string): number | undefined; - getBoolean(key: string): boolean; - getOptionalBoolean(key: string): boolean | undefined; - getString(key: string): string; - getOptionalString(key: string): string | undefined; - getStringArray(key: string): string[]; - getOptionalStringArray(key: string): string[] | undefined; + has(key: string): boolean; + keys(): string[]; + get(key?: string): T; + getOptional(key?: string): T | undefined; + getConfig(key: string): Config; + getOptionalConfig(key: string): Config | undefined; + getConfigArray(key: string): Config[]; + getOptionalConfigArray(key: string): Config[] | undefined; + getNumber(key: string): number; + getOptionalNumber(key: string): number | undefined; + getBoolean(key: string): boolean; + getOptionalBoolean(key: string): boolean | undefined; + getString(key: string): string; + getOptionalString(key: string): string | undefined; + getStringArray(key: string): string[]; + getOptionalStringArray(key: string): string[] | undefined; }; // @public (undocumented) export class ConfigReader implements Config { - constructor(data: JsonObject | undefined, context?: string, fallback?: ConfigReader | undefined, prefix?: string); - // (undocumented) - static fromConfigs(configs: AppConfig[]): ConfigReader; - // (undocumented) - get(key?: string): T; - // (undocumented) - getBoolean(key: string): boolean; - // (undocumented) - getConfig(key: string): ConfigReader; - // (undocumented) - getConfigArray(key: string): ConfigReader[]; - // (undocumented) - getNumber(key: string): number; - // (undocumented) - getOptional(key?: string): T | undefined; - // (undocumented) - getOptionalBoolean(key: string): boolean | undefined; - // (undocumented) - getOptionalConfig(key: string): ConfigReader | undefined; - // (undocumented) - getOptionalConfigArray(key: string): ConfigReader[] | undefined; - // (undocumented) - getOptionalNumber(key: string): number | undefined; - // (undocumented) - getOptionalString(key: string): string | undefined; - // (undocumented) - getOptionalStringArray(key: string): string[] | undefined; - // (undocumented) - getString(key: string): string; - // (undocumented) - getStringArray(key: string): string[]; - // (undocumented) - has(key: string): boolean; - // (undocumented) - keys(): string[]; - } - -// @public (undocumented) -export interface JsonArray extends Array { + constructor( + data: JsonObject | undefined, + context?: string, + fallback?: ConfigReader | undefined, + prefix?: string, + ); + // (undocumented) + static fromConfigs(configs: AppConfig[]): ConfigReader; + // (undocumented) + get(key?: string): T; + // (undocumented) + getBoolean(key: string): boolean; + // (undocumented) + getConfig(key: string): ConfigReader; + // (undocumented) + getConfigArray(key: string): ConfigReader[]; + // (undocumented) + getNumber(key: string): number; + // (undocumented) + getOptional(key?: string): T | undefined; + // (undocumented) + getOptionalBoolean(key: string): boolean | undefined; + // (undocumented) + getOptionalConfig(key: string): ConfigReader | undefined; + // (undocumented) + getOptionalConfigArray(key: string): ConfigReader[] | undefined; + // (undocumented) + getOptionalNumber(key: string): number | undefined; + // (undocumented) + getOptionalString(key: string): string | undefined; + // (undocumented) + getOptionalStringArray(key: string): string[] | undefined; + // (undocumented) + getString(key: string): string; + // (undocumented) + getStringArray(key: string): string[]; + // (undocumented) + has(key: string): boolean; + // (undocumented) + keys(): string[]; } +// @public (undocumented) +export interface JsonArray extends Array {} + // @public (undocumented) export type JsonObject = { - [key in string]?: JsonValue; + [key in string]?: JsonValue; }; // @public (undocumented) @@ -84,7 +87,5 @@ export type JsonPrimitive = number | string | boolean | null; // @public (undocumented) export type JsonValue = JsonObject | JsonArray | JsonPrimitive; - // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 70a6a7a29b..d45b4e9e75 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 3fe74aa637..b1ba536797 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 99977d19cd..11346d4a50 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 76bbc2f9bf..d8f12f4f6e 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-api/.eslintrc.js b/packages/core-api/.eslintrc.js deleted file mode 100644 index d592a653c8..0000000000 --- a/packages/core-api/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], - rules: { - // TODO: add prop types to JS and remove - 'react/prop-types': 0, - 'jest/expect-expect': 0, - }, -}; diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md deleted file mode 100644 index 2282976d74..0000000000 --- a/packages/core-api/CHANGELOG.md +++ /dev/null @@ -1,382 +0,0 @@ -# @backstage/core-api - -## 0.2.22 - -### Patch Changes - -- 9bca2a252: Improve forwards compatibility with `@backstage/core-app-api` and `@backstage/core-plugin-api` by re-using route reference types and factory methods from `@backstage/core-plugin-api`. -- Updated dependencies [75b8537ce] -- Updated dependencies [da8cba44f] - - @backstage/core-plugin-api@0.1.2 - -## 0.2.21 - -### Patch Changes - -- 0160678b1: Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`. -- Updated dependencies [031ccd45f] -- Updated dependencies [e7c5e4b30] - - @backstage/core-plugin-api@0.1.1 - - @backstage/theme@0.2.8 - -## 0.2.20 - -### Patch Changes - -- d597a50c6: Add a global type definition for `Symbol.observable`, fix type checking in projects that didn't already have it defined. - -## 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 - -- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 -- 675a569a9: chore: bump `react-use` dependency in all packages - -## 0.2.17 - -### Patch Changes - -- ab07d77f6: Add support for discovering plugins through the app element tree, removing the need to register them explicitly. -- 50ce875a0: Fixed a potentially confusing error being thrown about misuse of routable extensions where the error was actually something different. -- Updated dependencies [931b21a12] - - @backstage/theme@0.2.6 - -## 0.2.16 - -### Patch Changes - -- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading - errors visible to the user. -- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. -- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. - -## 0.2.15 - -### Patch Changes - -- 76deafd31: Changed the signature of `createRoutableExtension` to include null -- 01ccef4c7: Introduce `useRouteRefParams` to `core-api` to retrieve typed route parameters. -- Updated dependencies [4618774ff] - - @backstage/theme@0.2.5 - -## 0.2.14 - -### Patch Changes - -- a51dc0006: Export `SubRouteRef` type, and allow `SubRouteRef`s to be assigned to `plugin.routes`. -- e7f9b9435: Allow elements to be used multiple times in the app element tree. -- 34ff49b0f: Allow extension components to also return `null` in addition to a `JSX.Element`. -- d88dd219e: Internal refactor to allow for future package splits. As part of this `ApiRef`s are now identified by their ID rather than their reference. -- c8b54c370: Added new Docs Icon to Core Icons -- Updated dependencies [0434853a5] - - @backstage/config@0.1.4 - -## 0.2.13 - -### Patch Changes - -- 13524b80b: Fully deprecate `title` option of `RouteRef`s and introduce `id` instead. -- e74b07578: Fixed a bug where FlatRoutes didn't handle React Fragments properly. -- 6fb4258a8: Add `SubRouteRef`s, which can be used to create a route ref with a fixed path relative to an absolute `RouteRef`. They are useful if you for example have a page that is mounted at a sub route of a routable extension component, and you want other plugins to be able to route to that page. - - For example: - - ```tsx - // routes.ts - const rootRouteRef = createRouteRef({ id: 'root' }); - const detailsRouteRef = createSubRouteRef({ - id: 'root-sub', - parent: rootRouteRef, - path: '/details', - }); - - // plugin.ts - export const myPlugin = createPlugin({ - routes: { - root: rootRouteRef, - details: detailsRouteRef, - } - }) - - export const MyPage = plugin.provide(createRoutableExtension({ - component: () => import('./components/MyPage').then(m => m.MyPage), - mountPoint: rootRouteRef, - })) - - // components/MyPage.tsx - const MyPage = () => ( - - {/* myPlugin.routes.root will take the user to this page */} - }> - - {/* myPlugin.routes.details will take the user to this page */} - }> - - ) - ``` - -- 395885905: Wait for `configApi` to be ready before using `featureFlagsApi` -- Updated dependencies [2089de76b] - - @backstage/theme@0.2.4 - -## 0.2.12 - -### Patch Changes - -- 40c0fdbaa: Added support for optional external route references. By setting `optional: true` when creating an `ExternalRouteRef` it is no longer a requirement to bind the route in the app. If the app isn't bound `useRouteRef` will return `undefined`. -- 2a271d89e: Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions. - -## 0.2.11 - -### Patch Changes - -- 3a58084b6: The `FlatRoutes` components now renders the not found page of the app if no routes are matched. -- 1407b34c6: More informative error message for missing ApiContext. -- b6c4f485d: Fix error when querying Backstage Identity with SAML authentication -- 3a58084b6: Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. -- Updated dependencies [a1f5e6545] - - @backstage/config@0.1.3 - -## 0.2.10 - -### Patch Changes - -- f10950bd2: Minor refactoring of BackstageApp.getSystemIcons to support custom registered - icons. Custom Icons can be added using: - - ```tsx - import AlarmIcon from '@material-ui/icons/Alarm'; - import MyPersonIcon from './MyPerson'; - - const app = createApp({ - icons: { - user: MyPersonIcon // override system icon - alert: AlarmIcon, // Custom icon - }, - }); - ``` - -- fd3f2a8c0: Export `createExternalRouteRef`, as well as give it an `id` for easier debugging, and fix parameter requirements when used with `useRouteRef`. - -## 0.2.9 - -### Patch Changes - -- ab0892358: Remove test dependencies from production package list - -## 0.2.8 - -### Patch Changes - -- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes. -- 86c3c652a: Deprecate `RouteRef` path parameter and member, and remove deprecated `routeRef.createSubRouteRef`. -- 27f2af935: Delay auth loginPopup close to avoid race condition with callers of authFlowHelpers. - -## 0.2.7 - -### Patch Changes - -- d681db2b5: Fix for GitHub and SAML auth not properly updating session state when already logged in. -- 1dc445e89: Introduce new plugin extension API -- Updated dependencies [1dc445e89] - - @backstage/test-utils@0.1.6 - -## 0.2.6 - -### Patch Changes - -- 7dd2ef7d1: Use auth provider ID to create unique session storage keys for GitHub and SAML Auth. - -## 0.2.5 - -### Patch Changes - -- b6557c098: Update ApiFactory type to correctly infer API type and disallow mismatched implementations. - - This fixes for example the following code: - - ```ts - interface MyApi { - myMethod(): void - } - - const myApiRef = createApiRef({...}); - - createApiFactory({ - api: myApiRef, - deps: {}, - // This should've caused an error, since the empty object does not fully implement MyApi - factory: () => ({}), - }) - ``` - -- d8d5a17da: Deprecated the `ConcreteRoute`, `MutableRouteRef`, `AbsoluteRouteRef` types and added a new `RouteRef` type as replacement. - - Deprecated and disabled the `createSubRoute` method of `AbsoluteRouteRef`. - - Add an as of yet unused `params` option to `createRouteRef`. - -- Updated dependencies [e3bd9fc2f] -- Updated dependencies [e1f4e24ef] -- Updated dependencies [1665ae8bb] -- Updated dependencies [e3bd9fc2f] - - @backstage/config@0.1.2 - - @backstage/test-utils@0.1.5 - - @backstage/theme@0.2.2 - -## 0.2.4 - -### Patch Changes - -- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError - - @backstage/test-utils@0.1.4 - -## 0.2.3 - -### Patch Changes - -- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure - -## 0.2.2 - -### Patch Changes - -- 9b9e86f8a: export oidc provider - -## 0.2.1 - -### Patch Changes - -- c5bab94ab: Updated the AuthApi `.create` methods to configure the default scope of the corresponding Auth Api. As a result the - default scope is configurable when overwriting the Core Api in the app. - - ``` - GithubAuth.create({ - discoveryApi, - oauthRequestApi, - defaultScopes: ['read:user', 'repo'], - }), - ``` - - Replaced redundant CreateOptions of each Auth Api with the OAuthApiCreateOptions type. - - ``` - export type OAuthApiCreateOptions = AuthApiCreateOptions & { - oauthRequestApi: OAuthRequestApi; - defaultScopes?: string[]; - }; - - export type AuthApiCreateOptions = { - discoveryApi: DiscoveryApi; - environment?: string; - provider?: AuthProvider & { id: string }; - }; - ``` - -- Updated dependencies [4577e377b] - - @backstage/theme@0.2.1 - -## 0.2.0 - -### Minor Changes - -- 819a70229: Add SAML login to backstage - - ![](https://user-images.githubusercontent.com/872486/92251660-bb9e3400-eeff-11ea-86fe-1f2a0262cd31.png) - - ![](https://user-images.githubusercontent.com/872486/93851658-1a76f200-fce3-11ea-990b-26ca1a327a15.png) - -- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the GitHub Auth Api. As a result the - default scope is configurable when overwriting the Core Api in the app. - - ``` - GithubAuth.create({ - discoveryApi, - oauthRequestApi, - defaultScopes: ['read:user', 'repo'], - }), - ``` - -- cbab5bbf8: Refactored the FeatureFlagsApi to make it easier to re-implement. Existing usage of particularly getUserFlags can be replaced with isActive() or save(). - -### Patch Changes - -- cbbd271c4: Add initial RouteRefRegistry - - Starting out some work to bring routing back and working as part of the work towards finalizing #1536 - - This is some of the groundwork of an experiment we're working on to enable routing via RouteRefs, while letting the app itself look something like this: - - ```jsx - const App = () => ( - - - - {' '} - // catalogRouteRef - - - - - - - // statusRouteRef - - - - - - - - - - // sentryRouteRef - - - - - - - - - - - - - - - - ); - ``` - - As part of inverting the composition of the app, route refs and routing in general was somewhat broken, intentionally. Right now it's not really possible to easily route to different parts of the app from a plugin, or even different parts of the plugin that are not within the same router. - - The core part of the experiment is to construct a map of ApiRef[] -> path overrides. Each key in the map is the list of route refs to traversed to reach a leaf in the routing tree, and the value is the path override at that point. For example, the above tree would add entries like [techDocsRouteRef] -> '/docs', and [entityRouteRef, apiDocsRouteRef] -> '/api'. By mapping out the entire app in this structure, the idea is that we can navigate to any point in the app using RouteRefs. - - The RouteRefRegistry is an implementation of such a map, and the idea is to add it in master to make it a bit easier to experiment and iterate. This is not an exposed API at this point. - - We've explored a couple of alternatives for how to enable routing, but it's boiled down to either a solution centred around the route map mentioned above, or treating all routes as static and globally unique, with no room for flexibility, customization or conflicts between different plugins. We're starting out pursuing this options 😁. We also expect that a the app-wide routing table will make things like dynamic loading a lot cleaner, as there would be a much more clear handoff between the main chunk and dynamic chunks. - -- 26e69ab1a: Remove cost insights example client from demo app and export from plugin - Create cost insights dev plugin using example client - Make PluginConfig and dependent types public -- Updated dependencies [ae5983387] -- Updated dependencies [0d4459c08] - - @backstage/theme@0.2.0 - - @backstage/test-utils@0.1.2 diff --git a/packages/core-api/README.md b/packages/core-api/README.md deleted file mode 100644 index a4325a9c5d..0000000000 --- a/packages/core-api/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# @backstage/core-api - -This package provides the core API used by Backstage plugins and apps. - -## Installation - -Do not install this package directly, it is reexported by `@backstage/core`. Install that instead: - -```sh -npm install --save @backstage/core -``` - -or - -```sh -yarn add @backstage/core -``` - -## Documentation - -- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) -- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/core-api/package.json b/packages/core-api/package.json deleted file mode 100644 index 1eaeeb24b1..0000000000 --- a/packages/core-api/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@backstage/core-api", - "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.22", - "private": false, - "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/core-api" - }, - "keywords": [ - "backstage" - ], - "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", - "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" - }, - "dependencies": { - "@backstage/config": "^0.1.4", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/theme": "^0.2.8", - "@material-ui/core": "^4.11.0", - "@material-ui/icons": "^4.9.1", - "@types/react": "^16.9", - "@types/prop-types": "^15.7.3", - "prop-types": "^15.7.2", - "react": "^16.12.0", - "react-router-dom": "6.0.0-beta.0", - "react-use": "^17.2.4", - "zen-observable": "^0.8.15" - }, - "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/test-utils": "^0.1.13", - "@backstage/test-utils-core": "^0.1.1", - "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^11.2.5", - "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^13.1.8", - "@types/jest": "^26.0.7", - "@types/node": "^14.14.32", - "@types/zen-observable": "^0.8.0", - "cross-fetch": "^3.0.6", - "msw": "^0.21.3" - }, - "files": [ - "dist" - ] -} diff --git a/packages/core-api/src/apis/definitions/AlertApi.ts b/packages/core-api/src/apis/definitions/AlertApi.ts deleted file mode 100644 index 91641ac35b..0000000000 --- a/packages/core-api/src/apis/definitions/AlertApi.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { createApiRef, ApiRef } from '../system'; -import { Observable } from '../../types'; - -export type AlertMessage = { - message: string; - // Severity will default to success since that is what material ui defaults the value to. - severity?: 'success' | 'info' | 'warning' | 'error'; -}; - -/** - * The alert API is used to report alerts to the app, and display them to the user. - */ - -export type AlertApi = { - /** - * Post an alert for handling by the application. - */ - post(alert: AlertMessage): void; - - /** - * Observe alerts posted by other parts of the application. - */ - alert$(): Observable; -}; - -export const alertApiRef: ApiRef = createApiRef({ - id: 'core.alert', - description: 'Used to report alerts and forward them to the app', -}); diff --git a/packages/core-api/src/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts deleted file mode 100644 index 515e8df082..0000000000 --- a/packages/core-api/src/apis/definitions/AppThemeApi.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { BackstageTheme } from '@backstage/theme'; -import { Observable } from '../../types'; -import { SvgIconProps } from '@material-ui/core'; - -/** - * Describes a theme provided by the app. - */ -export type AppTheme = { - /** - * ID used to remember theme selections. - */ - id: string; - - /** - * Title of the theme - */ - title: string; - - /** - * Theme variant - */ - variant: 'light' | 'dark'; - - /** - * The specialized MaterialUI theme instance. - */ - theme: BackstageTheme; - - /** - * An Icon for the theme mode setting. - */ - icon?: React.ReactElement; -}; - -/** - * The AppThemeApi gives access to the current app theme, and allows switching - * to other options that have been registered as a part of the App. - */ -export type AppThemeApi = { - /** - * Get a list of available themes. - */ - getInstalledThemes(): AppTheme[]; - - /** - * Observe the currently selected theme. A value of undefined means no specific theme has been selected. - */ - activeThemeId$(): Observable; - - /** - * Get the current theme ID. Returns undefined if no specific theme is selected. - */ - getActiveThemeId(): string | undefined; - - /** - * Set a specific theme to use in the app, overriding the default theme selection. - * - * Clear the selection by passing in undefined. - */ - setActiveThemeId(themeId?: string): void; -}; - -export const appThemeApiRef: ApiRef = createApiRef({ - id: 'core.apptheme', - description: 'API Used to configure the app theme, and enumerate options', -}); diff --git a/packages/core-api/src/apis/definitions/ConfigApi.ts b/packages/core-api/src/apis/definitions/ConfigApi.ts deleted file mode 100644 index 459a361cf7..0000000000 --- a/packages/core-api/src/apis/definitions/ConfigApi.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { Config } from '@backstage/config'; - -/** - * The Config API is used to provide a mechanism to access the - * runtime configuration of the system. - */ -export type ConfigApi = Config; - -export const configApiRef: ApiRef = createApiRef({ - id: 'core.config', - description: 'Used to access runtime configuration', -}); diff --git a/packages/core-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-api/src/apis/definitions/DiscoveryApi.ts deleted file mode 100644 index f62a97b61a..0000000000 --- a/packages/core-api/src/apis/definitions/DiscoveryApi.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; - -/** - * The discovery API is used to provide a mechanism for plugins to - * discover the endpoint to use to talk to their backend counterpart. - * - * The purpose of the discovery API is to allow for many different deployment - * setups and routing methods through a central configuration, instead - * of letting each individual plugin manage that configuration. - * - * Implementations of the discovery API can be a simple as a URL pattern - * using the pluginId, but could also have overrides for individual plugins, - * or query a separate discovery service. - */ -export type DiscoveryApi = { - /** - * Returns the HTTP base backend URL for a given plugin, without a trailing slash. - * - * This method must always be called just before making a request, as opposed to - * fetching the URL when constructing an API client. That is to ensure that more - * flexible routing patterns can be supported. - * - * For example, asking for the URL for `auth` may return something - * like `https://backstage.example.com/api/auth` - */ - getBaseUrl(pluginId: string): Promise; -}; - -export const discoveryApiRef: ApiRef = createApiRef({ - id: 'core.discovery', - description: 'Provides service discovery of backend plugins', -}); diff --git a/packages/core-api/src/apis/definitions/ErrorApi.ts b/packages/core-api/src/apis/definitions/ErrorApi.ts deleted file mode 100644 index 6205f8e058..0000000000 --- a/packages/core-api/src/apis/definitions/ErrorApi.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; - -/** - * Mirrors the JavaScript Error class, for the purpose of - * providing documentation and optional fields. - */ -type Error = { - name: string; - message: string; - stack?: string; -}; - -/** - * Provides additional information about an error that was posted to the application. - */ -export type ErrorContext = { - // If set to true, this error should not be displayed to the user. Defaults to false. - hidden?: boolean; -}; - -/** - * The error API is used to report errors to the app, and display them to the user. - * - * Plugins can use this API as a method of displaying errors to the user, but also - * to report errors for collection by error reporting services. - * - * If an error can be displayed inline, e.g. as feedback in a form, that should be - * preferred over relying on this API to display the error. The main use of this API - * for displaying errors should be for asynchronous errors, such as a failing background process. - * - * Even if an error is displayed inline, it should still be reported through this API - * if it would be useful to collect or log it for debugging purposes, but with - * the hidden flag set. For example, an error arising from form field validation - * should probably not be reported, while a failed REST call would be useful to report. - */ -export type ErrorApi = { - /** - * Post an error for handling by the application. - */ - post(error: Error, context?: ErrorContext): void; - - /** - * Observe errors posted by other parts of the application. - */ - error$(): Observable<{ error: Error; context?: ErrorContext }>; -}; - -export const errorApiRef: ApiRef = createApiRef({ - id: 'core.error', - description: 'Used to report errors and forward them to the app', -}); diff --git a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts deleted file mode 100644 index 243af562b1..0000000000 --- a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; - -/** - * The feature flags API is used to toggle functionality to users across plugins and Backstage. - * - * Plugins can use this API to register feature flags that they have available - * for users to enable/disable, and this API will centralize the current user's - * state of which feature flags they would like to enable. - * - * This is ideal for Backstage plugins, as well as your own App, to trial incomplete - * or unstable upcoming features. Although there will be a common interface for users - * to enable and disable feature flags, this API acts as another way to enable/disable. - */ - -export type FeatureFlag = { - name: string; - pluginId: string; -}; - -export enum FeatureFlagState { - None = 0, - Active = 1, -} - -/** - * Options to use when saving feature flags. - */ -export type FeatureFlagsSaveOptions = { - /** - * The new feature flag states to save. - */ - states: Record; - - /** - * Whether the saves states should be merged into the existing ones, or replace them. - * - * Defaults to false. - */ - merge?: boolean; -}; - -export type UserFlags = {}; - -export interface FeatureFlagsApi { - /** - * Registers a new feature flag. Once a feature flag has been registered it - * can be toggled by users, and read back to enable or disable features. - */ - registerFlag(flag: FeatureFlag): void; - - /** - * Get a list of all registered flags. - */ - getRegisteredFlags(): FeatureFlag[]; - - /** - * Whether the feature flag with the given name is currently activated for the user. - */ - isActive(name: string): boolean; - - /** - * Save the user's choice of feature flag states. - */ - save(options: FeatureFlagsSaveOptions): void; -} - -export const featureFlagsApiRef: ApiRef = createApiRef({ - id: 'core.featureflags', - description: 'Used to toggle functionality in features across Backstage', -}); diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts deleted file mode 100644 index 2d29709b02..0000000000 --- a/packages/core-api/src/apis/definitions/IdentityApi.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { ProfileInfo } from './auth'; - -/** - * The Identity API used to identify and get information about the signed in user. - */ -export type IdentityApi = { - /** - * The ID of the signed in user. This ID is not meant to be presented to the user, but used - * as an opaque string to pass on to backends or use in frontend logic. - * - * TODO: The intention of the user ID is to be able to tie the user to an identity - * that is known by the catalog and/or identity backend. It should for example - * be possible to fetch all owned components using this ID. - */ - getUserId(): string; - - // TODO: getProfile(): Promise - We want this to be async when added, but needs more work. - /** - * The profile of the signed in user. - */ - getProfile(): ProfileInfo; - - /** - * An OpenID Connect ID Token which proves the identity of the signed in user. - * - * The ID token will be undefined if the signed in user does not have a verified - * identity, such as a demo user or mocked user for e2e tests. - */ - getIdToken(): Promise; - - /** - * Sign out the current user - */ - signOut(): Promise; -}; - -export const identityApiRef: ApiRef = createApiRef({ - id: 'core.identity', - description: 'Provides access to the identity of the signed in user', -}); diff --git a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts deleted file mode 100644 index fc4f1ef166..0000000000 --- a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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 { IconComponent } from '../../icons/types'; -import { Observable } from '../../types'; -import { ApiRef, createApiRef } from '../system'; - -/** - * Information about the auth provider that we're requesting a login towards. - * - * This should be shown to the user so that they can be informed about what login is being requested - * before a popup is shown. - */ -export type AuthProvider = { - /** - * Title for the auth provider, for example "GitHub" - */ - title: string; - - /** - * Icon for the auth provider. - */ - icon: IconComponent; -}; - -/** - * Describes how to handle auth requests. Both how to show them to the user, and what to do when - * the user accesses the auth request. - */ -export type AuthRequesterOptions = { - /** - * Information about the auth provider, which will be forwarded to auth requests. - */ - provider: AuthProvider; - - /** - * Implementation of the auth flow, which will be called synchronously when - * trigger() is called on an auth requests. - */ - onAuthRequest(scopes: Set): Promise; -}; - -/** - * Function used to trigger new auth requests for a set of scopes. - * - * The returned promise will resolve to the same value returned by the onAuthRequest in the - * AuthRequesterOptions. Or rejected, if the request is rejected. - * - * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the - * union of all requested scopes. - */ -export type AuthRequester = ( - scopes: Set, -) => Promise; - -/** - * An pending auth request for a single auth provider. The request will remain in this pending - * state until either reject() or trigger() is called. - * - * Any new requests for the same provider are merged into the existing pending request, meaning - * there will only ever be a single pending request for a given provider. - */ -export type PendingAuthRequest = { - /** - * Information about the auth provider, as given in the AuthRequesterOptions - */ - provider: AuthProvider; - - /** - * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". - */ - reject: () => void; - - /** - * Trigger the auth request to continue the auth flow, by for example showing a popup. - * - * Synchronously calls onAuthRequest with all scope currently in the request. - */ - trigger(): Promise; -}; - -/** - * Provides helpers for implemented OAuth login flows within Backstage. - */ -export type OAuthRequestApi = { - /** - * A utility for showing login popups or similar things, and merging together multiple requests for - * different scopes into one request that includes all scopes. - * - * The passed in options provide information about the login provider, and how to handle auth requests. - * - * The returned AuthRequester function is used to request login with new scopes. These requests - * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests - * triggers an auth flow. - * - * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. - */ - createAuthRequester( - options: AuthRequesterOptions, - ): AuthRequester; - - /** - * Observers pending auth requests. The returned observable will emit all - * current active auth request, at most one for each created auth requester. - * - * Each request has its own info about the login provider, forwarded from the auth requester options. - * - * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. - * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". - * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the onAuthRequest call. - */ - authRequest$(): Observable; -}; - -export const oauthRequestApiRef: ApiRef = createApiRef({ - id: 'core.oauthrequest', - description: 'An API for implementing unified OAuth flows in Backstage', -}); diff --git a/packages/core-api/src/apis/definitions/StorageApi.ts b/packages/core-api/src/apis/definitions/StorageApi.ts deleted file mode 100644 index 2482af6d63..0000000000 --- a/packages/core-api/src/apis/definitions/StorageApi.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; -import { ErrorApi } from './ErrorApi'; - -export type StorageValueChange = { - key: string; - newValue?: T; -}; - -export type CreateStorageApiOptions = { - errorApi: ErrorApi; - namespace?: string; -}; - -export interface StorageApi { - /** - * Create a bucket to store data in. - * @param {String} name Namespace for the storage to be stored under, - * will inherit previous namespaces too - */ - forBucket(name: string): StorageApi; - - /** - * Get the current value for persistent data, use observe$ to be notified of updates. - * - * @param {String} key Unique key associated with the data. - * @return {Object} data The data that should is stored. - */ - get(key: string): T | undefined; - - /** - * Remove persistent data. - * - * @param {String} key Unique key associated with the data. - */ - remove(key: string): Promise; - - /** - * Save persistent data, and emit messages to anyone that is using observe$ for this key - * - * @param {String} key Unique key associated with the data. - */ - set(key: string, data: any): Promise; - - /** - * Observe changes on a particular key in the bucket - * @param {String} key Unique key associated with the data - */ - observe$(key: string): Observable>; -} - -export const storageApiRef: ApiRef = createApiRef({ - id: 'core.storage', - description: 'Provides the ability to store data which is unique to the user', -}); diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts deleted file mode 100644 index 30b07887ad..0000000000 --- a/packages/core-api/src/apis/definitions/auth.ts +++ /dev/null @@ -1,347 +0,0 @@ -/* - * 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; - -/** - * This file contains declarations for common interfaces of auth-related APIs. - * The declarations should be used to signal which type of authentication and - * authorization methods each separate auth provider supports. - * - * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, - * would be declared as follows: - * - * const googleAuthApiRef = createApiRef({ ... }) - */ - -/** - * An array of scopes, or a scope string formatted according to the - * auth provider, which is typically a space separated list. - * - * See the documentation for each auth provider for the list of scopes - * supported by each provider. - */ -export type OAuthScope = string | string[]; - -export type AuthRequestOptions = { - /** - * If this is set to true, the user will not be prompted to log in, - * and an empty response will be returned if there is no existing session. - * - * This can be used to perform a check whether the user is logged in, or if you don't - * want to force a user to be logged in, but provide functionality if they already are. - * - * @default false - */ - optional?: boolean; - - /** - * If this is set to true, the request will bypass the regular oauth login modal - * and open the login popup directly. - * - * The method must be called synchronously from a user action for this to work in all browsers. - * - * @default false - */ - instantPopup?: boolean; -}; - -/** - * This API provides access to OAuth 2 credentials. It lets you request access tokens, - * which can be used to act on behalf of the user when talking to APIs. - */ -export type OAuthApi = { - /** - * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows - * you to make requests on behalf of the user, and the copes may grant you broader access, depending - * on the auth provider. - * - * Each auth provider has separate handling of scope, so you need to look at the documentation - * for each one to know what scope you need to request. - * - * This method is cheap and should be called each time an access token is used. Do not for example - * store the access token in React component state, as that could cause the token to expire. Instead - * fetch a new access token for each request. - * - * Be sure to include all required scopes when requesting an access token. When testing your implementation - * it is best to log out the Backstage session and then visit your plugin page directly, as - * you might already have some required scopes in your existing session. Not requesting the correct - * scopes can lead to 403 or other authorization errors, which can be tricky to debug. - * - * If the user has not yet granted access to the provider and the set of requested scopes, the user - * will be prompted to log in. The returned promise will not resolve until the user has - * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. - */ - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * This API provides access to OpenID Connect credentials. It lets you request ID tokens, - * which can be passed to backend services to prove the user's identity. - */ -export type OpenIdConnectApi = { - /** - * Requests an OpenID Connect ID Token. - * - * This method is cheap and should be called each time an ID token is used. Do not for example - * store the id token in React component state, as that could cause the token to expire. Instead - * fetch a new id token for each request. - * - * If the user has not yet logged in to Google inside Backstage, the user will be prompted - * to log in. The returned promise will not resolve until the user has successfully logged in. - * The returned promise can be rejected, but only if the user rejects the login request. - */ - getIdToken(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to profile information of the user from an auth provider. - */ -export type ProfileInfoApi = { - /** - * Get profile information for the user as supplied by this auth provider. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details. - */ - getProfile(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to the user's identity within Backstage. - * - * An auth provider that implements this interface can be used to sign-in to backstage. It is - * not intended to be used directly from a plugin, but instead serves as a connection between - * this authentication method and the app's @IdentityApi - */ -export type BackstageIdentityApi = { - /** - * Get the user's identity within Backstage. This should normally not be called directly, - * use the @IdentityApi instead. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details. - */ - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; - -export type BackstageIdentity = { - /** - * The backstage user ID. - */ - id: string; - - /** - * An ID token that can be used to authenticate the user within Backstage. - */ - idToken: string; -}; - -/** - * Profile information of the user. - */ -export type ProfileInfo = { - /** - * Email ID. - */ - email?: string; - - /** - * Display name that can be presented to the user. - */ - displayName?: string; - - /** - * URL to an avatar image of the user. - */ - picture?: string; -}; - -/** - * Session state values passed to subscribers of the SessionApi. - */ -export enum SessionState { - SignedIn = 'SignedIn', - SignedOut = 'SignedOut', -} - -/** - * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. - */ -export type SessionApi = { - /** - * Sign in with a minimum set of permissions. - */ - signIn(): Promise; - - /** - * Sign out from the current session. This will reload the page. - */ - signOut(): Promise; - - /** - * Observe the current state of the auth session. Emits the current state on subscription. - */ - sessionState$(): Observable; -}; - -/** - * Provides authentication towards Google APIs and identities. - * - * See https://developers.google.com/identity/protocols/googlescopes for a full list of supported scopes. - * - * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, - * email and expiration information. Do not rely on any other fields, as they might not be present. - */ -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.google', - description: 'Provides authentication towards Google APIs and identities', -}); - -/** - * Provides authentication towards GitHub APIs. - * - * See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ - * for a full list of supported scopes. - */ -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.github', - description: 'Provides authentication towards GitHub APIs', -}); - -/** - * Provides authentication towards Okta APIs. - * - * See https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/ - * for a full list of supported scopes. - */ -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.okta', - description: 'Provides authentication towards Okta APIs', -}); - -/** - * Provides authentication towards GitLab APIs. - * - * See https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token - * for a full list of supported scopes. - */ -export const gitlabAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.gitlab', - description: 'Provides authentication towards GitLab APIs', -}); - -/** - * Provides authentication towards Auth0 APIs. - * - * See https://auth0.com/docs/scopes/current/oidc-scopes - * for a full list of supported scopes. - */ -export const auth0AuthApiRef: ApiRef< - OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.auth0', - description: 'Provides authentication towards Auth0 APIs', -}); - -/** - * Provides authentication towards Microsoft APIs and identities. - * - * For more info and a full list of supported scopes, see: - * - https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent - * - https://docs.microsoft.com/en-us/graph/permissions-reference - */ -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.microsoft', - description: 'Provides authentication towards Microsoft APIs and identities', -}); - -/** - * Provides authentication for custom identity providers. - */ -export const oauth2ApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.oauth2', - description: 'Example of how to use oauth2 custom provider', -}); - -/** - * Provides authentication for custom OpenID Connect identity providers. - */ -export const oidcAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.oidc', - description: 'Example of how to use oidc custom provider', -}); - -/** - * Provides authentication for saml based identity providers - */ -export const samlAuthApiRef: ApiRef< - ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.saml', - description: 'Example of how to use SAML custom provider', -}); - -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.onelogin', - description: 'Provides authentication towards OneLogin APIs and identities', -}); diff --git a/packages/core-api/src/apis/definitions/index.ts b/packages/core-api/src/apis/definitions/index.ts deleted file mode 100644 index e29d1022c4..0000000000 --- a/packages/core-api/src/apis/definitions/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -// This folder contains definitions for all core APIs. -// -// Plugins should rely on these APIs for functionality as much as possible. -// -// If you think some API definition is missing, please open an Issue or send a PR! - -export * from './auth'; - -export * from './AlertApi'; -export * from './AppThemeApi'; -export * from './ConfigApi'; -export * from './DiscoveryApi'; -export * from './ErrorApi'; -export * from './FeatureFlagsApi'; -export * from './IdentityApi'; -export * from './OAuthRequestApi'; -export * from './StorageApi'; diff --git a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts deleted file mode 100644 index 0c23fe5219..0000000000 --- a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { AppTheme } from '../../definitions'; -import { AppThemeSelector } from './AppThemeSelector'; - -describe('AppThemeSelector', () => { - it('should should select new themes', async () => { - const selector = new AppThemeSelector([]); - - expect(selector.getInstalledThemes()).toEqual([]); - - const subFn = jest.fn(); - selector.activeThemeId$().subscribe(subFn); - expect(selector.getActiveThemeId()).toBe(undefined); - await 'wait a tick'; - expect(subFn).toHaveBeenLastCalledWith(undefined); - - selector.setActiveThemeId('x'); - expect(subFn).toHaveBeenLastCalledWith('x'); - expect(selector.getActiveThemeId()).toBe('x'); - - selector.setActiveThemeId(undefined); - expect(subFn).toHaveBeenLastCalledWith(undefined); - expect(selector.getActiveThemeId()).toBe(undefined); - }); - - it('should return a new array of themes', () => { - const themes = new Array(); - const selector = new AppThemeSelector(themes); - - expect(selector.getInstalledThemes()).toEqual(themes); - expect(selector.getInstalledThemes()).not.toBe(themes); - }); - - it('should store theme in local storage', async () => { - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe( - undefined, - ); - localStorage.setItem('theme', 'x'); - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe('x'); - localStorage.removeItem('theme'); - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe( - undefined, - ); - - const addListenerSpy = jest.spyOn(window, 'addEventListener'); - const selector = AppThemeSelector.createWithStorage([]); - - expect(addListenerSpy).toHaveBeenCalledTimes(1); - expect(addListenerSpy).toHaveBeenCalledWith( - 'storage', - expect.any(Function), - ); - - selector.setActiveThemeId('y'); - await 'wait a tick'; - expect(localStorage.getItem('theme')).toBe('y'); - - selector.setActiveThemeId(undefined); - await 'wait a tick'; - expect(localStorage.getItem('theme')).toBe(null); - - localStorage.setItem('theme', 'z'); - expect(selector.getActiveThemeId()).toBe(undefined); - - const listener = addListenerSpy.mock.calls[0][1] as EventListener; - listener({ key: 'theme' } as StorageEvent); - - expect(selector.getActiveThemeId()).toBe('z'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts deleted file mode 100644 index 837c5f54db..0000000000 --- a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { AppThemeApi, AppTheme } from '../../definitions'; -import { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -const STORAGE_KEY = 'theme'; - -export class AppThemeSelector implements AppThemeApi { - static createWithStorage(themes: AppTheme[]) { - const selector = new AppThemeSelector(themes); - - if (!window.localStorage) { - return selector; - } - - const initialThemeId = - window.localStorage.getItem(STORAGE_KEY) ?? undefined; - - selector.setActiveThemeId(initialThemeId); - - selector.activeThemeId$().subscribe(themeId => { - if (themeId) { - window.localStorage.setItem(STORAGE_KEY, themeId); - } else { - window.localStorage.removeItem(STORAGE_KEY); - } - }); - - window.addEventListener('storage', event => { - if (event.key === STORAGE_KEY) { - const themeId = localStorage.getItem(STORAGE_KEY) ?? undefined; - selector.setActiveThemeId(themeId); - } - }); - - return selector; - } - - private activeThemeId: string | undefined; - private readonly subject = new BehaviorSubject(undefined); - - constructor(private readonly themes: AppTheme[]) {} - - getInstalledThemes(): AppTheme[] { - return this.themes.slice(); - } - - activeThemeId$(): Observable { - return this.subject; - } - - getActiveThemeId(): string | undefined { - return this.activeThemeId; - } - - setActiveThemeId(themeId?: string): void { - this.activeThemeId = themeId; - this.subject.next(themeId); - } -} diff --git a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts b/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts deleted file mode 100644 index 9597443b98..0000000000 --- a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 { UrlPatternDiscovery } from './UrlPatternDiscovery'; - -describe('UrlPatternDiscovery', () => { - it('should not require interpolation', async () => { - const discoveryApi = UrlPatternDiscovery.compile('http://example.com'); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'http://example.com', - ); - }); - - it('should use a plain pattern', async () => { - const discoveryApi = UrlPatternDiscovery.compile( - 'http://localhost:7000/{{ pluginId }}', - ); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'http://localhost:7000/my-plugin', - ); - }); - - it('should allow for multiple interpolation points', async () => { - const discoveryApi = UrlPatternDiscovery.compile( - 'https://{{pluginId }}.example.com/api/{{ pluginId}}', - ); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'https://my-plugin.example.com/api/my-plugin', - ); - }); - - it('should validate that the pattern is a valid URL', () => { - expect(() => { - UrlPatternDiscovery.compile('example.com'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: example.com'); - - expect(() => { - UrlPatternDiscovery.compile('http://'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: http://'); - - expect(() => { - UrlPatternDiscovery.compile('abc123'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: abc123'); - - expect(() => { - UrlPatternDiscovery.compile('http://example.com:{{pluginId}}'); - }).toThrow( - 'Invalid discovery URL pattern, Invalid URL: http://example.com:pluginId', - ); - - expect(() => { - UrlPatternDiscovery.compile('/{{pluginId}}'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: /pluginId'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}?forbidden'); - }).toThrow('Invalid discovery URL pattern, URL must not have a query'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}#forbidden'); - }).toThrow('Invalid discovery URL pattern, URL must not have a hash'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}/'); - }).toThrow('Invalid discovery URL pattern, URL must not end with a slash'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/'); - }).toThrow('Invalid discovery URL pattern, URL must not end with a slash'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts b/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts deleted file mode 100644 index ca48784584..0000000000 --- a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { DiscoveryApi } from '../../definitions/DiscoveryApi'; - -/** - * UrlPatternDiscovery is a lightweight DiscoveryApi implementation. - * It uses a single template string to construct URLs for each plugin. - */ -export class UrlPatternDiscovery implements DiscoveryApi { - /** - * Creates a new UrlPatternDiscovery given a template. The the only - * interpolation done for the template is to replace instances of `{{pluginId}}` - * with the ID of the plugin being requested. - * - * Example pattern: `http://localhost:7000/api/{{ pluginId }}` - */ - static compile(pattern: string): UrlPatternDiscovery { - const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/); - - try { - const urlStr = parts.join('pluginId'); - const url = new URL(urlStr); - if (url.hash) { - throw new Error('URL must not have a hash'); - } - if (url.search) { - throw new Error('URL must not have a query'); - } - if (urlStr.endsWith('/')) { - throw new Error('URL must not end with a slash'); - } - } catch (error) { - throw new Error(`Invalid discovery URL pattern, ${error.message}`); - } - - return new UrlPatternDiscovery(parts); - } - - private constructor(private readonly parts: string[]) {} - - async getBaseUrl(pluginId: string): Promise { - return this.parts.join(pluginId); - } -} diff --git a/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts deleted file mode 100644 index 84bc958698..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { AlertApi, ErrorApi, ErrorContext } from '../../definitions'; - -/** - * Decorates an ErrorApi by also forwarding error messages - * to the alertApi with an 'error' severity. - */ -export class ErrorAlerter implements ErrorApi { - constructor( - private readonly alertApi: AlertApi, - private readonly errorApi: ErrorApi, - ) {} - - post(error: Error, context?: ErrorContext) { - if (!context?.hidden) { - this.alertApi.post({ message: error.message, severity: 'error' }); - } - - return this.errorApi.post(error, context); - } - - error$() { - return this.errorApi.error$(); - } -} diff --git a/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts deleted file mode 100644 index 5993ede367..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { PublishSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; -import { ErrorApi, ErrorContext } from '../../definitions'; - -/** - * Base implementation for the ErrorApi that simply forwards errors to consumers. - */ -export class ErrorApiForwarder implements ErrorApi { - private readonly subject = new PublishSubject<{ - error: Error; - context?: ErrorContext; - }>(); - - post(error: Error, context?: ErrorContext) { - this.subject.next({ error, context }); - } - - error$(): Observable<{ error: Error; context?: ErrorContext }> { - return this.subject; - } -} diff --git a/packages/core-api/src/apis/implementations/ErrorApi/index.ts b/packages/core-api/src/apis/implementations/ErrorApi/index.ts deleted file mode 100644 index 757dfd0d8f..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { ErrorAlerter } from './ErrorAlerter'; -export { ErrorApiForwarder } from './ErrorApiForwarder'; diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx b/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx deleted file mode 100644 index 2902e399c9..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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 { LocalStorageFeatureFlags } from './LocalStorageFeatureFlags'; -import { FeatureFlagState, FeatureFlagsApi } from '../../definitions'; - -describe('FeatureFlags', () => { - beforeEach(() => { - window.localStorage.clear(); - }); - - describe('getFlags', () => { - let featureFlags: FeatureFlagsApi; - - beforeEach(() => { - featureFlags = new LocalStorageFeatureFlags(); - }); - - it('returns no flags', () => { - expect(featureFlags.getRegisteredFlags()).toEqual([]); - }); - - it('loads flags from local storage', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 1, - 'feature-flag-three': 0, - 'feature-flag-four': 2, - 'feature-flag-five': 'not-valid', - }), - ); - - expect(featureFlags.isActive('feature-flag-one')).toBe(true); - expect(featureFlags.isActive('feature-flag-two')).toBe(true); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - expect(featureFlags.isActive('feature-flag-four')).toBe(false); - expect(featureFlags.isActive('feature-flag-five')).toBe(false); - }); - - it('sets the correct values', () => { - featureFlags.save({ - states: { - 'feature-flag-zero': FeatureFlagState.Active, - }, - }); - - expect(featureFlags.isActive('feature-flag-zero')).toBe(true); - expect(window.localStorage.getItem('featureFlags')).toEqual( - '{"feature-flag-zero":1}', - ); - }); - - it('deletes the correct values', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 0, - 'feature-flag-tree': 1, - 'feature-flag-four': 0, - }), - ); - - featureFlags.save({ - states: { - 'feature-flag-one': FeatureFlagState.None, - 'feature-flag-two': FeatureFlagState.Active, - }, - }); - - expect(window.localStorage.getItem('featureFlags')).toEqual( - '{"feature-flag-two":1}', - ); - }); - - it('clears all values', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 1, - 'feature-flag-three': 0, - }), - ); - - expect(featureFlags.isActive('feature-flag-one')).toBe(true); - expect(featureFlags.isActive('feature-flag-two')).toBe(true); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - - featureFlags.save({ states: {} }); - - expect(featureFlags.isActive('feature-flag-one')).toBe(false); - expect(featureFlags.isActive('feature-flag-two')).toBe(false); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - - expect(window.localStorage.getItem('featureFlags')).toEqual('{}'); - }); - }); - - describe('getRegisteredFlags', () => { - let featureFlags: FeatureFlagsApi; - - beforeEach(() => { - featureFlags = new LocalStorageFeatureFlags(); - featureFlags.registerFlag({ - name: 'registered-flag-1', - pluginId: 'plugin-one', - }); - featureFlags.registerFlag({ - name: 'registered-flag-2', - pluginId: 'plugin-one', - }); - featureFlags.registerFlag({ - name: 'registered-flag-3', - pluginId: 'plugin-two', - }); - }); - - it('should return an empty list', () => { - featureFlags = new LocalStorageFeatureFlags(); - expect(featureFlags.getRegisteredFlags()).toEqual([]); - }); - - it('should return an valid list', () => { - expect(featureFlags.getRegisteredFlags()).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - }); - - it('should provide a copy of the list of flags', () => { - const flags = featureFlags.getRegisteredFlags(); - expect(flags).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - flags.splice(2, 1); - expect(flags).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - ]); - expect(featureFlags.getRegisteredFlags()).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - }); - - it('should get the correct values', () => { - const getByName = (name: string) => - featureFlags.getRegisteredFlags().find(flag => flag.name === name); - - expect(getByName('registered-flag-0')).toBeUndefined(); - expect(getByName('registered-flag-1')).toEqual({ - name: 'registered-flag-1', - pluginId: 'plugin-one', - }); - expect(getByName('registered-flag-2')).toEqual({ - name: 'registered-flag-2', - pluginId: 'plugin-one', - }); - expect(getByName('registered-flag-3')).toEqual({ - name: 'registered-flag-3', - pluginId: 'plugin-two', - }); - }); - - it('throws an error if length is less than three characters', () => { - expect(() => - featureFlags.registerFlag({ - name: 'ab', - pluginId: 'plugin-three', - }), - ).toThrow(/minimum length of three characters/i); - }); - - it('throws an error if length is greater than 150 characters', () => { - expect(() => - featureFlags.registerFlag({ - name: - 'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat', - pluginId: 'plugin-three', - }), - ).toThrow(/not exceed 150 characters/i); - }); - - it('throws an error if name does not start with a lowercase letter', () => { - expect(() => - featureFlags.registerFlag({ - name: '123456789', - pluginId: 'plugin-three', - }), - ).toThrow(/start with a lowercase letter/i); - }); - - it('throws an error if name contains characters other than lowercase letters, numbers and hyphens', () => { - expect(() => - featureFlags.registerFlag({ - name: 'Invalid_Feature_Flag', - pluginId: 'plugin-three', - }), - ).toThrow(/only contain lowercase letters, numbers and hyphens/i); - }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx b/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx deleted file mode 100644 index 00b3eff906..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 { - FeatureFlagState, - FeatureFlagsApi, - FeatureFlag, - FeatureFlagsSaveOptions, -} from '../../definitions'; - -export function validateFlagName(name: string): void { - if (name.length < 3) { - throw new Error( - `The '${name}' feature flag must have a minimum length of three characters.`, - ); - } - - if (name.length > 150) { - throw new Error( - `The '${name}' feature flag must not exceed 150 characters.`, - ); - } - - if (!name.match(/^[a-z]+[a-z0-9-]+$/)) { - throw new Error( - `The '${name}' feature flag must start with a lowercase letter and only contain lowercase letters, numbers and hyphens. ` + - 'Examples: feature-flag-one, alpha, release-2020', - ); - } -} - -/** - * Create the FeatureFlags implementation based on the API. - */ -export class LocalStorageFeatureFlags implements FeatureFlagsApi { - private registeredFeatureFlags: FeatureFlag[] = []; - private flags?: Map; - - registerFlag(flag: FeatureFlag) { - validateFlagName(flag.name); - this.registeredFeatureFlags.push(flag); - } - - getRegisteredFlags(): FeatureFlag[] { - return this.registeredFeatureFlags.slice(); - } - - isActive(name: string): boolean { - if (!this.flags) { - this.flags = this.load(); - } - return this.flags.get(name) === FeatureFlagState.Active; - } - - save(options: FeatureFlagsSaveOptions): void { - if (!this.flags) { - this.flags = this.load(); - } - if (!options.merge) { - this.flags.clear(); - } - for (const [name, state] of Object.entries(options.states)) { - this.flags.set(name, state); - } - - const enabled = Array.from(this.flags.entries()).filter( - ([, state]) => state === FeatureFlagState.Active, - ); - window.localStorage.setItem( - 'featureFlags', - JSON.stringify(Object.fromEntries(enabled)), - ); - } - - private load(): Map { - try { - const jsonStr = window.localStorage.getItem('featureFlags'); - if (!jsonStr) { - return new Map(); - } - const json = JSON.parse(jsonStr) as unknown; - if (typeof json !== 'object' || json === null || Array.isArray(json)) { - return new Map(); - } - - const entries = Object.entries(json).filter(([name, value]) => { - validateFlagName(name); - return value === FeatureFlagState.Active; - }); - - return new Map(entries); - } catch { - return new Map(); - } - } -} diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts b/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts deleted file mode 100644 index 33990584f3..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { LocalStorageFeatureFlags } from './LocalStorageFeatureFlags'; diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts deleted file mode 100644 index 32170acc6f..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 MockOAuthApi from './MockOAuthApi'; -import PowerIcon from '@material-ui/icons/Power'; - -describe('MockOAuthApi', () => { - it('should trigger all requests', async () => { - const authResult = { is: 'done' }; - const mock = new MockOAuthApi(); - - const authHandler1 = jest.fn().mockImplementation(() => authResult); - const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler1, - }); - - const authHandler2 = jest.fn().mockResolvedValue('other'); - const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler2, - }); - - const promises = [ - requester1(new Set(['a'])), - requester1(new Set(['b'])), - requester2(new Set(['a', 'b'])), - requester2(new Set(['b', 'c'])), - requester2(new Set(['c', 'a'])), - ]; - - await expect( - Promise.race([Promise.all(promises), 'waiting']), - ).resolves.toBe('waiting'); - - await mock.triggerAll(); - - await expect(Promise.all(promises)).resolves.toEqual([ - authResult, - authResult, - 'other', - 'other', - 'other', - ]); - - expect(authHandler1).toHaveBeenCalledTimes(1); - expect(authHandler1).toHaveBeenCalledWith(new Set(['a', 'b'])); - expect(authHandler2).toHaveBeenCalledTimes(1); - expect(authHandler2).toHaveBeenCalledWith(new Set(['a', 'b', 'c'])); - }); - - it('should reject all requests', async () => { - const mock = new MockOAuthApi(); - - const authHandler1 = jest.fn(); - const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler1, - }); - - const authHandler2 = jest.fn(); - const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler2, - }); - - const promises = [ - requester1(new Set(['a'])), - requester1(new Set(['b'])), - requester2(new Set(['a', 'b'])), - requester2(new Set(['b', 'c'])), - requester2(new Set(['c', 'a'])), - ]; - - await expect( - Promise.race([Promise.all(promises), 'waiting']), - ).resolves.toBe('waiting'); - - await mock.rejectAll(); - - for (const promise of promises) { - await expect(promise).rejects.toMatchObject({ name: 'RejectedError' }); - } - - expect(authHandler1).not.toHaveBeenCalled(); - expect(authHandler2).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts deleted file mode 100644 index 523f713f45..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { OAuthRequestApi, AuthRequesterOptions } from '../../definitions'; -import { OAuthRequestManager } from './OAuthRequestManager'; - -export default class MockOAuthApi implements OAuthRequestApi { - private readonly real = new OAuthRequestManager(); - - createAuthRequester(options: AuthRequesterOptions) { - return this.real.createAuthRequester(options); - } - - authRequest$() { - return this.real.authRequest$(); - } - - async triggerAll() { - await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - - return new Promise(resolve => { - const subscription = this.authRequest$().subscribe(requests => { - subscription.unsubscribe(); - Promise.all(requests.map(request => request.trigger())).then(() => - resolve(), - ); - }); - }); - } - - async rejectAll() { - await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - - return new Promise(resolve => { - const subscription = this.authRequest$().subscribe(requests => { - subscription.unsubscribe(); - requests.map(request => request.reject()); - resolve(); - }); - }); - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts deleted file mode 100644 index 280321daa0..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 { waitFor } from '@testing-library/react'; -import { OAuthPendingRequests } from './OAuthPendingRequests'; - -describe('OAuthPendingRequests', () => { - it('notifies new observers about current state', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - - const input = new Set(['a', 'b']); - target.pending().subscribe({ next, error }); - target.request(input); - - await waitFor(() => expect(next).toBeCalledTimes(2)); - expect(next.mock.calls[0][0].scopes).toBeUndefined(); - expect(next.mock.calls[1][0].scopes.toString()).toBe(input.toString()); - expect(error.mock.calls.length).toBe(0); - }); - - it('resolves requests and notifies observers', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - const request2 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - target.resolve(new Set(['a']), 'session1'); - target.resolve(new Set(['a']), 'session2'); - - await expect(request1).resolves.toBe('session1'); - await expect(request2).resolves.toBe('session1'); - expect(next).toBeCalledTimes(3); // once on subscription, twice on resolve - expect(error).toBeCalledTimes(0); - }); - - it('can resolve through the observable', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(pendingRequest => pendingRequest.resolve('done')); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - - await expect(request1).resolves.toBe('done'); - expect(next).toBeCalledTimes(2); // once with data on subscription, once empty after resolution - expect(error).toBeCalledTimes(0); - }); - - it('rejects requests and notifies observers only once', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - const rejection = new Error('eek'); - - const request1 = target.request(new Set(['a'])); - const request2 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - target.reject(rejection); - target.resolve(new Set(['a']), 'session'); - - await expect(request1).rejects.toBe(rejection); - await expect(request2).rejects.toBe(rejection); - expect(next).toBeCalledTimes(3); // once on subscription, once or reject, once on resolve - expect(error).toBeCalledTimes(0); - }); - - it('can reject through the observable', async () => { - const target = new OAuthPendingRequests(); - const rejection = new Error('nope'); - const next = jest.fn(pendingRequest => pendingRequest.reject(rejection)); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - - await expect(request1).rejects.toBe(rejection); - expect(next).toBeCalledTimes(2); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts deleted file mode 100644 index f0710cb466..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -type RequestQueueEntry = { - scopes: Set; - resolve: (value: ResultType | PromiseLike) => void; - reject: (reason: Error) => void; -}; - -export type PendingRequest = { - scopes: Set | undefined; - resolve: (value: ResultType) => void; - reject: (reason: Error) => void; -}; - -export function hasScopes( - searched: Set, - searchFor: Set, -): boolean { - for (const scope of searchFor) { - if (!searched.has(scope)) { - return false; - } - } - return true; -} - -export function joinScopes( - scopes: Set, - ...moreScopess: Set[] -): Set { - const result = new Set(scopes); - - for (const moreScopes of moreScopess) { - for (const scope of moreScopes) { - result.add(scope); - } - } - - return result; -} - -/** - * The OAuthPendingRequests class is a utility for managing and observing - * a stream of requests for oauth scopes for a single provider, and resolving - * them correctly once requests are fulfilled. - */ -export class OAuthPendingRequests { - private requests: RequestQueueEntry[] = []; - private subject = new BehaviorSubject>( - this.getCurrentPending(), - ); - - request(scopes: Set): Promise { - return new Promise((resolve, reject) => { - this.requests.push({ scopes, resolve, reject }); - - this.subject.next(this.getCurrentPending()); - }); - } - - resolve(scopes: Set, result: ResultType): void { - this.requests = this.requests.filter(request => { - if (hasScopes(scopes, request.scopes)) { - request.resolve(result); - return false; - } - return true; - }); - - this.subject.next(this.getCurrentPending()); - } - - reject(error: Error) { - this.requests.forEach(request => request.reject(error)); - this.requests = []; - - this.subject.next(this.getCurrentPending()); - } - - pending(): Observable> { - return this.subject; - } - - private getCurrentPending(): PendingRequest { - const currentScopes = - this.requests.length === 0 - ? undefined - : this.requests - .slice(1) - .reduce( - (acc, current) => joinScopes(acc, current.scopes), - this.requests[0].scopes, - ); - - return { - scopes: currentScopes, - resolve: (value: ResultType) => { - if (currentScopes) { - this.resolve(currentScopes, value); - } - }, - reject: (reason: Error) => { - if (currentScopes) { - this.reject(reason); - } - }, - }; - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts deleted file mode 100644 index 46a5362f35..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 ProviderIcon from '@material-ui/icons/AcUnit'; -import { OAuthRequestManager } from './OAuthRequestManager'; - -describe('OAuthRequestManager', () => { - it('should forward a requests', async () => { - const manager = new OAuthRequestManager(); - - const reqSpy = jest.fn(); - manager.authRequest$().subscribe(reqSpy); - - const requester = manager.createAuthRequester({ - provider: { - title: 'My Provider', - icon: ProviderIcon, - }, - onAuthRequest: async () => 'hello', - }); - - expect(reqSpy).toHaveBeenCalledTimes(0); - await 'a tick'; - expect(reqSpy).toHaveBeenCalledTimes(2); - expect(reqSpy).toHaveBeenLastCalledWith([]); - - const req = requester(new Set(['my-scope'])); - - expect(reqSpy).toHaveBeenCalledTimes(3); - expect(reqSpy).toHaveBeenLastCalledWith([ - expect.objectContaining({ - reject: expect.any(Function), - trigger: expect.any(Function), - }), - ]); - - await expect(Promise.race([req, Promise.resolve('not yet')])).resolves.toBe( - 'not yet', - ); - - const [request] = reqSpy.mock.calls[2][0]; - request.trigger(); - - await expect(req).resolves.toBe('hello'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts deleted file mode 100644 index a0a01d1bc9..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 { - OAuthRequestApi, - PendingAuthRequest, - AuthRequester, - AuthRequesterOptions, -} from '../../definitions'; -import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; -import { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -/** - * The OAuthRequestManager is an implementation of the OAuthRequestApi. - * - * The purpose of this class and the API is to read a stream of incoming requests - * of OAuth access tokens from different providers with varying scope, and funnel - * them all together into a single request for each OAuth provider. - */ -export class OAuthRequestManager implements OAuthRequestApi { - private readonly subject = new BehaviorSubject([]); - private currentRequests: PendingAuthRequest[] = []; - private handlerCount = 0; - - createAuthRequester(options: AuthRequesterOptions): AuthRequester { - const handler = new OAuthPendingRequests(); - - const index = this.handlerCount; - this.handlerCount++; - - handler.pending().subscribe({ - next: scopeRequest => { - const newRequests = this.currentRequests.slice(); - const request = this.makeAuthRequest(scopeRequest, options); - if (!request) { - delete newRequests[index]; - } else { - newRequests[index] = request; - } - this.currentRequests = newRequests; - // Convert from sparse array to array of present items only - this.subject.next(newRequests.filter(Boolean)); - }, - }); - - return scopes => { - return handler.request(scopes); - }; - } - - // Converts the pending request and popup options into a popup request that we can forward to subscribers. - private makeAuthRequest( - request: PendingRequest, - options: AuthRequesterOptions, - ): PendingAuthRequest | undefined { - const { scopes } = request; - if (!scopes) { - return undefined; - } - - return { - provider: options.provider, - trigger: async () => { - const result = await options.onAuthRequest(scopes); - request.resolve(result); - }, - reject: () => { - const error = new Error('Login failed, rejected by user'); - error.name = 'RejectedError'; - request.reject(error); - }, - }; - } - - authRequest$(): Observable { - return this.subject; - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts deleted file mode 100644 index 0fe9bfae0f..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { OAuthRequestManager } from './OAuthRequestManager'; diff --git a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts deleted file mode 100644 index 81140ffcc9..0000000000 --- a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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 { WebStorage } from './WebStorage'; -import { CreateStorageApiOptions, StorageApi } from '../../definitions'; - -describe('WebStorage Storage API', () => { - const mockErrorApi = { post: jest.fn(), error$: jest.fn() }; - const createWebStorage = ( - args?: Partial, - ): StorageApi => { - return WebStorage.create({ - errorApi: mockErrorApi, - ...args, - }); - }; - it('should return undefined for values which are unset', async () => { - const storage = createWebStorage(); - - expect(storage.get('myfakekey')).toBeUndefined(); - }); - - it('should allow the setting and getting of the simple data structures', async () => { - const storage = createWebStorage(); - - await storage.set('myfakekey', 'helloimastring'); - await storage.set('mysecondfakekey', 1234); - await storage.set('mythirdfakekey', true); - expect(storage.get('myfakekey')).toBe('helloimastring'); - expect(storage.get('mysecondfakekey')).toBe(1234); - expect(storage.get('mythirdfakekey')).toBe(true); - }); - - it('should allow setting of complex datastructures', async () => { - const storage = createWebStorage(); - - const mockData = { - something: 'here', - is: [{ super: { complex: [{ but: 'something', why: true }] } }], - }; - - await storage.set('myfakekey', mockData); - - expect(storage.get('myfakekey')).toEqual(mockData); - }); - - it('should subscribe to key changes when setting a new value', async () => { - const storage = createWebStorage(); - - const wrongKeyNextHandler = jest.fn(); - const selectedKeyNextHandler = jest.fn(); - const mockData = { hello: 'im a great new value' }; - - await new Promise(resolve => { - storage.observe$('correctKey').subscribe({ - next: (...args) => { - selectedKeyNextHandler(...args); - resolve(); - }, - }); - - storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); - - storage.set('correctKey', mockData); - }); - - expect(wrongKeyNextHandler).not.toHaveBeenCalled(); - expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); - expect(selectedKeyNextHandler).toHaveBeenCalledWith({ - key: 'correctKey', - newValue: mockData, - }); - }); - - it('should subscribe to key changes when deleting a value', async () => { - const storage = createWebStorage(); - - const wrongKeyNextHandler = jest.fn(); - const selectedKeyNextHandler = jest.fn(); - const mockData = { hello: 'im a great new value' }; - - storage.set('correctKey', mockData); - - await new Promise(resolve => { - storage.observe$('correctKey').subscribe({ - next: (...args) => { - selectedKeyNextHandler(...args); - resolve(); - }, - }); - - storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); - - storage.remove('correctKey'); - }); - - expect(wrongKeyNextHandler).not.toHaveBeenCalled(); - expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); - expect(selectedKeyNextHandler).toHaveBeenCalledWith({ - key: 'correctKey', - newValue: undefined, - }); - }); - - it('should be able to create different buckets for different uses', async () => { - const rootStorage = createWebStorage(); - - const firstStorage = rootStorage.forBucket('userSettings'); - const secondStorage = rootStorage.forBucket('profileSettings'); - const keyName = 'blobby'; - - await firstStorage.set(keyName, 'boop'); - await secondStorage.set(keyName, 'deerp'); - - expect(firstStorage.get(keyName)).not.toBe(secondStorage.get(keyName)); - expect(firstStorage.get(keyName)).toBe('boop'); - expect(secondStorage.get(keyName)).toBe('deerp'); - }); - - it('should not clash with other namesapces when creating buckets', async () => { - const rootStorage = createWebStorage(); - - // when getting key test2 it will translate to /profile/something/deep/test2 - const firstStorage = rootStorage - .forBucket('profile') - .forBucket('something') - .forBucket('deep'); - // when getting key deep/test2 it will translate to /profile/something/deep/test2 - const secondStorage = rootStorage.forBucket('profile/something'); - - await firstStorage.set('test2', { error: true }); - - expect(secondStorage.get('deep/test2')).toBe(undefined); - }); - - it('should call the error api when the json can not be parsed in local storage', async () => { - const rootStorage = createWebStorage({ - namespace: '/Test/Mock/Thing', - }); - - localStorage.setItem('/Test/Mock/Thing/key', '{smd: asdouindA}'); - - const value = rootStorage.get('key'); - - expect(value).toBe(undefined); - expect(mockErrorApi.post).toHaveBeenCalledWith(expect.any(Error)); - expect(mockErrorApi.post).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Error when parsing JSON config from storage for: key', - }), - ); - }); - - it('should return a singleton for the same namespace and same bucket', async () => { - const rootStorage = createWebStorage({ - namespace: '/Test/Mock/Thing/Thing ', - }); - - expect(rootStorage.forBucket('test')).toBe(rootStorage.forBucket('test')); - }); -}); diff --git a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts deleted file mode 100644 index b9b01cd20b..0000000000 --- a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { - StorageApi, - StorageValueChange, - ErrorApi, - CreateStorageApiOptions, -} from '../../definitions'; -import { Observable } from '../../../types'; -import ObservableImpl from 'zen-observable'; - -const buckets = new Map(); - -export class WebStorage implements StorageApi { - constructor( - private readonly namespace: string, - private readonly errorApi: ErrorApi, - ) {} - - static create(options: CreateStorageApiOptions): WebStorage { - return new WebStorage(options.namespace ?? '', options.errorApi); - } - - get(key: string): T | undefined { - try { - const storage = JSON.parse(localStorage.getItem(this.getKeyName(key))!); - return storage ?? undefined; - } catch (e) { - this.errorApi.post( - new Error(`Error when parsing JSON config from storage for: ${key}`), - ); - } - - return undefined; - } - - forBucket(name: string): WebStorage { - const bucketPath = `${this.namespace}/${name}`; - if (!buckets.has(bucketPath)) { - buckets.set(bucketPath, new WebStorage(bucketPath, this.errorApi)); - } - return buckets.get(bucketPath)!; - } - - async set(key: string, data: T): Promise { - localStorage.setItem(this.getKeyName(key), JSON.stringify(data, null, 2)); - this.notifyChanges({ key, newValue: data }); - } - - async remove(key: string): Promise { - localStorage.removeItem(this.getKeyName(key)); - this.notifyChanges({ key, newValue: undefined }); - } - - observe$(key: string): Observable> { - return this.observable.filter(({ key: messageKey }) => messageKey === key); - } - - private getKeyName(key: string) { - return `${this.namespace}/${encodeURIComponent(key)}`; - } - - private notifyChanges(message: StorageValueChange) { - for (const subscription of this.subscribers) { - subscription.next(message); - } - } - - private subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - private readonly observable = new ObservableImpl( - subscriber => { - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }, - ); -} diff --git a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts deleted file mode 100644 index b41e705726..0000000000 --- a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 Auth0Icon from '@material-ui/icons/AcUnit'; -import { auth0AuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'auth0', - title: 'Auth0', - icon: Auth0Icon, -}; - -class Auth0Auth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['openid', `email`, `profile`], - }: OAuthApiCreateOptions): typeof auth0AuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} - -export default Auth0Auth; diff --git a/packages/core-api/src/apis/implementations/auth/auth0/index.ts b/packages/core-api/src/apis/implementations/auth/auth0/index.ts deleted file mode 100644 index dda27d0fa3..0000000000 --- a/packages/core-api/src/apis/implementations/auth/auth0/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as Auth0Auth } from './Auth0Auth'; diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts deleted file mode 100644 index 3ff29cc6ef..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 GithubAuth from './GithubAuth'; - -describe('GithubAuth', () => { - it('should get access token', async () => { - const getSession = jest - .fn() - .mockResolvedValue({ providerInfo: { accessToken: 'access-token' } }); - const githubAuth = new GithubAuth({ getSession } as any); - - expect(await githubAuth.getAccessToken()).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts deleted file mode 100644 index a7c945a907..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 GithubIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { GithubSession } from './types'; -import { - OAuthApi, - SessionApi, - SessionState, - ProfileInfo, - BackstageIdentity, - AuthRequestOptions, -} from '../../../definitions/auth'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { - AuthSessionStore, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; -import { Observable } from '../../../../types'; -import { OAuthApiCreateOptions } from '../types'; - -export type GithubAuthResponse = { - providerInfo: { - accessToken: string; - scope: string; - expiresInSeconds: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'github', - title: 'GitHub', - icon: GithubIcon, -}; - -class GithubAuth implements OAuthApi, SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['read:user'], - }: OAuthApiCreateOptions) { - const connector = new DefaultAuthConnector({ - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: GithubAuthResponse): GithubSession { - return { - ...res, - providerInfo: { - accessToken: res.providerInfo.accessToken, - scopes: GithubAuth.normalizeScope(res.providerInfo.scope), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, - }); - - const sessionManager = new StaticAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }); - - const authSessionStore = new AuthSessionStore({ - manager: sessionManager, - storageKey: `${provider.id}Session`, - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }); - - return new GithubAuth(authSessionStore); - } - - constructor(private readonly sessionManager: SessionManager) {} - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken(scope?: string, options?: AuthRequestOptions) { - const session = await this.sessionManager.getSession({ - ...options, - scopes: GithubAuth.normalizeScope(scope), - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - static normalizeScope(scope?: string): Set { - if (!scope) { - return new Set(); - } - - const scopeList = Array.isArray(scope) - ? scope - : scope.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeList); - } -} -export default GithubAuth; diff --git a/packages/core-api/src/apis/implementations/auth/github/index.ts b/packages/core-api/src/apis/implementations/auth/github/index.ts deleted file mode 100644 index 9e1722f4a4..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export * from './types'; -export { default as GithubAuth } from './GithubAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/github/types.ts b/packages/core-api/src/apis/implementations/auth/github/types.ts deleted file mode 100644 index cc42d59fec..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { BackstageIdentity, ProfileInfo } from '../../../definitions'; - -export type GithubSession = { - providerInfo: { - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts b/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts deleted file mode 100644 index 6a592c7fbe..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; -import GitlabAuth from './GitlabAuth'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('GitlabAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - [ - 'read_user api write_repository', - ['read_user', 'api', 'write_repository'], - ], - ['read_repository sudo', ['read_repository', 'sudo']], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const gitlabAuth = GitlabAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - gitlabAuth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts b/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts deleted file mode 100644 index 3f4bc814e3..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 GitlabIcon from '@material-ui/icons/AcUnit'; -import { gitlabAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'gitlab', - title: 'GitLab', - icon: GitlabIcon, -}; - -class GitlabAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['read_user'], - }: OAuthApiCreateOptions): typeof gitlabAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} -export default GitlabAuth; diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/index.ts b/packages/core-api/src/apis/implementations/auth/gitlab/index.ts deleted file mode 100644 index 42d7210551..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as GitlabAuth } from './GitlabAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts deleted file mode 100644 index 9e8569c5cf..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 GoogleAuth from './GoogleAuth'; -import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; - -const PREFIX = 'https://www.googleapis.com/auth/'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('GoogleAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - ['email', [`${PREFIX}userinfo.email`]], - ['profile', [`${PREFIX}userinfo.profile`]], - ['openid', ['openid']], - ['userinfo.email', [`${PREFIX}userinfo.email`]], - [ - 'userinfo.profile email', - [`${PREFIX}userinfo.profile`, `${PREFIX}userinfo.email`], - ], - [ - `profile ${PREFIX}userinfo.email`, - [`${PREFIX}userinfo.profile`, `${PREFIX}userinfo.email`], - ], - [`${PREFIX}userinfo.profile`, [`${PREFIX}userinfo.profile`]], - ['a', [`${PREFIX}a`]], - ['a b\tc', [`${PREFIX}a`, `${PREFIX}b`, `${PREFIX}c`]], - [`${PREFIX}a b`, [`${PREFIX}a`, `${PREFIX}b`]], - [`${PREFIX}a`, [`${PREFIX}a`]], - - // Some incorrect scopes that we don't try to fix - [`${PREFIX}email`, [`${PREFIX}email`]], - [`${PREFIX}profile`, [`${PREFIX}profile`]], - [`${PREFIX}openid`, [`${PREFIX}openid`]], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const googleAuth = GoogleAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - googleAuth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts deleted file mode 100644 index fbc902cdd8..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 GoogleIcon from '@material-ui/icons/AcUnit'; -import { googleAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'google', - title: 'Google', - icon: GoogleIcon, -}; - -const SCOPE_PREFIX = 'https://www.googleapis.com/auth/'; - -class GoogleAuth { - static create({ - discoveryApi, - oauthRequestApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - defaultScopes = [ - 'openid', - `${SCOPE_PREFIX}userinfo.email`, - `${SCOPE_PREFIX}userinfo.profile`, - ], - }: OAuthApiCreateOptions): typeof googleAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - scopeTransform(scopes: string[]) { - return scopes.map(scope => { - if (scope === 'openid') { - return scope; - } - - if (scope === 'profile' || scope === 'email') { - return `${SCOPE_PREFIX}userinfo.${scope}`; - } - - if (scope.startsWith(SCOPE_PREFIX)) { - return scope; - } - - return `${SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} -export default GoogleAuth; diff --git a/packages/core-api/src/apis/implementations/auth/google/index.ts b/packages/core-api/src/apis/implementations/auth/google/index.ts deleted file mode 100644 index 2521d46046..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as GoogleAuth } from './GoogleAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts deleted file mode 100644 index 7ef78d19cf..0000000000 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -export * from './github'; -export * from './gitlab'; -export * from './google'; -export * from './oauth2'; -export * from './okta'; -export * from './saml'; -export * from './auth0'; -export * from './microsoft'; -export * from './onelogin'; diff --git a/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts deleted file mode 100644 index 3e2711db3b..0000000000 --- a/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 MicrosoftIcon from '@material-ui/icons/AcUnit'; -import { microsoftAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'microsoft', - title: 'Microsoft', - icon: MicrosoftIcon, -}; - -class MicrosoftAuth { - static create({ - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - discoveryApi, - defaultScopes = [ - 'openid', - 'offline_access', - 'profile', - 'email', - 'User.Read', - ], - }: OAuthApiCreateOptions): typeof microsoftAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} - -export default MicrosoftAuth; diff --git a/packages/core-api/src/apis/implementations/auth/microsoft/index.ts b/packages/core-api/src/apis/implementations/auth/microsoft/index.ts deleted file mode 100644 index 77328d8557..0000000000 --- a/packages/core-api/src/apis/implementations/auth/microsoft/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as MicrosoftAuth } from './MicrosoftAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts deleted file mode 100644 index 93db2c732d..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 OAuth2 from './OAuth2'; - -const theFuture = new Date(Date.now() + 3600000); -const thePast = new Date(Date.now() - 10); - -const PREFIX = 'https://www.googleapis.com/auth/'; - -const scopeTransform = (x: string[]) => x; - -describe('OAuth2', () => { - it('should get refreshed access token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getAccessToken('my-scope my-scope2')).toBe( - 'access-token', - ); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-scope', 'my-scope2']), - ); - }); - - it('should transform scopes', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`), - }); - - expect(await oauth2.getAccessToken('my-scope')).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-prefix/my-scope']), - ); - }); - - it('should get refreshed id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getIdToken()).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should get optional id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getIdToken({ optional: true })).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should share popup closed errors', async () => { - const error = new Error('NOPE'); - error.name = 'RejectedError'; - const getSession = jest - .fn() - .mockResolvedValueOnce({ - providerInfo: { - accessToken: 'access-token', - expiresAt: theFuture, - scopes: new Set([`${PREFIX}not-enough`]), - }, - }) - .mockRejectedValue(error); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Make sure we have a session before we do the double request, so that we get past the !this.currentSession check - await expect(oauth2.getAccessToken()).resolves.toBe('access-token'); - - const promise1 = oauth2.getAccessToken('more'); - const promise2 = oauth2.getAccessToken('more'); - await expect(promise1).rejects.toBe(error); - await expect(promise2).rejects.toBe(error); - expect(getSession).toBeCalledTimes(3); - }); - - it('should wait for all session refreshes', async () => { - const initialSession = { - providerInfo: { - idToken: 'token1', - expiresAt: theFuture, - scopes: new Set(), - }, - }; - const getSession = jest - .fn() - .mockResolvedValueOnce(initialSession) - .mockResolvedValue({ - providerInfo: { - idToken: 'token2', - expiresAt: theFuture, - scopes: new Set(), - }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Grab the expired session first - await expect(oauth2.getIdToken()).resolves.toBe('token1'); - expect(getSession).toBeCalledTimes(1); - - initialSession.providerInfo.expiresAt = thePast; - - const promise1 = oauth2.getIdToken(); - const promise2 = oauth2.getIdToken(); - const promise3 = oauth2.getIdToken(); - await expect(promise1).resolves.toBe('token2'); - await expect(promise2).resolves.toBe('token2'); - await expect(promise3).resolves.toBe('token2'); - expect(getSession).toBeCalledTimes(4); // De-duping of session requests happens in client - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts deleted file mode 100644 index 73ad3dcf14..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 OAuth2Icon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { Observable } from '../../../../types'; -import { - AuthRequestOptions, - BackstageIdentity, - OAuthApi, - OpenIdConnectApi, - ProfileInfo, - ProfileInfoApi, - SessionState, - SessionApi, - BackstageIdentityApi, -} from '../../../definitions/auth'; -import { OAuth2Session } from './types'; -import { OAuthApiCreateOptions } from '../types'; - -type Options = { - sessionManager: SessionManager; - scopeTransform: (scopes: string[]) => string[]; -}; - -type CreateOptions = OAuthApiCreateOptions & { - scopeTransform?: (scopes: string[]) => string[]; -}; - -export type OAuth2Response = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'oauth2', - title: 'Your Identity Provider', - icon: OAuth2Icon, -}; - -class OAuth2 - implements - OAuthApi, - OpenIdConnectApi, - ProfileInfoApi, - BackstageIdentityApi, - SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, - }: CreateOptions) { - const connector = new DefaultAuthConnector({ - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: OAuth2Response): OAuth2Session { - return { - ...res, - providerInfo: { - idToken: res.providerInfo.idToken, - accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( - scopeTransform, - res.providerInfo.scope, - ), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, - }); - - const sessionManager = new RefreshingAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: OAuth2Session) => session.providerInfo.scopes, - sessionShouldRefresh: (session: OAuth2Session) => { - const expiresInSec = - (session.providerInfo.expiresAt.getTime() - Date.now()) / 1000; - return expiresInSec < 60 * 5; - }, - }); - - return new OAuth2({ sessionManager, scopeTransform }); - } - - private readonly sessionManager: SessionManager; - private readonly scopeTransform: (scopes: string[]) => string[]; - - constructor(options: Options) { - this.sessionManager = options.sessionManager; - this.scopeTransform = options.scopeTransform; - } - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken( - scope?: string | string[], - options?: AuthRequestOptions, - ) { - const normalizedScopes = OAuth2.normalizeScopes(this.scopeTransform, scope); - const session = await this.sessionManager.getSession({ - ...options, - scopes: normalizedScopes, - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getIdToken(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.providerInfo.idToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - private static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], - scopes?: string | string[], - ): Set { - if (!scopes) { - return new Set(); - } - - const scopeList = Array.isArray(scopes) - ? scopes - : scopes.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeTransform(scopeList)); - } -} - -export default OAuth2; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/index.ts b/packages/core-api/src/apis/implementations/auth/oauth2/index.ts deleted file mode 100644 index 52bcb1df2c..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { default as OAuth2 } from './OAuth2'; -export * from './types'; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-api/src/apis/implementations/auth/oauth2/types.ts deleted file mode 100644 index 0e9c6f124d..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; - -export type OAuth2Session = { - providerInfo: { - idToken: string; - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts b/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts deleted file mode 100644 index d6b1a07d9d..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 OktaAuth from './OktaAuth'; -import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; - -const PREFIX = 'okta.'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('OktaAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - ['openid', ['openid']], - ['profile email', ['profile', 'email']], - [`${PREFIX}groups.manage`, [`${PREFIX}groups.manage`]], - ['groups.read', [`${PREFIX}groups.read`]], - [ - `${PREFIX}groups.manage groups.read, openid`, - [`${PREFIX}groups.manage`, `${PREFIX}groups.read`, 'openid'], - ], - [`email\t ${PREFIX}groups.read`, ['email', `${PREFIX}groups.read`]], - - // Some incorrect scopes that we don't try to fix - [`${PREFIX}email`, [`${PREFIX}email`]], - [`${PREFIX}profile`, [`${PREFIX}profile`]], - [`${PREFIX}openid`, [`${PREFIX}openid`]], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const auth = OktaAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - auth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts b/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts deleted file mode 100644 index 2df908e9fb..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 OktaIcon from '@material-ui/icons/AcUnit'; -import { oktaAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'okta', - title: 'Okta', - icon: OktaIcon, -}; - -const OKTA_OIDC_SCOPES: Set = new Set([ - 'openid', - 'profile', - 'email', - 'phone', - 'address', - 'groups', - 'offline_access', -]); - -const OKTA_SCOPE_PREFIX: string = 'okta.'; - -class OktaAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['openid', 'email', 'profile', 'offline_access'], - }: OAuthApiCreateOptions): typeof oktaAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - scopeTransform(scopes) { - return scopes.map(scope => { - if (OKTA_OIDC_SCOPES.has(scope)) { - return scope; - } - - if (scope.startsWith(OKTA_SCOPE_PREFIX)) { - return scope; - } - - return `${OKTA_SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} - -export default OktaAuth; diff --git a/packages/core-api/src/apis/implementations/auth/okta/index.ts b/packages/core-api/src/apis/implementations/auth/okta/index.ts deleted file mode 100644 index 4cc774b26b..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as OktaAuth } from './OktaAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts deleted file mode 100644 index ff5c7c1990..0000000000 --- a/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 OneLoginIcon from '@material-ui/icons/AcUnit'; -import { oneloginAuthApiRef } from '../../../definitions/auth'; -import { - OAuthRequestApi, - AuthProvider, - DiscoveryApi, -} from '../../../definitions'; -import { OAuth2 } from '../oauth2'; - -type CreateOptions = { - discoveryApi: DiscoveryApi; - oauthRequestApi: OAuthRequestApi; - - environment?: string; - provider?: AuthProvider & { id: string }; -}; - -const DEFAULT_PROVIDER = { - id: 'onelogin', - title: 'onelogin', - icon: OneLoginIcon, -}; - -const OIDC_SCOPES: Set = new Set([ - 'openid', - 'profile', - 'email', - 'phone', - 'address', - 'groups', - 'offline_access', -]); - -const SCOPE_PREFIX: string = 'onelogin.'; - -class OneLoginAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - }: CreateOptions): typeof oneloginAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes: ['openid', 'email', 'profile', 'offline_access'], - scopeTransform(scopes) { - return scopes.map(scope => { - if (OIDC_SCOPES.has(scope)) { - return scope; - } - - if (scope.startsWith(SCOPE_PREFIX)) { - return scope; - } - - return `${SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} - -export default OneLoginAuth; diff --git a/packages/core-api/src/apis/implementations/auth/onelogin/index.ts b/packages/core-api/src/apis/implementations/auth/onelogin/index.ts deleted file mode 100644 index 1d163207db..0000000000 --- a/packages/core-api/src/apis/implementations/auth/onelogin/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { default as OneLoginAuth } from './OneLoginAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts deleted file mode 100644 index af97ad8f36..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 SamlIcon from '@material-ui/icons/AcUnit'; -import { DirectAuthConnector } from '../../../../lib/AuthConnector'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { Observable } from '../../../../types'; -import { - ProfileInfo, - BackstageIdentity, - SessionState, - AuthRequestOptions, - ProfileInfoApi, - BackstageIdentityApi, - SessionApi, -} from '../../../definitions/auth'; -import { SamlSession } from './types'; -import { - AuthSessionStore, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; -import { AuthApiCreateOptions } from '../types'; - -export type SamlAuthResponse = { - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'saml', - title: 'SAML', - icon: SamlIcon, -}; - -class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - }: AuthApiCreateOptions) { - const connector = new DirectAuthConnector({ - discoveryApi, - environment, - provider, - }); - - const sessionManager = new StaticAuthSessionManager({ - connector, - }); - - const authSessionStore = new AuthSessionStore({ - manager: sessionManager, - storageKey: `${provider.id}Session`, - }); - - return new SamlAuth(authSessionStore); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - constructor(private readonly sessionManager: SessionManager) {} - - async signIn() { - await this.getBackstageIdentity({}); - } - async signOut() { - await this.sessionManager.removeSession(); - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } -} - -export default SamlAuth; diff --git a/packages/core-api/src/apis/implementations/auth/saml/index.ts b/packages/core-api/src/apis/implementations/auth/saml/index.ts deleted file mode 100644 index c2436ab435..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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. - */ -export { default as SamlAuth } from './SamlAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/types.ts b/packages/core-api/src/apis/implementations/auth/saml/types.ts deleted file mode 100644 index 296f70b0ea..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; - -export type SamlSession = { - userId: string; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/types.ts b/packages/core-api/src/apis/implementations/auth/types.ts deleted file mode 100644 index db3b718d25..0000000000 --- a/packages/core-api/src/apis/implementations/auth/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { AuthProvider, DiscoveryApi, OAuthRequestApi } from '../../definitions'; - -export type OAuthApiCreateOptions = AuthApiCreateOptions & { - oauthRequestApi: OAuthRequestApi; - defaultScopes?: string[]; -}; - -export type AuthApiCreateOptions = { - discoveryApi: DiscoveryApi; - environment?: string; - provider?: AuthProvider & { id: string }; -}; diff --git a/packages/core-api/src/apis/implementations/index.ts b/packages/core-api/src/apis/implementations/index.ts deleted file mode 100644 index d0df2760ab..0000000000 --- a/packages/core-api/src/apis/implementations/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -// This folder contains implementations for all core APIs. -// -// Plugins should rely on these APIs for functionality as much as possible. - -export * from './auth'; - -export * from './AlertApi'; -export * from './AppThemeApi'; -export * from './ConfigApi'; -export * from './DiscoveryApi'; -export * from './ErrorApi'; -export * from './FeatureFlagsApi'; -export * from './OAuthRequestApi'; -export * from './StorageApi'; diff --git a/packages/core-api/src/apis/index.ts b/packages/core-api/src/apis/index.ts deleted file mode 100644 index 03569f9570..0000000000 --- a/packages/core-api/src/apis/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -export * from './system'; -export * from './definitions'; -export * from './implementations'; diff --git a/packages/core-api/src/apis/system/ApiAggregator.test.ts b/packages/core-api/src/apis/system/ApiAggregator.test.ts deleted file mode 100644 index 2d14087fa2..0000000000 --- a/packages/core-api/src/apis/system/ApiAggregator.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 { ApiAggregator } from './ApiAggregator'; -import { createApiRef } from './ApiRef'; -import { ApiRegistry } from './ApiRegistry'; - -describe('ApiAggregator', () => { - const apiARef = createApiRef({ id: 'a', description: '' }); - const apiBRef = createApiRef({ id: 'b', description: '' }); - - it('should forward implementations', () => { - const agg = new ApiAggregator( - ApiRegistry.from([ - [apiARef, 5], - [apiBRef, 10], - ]), - ); - expect(agg.get(apiARef)).toBe(5); - expect(agg.get(apiBRef)).toBe(10); - }); - - it('should return the first implementation', () => { - const agg = new ApiAggregator( - ApiRegistry.from([ - [apiARef, 1], - [apiARef, 2], - ]), - ); - expect(agg.get(apiARef)).toBe(2); - expect(agg.get(apiBRef)).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiAggregator.ts b/packages/core-api/src/apis/system/ApiAggregator.ts deleted file mode 100644 index 1587a1d10b..0000000000 --- a/packages/core-api/src/apis/system/ApiAggregator.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { ApiRef, ApiHolder } from './types'; - -/** - * An ApiHolder that queries multiple other holders from for - * an Api implementation, returning the first one encountered.. - */ -export class ApiAggregator implements ApiHolder { - private readonly holders: ApiHolder[]; - - constructor(...holders: ApiHolder[]) { - this.holders = holders; - } - - get(apiRef: ApiRef): T | undefined { - for (const holder of this.holders) { - const api = holder.get(apiRef); - if (api) { - return api; - } - } - return undefined; - } -} diff --git a/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts deleted file mode 100644 index a51ed03fea..0000000000 --- a/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { ApiFactoryRegistry } from './ApiFactoryRegistry'; -import { createApiRef } from './ApiRef'; - -const aRef = createApiRef({ id: 'a', description: '' }); -const aFactory1 = { api: aRef, deps: {}, factory: () => 1 }; -const aFactory2 = { api: aRef, deps: {}, factory: () => 2 }; -const bRef = createApiRef({ id: 'b', description: '' }); -const bFactory = { api: bRef, deps: {}, factory: () => 'x' }; -const cRef = createApiRef({ id: 'c', description: '' }); -const cFactory = { api: cRef, deps: {}, factory: () => 'y' }; - -describe('ApiFactoryRegistry', () => { - it('should be empty when created', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.getAllApis()).toEqual(new Set()); - }); - - it('should register a factory', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.getAllApis()).toEqual(new Set([aRef])); - }); - - it('should prioritize factories based on scope', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('default', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('app', aFactory2)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory2); - expect(registry.register('default', aFactory1)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory2); - expect(registry.register('static', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('static', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('app', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.getAllApis()).toEqual(new Set([aRef])); - }); - - it('should register multiple factories without conflict', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('static', aFactory1)).toBe(true); - expect(registry.register('default', bFactory)).toBe(true); - expect(registry.register('app', cFactory)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.get(bRef)).toBe(bFactory); - expect(registry.get(cRef)).toBe(cFactory); - expect(registry.getAllApis()).toEqual(new Set([aRef, bRef, cRef])); - }); - - it('should identify ApiRefs by id but still return the correct factory ref when listing all apis', () => { - const ref1 = createApiRef({ id: 'a', description: 'ref1' }); - const ref2 = createApiRef({ id: 'a', description: 'ref2' }); - - const factory1 = { api: ref1, deps: {}, factory: () => 3 }; - const factory2 = { api: ref2, deps: {}, factory: () => 3 }; - - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', factory1)).toBe(true); - expect(registry.register('default', factory2)).toBe(false); - expect(registry.get(ref1)).toEqual(factory1); - expect(registry.get(ref2)).toEqual(factory1); - expect(registry.getAllApis()).toEqual(new Set([ref1])); - - expect(registry.register('app', factory2)).toBe(true); - expect(registry.get(ref1)).toEqual(factory2); - expect(registry.get(ref2)).toEqual(factory2); - expect(registry.getAllApis()).toEqual(new Set([ref2])); - expect(registry.getAllApis()).not.toEqual(new Set([ref1])); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts deleted file mode 100644 index c5a76ee1d4..0000000000 --- a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 { - ApiRef, - ApiFactoryHolder, - ApiFactory, - AnyApiRef, - AnyApiFactory, -} from './types'; - -type ApiFactoryScope = - | 'default' // Default factories registered by core and plugins - | 'app' // Factories registered in the app, overriding default ones - | 'static'; // APIs that can't be overridden, e.g. config - -enum ScopePriority { - default = 10, - app = 50, - static = 100, -} - -type FactoryTuple = { - priority: number; - factory: AnyApiFactory; -}; - -/** - * ApiFactoryRegistry is an ApiFactoryHolder implementation that enables - * registration of API Factories with different scope. - * - * Each scope has an assigned priority, where factories registered with - * higher priority scopes override ones with lower priority. - */ -export class ApiFactoryRegistry implements ApiFactoryHolder { - private readonly factories = new Map(); - - /** - * Register a new API factory. Returns true if the factory was added - * to the registry. - * - * A factory will not be added to the registry if there is already - * an existing factory with the same or higher priority. - */ - register( - scope: ApiFactoryScope, - factory: ApiFactory, - ) { - const priority = ScopePriority[scope]; - const existing = this.factories.get(factory.api.id); - if (existing && existing.priority >= priority) { - return false; - } - - this.factories.set(factory.api.id, { priority, factory }); - return true; - } - - get( - api: ApiRef, - ): ApiFactory | undefined { - const tuple = this.factories.get(api.id); - if (!tuple) { - return undefined; - } - return tuple.factory as ApiFactory; - } - - getAllApis(): Set { - const refs = new Set(); - for (const { factory } of this.factories.values()) { - refs.add(factory.api); - } - return refs; - } -} diff --git a/packages/core-api/src/apis/system/ApiProvider.test.tsx b/packages/core-api/src/apis/system/ApiProvider.test.tsx deleted file mode 100644 index e95842aebc..0000000000 --- a/packages/core-api/src/apis/system/ApiProvider.test.tsx +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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, { Context, useContext } from 'react'; -import { ApiProvider, useApi, withApis } from './ApiProvider'; -import { createApiRef } from './ApiRef'; -import { ApiRegistry } from './ApiRegistry'; -import { render } from '@testing-library/react'; -import { withLogCollector } from '@backstage/test-utils-core'; -import { getGlobalSingleton } from '../../lib/globalObject'; -import { ApiHolder, ApiRef } from './types'; -import { VersionedValue } from '../../lib/versionedValues'; - -describe('ApiProvider', () => { - type Api = () => string; - const apiRef = createApiRef({ id: 'x', description: '' }); - const registry = ApiRegistry.from([[apiRef, () => 'hello']]); - - const MyHookConsumer = () => { - const api = useApi(apiRef); - return

hook message: {api()}

; - }; - - const MyHocConsumer = withApis({ getMessage: apiRef })(({ getMessage }) => { - return

hoc message: {getMessage()}

; - }); - - it('should provide apis', () => { - const renderedHook = render( - - - , - ); - renderedHook.getByText('hook message: hello'); - - const renderedHoc = render( - - - , - ); - renderedHoc.getByText('hoc message: hello'); - }); - - it('should provide nested access to apis', () => { - const aRef = createApiRef({ id: 'a', description: '' }); - const bRef = createApiRef({ id: 'b', description: '' }); - - const MyComponent = () => { - const a = useApi(aRef); - const b = useApi(bRef); - return ( -
- a={a} b={b} -
- ); - }; - - const renderedHook = render( - - - - - , - ); - renderedHook.getByText('a=z b=y'); - }); - - it('should ignore deps in prototype', () => { - // 100% coverage + happy typescript = hasOwnProperty + this atrocity - const xRef = createApiRef({ id: 'x', description: '' }); - - const proto = { x: xRef }; - const props = { getMessage: { enumerable: true, value: apiRef } }; - const obj = Object.create(proto, props) as { - getMessage: typeof apiRef; - x: typeof xRef; - }; - - const MyWeirdHocConsumer = withApis(obj)(({ getMessage }) => { - return

hoc message: {getMessage()}

; - }); - - const renderedHoc = render( - - - , - ); - renderedHoc.getByText('hoc message: hello'); - }); - - it('should error if no provider is available', () => { - expect( - withLogCollector(['error'], () => { - expect(() => { - render(); - }).toThrow(/^No ApiProvider available in react context. /); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context. /, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - - expect( - withLogCollector(['error'], () => { - expect(() => { - render(); - }).toThrow(/^No ApiProvider available in react context. /); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context. /, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - }); - - it('should error if api is not available', () => { - expect( - withLogCollector(['error'], () => { - expect(() => { - render( - - - , - ); - }).toThrow('No implementation available for apiRef{x}'); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No implementation available for apiRef{x}\]/, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - - expect( - withLogCollector(['error'], () => { - expect(() => { - render( - - - , - ); - }).toThrow('No implementation available for apiRef{x}'); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No implementation available for apiRef{x}\]/, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - }); -}); - -describe('v1 consumer', () => { - const ApiContext = getGlobalSingleton< - Context> - >('api-context'); - - function useMockApiV1(apiRef: ApiRef): T { - const impl = useContext(ApiContext)?.atVersion(1)?.get(apiRef); - if (!impl) { - throw new Error('no impl'); - } - return impl; - } - - type Api = () => string; - const apiRef = createApiRef({ id: 'x', description: '' }); - const registry = ApiRegistry.with(apiRef, () => 'hello'); - - const MyHookConsumerV1 = () => { - const api = useMockApiV1(apiRef); - return

hook message: {api()}

; - }; - - it('should provide apis', () => { - const renderedHook = render( - - - , - ); - renderedHook.getByText('hook message: hello'); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiProvider.tsx b/packages/core-api/src/apis/system/ApiProvider.tsx deleted file mode 100644 index 807b2ff7e0..0000000000 --- a/packages/core-api/src/apis/system/ApiProvider.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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, { - createContext, - useContext, - ReactNode, - PropsWithChildren, - Context, - useMemo, -} from 'react'; -import PropTypes from 'prop-types'; -import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; -import { ApiAggregator } from './ApiAggregator'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../../lib/globalObject'; -import { - VersionedValue, - createVersionedValueMap, -} from '../../lib/versionedValues'; - -const missingHolderMessage = - 'No ApiProvider available in react context. ' + - 'A common cause of this error is that multiple versions of @backstage/core-api are installed. ' + - `You can check if that is the case using 'yarn backstage-cli versions:check', and can in many cases ` + - `fix the issue either with the --fix flag or using 'yarn backstage-cli versions:bump'`; - -type ApiProviderProps = { - apis: ApiHolder; - children: ReactNode; -}; - -type ApiContextType = VersionedValue<{ 1: ApiHolder }> | undefined; -const ApiContext = getOrCreateGlobalSingleton('api-context', () => - createContext(undefined), -); - -export const ApiProvider = ({ - apis, - children, -}: PropsWithChildren) => { - const parentHolder = useContext(ApiContext)?.atVersion(1); - const versionedValue = useMemo( - () => - createVersionedValueMap({ - 1: parentHolder ? new ApiAggregator(apis, parentHolder) : apis, - }), - [parentHolder, apis], - ); - - return ; -}; - -ApiProvider.propTypes = { - apis: PropTypes.shape({ get: PropTypes.func.isRequired }).isRequired, - children: PropTypes.node, -}; - -export function useApiHolder(): ApiHolder { - const versionedHolder = useContext( - getGlobalSingleton>('api-context'), - ); - - if (!versionedHolder) { - throw new Error(missingHolderMessage); - } - - const apiHolder = versionedHolder.atVersion(1); - if (!apiHolder) { - throw new Error('ApiContext v1 not available'); - } - - return apiHolder; -} - -export function useApi(apiRef: ApiRef): T { - const apiHolder = useApiHolder(); - - const api = apiHolder.get(apiRef); - if (!api) { - throw new Error(`No implementation available for ${apiRef}`); - } - return api; -} - -export function withApis(apis: TypesToApiRefs) { - return function withApisWrapper

( - WrappedComponent: React.ComponentType

, - ) { - const Hoc = (props: PropsWithChildren>) => { - const apiHolder = useApiHolder(); - - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = apiHolder.get(ref); - if (!api) { - throw new Error(`No implementation available for ${ref}`); - } - impls[key] = api; - } - } - - return ; - }; - const displayName = - WrappedComponent.displayName || WrappedComponent.name || 'Component'; - - Hoc.displayName = `withApis(${displayName})`; - - return Hoc; - }; -} diff --git a/packages/core-api/src/apis/system/ApiRef.test.ts b/packages/core-api/src/apis/system/ApiRef.test.ts deleted file mode 100644 index b9ea1470cd..0000000000 --- a/packages/core-api/src/apis/system/ApiRef.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 { createApiRef } from './ApiRef'; - -describe('ApiRef', () => { - it('should be created', () => { - const ref = createApiRef({ id: 'abc', description: '123' }); - expect(ref.id).toBe('abc'); - expect(ref.description).toBe('123'); - expect(String(ref)).toBe('apiRef{abc}'); - expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); - }); - - it('should reject invalid ids', () => { - for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { - expect(createApiRef({ id, description: '123' }).id).toBe(id); - } - - for (const id of [ - '123', - 'ab-3', - 'ab_c', - '.', - '2ac', - 'ab.3a', - '.abc', - 'abc.', - 'ab..s', - '', - '_', - ]) { - expect(() => createApiRef({ id, description: '123' }).id).toThrow( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, - ); - } - }); -}); diff --git a/packages/core-api/src/apis/system/ApiRef.ts b/packages/core-api/src/apis/system/ApiRef.ts deleted file mode 100644 index e61036c61c..0000000000 --- a/packages/core-api/src/apis/system/ApiRef.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 type { ApiRef } from './types'; - -export type ApiRefConfig = { - id: string; - description?: string; -}; - -class ApiRefImpl implements ApiRef { - constructor(private readonly config: ApiRefConfig) { - const valid = config.id - .split('.') - .flatMap(part => part.split('-')) - .every(part => part.match(/^[a-z][a-z0-9]*$/)); - if (!valid) { - throw new Error( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, - ); - } - } - - get id(): string { - return this.config.id; - } - - get description() { - return this.config.description; - } - - // Utility for getting type of an api, using `typeof apiRef.T` - get T(): T { - throw new Error(`tried to read ApiRef.T of ${this}`); - } - - toString() { - return `apiRef{${this.config.id}}`; - } -} - -export function createApiRef(config: ApiRefConfig): ApiRef { - return new ApiRefImpl(config); -} diff --git a/packages/core-api/src/apis/system/ApiRegistry.test.ts b/packages/core-api/src/apis/system/ApiRegistry.test.ts deleted file mode 100644 index 93dd6dc085..0000000000 --- a/packages/core-api/src/apis/system/ApiRegistry.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { ApiRegistry } from './ApiRegistry'; -import { createApiRef } from './ApiRef'; - -describe('ApiRegistry', () => { - const x1Ref = createApiRef({ id: 'x1', description: '' }); - const x1DuplicateRef = createApiRef({ id: 'x1', description: '' }); - const x2Ref = createApiRef({ id: 'x2', description: '' }); - - it('should be created', () => { - const registry = ApiRegistry.from([]); - expect(registry.get(x1Ref)).toBe(undefined); - }); - - it('should be created with APIs', () => { - const registry = ApiRegistry.from([ - [x1Ref, 3], - [x2Ref, 'y'], - ]); - expect(registry.get(x1Ref)).toBe(3); - expect(registry.get(x1DuplicateRef)).toBe(3); - expect(registry.get(x2Ref)).toBe('y'); - }); - - it('should be built', () => { - const registry = ApiRegistry.builder().build(); - expect(registry.get(x1Ref)).toBe(undefined); - expect(registry.get(x1DuplicateRef)).toBe(undefined); - }); - - it('should be built with APIs', () => { - const builder = ApiRegistry.builder(); - builder.add(x1Ref, 3); - builder.add(x2Ref, 'y'); - - const registry = builder.build(); - expect(registry.get(x1Ref)).toBe(3); - expect(registry.get(x1DuplicateRef)).toBe(3); - expect(registry.get(x2Ref)).toBe('y'); - }); - - it('should be created with API', () => { - const reg1 = ApiRegistry.with(x1Ref, 3); - const reg2 = reg1.with(x2Ref, 'y'); - const reg3 = reg2.with(x2Ref, 'z'); - const reg4 = reg3.with(x1Ref, 2); - const reg5 = reg3.with(x1DuplicateRef, 4); - - expect(reg1.get(x1Ref)).toBe(3); - expect(reg1.get(x2Ref)).toBe(undefined); - expect(reg2.get(x1Ref)).toBe(3); - expect(reg2.get(x2Ref)).toBe('y'); - expect(reg3.get(x1Ref)).toBe(3); - expect(reg3.get(x2Ref)).toBe('z'); - expect(reg4.get(x1Ref)).toBe(2); - expect(reg4.get(x2Ref)).toBe('z'); - expect(reg5.get(x1Ref)).toBe(4); - expect(reg5.get(x2Ref)).toBe('z'); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiRegistry.ts b/packages/core-api/src/apis/system/ApiRegistry.ts deleted file mode 100644 index 01101b2b62..0000000000 --- a/packages/core-api/src/apis/system/ApiRegistry.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 { ApiRef, ApiHolder } from './types'; - -type ApiImpl = readonly [ApiRef, T]; - -class ApiRegistryBuilder { - private apis: [string, unknown][] = []; - - add(api: ApiRef, impl: I): I { - this.apis.push([api.id, impl]); - return impl; - } - - build(): ApiRegistry { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return new ApiRegistry(new Map(this.apis)); - } -} - -export class ApiRegistry implements ApiHolder { - static builder() { - return new ApiRegistryBuilder(); - } - - static from(apis: ApiImpl[]) { - return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl]))); - } - - /** - * Creates a new ApiRegistry with a single API implementation. - * - * @param api ApiRef for the API to add - * @param impl Implementation of the API to add - */ - static with(api: ApiRef, impl: T): ApiRegistry { - return new ApiRegistry(new Map([[api.id, impl]])); - } - - constructor(private readonly apis: Map) {} - - /** - * Returns a new ApiRegistry with the provided API added to the existing ones. - * - * @param api ApiRef for the API to add - * @param impl Implementation of the API to add - */ - with(api: ApiRef, impl: T): ApiRegistry { - return new ApiRegistry(new Map([...this.apis, [api.id, impl]])); - } - - get(api: ApiRef): T | undefined { - return this.apis.get(api.id) as T | undefined; - } -} diff --git a/packages/core-api/src/apis/system/ApiResolver.test.ts b/packages/core-api/src/apis/system/ApiResolver.test.ts deleted file mode 100644 index 7a46d2db3b..0000000000 --- a/packages/core-api/src/apis/system/ApiResolver.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 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 { ApiResolver } from './ApiResolver'; -import { createApiRef } from './ApiRef'; -import { ApiFactoryRegistry } from './ApiFactoryRegistry'; - -const aRef = createApiRef({ id: 'a', description: '' }); -const otherARef = createApiRef({ id: 'a', description: 'other' }); -const bRef = createApiRef({ id: 'b', description: '' }); -const otherBRef = createApiRef({ id: 'b', description: 'other' }); -const cRef = createApiRef<{ x: string }>({ id: 'c', description: '' }); -const otherCRef = createApiRef<{ x: string }>({ - id: 'c', - description: 'other', -}); - -function createRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: {}, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: {}, - factory: () => 'b', - }); - registry.register('default', { - api: cRef, - deps: { b: otherBRef }, - factory: ({ b }) => ({ x: 'x', b }), - }); - return registry; -} - -function createSelfCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { a: aRef }, - factory: () => 1, - }); - return registry; -} - -function createShortCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: bRef }, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: { a: aRef }, - factory: () => 'x', - }); - return registry; -} - -function createShortCyclicRegistryWithOther() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: bRef }, - factory: () => 1, - }); - registry.register('default', { - api: otherBRef, - deps: { a: otherARef }, - factory: () => 'x', - }); - return registry; -} - -function createLongCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: otherBRef }, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: { c: cRef }, - factory: () => 'b', - }); - registry.register('default', { - api: cRef, - deps: { a: aRef }, - factory: () => ({ x: 'x' }), - }); - return registry; -} - -describe('ApiResolver', () => { - it('should be created empty', () => { - const resolver = new ApiResolver(new ApiFactoryRegistry()); - expect(resolver.get(aRef)).toBe(undefined); - expect(resolver.get(bRef)).toBe(undefined); - expect(resolver.get(otherBRef)).toBe(undefined); - expect(resolver.get(cRef)).toBe(undefined); - }); - - it('should instantiate APIs', () => { - const resolver = new ApiResolver(createRegistry()); - expect(resolver.get(aRef)).toBe(1); - expect(resolver.get(otherARef)).toBe(1); - expect(resolver.get(bRef)).toBe('b'); - expect(resolver.get(otherBRef)).toBe('b'); - expect(resolver.get(cRef)).toEqual({ x: 'x', b: 'b' }); - expect(resolver.get(cRef)).toBe(resolver.get(otherCRef)); - }); - - it('should detect self dependency cycles', () => { - const resolver = new ApiResolver(createSelfCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - }); - - it('should detect short dependency cycles', () => { - const resolver = new ApiResolver(createShortCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - }); - - it('should detect short dependency cycles with other refs', () => { - const resolver = new ApiResolver(createShortCyclicRegistryWithOther()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(otherARef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(otherBRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - }); - - it('should detect long dependency cycles', () => { - const resolver = new ApiResolver(createLongCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - // Second call for same ref should still throw - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(otherBRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(cRef)).toThrow( - 'Circular dependency of api factory for apiRef{c}', - ); - }); - - it('should validate a factory holder', () => { - expect(() => { - ApiResolver.validateFactories(createRegistry(), [ - aRef, - bRef, - otherBRef, - cRef, - ]); - }).not.toThrow(); - }); - - it('should find self cycles with validation', () => { - const self = createSelfCyclicRegistry(); - expect(() => ApiResolver.validateFactories(self, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(self, [otherARef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - }); - - it('should find dependency cycles with validation', () => { - const short = createShortCyclicRegistry(); - expect(() => ApiResolver.validateFactories(short, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(short, [otherARef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(short, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(short, [otherBRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - - const shortOther = createShortCyclicRegistryWithOther(); - expect(() => ApiResolver.validateFactories(shortOther, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => - ApiResolver.validateFactories(shortOther, [otherARef]), - ).toThrow('Circular dependency of api factory for apiRef{a}'); - expect(() => ApiResolver.validateFactories(shortOther, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => - ApiResolver.validateFactories(shortOther, [otherBRef]), - ).toThrow('Circular dependency of api factory for apiRef{b}'); - - const long = createLongCyclicRegistry(); - expect(() => - ApiResolver.validateFactories(long, long.getAllApis()), - ).toThrow('Circular dependency of api factory for apiRef{a}'); - expect(() => ApiResolver.validateFactories(long, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(long, [otherBRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(long, [cRef])).toThrow( - 'Circular dependency of api factory for apiRef{c}', - ); - }); - - it('should only call factory func once', () => { - const registry = new ApiFactoryRegistry(); - const factory = jest.fn().mockReturnValue(2); - registry.register('default', { - api: aRef, - deps: {}, - factory, - }); - - const resolver = new ApiResolver(registry); - expect(factory).toHaveBeenCalledTimes(0); - expect(resolver.get(aRef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - expect(resolver.get(aRef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - expect(resolver.get(otherARef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiResolver.ts b/packages/core-api/src/apis/system/ApiResolver.ts deleted file mode 100644 index 9738e09622..0000000000 --- a/packages/core-api/src/apis/system/ApiResolver.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { - ApiRef, - ApiHolder, - ApiFactoryHolder, - AnyApiRef, - TypesToApiRefs, -} from './types'; - -export class ApiResolver implements ApiHolder { - /** - * Validate factories by making sure that each of the apis can be created - * without hitting any circular dependencies. - */ - static validateFactories( - factories: ApiFactoryHolder, - apis: Iterable, - ) { - for (const api of apis) { - const heap = [api]; - const allDeps = new Set(); - - while (heap.length) { - const apiRef = heap.shift()!; - const factory = factories.get(apiRef); - if (!factory) { - continue; - } - - for (const dep of Object.values(factory.deps)) { - if (dep.id === api.id) { - throw new Error(`Circular dependency of api factory for ${api}`); - } - if (!allDeps.has(dep)) { - allDeps.add(dep); - heap.push(dep); - } - } - } - } - } - - private readonly apis = new Map(); - - constructor(private readonly factories: ApiFactoryHolder) {} - - get(ref: ApiRef): T | undefined { - return this.load(ref); - } - - private load(ref: ApiRef, loading: AnyApiRef[] = []): T | undefined { - const impl = this.apis.get(ref.id); - if (impl) { - return impl as T; - } - - const factory = this.factories.get(ref); - if (!factory) { - return undefined; - } - - if (loading.includes(factory.api)) { - throw new Error(`Circular dependency of api factory for ${factory.api}`); - } - - const deps = this.loadDeps(ref, factory.deps, [...loading, factory.api]); - const api = factory.factory(deps); - this.apis.set(ref.id, api); - return api as T; - } - - private loadDeps( - dependent: ApiRef, - apis: TypesToApiRefs, - loading: AnyApiRef[], - ): T { - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = this.load(ref, loading); - if (!api) { - throw new Error( - `No API factory available for dependency ${ref} of dependent ${dependent}`, - ); - } - impls[key] = api; - } - } - - return impls; - } -} diff --git a/packages/core-api/src/apis/system/helpers.ts b/packages/core-api/src/apis/system/helpers.ts deleted file mode 100644 index cabff73060..0000000000 --- a/packages/core-api/src/apis/system/helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { ApiRef, ApiFactory, TypesToApiRefs } from './types'; - -/** - * Used to infer types for a standalone ApiFactory that isn't immediately passed - * to another function. - * This function doesn't actually do anything, it's only used to infer types. - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } ->(factory: ApiFactory): ApiFactory; -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } ->( - factory: ApiFactory | ApiRef, - instance?: Impl, -): ApiFactory { - if ('id' in factory) { - return { - api: factory, - deps: {} as TypesToApiRefs, - factory: () => instance!, - }; - } - return factory; -} diff --git a/packages/core-api/src/apis/system/index.ts b/packages/core-api/src/apis/system/index.ts deleted file mode 100644 index 10b2e0f084..0000000000 --- a/packages/core-api/src/apis/system/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -export { ApiProvider, useApi, useApiHolder } from './ApiProvider'; -export { ApiRegistry } from './ApiRegistry'; -export { ApiResolver } from './ApiResolver'; -export { ApiFactoryRegistry } from './ApiFactoryRegistry'; -export { createApiRef } from './ApiRef'; -export * from './types'; -export * from './helpers'; diff --git a/packages/core-api/src/apis/system/types.ts b/packages/core-api/src/apis/system/types.ts deleted file mode 100644 index a4bc95a3c3..0000000000 --- a/packages/core-api/src/apis/system/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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. - */ - -export type ApiRef = { - id: string; - description?: string; - T: T; -}; - -export type AnyApiRef = ApiRef; - -export type ApiRefType = T extends ApiRef ? U : never; - -export type TypesToApiRefs = { [key in keyof T]: ApiRef }; - -export type ApiRefsToTypes }> = { - [key in keyof T]: ApiRefType; -}; - -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; - -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; - -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { [key in string]: unknown } ->; - -export type ApiFactoryHolder = { - get( - api: ApiRef, - ): ApiFactory | undefined; -}; diff --git a/packages/core-api/src/app/App.test.tsx b/packages/core-api/src/app/App.test.tsx deleted file mode 100644 index 3cade002de..0000000000 --- a/packages/core-api/src/app/App.test.tsx +++ /dev/null @@ -1,375 +0,0 @@ -/* - * 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 { - configApiRef, - createApiFactory, - featureFlagsApiRef, - LocalStorageFeatureFlags, -} from '../apis'; -import { renderWithEffects, withLogCollector } from '@backstage/test-utils'; -import { lightTheme } from '@backstage/theme'; -import { render, screen } from '@testing-library/react'; -import React, { PropsWithChildren } from 'react'; -import { BrowserRouter, Routes } from 'react-router-dom'; -import { createRoutableExtension } from '../extensions'; -import { defaultSystemIcons } from '../icons'; -import { createPlugin } from '../plugin'; -import { useRouteRef } from '../routing/hooks'; -import { - createExternalRouteRef, - createRouteRef, - createSubRouteRef, -} from '../routing'; -import { generateBoundRoutes, PrivateAppImpl } from './App'; - -describe('generateBoundRoutes', () => { - it('runs happy path', () => { - const external = { myRoute: createExternalRouteRef({ id: '1' }) }; - const ref = createRouteRef({ id: 'ref-1' }); - const result = generateBoundRoutes(({ bind }) => { - bind(external, { myRoute: ref }); - }); - - expect(result.get(external.myRoute)).toBe(ref); - }); - - it('throws on unknown keys', () => { - const external = { myRoute: createExternalRouteRef({ id: '2' }) }; - const ref = createRouteRef({ id: 'ref-2' }); - expect(() => - generateBoundRoutes(({ bind }) => { - bind(external, { someOtherRoute: ref } as any); - }), - ).toThrow('Key someOtherRoute is not an existing external route'); - }); -}); - -describe('Integration Test', () => { - const plugin1RouteRef = createRouteRef({ id: 'ref-1' }); - const plugin2RouteRef = createRouteRef({ id: 'ref-2', params: ['x'] }); - const subRouteRef1 = createSubRouteRef({ - id: 'sub1', - path: '/sub1', - parent: plugin1RouteRef, - }); - const subRouteRef2 = createSubRouteRef({ - id: 'sub2', - path: '/sub2/:x', - parent: plugin1RouteRef, - }); - const subRouteRef3 = createSubRouteRef({ - id: 'sub3', - path: '/sub3', - parent: plugin2RouteRef, - }); - const subRouteRef4 = createSubRouteRef({ - id: 'sub4', - path: '/sub4/:y', - parent: plugin2RouteRef, - }); - const extRouteRef1 = createExternalRouteRef({ id: 'extRouteRef1' }); - const extRouteRef2 = createExternalRouteRef({ - id: 'extRouteRef2', - params: ['x'], - }); - const extRouteRef3 = createExternalRouteRef({ - id: 'extRouteRef3', - optional: true, - }); - const extRouteRef4 = createExternalRouteRef({ - id: 'extRouteRef4', - optional: true, - params: ['x'], - }); - - const plugin1 = createPlugin({ - id: 'blob', - // Both absolute and sub route refs should be assignable to the plugin routes - routes: { - ref1: plugin1RouteRef, - ref2: plugin2RouteRef, - ref3: subRouteRef1, - }, - externalRoutes: { - extRouteRef1, - extRouteRef2, - extRouteRef3, - extRouteRef4, - }, - }); - - const plugin2 = createPlugin({ - id: 'plugin2', - }); - - const HiddenComponent = plugin2.provide( - createRoutableExtension({ - component: () => Promise.resolve((_: { path?: string }) =>

), - mountPoint: plugin2RouteRef, - }), - ); - - const ExposedComponent = plugin1.provide( - createRoutableExtension({ - component: () => - Promise.resolve((_: PropsWithChildren<{ path?: string }>) => { - const link1 = useRouteRef(plugin1RouteRef); - const link2 = useRouteRef(plugin2RouteRef); - const subLink1 = useRouteRef(subRouteRef1); - const subLink2 = useRouteRef(subRouteRef2); - const subLink3 = useRouteRef(subRouteRef3); - const subLink4 = useRouteRef(subRouteRef4); - const extLink1 = useRouteRef(extRouteRef1); - const extLink2 = useRouteRef(extRouteRef2); - const extLink3 = useRouteRef(extRouteRef3); - const extLink4 = useRouteRef(extRouteRef4); - return ( -
- link1: {link1()} - link2: {link2({ x: 'a' })} - subLink1: {subLink1()} - subLink2: {subLink2({ x: 'a' })} - subLink3: {subLink3({ x: 'b' })} - subLink4: {subLink4({ x: 'c', y: 'd' })} - extLink1: {extLink1()} - extLink2: {extLink2({ x: 'a' })} - extLink3: {extLink3?.() ?? ''} - extLink4: {extLink4?.({ x: 'b' }) ?? ''} -
- ); - }), - mountPoint: plugin1RouteRef, - }), - ); - - const components = { - NotFoundErrorPage: () => null, - BootErrorPage: () => null, - Progress: () => null, - Router: BrowserRouter, - }; - - it('runs happy paths', async () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - extRouteRef3: subRouteRef1, - extRouteRef4: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - - await renderWithEffects( - - - - - - - - , - ); - - expect(screen.getByText('link1: /')).toBeInTheDocument(); - expect(screen.getByText('link2: /foo/a')).toBeInTheDocument(); - expect(screen.getByText('subLink1: /sub1')).toBeInTheDocument(); - expect(screen.getByText('subLink2: /sub2/a')).toBeInTheDocument(); - expect(screen.getByText('subLink3: /foo/b/sub3')).toBeInTheDocument(); - expect(screen.getByText('subLink4: /foo/c/sub4/d')).toBeInTheDocument(); - expect(screen.getByText('extLink1: /')).toBeInTheDocument(); - expect(screen.getByText('extLink2: /foo/a')).toBeInTheDocument(); - expect(screen.getByText('extLink3: /sub1')).toBeInTheDocument(); - expect(screen.getByText('extLink4: /foo/b')).toBeInTheDocument(); - - // Plugins should be discovered through element tree - expect(app.getPlugins()).toEqual([plugin1, plugin2]); - }); - - it('runs happy paths without optional routes', async () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - - await renderWithEffects( - - - - - - - - , - ); - - expect(screen.getByText('extLink1: /')).toBeInTheDocument(); - expect(screen.getByText('extLink2: /foo')).toBeInTheDocument(); - expect(screen.getByText('extLink3: ')).toBeInTheDocument(); - expect(screen.getByText('extLink4: ')).toBeInTheDocument(); - }); - - it('should wait for the config to load before calling feature flags', async () => { - const storageFlags = new LocalStorageFeatureFlags(); - jest.spyOn(storageFlags, 'registerFlag'); - - const apis = [ - createApiFactory({ - api: featureFlagsApiRef, - deps: { configApi: configApiRef }, - factory() { - return storageFlags; - }, - }), - ]; - - const app = new PrivateAppImpl({ - apis, - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [ - createPlugin({ - id: 'test', - register: p => p.featureFlags.register('name'), - }), - ], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - - await renderWithEffects( - - - - - - - - , - ); - - expect(storageFlags.registerFlag).toHaveBeenCalledWith({ - name: 'name', - pluginId: 'test', - }); - }); - - it('should throw some error when the route has duplicate params', () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - const { error: errorLogs } = withLogCollector(() => { - expect(() => - render( - - - - - - - - - , - ), - ).toThrow( - 'Parameter :thing is duplicated in path /test/:thing/some/:thing', - ); - }); - expect(errorLogs).toEqual([ - expect.stringContaining( - 'Parameter :thing is duplicated in path /test/:thing/some/:thing', - ), - expect.stringContaining( - 'The above error occurred in the component', - ), - ]); - }); -}); diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx deleted file mode 100644 index 0557f4159c..0000000000 --- a/packages/core-api/src/app/App.tsx +++ /dev/null @@ -1,525 +0,0 @@ -/* - * 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 { Config } from '@backstage/config'; -import React, { - ComponentType, - PropsWithChildren, - ReactElement, - useEffect, - useMemo, - useState, -} from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { - AnyApiFactory, - ApiHolder, - ApiProvider, - ApiRegistry, - AppTheme, - appThemeApiRef, - AppThemeSelector, - configApiRef, - ConfigReader, - LocalStorageFeatureFlags, - useApi, -} from '../apis'; -import { - AppThemeApi, - ConfigApi, - featureFlagsApiRef, - identityApiRef, -} from '../apis/definitions'; -import { ApiFactoryRegistry, ApiResolver } from '../apis/system'; -import { - childDiscoverer, - routeElementDiscoverer, - traverseElementTree, -} from '../extensions/traversal'; -import { IconComponent, IconComponentMap, IconKey } from '../icons'; -import { BackstagePlugin } from '../plugin'; -import { pluginCollector } from '../plugin/collectors'; -import { AnyRoutes } from '../plugin/types'; -import { RouteRef, ExternalRouteRef, SubRouteRef } from '../routing'; -import { - routeObjectCollector, - routeParentCollector, - routePathCollector, -} from '../routing/collectors'; -import { RoutingProvider } from '../routing/hooks'; -import { validateRoutes } from '../routing/validation'; -import { AppContextProvider } from './AppContext'; -import { AppIdentity } from './AppIdentity'; -import { AppThemeProvider } from './AppThemeProvider'; -import { - AppComponents, - AppConfigLoader, - AppContext, - AppOptions, - AppRouteBinder, - BackstageApp, - SignInPageProps, - SignInResult, -} from './types'; - -export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { - const result = new Map(); - - if (bindRoutes) { - const bind: AppRouteBinder = (externalRoutes, targetRoutes: AnyRoutes) => { - for (const [key, value] of Object.entries(targetRoutes)) { - const externalRoute = externalRoutes[key]; - if (!externalRoute) { - throw new Error(`Key ${key} is not an existing external route`); - } - if (!value && !externalRoute.optional) { - throw new Error( - `External route ${key} is required but was undefined`, - ); - } - if (value) { - result.set(externalRoute, value); - } - } - }; - bindRoutes({ bind }); - } - - return result; -} - -type FullAppOptions = { - apis: Iterable; - icons: IconComponentMap; - plugins: BackstagePlugin[]; - components: AppComponents; - themes: AppTheme[]; - configLoader?: AppConfigLoader; - defaultApis: Iterable; - bindRoutes?: AppOptions['bindRoutes']; -}; - -function useConfigLoader( - configLoader: AppConfigLoader | undefined, - components: AppComponents, - appThemeApi: AppThemeApi, -): { api: ConfigApi } | { node: JSX.Element } { - // Keeping this synchronous when a config loader isn't set simplifies tests a lot - const hasConfig = Boolean(configLoader); - const config = useAsync(configLoader || (() => Promise.resolve([]))); - - let noConfigNode = undefined; - - if (hasConfig && config.loading) { - const { Progress } = components; - noConfigNode = ; - } else if (config.error) { - const { BootErrorPage } = components; - noConfigNode = ; - } - - // Before the config is loaded we can't use a router, so exit early - if (noConfigNode) { - return { - node: ( - - {noConfigNode} - - ), - }; - } - - const configReader = ConfigReader.fromConfigs(config.value ?? []); - - return { api: configReader }; -} - -class AppContextImpl implements AppContext { - constructor(private readonly app: PrivateAppImpl) {} - - getPlugins(): BackstagePlugin[] { - // eslint-disable-next-line no-console - console.warn('appContext.getPlugins() is deprecated and will be removed'); - return this.app.getPlugins(); - } - - getSystemIcon(key: IconKey): IconComponent | undefined { - return this.app.getSystemIcon(key); - } - - getComponents(): AppComponents { - return this.app.getComponents(); - } - - getProvider(): React.ComponentType<{}> { - // eslint-disable-next-line no-console - console.warn('appContext.getProvider() is deprecated and will be removed'); - return this.app.getProvider(); - } - - getRouter(): React.ComponentType<{}> { - // eslint-disable-next-line no-console - console.warn('appContext.getRouter() is deprecated and will be removed'); - return this.app.getRouter(); - } - - getRoutes(): JSX.Element[] { - // eslint-disable-next-line no-console - console.warn('appContext.getRoutes() is deprecated and will be removed'); - return this.app.getRoutes(); - } -} - -export class PrivateAppImpl implements BackstageApp { - private apiHolder?: ApiHolder; - private configApi?: ConfigApi; - - private readonly apis: Iterable; - private readonly icons: IconComponentMap; - private readonly plugins: Set>; - private readonly components: AppComponents; - private readonly themes: AppTheme[]; - private readonly configLoader?: AppConfigLoader; - private readonly defaultApis: Iterable; - private readonly bindRoutes: AppOptions['bindRoutes']; - - private readonly identityApi = new AppIdentity(); - - constructor(options: FullAppOptions) { - this.apis = options.apis; - this.icons = options.icons; - this.plugins = new Set(options.plugins); - this.components = options.components; - this.themes = options.themes; - this.configLoader = options.configLoader; - this.defaultApis = options.defaultApis; - this.bindRoutes = options.bindRoutes; - } - - getPlugins(): BackstagePlugin[] { - return Array.from(this.plugins); - } - - getSystemIcon(key: IconKey): IconComponent | undefined { - return this.icons[key]; - } - - getComponents(): AppComponents { - return this.components; - } - - getRoutes(): JSX.Element[] { - const routes = new Array(); - - const { NotFoundErrorPage } = this.components; - - for (const plugin of this.plugins.values()) { - for (const output of plugin.output()) { - switch (output.type) { - case 'legacy-route': { - const { path, component: Component } = output; - routes.push( - } />, - ); - break; - } - case 'route': { - const { target, component: Component } = output; - routes.push( - } - />, - ); - break; - } - case 'legacy-redirect-route': { - const { path, target } = output; - routes.push(); - break; - } - case 'redirect-route': { - const { from, to } = output; - routes.push(); - break; - } - default: - break; - } - } - } - - routes.push( - } - />, - ); - - return routes; - } - - getProvider(): ComponentType<{}> { - const appContext = new AppContextImpl(this); - - const Provider = ({ children }: PropsWithChildren<{}>) => { - const appThemeApi = useMemo( - () => AppThemeSelector.createWithStorage(this.themes), - [], - ); - - const { routePaths, routeParents, routeObjects } = useMemo(() => { - const result = traverseElementTree({ - root: children, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - collectedPlugins: pluginCollector, - }, - }); - - validateRoutes(result.routePaths, result.routeParents); - - // TODO(Rugvip): Restructure the public API so that we can get an immediate view of - // the app, rather than having to wait for the provider to render. - // For now we need to push the additional plugins we find during - // collection and then make sure we initialize things afterwards. - result.collectedPlugins.forEach(plugin => this.plugins.add(plugin)); - this.verifyPlugins(this.plugins); - - // Initialize APIs once all plugins are available - this.getApiHolder(); - - return result; - }, [children]); - - const loadedConfig = useConfigLoader( - this.configLoader, - this.components, - appThemeApi, - ); - - const hasConfigApi = 'api' in loadedConfig; - if (hasConfigApi) { - const { api } = loadedConfig as { api: Config }; - this.configApi = api; - } - - useEffect(() => { - if (hasConfigApi) { - const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; - - for (const plugin of this.plugins.values()) { - for (const output of plugin.output()) { - switch (output.type) { - case 'feature-flag': { - featureFlagsApi.registerFlag({ - name: output.name, - pluginId: plugin.getId(), - }); - break; - } - default: - break; - } - } - } - } - }, [hasConfigApi, loadedConfig]); - - if ('node' in loadedConfig) { - // Loading or error - return loadedConfig.node; - } - - return ( - - - - - {children} - - - - - ); - }; - return Provider; - } - - getRouter(): ComponentType<{}> { - const { - Router: RouterComponent, - SignInPage: SignInPageComponent, - } = this.components; - - // This wraps the sign-in page and waits for sign-in to be completed before rendering the app - const SignInPageWrapper = ({ - component: Component, - children, - }: { - component: ComponentType; - children: ReactElement; - }) => { - const [result, setResult] = useState(); - - if (result) { - this.identityApi.setSignInResult(result); - return children; - } - - return ; - }; - - const AppRouter = ({ children }: PropsWithChildren<{}>) => { - const configApi = useApi(configApiRef); - - let { pathname } = new URL( - configApi.getOptionalString('app.baseUrl') ?? '/', - 'http://dummy.dev', // baseUrl can be specified as just a path - ); - if (pathname.endsWith('/')) { - pathname = pathname.replace(/\/$/, ''); - } - - // If the app hasn't configured a sign-in page, we just continue as guest. - if (!SignInPageComponent) { - this.identityApi.setSignInResult({ - userId: 'guest', - profile: { - email: 'guest@example.com', - displayName: 'Guest', - }, - }); - - return ( - - - {children}} /> - - - ); - } - - return ( - - - - {children}} /> - - - - ); - }; - - return AppRouter; - } - - private getApiHolder(): ApiHolder { - if (this.apiHolder) { - return this.apiHolder; - } - - const registry = new ApiFactoryRegistry(); - - registry.register('static', { - api: appThemeApiRef, - deps: {}, - factory: () => AppThemeSelector.createWithStorage(this.themes), - }); - registry.register('static', { - api: configApiRef, - deps: {}, - factory: () => { - if (!this.configApi) { - throw new Error( - 'Tried to access config API before config was loaded', - ); - } - return this.configApi; - }, - }); - registry.register('static', { - api: identityApiRef, - deps: {}, - factory: () => this.identityApi, - }); - - // It's possible to replace the feature flag API, but since we must have at least - // one implementation we add it here directly instead of through the defaultApis. - registry.register('default', { - api: featureFlagsApiRef, - deps: {}, - factory: () => new LocalStorageFeatureFlags(), - }); - for (const factory of this.defaultApis) { - registry.register('default', factory); - } - - for (const plugin of this.plugins) { - for (const factory of plugin.getApis()) { - if (!registry.register('default', factory)) { - throw new Error( - `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${ - factory.api - }`, - ); - } - } - } - - for (const factory of this.apis) { - if (!registry.register('app', factory)) { - throw new Error( - `Duplicate or forbidden API factory for ${factory.api} in app`, - ); - } - } - - ApiResolver.validateFactories(registry, registry.getAllApis()); - - this.apiHolder = new ApiResolver(registry); - - return this.apiHolder; - } - - /** - * @deprecated - */ - verify() {} - - private verifyPlugins(plugins: Iterable) { - const pluginIds = new Set(); - - for (const plugin of plugins) { - const id = plugin.getId(); - if (pluginIds.has(id)) { - throw new Error(`Duplicate plugin found '${id}'`); - } - pluginIds.add(id); - } - } -} diff --git a/packages/core-api/src/app/AppContext.test.tsx b/packages/core-api/src/app/AppContext.test.tsx deleted file mode 100644 index 55d98c18c0..0000000000 --- a/packages/core-api/src/app/AppContext.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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, { useContext, Context } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { VersionedValue } from '../lib/versionedValues'; -import { getGlobalSingleton } from '../lib/globalObject'; -import { AppContext as AppContextV1 } from './types'; -import { AppContextProvider } from './AppContext'; - -describe('v1 consumer', () => { - const AppContext = getGlobalSingleton< - Context> - >('app-context'); - - function useMockAppV1(): AppContextV1 { - const impl = useContext(AppContext)?.atVersion(1); - if (!impl) { - throw new Error('no impl'); - } - return impl; - } - - it('should provide an app context', () => { - const mockContext: AppContextV1 = { - getComponents: jest.fn(), - getSystemIcon: jest.fn(), - getPlugins: jest.fn(), - getProvider: jest.fn(), - getRouter: jest.fn(), - getRoutes: jest.fn(), - }; - - const renderedHook = renderHook(() => useMockAppV1(), { - wrapper: ({ children }) => ( - - ), - }); - const result = renderedHook.result.current; - - expect(mockContext.getComponents).toHaveBeenCalledTimes(0); - result.getComponents(); - expect(mockContext.getComponents).toHaveBeenCalledTimes(1); - - expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(0); - result.getSystemIcon('icon'); - expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(1); - expect(mockContext.getSystemIcon).toHaveBeenCalledWith('icon'); - - expect(mockContext.getPlugins).toHaveBeenCalledTimes(0); - result.getPlugins(); - expect(mockContext.getPlugins).toHaveBeenCalledTimes(1); - - expect(mockContext.getProvider).toHaveBeenCalledTimes(0); - result.getProvider(); - expect(mockContext.getProvider).toHaveBeenCalledTimes(1); - - expect(mockContext.getRouter).toHaveBeenCalledTimes(0); - result.getRouter(); - expect(mockContext.getRouter).toHaveBeenCalledTimes(1); - - expect(mockContext.getRoutes).toHaveBeenCalledTimes(0); - result.getRoutes(); - expect(mockContext.getRoutes).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/app/AppContext.tsx b/packages/core-api/src/app/AppContext.tsx deleted file mode 100644 index c143aa86bc..0000000000 --- a/packages/core-api/src/app/AppContext.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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, { - createContext, - PropsWithChildren, - useContext, - Context, - useMemo, -} from 'react'; -import { - VersionedValue, - createVersionedValueMap, -} from '../lib/versionedValues'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../lib/globalObject'; -import { AppContext as AppContextV1 } from './types'; - -type AppContextType = VersionedValue<{ 1: AppContextV1 }> | undefined; -const AppContext = getOrCreateGlobalSingleton('app-context', () => - createContext(undefined), -); - -type Props = { - appContext: AppContextV1; -}; - -export const AppContextProvider = ({ - appContext, - children, -}: PropsWithChildren) => { - const versionedValue = useMemo( - () => createVersionedValueMap({ 1: appContext }), - [appContext], - ); - - return ; -}; - -export const useApp = (): AppContextV1 => { - const versionedContext = useContext( - getGlobalSingleton>('app-context'), - ); - if (!versionedContext) { - throw new Error('No app context available'); - } - const appContext = versionedContext.atVersion(1); - if (!appContext) { - throw new Error('AppContext v1 not available'); - } - return appContext; -}; diff --git a/packages/core-api/src/app/AppIdentity.ts b/packages/core-api/src/app/AppIdentity.ts deleted file mode 100644 index d3e5fe567a..0000000000 --- a/packages/core-api/src/app/AppIdentity.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 { IdentityApi, ProfileInfo } from '../apis'; -import { SignInResult } from './types'; - -/** - * Implementation of the connection between the App-wide IdentityApi - * and sign-in page. - */ -export class AppIdentity implements IdentityApi { - private hasIdentity = false; - private userId?: string; - private profile?: ProfileInfo; - private idTokenFunc?: () => Promise; - private signOutFunc?: () => Promise; - - getUserId(): string { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi userId before app was loaded', - ); - } - return this.userId!; - } - - getProfile(): ProfileInfo { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi profile before app was loaded', - ); - } - return this.profile!; - } - - async getIdToken(): Promise { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi idToken before app was loaded', - ); - } - return this.idTokenFunc?.(); - } - - async signOut(): Promise { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi signOutFunc before app was loaded', - ); - } - await this.signOutFunc?.(); - location.reload(); - } - - // This is indirectly called by the sign-in page to continue into the app. - setSignInResult(result: SignInResult) { - if (this.hasIdentity) { - return; - } - if (!result.userId) { - throw new Error('Invalid sign-in result, userId not set'); - } - if (!result.profile) { - throw new Error('Invalid sign-in result, profile not set'); - } - this.hasIdentity = true; - this.userId = result.userId; - this.profile = result.profile; - this.idTokenFunc = result.getIdToken; - this.signOutFunc = result.signOut; - } -} diff --git a/packages/core-api/src/app/AppThemeProvider.tsx b/packages/core-api/src/app/AppThemeProvider.tsx deleted file mode 100644 index 993de23a7a..0000000000 --- a/packages/core-api/src/app/AppThemeProvider.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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, { useMemo, useEffect, useState, PropsWithChildren } from 'react'; -import { ThemeProvider, CssBaseline } from '@material-ui/core'; -import { useApi, appThemeApiRef, AppTheme } from '../apis'; -import { useObservable } from 'react-use'; - -// This tries to find the most accurate match, but also falls back to less -// accurate results in order to avoid errors. -function resolveTheme( - themeId: string | undefined, - shouldPreferDark: boolean, - themes: AppTheme[], -) { - if (themeId !== undefined) { - const selectedTheme = themes.find(theme => theme.id === themeId); - if (selectedTheme) { - return selectedTheme; - } - } - - if (shouldPreferDark) { - const darkTheme = themes.find(theme => theme.variant === 'dark'); - if (darkTheme) { - return darkTheme; - } - } - - const lightTheme = themes.find(theme => theme.variant === 'light'); - if (lightTheme) { - return lightTheme; - } - - return themes[0]; -} - -const useShouldPreferDarkTheme = () => { - const mediaQuery = useMemo( - () => window.matchMedia('(prefers-color-scheme: dark)'), - [], - ); - const [shouldPreferDark, setPrefersDark] = useState(mediaQuery.matches); - - useEffect(() => { - const listener = (event: MediaQueryListEvent) => { - setPrefersDark(event.matches); - }; - mediaQuery.addListener(listener); - return () => { - mediaQuery.removeListener(listener); - }; - }, [mediaQuery]); - - return shouldPreferDark; -}; - -export function AppThemeProvider({ children }: PropsWithChildren<{}>) { - const appThemeApi = useApi(appThemeApiRef); - const themeId = useObservable( - appThemeApi.activeThemeId$(), - appThemeApi.getActiveThemeId(), - ); - - // Browser feature detection won't change over time, so ignore lint rule - const shouldPreferDark = Boolean(window.matchMedia) - ? useShouldPreferDarkTheme() // eslint-disable-line react-hooks/rules-of-hooks - : false; - - const appTheme = resolveTheme( - themeId, - shouldPreferDark, - appThemeApi.getInstalledThemes(), - ); - if (!appTheme) { - throw new Error('App has no themes'); - } - - return ( - - {children} - - ); -} diff --git a/packages/core-api/src/app/index.ts b/packages/core-api/src/app/index.ts deleted file mode 100644 index 17610ea3ee..0000000000 --- a/packages/core-api/src/app/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { useApp } from './AppContext'; -export * from './types'; diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts deleted file mode 100644 index df0f65f33f..0000000000 --- a/packages/core-api/src/app/types.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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 { ComponentType } from 'react'; -import { IconComponent, IconComponentMap, IconKey } from '../icons/types'; -import { AnyExternalRoutes, BackstagePlugin } from '../plugin/types'; -import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing/types'; -import { AnyApiFactory } from '../apis/system'; -import { AppTheme, ProfileInfo } from '../apis/definitions'; -import { AppConfig } from '@backstage/config'; - -export type BootErrorPageProps = { - step: 'load-config' | 'load-chunk'; - error: Error; -}; - -export type SignInResult = { - /** - * User ID that will be returned by the IdentityApi - */ - userId: string; - - profile: ProfileInfo; - - /** - * Function used to retrieve an ID token for the signed in user. - */ - getIdToken?: () => Promise; - - /** - * Sign out handler that will be called if the user requests to sign out. - */ - signOut?: () => Promise; -}; - -export type SignInPageProps = { - /** - * Set the sign-in result for the app. This should only be called once. - */ - onResult(result: SignInResult): void; -}; - -export type AppComponents = { - NotFoundErrorPage: ComponentType<{}>; - BootErrorPage: ComponentType; - Progress: ComponentType<{}>; - Router: ComponentType<{}>; - - /** - * An optional sign-in page that will be rendered instead of the AppRouter at startup. - * - * If a sign-in page is set, it will always be shown before the app, and it is up - * to the sign-in page to handle e.g. saving of login methods for subsequent visits. - * - * The sign-in page will be displayed until it has passed up a result to the parent, - * and which point the AppRouter and all of its children will be rendered instead. - */ - SignInPage?: ComponentType; -}; - -/** - * A function that loads in the App config that will be accessible via the ConfigApi. - * - * If multiple config objects are returned in the array, values in the earlier configs - * will override later ones. - */ -export type AppConfigLoader = () => Promise; - -/** - * Extracts a union of the keys in a map whose value extends the given type - */ -type KeysWithType = { - [key in keyof Obj]: Obj[key] extends Type ? key : never; -}[keyof Obj]; - -/** - * Takes a map Map required values and makes all keys matching Keys optional - */ -type PartialKeys< - Map extends { [name in string]: any }, - Keys extends keyof Map -> = Partial> & Required>; - -/** - * Creates a map of target routes with matching parameters based on a map of external routes. - */ -type TargetRouteMap = { - [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< - infer Params, - any - > - ? RouteRef | SubRouteRef - : never; -}; - -export type AppRouteBinder = ( - externalRoutes: ExternalRoutes, - targetRoutes: PartialKeys< - TargetRouteMap, - KeysWithType> - >, -) => void; - -export type AppOptions = { - /** - * A collection of ApiFactories to register in the application to either - * add add new ones, or override factories provided by default or by plugins. - */ - apis?: Iterable; - - /** - * Supply icons to override the default ones. - */ - icons?: IconComponentMap; - - /** - * A list of all plugins to include in the app. - */ - plugins?: BackstagePlugin[]; - - /** - * Supply components to the app to override the default ones. - */ - components?: Partial; - - /** - * Themes provided as a part of the app. By default two themes are included, one - * light variant of the default backstage theme, and one dark. - * - * This is the default config: - * - * ``` - * [{ - * id: 'light', - * title: 'Light Theme', - * variant: 'light', - * theme: lightTheme, - * icon: , - * }, { - * id: 'dark', - * title: 'Dark Theme', - * variant: 'dark', - * theme: darkTheme, - * icon: , - * }] - * ``` - */ - themes?: AppTheme[]; - - /** - * A function that loads in App configuration that will be accessible via - * the ConfigApi. - * - * Defaults to an empty config. - * - * TODO(Rugvip): Omitting this should instead default to loading in configuration - * that was packaged by the backstage-cli and default docker container boot script. - */ - configLoader?: AppConfigLoader; - - /** - * A function that is used to register associations between cross-plugin route - * references, enabling plugins to navigate between each other. - * - * The `bind` function that is passed in should be used to bind all external - * routes of all used plugins. - * - * ```ts - * bindRoutes({ bind }) { - * bind(docsPlugin.externalRoutes, { - * homePage: managePlugin.routes.managePage, - * }) - * bind(homePagePlugin.externalRoutes, { - * settingsPage: settingsPlugin.routes.settingsPage, - * }) - * } - * ``` - */ - bindRoutes?(context: { bind: AppRouteBinder }): void; -}; - -export type BackstageApp = { - /** - * Returns all plugins registered for the app. - */ - getPlugins(): BackstagePlugin[]; - - /** - * Get a common or custom icon for this app. - */ - getSystemIcon(key: IconKey): IconComponent | undefined; - - /** - * Provider component that should wrap the Router created with getRouter() - * and any other components that need to be within the app context. - */ - getProvider(): ComponentType<{}>; - - /** - * Router component that should wrap the App Routes create with getRoutes() - * and any other components that should only be available while signed in. - */ - getRouter(): ComponentType<{}>; - - /** - * Routes component that contains all routes for plugin pages in the app. - * - * @deprecated Registering routes in plugins is deprecated and this method will be removed. - */ - getRoutes(): JSX.Element[]; -}; - -export type AppContext = { - /** - * @deprecated Will be removed - */ - getPlugins(): BackstagePlugin[]; - - /** - * Get a common or custom icon for this app. - */ - getSystemIcon(key: IconKey): IconComponent | undefined; - - /** - * Get the components registered for various purposes in the app. - */ - getComponents(): AppComponents; - - /** - * @deprecated Will be removed - */ - getProvider(): ComponentType<{}>; - - /** - * @deprecated Will be removed - */ - getRouter(): ComponentType<{}>; - - /** - * @deprecated Will be removed - */ - getRoutes(): JSX.Element[]; -}; diff --git a/packages/core-api/src/extensions/componentData.test.tsx b/packages/core-api/src/extensions/componentData.test.tsx deleted file mode 100644 index 417fe07414..0000000000 --- a/packages/core-api/src/extensions/componentData.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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'; -import { attachComponentData, getComponentData } from './componentData'; - -describe('elementData', () => { - it('should attach a single piece of data', () => { - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - - it('should attach several distinct pieces of data', () => { - const data1 = { foo: 'bar' }; - const data2 = { test: 'value' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data1); - attachComponentData(Component, 'second', data2); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data1); - expect(getComponentData(element, 'second')).toBe(data2); - }); - - it('returns undefined for missing data', () => { - const data = { foo: 'bar' }; - const Component1 = () => null; - const Component2 = () => null; - attachComponentData(Component2, 'my-data', data); - - const element1 = ; - const element2 = ; - expect(getComponentData(element1, 'missing')).toBeUndefined(); - expect(getComponentData(element2, 'missing')).toBeUndefined(); - }); - - it('should throw when attempting to overwrite data', () => { - const data = { foo: 'bar' }; - const MyComponent = () => null; - attachComponentData(MyComponent, 'my-data', data); - expect(() => attachComponentData(MyComponent, 'my-data', data)).toThrow( - 'Attempted to attach duplicate data "my-data" to component "MyComponent"', - ); - }); - - describe('works across versions', () => { - function getDataSymbol() { - const Component = () => null; - attachComponentData(Component, 'my-data', {}); - const [symbol] = Object.getOwnPropertySymbols(Component); - return symbol; - } - - it('should should be able to get data from older versions', () => { - const symbol = getDataSymbol(); - - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - expect((element as any).type[symbol].map.get('my-data')).toBe(data); - }); - - it('should should be able to attach data for older versions', () => { - const symbol = getDataSymbol(); - - const data = { foo: 'bar' }; - const Component = () => null; - (Component as any)[symbol] = { - map: new Map([['my-data', data]]), - }; - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - - it('should be able to get data from newer versions', () => { - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - const container = (global as any)[ - '__@backstage/component-data-store__' - ].get(element.type); - expect(container.map.get('my-data')).toBe(data); - }); - - it('should should be able to attach data for newer versions', () => { - const data = { foo: 'bar' }; - const Component = () => null; - (global as any)['__@backstage/component-data-store__'].set(Component, { - map: new Map([['my-data', data]]), - }); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - }); -}); diff --git a/packages/core-api/src/extensions/componentData.tsx b/packages/core-api/src/extensions/componentData.tsx deleted file mode 100644 index fc8594039c..0000000000 --- a/packages/core-api/src/extensions/componentData.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 { ComponentType, ReactNode } from 'react'; -import { getOrCreateGlobalSingleton } from '../lib/globalObject'; - -// TODO(Rugvip): Access via symbol is deprecated, remove once on 0.3.x -const DATA_KEY = Symbol('backstage-component-data'); - -type ComponentWithData

= ComponentType

& { - [DATA_KEY]?: DataContainer; -}; - -type DataContainer = { - map: Map; -}; - -type MaybeComponentNode = ReactNode & { - type?: ComponentType & { [DATA_KEY]?: DataContainer }; -}; - -// The store is bridged across versions using the global object -const store = getOrCreateGlobalSingleton( - 'component-data-store', - () => new WeakMap, DataContainer>(), -); - -export function attachComponentData

( - component: ComponentType

, - type: string, - data: unknown, -) { - const dataComponent = component as ComponentWithData

; - - let container = store.get(component) || dataComponent[DATA_KEY]; - if (!container) { - container = { map: new Map() }; - store.set(component, container); - dataComponent[DATA_KEY] = container; - } - - if (container.map.has(type)) { - const name = component.displayName || component.name; - throw new Error( - `Attempted to attach duplicate data "${type}" to component "${name}"`, - ); - } - - container.map.set(type, data); -} - -export function getComponentData( - node: ReactNode, - type: string, -): T | undefined { - if (!node) { - return undefined; - } - - const component = (node as MaybeComponentNode).type; - if (!component) { - return undefined; - } - - const container = store.get(component) || component[DATA_KEY]; - if (!container) { - return undefined; - } - - return container.map.get(type) as T | undefined; -} diff --git a/packages/core-api/src/extensions/extensions.test.tsx b/packages/core-api/src/extensions/extensions.test.tsx deleted file mode 100644 index 26755b3bcf..0000000000 --- a/packages/core-api/src/extensions/extensions.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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'; -import { createPlugin } from '../plugin'; -import { createRouteRef } from '../routing'; -import { getComponentData } from './componentData'; -import { - createComponentExtension, - createReactExtension, - createRoutableExtension, -} from './extensions'; - -const plugin = createPlugin({ - id: 'my-plugin', -}); - -describe('extensions', () => { - it('should create a react extension with component data', () => { - const Component = () =>

; - - const extension = createReactExtension({ - component: { - sync: Component, - }, - data: { - myData: { foo: 'bar' }, - }, - }); - - const ExtensionComponent = plugin.provide(extension); - const element = ; - - expect(getComponentData(element, 'core.plugin')).toBe(plugin); - expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' }); - }); - - it('should create react extensions of different types', () => { - const Component = () =>
; - const routeRef = createRouteRef({ path: '/foo', title: 'Foo' }); - - const extension1 = createComponentExtension({ - component: { - sync: Component, - }, - }); - - const extension2 = createRoutableExtension({ - component: () => Promise.resolve(Component), - mountPoint: routeRef, - }); - - const ExtensionComponent1 = plugin.provide(extension1); - const ExtensionComponent2 = plugin.provide(extension2); - - const element1 = ; - const element2 = ; - - expect(getComponentData(element1, 'core.plugin')).toBe(plugin); - expect(getComponentData(element2, 'core.plugin')).toBe(plugin); - expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef); - }); -}); diff --git a/packages/core-api/src/extensions/extensions.tsx b/packages/core-api/src/extensions/extensions.tsx deleted file mode 100644 index 9f6412f960..0000000000 --- a/packages/core-api/src/extensions/extensions.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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, { lazy, Suspense } from 'react'; -import { useApp } from '../app'; -import { BackstagePlugin, Extension } from '../plugin/types'; -import { RouteRef, useRouteRef } from '../routing'; -import { attachComponentData } from './componentData'; - -type ComponentLoader = - | { - lazy: () => Promise; - } - | { - sync: T; - }; - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createRoutableExtension< - T extends (props: any) => JSX.Element | null ->(options: { - component: () => Promise; - mountPoint: RouteRef; -}): Extension { - const { component, mountPoint } = options; - return createReactExtension({ - component: { - lazy: () => - component().then( - InnerComponent => { - const RoutableExtensionWrapper: any = (props: any) => { - // Validate that the routing is wired up correctly in the App.tsx - try { - useRouteRef(mountPoint); - } catch (error) { - if (error?.message.startsWith('No path for ')) { - throw new Error( - `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` + - 'Routable extension components may not be rendered by other components and must be ' + - 'directly available as an element within the App provider component.', - ); - } - throw error; - } - return ; - }; - - const componentName = - (InnerComponent as { displayName?: string }).displayName || - InnerComponent.name || - 'LazyComponent'; - - RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`; - - return RoutableExtensionWrapper as T; - }, - error => { - const RoutableExtensionWrapper: any = (_: any) => { - const app = useApp(); - const { BootErrorPage } = app.getComponents(); - - return ; - }; - return RoutableExtensionWrapper; - }, - ), - }, - data: { - 'core.mountPoint': mountPoint, - }, - }); -} - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createComponentExtension< - T extends (props: any) => JSX.Element | null ->(options: { component: ComponentLoader }): Extension { - const { component } = options; - return createReactExtension({ component }); -} - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createReactExtension< - T extends (props: any) => JSX.Element | null ->(options: { - component: ComponentLoader; - data?: Record; -}): Extension { - const { data = {} } = options; - - let Component: T; - if ('lazy' in options.component) { - const lazyLoader = options.component.lazy; - Component = (lazy(() => - lazyLoader().then(component => ({ default: component })), - ) as unknown) as T; - } else { - Component = options.component.sync; - } - const componentName = - (Component as { displayName?: string }).displayName || - Component.name || - 'Component'; - - return { - expose(plugin: BackstagePlugin) { - const Result: any = (props: any) => ( - - - - ); - - attachComponentData(Result, 'core.plugin', plugin); - for (const [key, value] of Object.entries(data)) { - attachComponentData(Result, key, value); - } - - Result.displayName = `Extension(${componentName})`; - return Result; - }, - }; -} diff --git a/packages/core-api/src/extensions/index.ts b/packages/core-api/src/extensions/index.ts deleted file mode 100644 index 26a0c597b1..0000000000 --- a/packages/core-api/src/extensions/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ - -export { attachComponentData, getComponentData } from './componentData'; -export { - createReactExtension, - createRoutableExtension, - createComponentExtension, -} from './extensions'; diff --git a/packages/core-api/src/extensions/traversal.test.tsx b/packages/core-api/src/extensions/traversal.test.tsx deleted file mode 100644 index 38571fdcc7..0000000000 --- a/packages/core-api/src/extensions/traversal.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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, { Children, isValidElement } from 'react'; -import { - childDiscoverer, - createCollector, - traverseElementTree, -} from './traversal'; - -describe('discovery', () => { - it('should collect element names', () => { - const root = ( -
-
-

Title

-

Text

-
-
-
-

Title

- Text -
-
- ); - - const { names } = traverseElementTree({ - root, - discoverers: [childDiscoverer], - collectors: { - names: createCollector( - () => Array(), - (acc, el) => { - if (typeof el.type === 'string') { - acc.push(el.type); - } - }, - ), - }, - }); - - expect(names).toEqual([ - 'main', - 'div', - 'hr', - 'div', - 'h1', - 'p', - 'h2', - 'span', - ]); - }); - - it('should collect element names while skipping one level of children', () => { - const root = ( -
-
-

Title

-

Text

-
-
-
-

Title

- Text -
-
- ); - - const { names } = traverseElementTree({ - root, - discoverers: [ - el => - Children.toArray(el.props.children).flatMap(child => - isValidElement(child) ? child?.props?.children : [], - ), - ], - collectors: { - names: createCollector( - () => Array(), - (acc, el) => { - if (typeof el.type === 'string') { - acc.push(el.type); - } - }, - ), - }, - }); - - expect(names).toEqual(['main', 'h1', 'p', 'h2', 'span']); - }); -}); diff --git a/packages/core-api/src/extensions/traversal.ts b/packages/core-api/src/extensions/traversal.ts deleted file mode 100644 index 4431bbd24c..0000000000 --- a/packages/core-api/src/extensions/traversal.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 { isValidElement, ReactNode, ReactElement, Children } from 'react'; - -export type Discoverer = (element: ReactElement) => ReactNode; - -export type Collector = () => { - accumulator: Result; - visit( - accumulator: Result, - element: ReactElement, - parent: ReactElement | undefined, - context: Context, - ): Context; -}; - -/** - * A function that allows you to traverse a tree of React elements using - * varying methods to discover child nodes and collect data along the way. - */ -export function traverseElementTree(options: { - root: ReactNode; - discoverers: Discoverer[]; - collectors: { [name in keyof Results]: Collector }; -}): Results { - const collectors: { - [name in string]: ReturnType>; - } = {}; - - // Bootstrap all collectors, initializing the accumulators and providing the visitor function - for (const name in options.collectors) { - if (options.collectors.hasOwnProperty(name)) { - collectors[name] = options.collectors[name](); - } - } - - // Internal representation of an element in the tree that we're iterating over - type QueueItem = { - node: ReactNode; - parent: ReactElement | undefined; - contexts: { [name in string]: unknown }; - }; - - const queue = [ - { - node: Children.toArray(options.root), - parent: undefined, - contexts: {}, - } as QueueItem, - ]; - - while (queue.length !== 0) { - const { node, parent, contexts } = queue.shift()!; - - // While the parent and the element we pass on to collectors and discoverers - // have been validated and are known to be React elements, the child nodes - // emitted by the discoverers are not. - Children.forEach(node, element => { - if (!isValidElement(element)) { - return; - } - - const nextContexts: QueueItem['contexts'] = {}; - - // Collectors populate their result data using the current node, and compute - // context for the next iteration - for (const name in collectors) { - if (collectors.hasOwnProperty(name)) { - const collector = collectors[name]; - - nextContexts[name] = collector.visit( - collector.accumulator, - element, - parent, - contexts[name], - ); - } - } - - // Discoverers provide ways to continue the traversal from the current element - for (const discoverer of options.discoverers) { - const children = discoverer(element); - if (children) { - queue.push({ - node: children, - parent: element, - contexts: nextContexts, - }); - } - } - }); - } - - return Object.fromEntries( - Object.entries(collectors).map(([name, c]) => [name, c.accumulator]), - ) as Results; -} - -export function createCollector( - accumulatorFactory: () => Result, - visit: ReturnType>['visit'], -): Collector { - return () => ({ accumulator: accumulatorFactory(), visit }); -} - -export function childDiscoverer(element: ReactElement): ReactNode { - return element.props?.children; -} - -export function routeElementDiscoverer(element: ReactElement): ReactNode { - if (element.props?.path && element.props?.element) { - return element.props?.element; - } - return undefined; -} diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx deleted file mode 100644 index fd3d0dcdab..0000000000 --- a/packages/core-api/src/icons/icons.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { SvgIconProps } from '@material-ui/core'; -import MuiMenuBookIcon from '@material-ui/icons/MenuBook'; -import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; -import MuiChatIcon from '@material-ui/icons/Chat'; -import MuiDashboardIcon from '@material-ui/icons/Dashboard'; -import MuiEmailIcon from '@material-ui/icons/Email'; -import MuiGitHubIcon from '@material-ui/icons/GitHub'; -import MuiHelpIcon from '@material-ui/icons/Help'; -import MuiPeopleIcon from '@material-ui/icons/People'; -import MuiPersonIcon from '@material-ui/icons/Person'; -import MuiWarningIcon from '@material-ui/icons/Warning'; -import MuiDocsIcon from '@material-ui/icons/Description'; - -import React from 'react'; -import { useApp } from '../app/AppContext'; -import { IconComponent, IconComponentMap, SystemIconKey } from './types'; - -export const defaultSystemIcons: IconComponentMap = { - brokenImage: MuiBrokenImageIcon, - // To be confirmed: see https://github.com/backstage/backstage/issues/4970 - catalog: MuiMenuBookIcon, - chat: MuiChatIcon, - dashboard: MuiDashboardIcon, - email: MuiEmailIcon, - github: MuiGitHubIcon, - group: MuiPeopleIcon, - help: MuiHelpIcon, - user: MuiPersonIcon, - warning: MuiWarningIcon, - docs: MuiDocsIcon, -}; - -const overridableSystemIcon = (key: SystemIconKey): IconComponent => { - const Component = (props: SvgIconProps) => { - const app = useApp(); - const Icon = app.getSystemIcon(key); - return Icon ? : ; - }; - return Component; -}; - -export const BrokenImageIcon = overridableSystemIcon('brokenImage'); -export const ChatIcon = overridableSystemIcon('chat'); -export const DashboardIcon = overridableSystemIcon('dashboard'); -export const EmailIcon = overridableSystemIcon('email'); -export const GitHubIcon = overridableSystemIcon('github'); -export const GroupIcon = overridableSystemIcon('group'); -export const HelpIcon = overridableSystemIcon('help'); -export const UserIcon = overridableSystemIcon('user'); -export const WarningIcon = overridableSystemIcon('warning'); -export const DocsIcon = overridableSystemIcon('docs'); diff --git a/packages/core-api/src/icons/index.ts b/packages/core-api/src/icons/index.ts deleted file mode 100644 index 4c97d27176..0000000000 --- a/packages/core-api/src/icons/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export * from './icons'; -export * from './types'; diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts deleted file mode 100644 index f2a9a3ba59..0000000000 --- a/packages/core-api/src/icons/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { ComponentType } from 'react'; -import { SvgIconProps } from '@material-ui/core'; - -export type SystemIconKey = - | 'brokenImage' - | 'chat' - | 'dashboard' - | 'email' - | 'github' - | 'group' - | 'help' - | 'user' - | 'warning' - | 'docs'; - -export type IconComponent = ComponentType; -export type IconKey = SystemIconKey | string; -export type IconComponentMap = { [key in IconKey]: IconComponent }; diff --git a/packages/core-api/src/index.ts b/packages/core-api/src/index.ts deleted file mode 100644 index 90bde248f6..0000000000 --- a/packages/core-api/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -export * from './public'; -import * as privateExports from './private'; - -export default privateExports; diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts deleted file mode 100644 index 391b86952d..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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 ProviderIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from './DefaultAuthConnector'; -import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi'; -import * as loginPopup from '../loginPopup'; -import { UrlPatternDiscovery } from '../../apis'; -import { msw } from '@backstage/test-utils'; -import { setupServer } from 'msw/node'; -import { rest } from 'msw'; - -const defaultOptions = { - discoveryApi: UrlPatternDiscovery.compile('http://my-host/api/{{pluginId}}'), - environment: 'production', - provider: { - id: 'my-provider', - title: 'My Provider', - icon: ProviderIcon, - }, - oauthRequestApi: new MockOAuthApi(), - sessionTransform: ({ expiresInSeconds, ...res }: any) => ({ - ...res, - scopes: new Set(res.scopes.split(' ')), - expiresAt: new Date(Date.now() + expiresInSeconds * 1000), - }), -}; - -describe('DefaultAuthConnector', () => { - const server = setupServer(); - msw.setupDefaultHandlers(server); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should refresh a session', async () => { - server.use( - rest.get('*', (_req, res, ctx) => - res( - ctx.json({ - idToken: 'mock-id-token', - accessToken: 'mock-access-token', - scopes: 'a b c', - expiresInSeconds: '60', - }), - ), - ), - ); - - const helper = new DefaultAuthConnector(defaultOptions); - const session = await helper.refreshSession(); - expect(session.idToken).toBe('mock-id-token'); - expect(session.accessToken).toBe('mock-access-token'); - expect(session.scopes).toEqual(new Set(['a', 'b', 'c'])); - expect(session.expiresAt.getTime()).toBeLessThan(Date.now() + 70000); - expect(session.expiresAt.getTime()).toBeGreaterThan(Date.now() + 50000); - }); - - it('should handle failure to refresh session', async () => { - server.use( - rest.get('*', (_req, res, ctx) => - res(ctx.status(500, 'Error: Network NOPE')), - ), - ); - - const helper = new DefaultAuthConnector(defaultOptions); - await expect(helper.refreshSession()).rejects.toThrow( - 'Auth refresh request failed, Error: Network NOPE', - ); - }); - - it('should handle failure response when refreshing session', async () => { - server.use(rest.get('*', (_req, res, ctx) => res(ctx.status(401, 'NOPE')))); - - const helper = new DefaultAuthConnector(defaultOptions); - await expect(helper.refreshSession()).rejects.toThrow( - 'Auth refresh request failed, NOPE', - ); - }); - - it('should fail if popup was rejected', async () => { - const mockOauth = new MockOAuthApi(); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: mockOauth, - }); - const promise = helper.createSession({ scopes: new Set(['a', 'b']) }); - await mockOauth.rejectAll(); - await expect(promise).rejects.toMatchObject({ name: 'RejectedError' }); - }); - - it('should create a session', async () => { - const mockOauth = new MockOAuthApi(); - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue({ - idToken: 'my-id-token', - accessToken: 'my-access-token', - scopes: 'a b', - expiresInSeconds: 3600, - }); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: mockOauth, - }); - - const sessionPromise = helper.createSession({ - scopes: new Set(['a', 'b']), - }); - - await mockOauth.triggerAll(); - - expect(popupSpy).toBeCalledTimes(1); - expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production', - }); - - await expect(sessionPromise).resolves.toEqual({ - idToken: 'my-id-token', - accessToken: 'my-access-token', - scopes: expect.any(Set), - expiresAt: expect.any(Date), - }); - }); - - it('should instantly show popup if option is set', async () => { - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue('my-session'); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: new MockOAuthApi(), - sessionTransform: str => str, - }); - - const sessionPromise = helper.createSession({ - scopes: new Set(), - instantPopup: true, - }); - - await expect(sessionPromise).resolves.toBe('my-session'); - - expect(popupSpy).toBeCalledTimes(1); - }); - - it('should use join func to join scopes', async () => { - const mockOauth = new MockOAuthApi(); - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue({ scopes: '' }); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - joinScopes: scopes => `-${[...scopes].join('')}-`, - oauthRequestApi: mockOauth, - }); - - helper.createSession({ scopes: new Set(['a', 'b']) }); - - await mockOauth.triggerAll(); - - expect(popupSpy).toBeCalledTimes(1); - expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production', - }); - }); -}); diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts deleted file mode 100644 index 1a1bab0eb9..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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 { - AuthRequester, - OAuthRequestApi, - AuthProvider, - DiscoveryApi, -} from '../../apis/definitions'; -import { showLoginPopup } from '../loginPopup'; -import { AuthConnector, CreateSessionOptions } from './types'; - -type Options = { - /** - * DiscoveryApi instance used to locate the auth backend endpoint. - */ - discoveryApi: DiscoveryApi; - /** - * Environment hint passed on to auth backend, for example 'production' or 'development' - */ - environment: string; - /** - * Information about the auth provider to be shown to the user. - * The ID Must match the backend auth plugin configuration, for example 'google'. - */ - provider: AuthProvider & { id: string }; - /** - * API used to instantiate an auth requester. - */ - oauthRequestApi: OAuthRequestApi; - /** - * Function used to join together a set of scopes, defaults to joining with a space character. - */ - joinScopes?: (scopes: Set) => string; - /** - * Function used to transform an auth response into the session type. - */ - sessionTransform?(response: any): AuthSession | Promise; -}; - -function defaultJoinScopes(scopes: Set) { - return [...scopes].join(' '); -} - -/** - * DefaultAuthConnector is the default auth connector in Backstage. It talks to the - * backend auth plugin through the standardized API, and requests user permission - * via the OAuthRequestApi. - */ -export class DefaultAuthConnector - implements AuthConnector { - private readonly discoveryApi: DiscoveryApi; - private readonly environment: string; - private readonly provider: AuthProvider & { id: string }; - private readonly joinScopesFunc: (scopes: Set) => string; - private readonly authRequester: AuthRequester; - private readonly sessionTransform: (response: any) => Promise; - - constructor(options: Options) { - const { - discoveryApi, - environment, - provider, - joinScopes = defaultJoinScopes, - oauthRequestApi, - sessionTransform = id => id, - } = options; - - this.authRequester = oauthRequestApi.createAuthRequester({ - provider, - onAuthRequest: scopes => this.showPopup(scopes), - }); - - this.discoveryApi = discoveryApi; - this.environment = environment; - this.provider = provider; - this.joinScopesFunc = joinScopes; - this.sessionTransform = sessionTransform; - } - - async createSession(options: CreateSessionOptions): Promise { - if (options.instantPopup) { - return this.showPopup(options.scopes); - } - return this.authRequester(options.scopes); - } - - async refreshSession(): Promise { - const res = await fetch( - await this.buildUrl('/refresh', { optional: true }), - { - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }, - ).catch(error => { - throw new Error(`Auth refresh request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error( - `Auth refresh request failed, ${res.statusText}`, - ); - error.status = res.status; - throw error; - } - - const authInfo = await res.json(); - - if (authInfo.error) { - const error = new Error(authInfo.error.message); - if (authInfo.error.name) { - error.name = authInfo.error.name; - } - throw error; - } - return await this.sessionTransform(authInfo); - } - - async removeSession(): Promise { - const res = await fetch(await this.buildUrl('/logout'), { - method: 'POST', - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }).catch(error => { - throw new Error(`Logout request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error(`Logout request failed, ${res.statusText}`); - error.status = res.status; - throw error; - } - } - - private async showPopup(scopes: Set): Promise { - const scope = this.joinScopesFunc(scopes); - const popupUrl = await this.buildUrl('/start', { scope }); - - const payload = await showLoginPopup({ - url: popupUrl, - name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, - width: 450, - height: 730, - }); - - return await this.sessionTransform(payload); - } - - private async buildUrl( - path: string, - query?: { [key: string]: string | boolean | undefined }, - ): Promise { - const baseUrl = await this.discoveryApi.getBaseUrl('auth'); - const queryString = this.buildQueryString({ - ...query, - env: this.environment, - }); - - return `${baseUrl}/${this.provider.id}${path}${queryString}`; - } - - private buildQueryString(query?: { - [key: string]: string | boolean | undefined; - }): string { - if (!query) { - return ''; - } - - const queryString = Object.entries(query) - .map(([key, value]) => { - if (typeof value === 'string') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } else if (value) { - return encodeURIComponent(key); - } - return undefined; - }) - .filter(Boolean) - .join('&'); - - if (!queryString) { - return ''; - } - return `?${queryString}`; - } -} diff --git a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts deleted file mode 100644 index e7764fcbaa..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 { AuthProvider, DiscoveryApi } from '../../apis/definitions'; -import { showLoginPopup } from '../loginPopup'; - -type Options = { - discoveryApi: DiscoveryApi; - environment?: string; - provider: AuthProvider & { id: string }; -}; -export class DirectAuthConnector { - private readonly discoveryApi: DiscoveryApi; - private readonly environment: string | undefined; - private readonly provider: AuthProvider & { id: string }; - - constructor(options: Options) { - const { discoveryApi, environment, provider } = options; - - this.discoveryApi = discoveryApi; - this.environment = environment; - this.provider = provider; - } - - async createSession(): Promise { - const popupUrl = await this.buildUrl('/start'); - const payload = await showLoginPopup({ - url: popupUrl, - name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, - width: 450, - height: 730, - }); - - return { - ...payload, - id: payload.profile.email, - }; - } - - async refreshSession(): Promise {} - - async removeSession(): Promise { - const res = await fetch(await this.buildUrl('/logout'), { - method: 'POST', - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }).catch(error => { - throw new Error(`Logout request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error(`Logout request failed, ${res.statusText}`); - error.status = res.status; - throw error; - } - } - - private async buildUrl(path: string): Promise { - const baseUrl = await this.discoveryApi.getBaseUrl('auth'); - return `${baseUrl}/${this.provider.id}${path}?env=${this.environment}`; - } -} diff --git a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts deleted file mode 100644 index cd7986ffd0..0000000000 --- a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { MockAuthConnector, mockAccessToken } from './MockAuthConnector'; - -describe('MockAuthConnector', () => { - it('should return mock tokens', async () => { - const helper = new MockAuthConnector(); - - await expect(helper.createSession()).resolves.toEqual({ - accessToken: mockAccessToken, - expiresAt: expect.any(Date), - scopes: expect.any(String), - }); - - await expect(helper.refreshSession()).resolves.toEqual({ - accessToken: mockAccessToken, - expiresAt: expect.any(Date), - scopes: expect.any(String), - }); - }); -}); diff --git a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts deleted file mode 100644 index 9134fd0773..0000000000 --- a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 { AuthConnector } from './types'; - -export const mockAccessToken = 'mock-access-token'; - -type MockSession = { - accessToken: string; - expiresAt: Date; - scopes: string; -}; - -const defaultMockSession: MockSession = { - accessToken: mockAccessToken, - expiresAt: new Date(), - scopes: 'profile email', -}; - -export class MockAuthConnector implements AuthConnector { - constructor(private readonly mockSession: MockSession = defaultMockSession) {} - - async createSession() { - return this.mockSession; - } - - async refreshSession() { - return this.mockSession; - } - - async removeSession() {} -} diff --git a/packages/core-api/src/lib/AuthConnector/index.ts b/packages/core-api/src/lib/AuthConnector/index.ts deleted file mode 100644 index 388619e2c1..0000000000 --- a/packages/core-api/src/lib/AuthConnector/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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. - */ - -export { DefaultAuthConnector } from './DefaultAuthConnector'; -export { DirectAuthConnector } from './DirectAuthConnector'; -export * from './types'; diff --git a/packages/core-api/src/lib/AuthConnector/types.ts b/packages/core-api/src/lib/AuthConnector/types.ts deleted file mode 100644 index 46175a265f..0000000000 --- a/packages/core-api/src/lib/AuthConnector/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -export type CreateSessionOptions = { - scopes: Set; - instantPopup?: boolean; -}; - -/** - * An AuthConnector is responsible for realizing auth session actions - * by for example communicating with a backend or interacting with the user. - */ -export type AuthConnector = { - createSession(options: CreateSessionOptions): Promise; - refreshSession(): Promise; - removeSession(): Promise; -}; diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts deleted file mode 100644 index 5c960f7876..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 { AuthSessionStore } from './AuthSessionStore'; -import { SessionManager } from './types'; - -const defaultOptions = { - storageKey: 'my-key', - sessionScopes: (session: string) => new Set(session.split(' ')), -}; - -class LocalStorage { - private store: Record = {}; - - getItem(key: string) { - return this.store[key] || null; - } - setItem(key: string, value: string) { - this.store[key] = value.toString(); - } - removeItem(key: string) { - delete this.store[key]; - } -} - -class MockManager implements SessionManager { - setSession = jest.fn(); - getSession = jest.fn(); - removeSession = jest.fn(); - sessionState$ = jest.fn(); -} - -describe('GheAuth AuthSessionStore', () => { - beforeEach(() => { - delete (window as any).localStorage; - (window as any).localStorage = new LocalStorage(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should load session', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('a b c'); - expect(manager.getSession).not.toHaveBeenCalled(); - expect(manager.setSession).toHaveBeenCalledWith('a b c'); - }); - - it('should not use session without enough scope', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('a b c d'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({ scopes: new Set(['d']) })).resolves.toBe( - 'a b c d', - ); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should not use expired session', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ - manager, - ...defaultOptions, - sessionShouldRefresh: () => true, - }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should not load missing session', async () => { - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - - expect(localStorage.getItem('my-key')).toBe('"123"'); - }); - - it('should ignore bad session values', async () => { - localStorage.setItem('my-key', 'derp'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should clear session', () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - store.removeSession(); - - expect(localStorage.getItem('my-key')).toBe(null); - expect(manager.removeSession).toHaveBeenCalled(); - }); - - it('should forward sessionState calls', () => { - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - store.sessionState$(); - expect(manager.sessionState$).toHaveBeenCalled(); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts deleted file mode 100644 index 224036d283..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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 { - SessionManager, - MutableSessionManager, - SessionScopesFunc, - SessionShouldRefreshFunc, - GetSessionOptions, -} from './types'; -import { SessionScopeHelper } from './common'; - -type Options = { - /** The connector used for acting on the auth session */ - manager: MutableSessionManager; - /** Storage key to use to store sessions */ - storageKey: string; - /** Used to get the scope of the session */ - sessionScopes?: SessionScopesFunc; - /** Used to check if the session needs to be refreshed, defaults to never refresh */ - sessionShouldRefresh?: SessionShouldRefreshFunc; -}; - -/** - * AuthSessionStore decorates another SessionManager with a functionality - * to store the session in local storage. - * - * Session is serialized to JSON with special support for following types: Set. - */ -export class AuthSessionStore implements SessionManager { - private readonly manager: MutableSessionManager; - private readonly storageKey: string; - private readonly sessionShouldRefreshFunc: SessionShouldRefreshFunc; - private readonly helper: SessionScopeHelper; - - constructor(options: Options) { - const { - manager, - storageKey, - sessionScopes, - sessionShouldRefresh = () => false, - } = options; - - this.manager = manager; - this.storageKey = storageKey; - this.sessionShouldRefreshFunc = sessionShouldRefresh; - this.helper = new SessionScopeHelper({ - sessionScopes, - defaultScopes: new Set(), - }); - } - - async getSession(options: GetSessionOptions): Promise { - const { scopes } = options; - const session = this.loadSession(); - - if (this.helper.sessionExistsAndHasScope(session, scopes)) { - const shouldRefresh = this.sessionShouldRefreshFunc(session!); - - if (!shouldRefresh) { - this.manager.setSession(session!); - return session!; - } - } - - const newSession = await this.manager.getSession(options); - this.saveSession(newSession); - return newSession; - } - - async removeSession() { - localStorage.removeItem(this.storageKey); - await this.manager.removeSession(); - } - - sessionState$() { - return this.manager.sessionState$(); - } - - private loadSession(): T | undefined { - try { - const sessionJson = localStorage.getItem(this.storageKey); - if (sessionJson) { - const session = JSON.parse(sessionJson, (_key, value) => { - if (value?.__type === 'Set') { - return new Set(value.__value); - } - return value; - }); - return session; - } - - return undefined; - } catch (error) { - localStorage.removeItem(this.storageKey); - return undefined; - } - } - - private saveSession(session: T | undefined) { - if (session === undefined) { - localStorage.removeItem(this.storageKey); - } else { - localStorage.setItem( - this.storageKey, - JSON.stringify(session, (_key, value) => { - if (value instanceof Set) { - return { - __type: 'Set', - __value: Array.from(value), - }; - } - return value; - }), - ); - } - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts deleted file mode 100644 index 9e22e4cd69..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; -import { SessionState } from '../../apis'; - -const defaultOptions = { - sessionScopes: (session: { scopes: Set }) => session.scopes, - sessionShouldRefresh: (session: { expired: boolean }) => session.expired, -}; - -describe('RefreshingAuthSessionManager', () => { - it('should save result from createSession', async () => { - const createSession = jest.fn().mockResolvedValue({ expired: false }); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const removeSession = jest.fn(); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession, removeSession }, - ...defaultOptions, - } as any); - const stateSubscriber = jest.fn(); - manager.sessionState$().subscribe(stateSubscriber); - - await Promise.resolve(); // Wait a tick for observer to post a value - - expect(stateSubscriber.mock.calls).toEqual([[SessionState.SignedOut]]); - await manager.getSession({}); - expect(createSession).toBeCalledTimes(1); - - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - ]); - await manager.getSession({}); - expect(createSession).toBeCalledTimes(1); - - expect(refreshSession).toBeCalledTimes(1); - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - ]); - - expect(removeSession).toHaveBeenCalledTimes(0); - await manager.removeSession(); - expect(removeSession).toHaveBeenCalledTimes(1); - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - [SessionState.SignedOut], - ]); - }); - - it('should ask consent only if scopes have changed', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockResolvedValue({ - scopes: new Set(['a']), - expired: false, - }); - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['b']) }); - expect(createSession).toBeCalledTimes(2); - }); - - it('should check for session expiry', async () => { - const createSession = jest.fn(); - const refreshSession = jest - .fn() - .mockRejectedValueOnce(new Error('NOPE')) - .mockResolvedValue({ scopes: new Set(['a']) }); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockResolvedValue({ - scopes: new Set(['a']), - expired: true, - }); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - expect(refreshSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - expect(refreshSession).toBeCalledTimes(2); - }); - - it('should handle user closed popup', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockRejectedValueOnce(new Error('some error')); - await expect( - manager.getSession({ scopes: new Set(['a']) }), - ).rejects.toThrow('some error'); - }); - - it('should not get optional session', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - expect(await manager.getSession({ optional: true })).toBe(undefined); - expect(createSession).toBeCalledTimes(0); - expect(refreshSession).toBeCalledTimes(1); - }); - - it('should forward option to instantly show auth popup and not attempt refresh', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - expect(await manager.getSession({ instantPopup: true })).toBe(undefined); - expect(createSession).toBeCalledTimes(1); - expect(createSession).toHaveBeenCalledWith({ - scopes: new Set(), - instantPopup: true, - }); - expect(refreshSession).toBeCalledTimes(0); - }); - - it('should remove session straight away', async () => { - const removeSession = jest.fn(); - const manager = new RefreshingAuthSessionManager({ - connector: { removeSession }, - ...defaultOptions, - } as any); - - await manager.removeSession(); - expect(removeSession).toHaveBeenCalled(); - expect(await manager.getSession({ optional: true })).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts deleted file mode 100644 index a098b384f9..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 { - SessionManager, - SessionScopesFunc, - SessionShouldRefreshFunc, - GetSessionOptions, -} from './types'; -import { AuthConnector } from '../AuthConnector'; -import { SessionScopeHelper, hasScopes } from './common'; -import { SessionStateTracker } from './SessionStateTracker'; - -type Options = { - /** The connector used for acting on the auth session */ - connector: AuthConnector; - /** Used to get the scope of the session */ - sessionScopes: SessionScopesFunc; - /** Used to check if the session needs to be refreshed */ - sessionShouldRefresh: SessionShouldRefreshFunc; - /** The default scopes that should always be present in a session, defaults to none. */ - defaultScopes?: Set; -}; - -/** - * RefreshingAuthSessionManager manages an underlying session that has - * and expiration time and needs to be refreshed periodically. - */ -export class RefreshingAuthSessionManager implements SessionManager { - private readonly connector: AuthConnector; - private readonly helper: SessionScopeHelper; - private readonly sessionScopesFunc: SessionScopesFunc; - private readonly sessionShouldRefreshFunc: SessionShouldRefreshFunc; - private readonly stateTracker = new SessionStateTracker(); - - private refreshPromise?: Promise; - private currentSession: T | undefined; - - constructor(options: Options) { - const { - connector, - defaultScopes = new Set(), - sessionScopes, - sessionShouldRefresh, - } = options; - - this.connector = connector; - this.sessionScopesFunc = sessionScopes; - this.sessionShouldRefreshFunc = sessionShouldRefresh; - this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); - } - - async getSession(options: GetSessionOptions): Promise { - if ( - this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) - ) { - const shouldRefresh = this.sessionShouldRefreshFunc(this.currentSession!); - if (!shouldRefresh) { - return this.currentSession!; - } - - try { - const refreshedSession = await this.collapsedSessionRefresh(); - const currentScopes = this.sessionScopesFunc(this.currentSession!); - const refreshedScopes = this.sessionScopesFunc(refreshedSession); - if (hasScopes(refreshedScopes, currentScopes)) { - this.currentSession = refreshedSession; - } - return refreshedSession; - } catch (error) { - if (options.optional) { - return undefined; - } - throw error; - } - } - - // The user may still have a valid refresh token in their cookies. Attempt to - // initiate a fresh session through the backend using that refresh token. - // - // We skip this check if an instant login popup is requested, as we need to - // stay in a synchronous call stack from the user interaction. The downside - // is that that the user will sometimes be requested to log in even if they - // already had an existing session. - if (!this.currentSession && !options.instantPopup) { - try { - const newSession = await this.collapsedSessionRefresh(); - this.currentSession = newSession; - // The session might not have the scopes requested so go back and check again - return this.getSession(options); - } catch { - // If the refresh attempt fails we assume we don't have a session, so continue to create one. - } - } - - // If we continue here we will show a popup, so exit if this is an optional session request. - if (options.optional) { - return undefined; - } - - // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession({ - ...options, - scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), - }); - this.stateTracker.setIsSignedIn(true); - return this.currentSession; - } - - async removeSession() { - this.currentSession = undefined; - await this.connector.removeSession(); - this.stateTracker.setIsSignedIn(false); - } - - sessionState$() { - return this.stateTracker.sessionState$(); - } - - private async collapsedSessionRefresh(): Promise { - if (this.refreshPromise) { - return this.refreshPromise; - } - - this.refreshPromise = this.connector.refreshSession(); - - try { - const session = await this.refreshPromise; - this.stateTracker.setIsSignedIn(true); - return session; - } finally { - delete this.refreshPromise; - } - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts b/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts deleted file mode 100644 index af3d8bc9b6..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { SessionState } from '../../apis/definitions'; -import { Observable } from '../../types'; -import { BehaviorSubject } from '../subjects'; - -export class SessionStateTracker { - private readonly subject = new BehaviorSubject( - SessionState.SignedOut, - ); - - private signedIn: boolean = false; - - setIsSignedIn(isSignedIn: boolean) { - if (this.signedIn !== isSignedIn) { - this.signedIn = isSignedIn; - this.subject.next( - this.signedIn ? SessionState.SignedIn : SessionState.SignedOut, - ); - } - } - - sessionState$(): Observable { - return this.subject; - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts deleted file mode 100644 index 6280750875..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 { StaticAuthSessionManager } from './StaticAuthSessionManager'; - -const defaultOptions = { - sessionScopes: (session: string) => new Set(session.split(' ')), -}; - -describe('StaticAuthSessionManager', () => { - const baseConnector = { - refreshSession() { - throw new Error('refreshSession should not be called'); - }, - removeSession() { - throw new Error('removeSession should not be called'); - }, - }; - - it('should get session by creating session once', async () => { - const createSession = jest.fn().mockResolvedValue('my-session'); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({})).resolves.toBe('my-session'); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({})).resolves.toBe('my-session'); - expect(createSession).toHaveBeenCalledTimes(1); - }); - - it('should fail to get session if user rejects the request', async () => { - const createSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({})).rejects.toThrow('NOPE'); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ optional: true })).resolves.toBe( - undefined, - ); - }); - - it('should only request auth once for same scopes', async () => { - const createSession = jest - .fn() - .mockImplementation(({ scopes }) => [...scopes].join(' ')); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({ scopes: new Set(['a']) })).resolves.toBe( - 'a', - ); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ scopes: new Set(['a']) })).resolves.toBe( - 'a', - ); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ scopes: new Set(['b']) })).resolves.toBe( - 'a b', - ); - expect(createSession).toHaveBeenCalledTimes(2); - }); - - it('should remove session and reload', async () => { - const removeSession = jest.fn(); - const manager = new StaticAuthSessionManager({ - connector: { removeSession }, - ...defaultOptions, - } as any); - - await manager.removeSession(); - expect(removeSession).toHaveBeenCalled(); - expect(await manager.getSession({ optional: true })).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts deleted file mode 100644 index e4f144b0a9..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { MutableSessionManager, GetSessionOptions } from './types'; -import { AuthConnector } from '../AuthConnector'; -import { SessionScopeHelper } from './common'; -import { SessionStateTracker } from './SessionStateTracker'; - -type Options = { - /** The connector used for acting on the auth session */ - connector: AuthConnector; - /** Used to get the scope of the session */ - sessionScopes?: (session: T) => Set; - /** The default scopes that should always be present in a session, defaults to none. */ - defaultScopes?: Set; -}; - -/** - * StaticAuthSessionManager manages an underlying session that does not expire. - */ -export class StaticAuthSessionManager implements MutableSessionManager { - private readonly connector: AuthConnector; - private readonly helper: SessionScopeHelper; - private readonly stateTracker = new SessionStateTracker(); - - private currentSession: T | undefined; - - constructor(options: Options) { - const { connector, defaultScopes = new Set(), sessionScopes } = options; - - this.connector = connector; - this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); - } - - setSession(session: T | undefined): void { - this.currentSession = session; - this.stateTracker.setIsSignedIn(Boolean(session)); - } - - async getSession(options: GetSessionOptions): Promise { - if ( - this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) - ) { - return this.currentSession; - } - - // If we continue here we will show a popup, so exit if this is an optional session request. - if (options.optional) { - return undefined; - } - - // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession({ - ...options, - scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), - }); - this.stateTracker.setIsSignedIn(true); - return this.currentSession; - } - - async removeSession() { - this.currentSession = undefined; - await this.connector.removeSession(); - this.stateTracker.setIsSignedIn(false); - } - - sessionState$() { - return this.stateTracker.sessionState$(); - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/common.ts b/packages/core-api/src/lib/AuthSessionManager/common.ts deleted file mode 100644 index ff2897535d..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/common.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 { SessionScopesFunc } from './types'; - -export function hasScopes( - searched: Set, - searchFor: Set, -): boolean { - for (const scope of searchFor) { - if (!searched.has(scope)) { - return false; - } - } - return true; -} - -type ScopeHelperOptions = { - sessionScopes: SessionScopesFunc | undefined; - defaultScopes?: Set; -}; - -export class SessionScopeHelper { - constructor(private readonly options: ScopeHelperOptions) {} - - sessionExistsAndHasScope( - session: T | undefined, - scopes?: Set, - ): boolean { - if (!session) { - return false; - } - if (!scopes) { - return true; - } - if (this.options.sessionScopes === undefined) { - return true; - } - const sessionScopes = this.options.sessionScopes(session); - return hasScopes(sessionScopes, scopes); - } - - getExtendedScope(session: T | undefined, scopes?: Set) { - const newScope = new Set(this.options.defaultScopes); - if (session && this.options.sessionScopes !== undefined) { - const sessionScopes = this.options.sessionScopes(session); - for (const scope of sessionScopes) { - newScope.add(scope); - } - } - if (scopes) { - for (const scope of scopes) { - newScope.add(scope); - } - } - return newScope; - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/index.ts b/packages/core-api/src/lib/AuthSessionManager/index.ts deleted file mode 100644 index 5f4dde8662..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -export { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; -export { StaticAuthSessionManager } from './StaticAuthSessionManager'; -export { AuthSessionStore } from './AuthSessionStore'; -export * from './types'; diff --git a/packages/core-api/src/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts deleted file mode 100644 index 332824e438..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { Observable } from '../../types'; -import { SessionState } from '../../apis/definitions'; - -export type GetSessionOptions = { - optional?: boolean; - instantPopup?: boolean; - scopes?: Set; -}; - -/** - * A sessions manager keeps track of the current session and makes sure that - * multiple simultaneous requests for sessions with different scope are handled - * in a correct way. - */ -export type SessionManager = { - getSession(options: GetSessionOptions): Promise; - - removeSession(): Promise; - - sessionState$(): Observable; -}; - -/** - * An extension of the session manager where the session can also be pushed from the manager. - */ -export interface MutableSessionManager extends SessionManager { - setSession(session: T | undefined): void; -} - -/** - * A function called to determine the scopes of a session. - */ -export type SessionScopesFunc = (session: T) => Set; - -/** - * A function called to determine whether it's time for a session to refresh. - * - * This should return true before the session expires, for example, if a session - * expires after 60 minutes, you could return true if the session is older than 45 minutes. - */ -export type SessionShouldRefreshFunc = (session: T) => boolean; diff --git a/packages/core-api/src/lib/globalObject.test.ts b/packages/core-api/src/lib/globalObject.test.ts deleted file mode 100644 index e72f027b46..0000000000 --- a/packages/core-api/src/lib/globalObject.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2021 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 { - getGlobalSingleton, - getOrCreateGlobalSingleton, - setGlobalSingleton, -} from './globalObject'; - -const anyGlobal = global as any; - -describe('getGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should return an existing value', () => { - const myThing = {}; - const myOtherThing = {}; - - anyGlobal['__@backstage/my-thing__'] = myThing; - expect(getGlobalSingleton('my-thing')).toBe(myThing); - expect(getGlobalSingleton('my-thing')).toBe(myThing); - anyGlobal['__@backstage/my-thing__'] = myOtherThing; - expect(getGlobalSingleton('my-thing')).toBe(myOtherThing); - }); - - it('should throw if the value is not set', () => { - expect(() => getGlobalSingleton('my-thing')).toThrow( - 'Global my-thing is not set', - ); - }); -}); - -describe('getOrCreateGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should return an existing value', () => { - const myThing = {}; - anyGlobal['__@backstage/my-thing__'] = myThing; - - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); - }); - - it('should should create a new value', () => { - const myNewThing = {}; - - expect(anyGlobal['__@backstage/my-thing__']).toBe(undefined); - expect(getOrCreateGlobalSingleton('my-thing', () => myNewThing)).toBe( - myNewThing, - ); - expect(anyGlobal['__@backstage/my-thing__']).toBe(myNewThing); - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myNewThing); - }); -}); - -describe('setGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should set a global value', () => { - setGlobalSingleton('my-thing', 'global value'); - - expect(anyGlobal['__@backstage/my-thing__']).toBe('global value'); - }); - - it('should throw if global value is set', () => { - anyGlobal['__@backstage/my-thing__'] = 'already defined'; - expect(() => setGlobalSingleton('my-thing', () => 'global value')).toThrow( - 'Global my-thing is already se', - ); - }); -}); diff --git a/packages/core-api/src/lib/globalObject.ts b/packages/core-api/src/lib/globalObject.ts deleted file mode 100644 index 87be58499d..0000000000 --- a/packages/core-api/src/lib/globalObject.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 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. - */ - -// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 -function getGlobalObject() { - if (typeof window !== 'undefined' && window.Math === Math) { - return window; - } - if (typeof self !== 'undefined' && self.Math === Math) { - return self; - } - // eslint-disable-next-line no-new-func - return Function('return this')(); -} - -const globalObject = getGlobalObject(); - -const makeKey = (id: string) => `__@backstage/${id}__`; - -/** - * Used to provide a global singleton value, failing if it is already set. - */ -export function setGlobalSingleton(id: string, value: unknown): void { - const key = makeKey(id); - if (key in globalObject) { - throw new Error(`Global ${id} is already set`); // TODO some sort of special build err - } - globalObject[key] = value; -} - -/** - * Used to access a global singleton value, failing if it is not already set. - */ -export function getGlobalSingleton(id: string): T { - const key = makeKey(id); - if (!(key in globalObject)) { - throw new Error(`Global ${id} is not set`); // TODO some sort of special build err - } - - return globalObject[key]; -} - -/** - * Serializes access to a global singleton value, with the first caller creating the value. - */ -export function getOrCreateGlobalSingleton( - id: string, - supplier: () => T, -): T { - const key = makeKey(id); - - let value = globalObject[key]; - if (value) { - return value; - } - - value = supplier(); - globalObject[key] = value; - return value; -} diff --git a/packages/core-api/src/lib/index.ts b/packages/core-api/src/lib/index.ts deleted file mode 100644 index 10f213b50f..0000000000 --- a/packages/core-api/src/lib/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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. - */ - -export * from './subjects'; -export * from './loginPopup'; -export * from './AuthConnector'; -export * from './AuthSessionManager'; diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts deleted file mode 100644 index 98541c268e..0000000000 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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 { showLoginPopup } from './loginPopup'; - -describe('showLoginPopup', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should show an auth popup', async () => { - const popupMock = { closed: false }; - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue(popupMock as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - - const payloadPromise = showLoginPopup({ - url: - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', - name: 'test-popup', - origin: 'my-origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(openSpy.mock.calls[0][0]).toBe( - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', - ); - expect(openSpy.mock.calls[0][1]).toBe('test-popup'); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - listener({} as MessageEvent); - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - // None of these should be accepted - listener({ source: popupMock } as MessageEvent); - listener({ origin: 'my-origin' } as MessageEvent); - listener({ data: { type: 'authorization_response' } } as MessageEvent); - listener({ - source: popupMock, - origin: 'my-origin', - data: {}, - } as MessageEvent); - listener({ - source: popupMock, - origin: 'my-origin', - data: { type: 'not-auth-result', response: {} }, - } as MessageEvent); - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - const myResponse = {}; - - // This should be accepted as a valid sessions response - listener({ - source: popupMock, - origin: 'my-origin', - data: { - type: 'authorization_response', - response: myResponse, - }, - } as MessageEvent); - - await expect(payloadPromise).resolves.toBe(myResponse); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should fail if popup returns error', async () => { - const popupMock = { closed: false }; - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue(popupMock as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'my-origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - - listener({ - source: popupMock, - origin: 'my-origin', - data: { - type: 'authorization_response', - error: { - message: 'NOPE', - name: 'NopeError', - }, - }, - } as MessageEvent); - - await expect(payloadPromise).rejects.toThrow({ - name: 'NopeError', - message: 'NOPE', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should fail if popup is closed', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://localhost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, popup was closed', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should indicate if origin does not match', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://differenthost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, Incorrect app origin, expected http://differenthost', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts deleted file mode 100644 index 716c4f9651..0000000000 --- a/packages/core-api/src/lib/loginPopup.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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. - */ - -/** - * Options used to open a login popup. - */ -export type LoginPopupOptions = { - /** - * The URL that the auth popup should point to - */ - url: string; - - /** - * The name of the popup, as in second argument to window.open - */ - name: string; - - /** - * The origin of the final popup page that will post a message to this window. - */ - origin: string; - - /** - * The width of the popup in pixels, defaults to 500 - */ - width?: number; - - /** - * The height of the popup in pixels, defaults to 700 - */ - height?: number; -}; - -type AuthResult = - | { - type: 'authorization_response'; - response: unknown; - } - | { - type: 'authorization_response'; - error: { - name: string; - message: string; - }; - }; - -/** - * Show a popup pointing to a URL that starts an auth flow. Implementing the receiving - * end of the postMessage mechanism outlined in https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00 - * - * The redirect handler of the flow should use postMessage to communicate back - * to the app window. The message posted to the app must match the AuthResult type. - * - * The returned promise resolves to the response of the message that was posted from the auth popup. - */ -export function showLoginPopup(options: LoginPopupOptions): Promise { - return new Promise((resolve, reject) => { - const width = options.width || 500; - const height = options.height || 700; - const left = window.screen.width / 2 - width / 2; - const top = window.screen.height / 2 - height / 2; - - const popup = window.open( - options.url, - options.name, - `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, - ); - - let targetOrigin = ''; - - if (!popup || typeof popup.closed === 'undefined' || popup.closed) { - const error = new Error('Failed to open auth popup.'); - error.name = 'PopupRejectedError'; - reject(error); - return; - } - - const messageListener = (event: MessageEvent) => { - if (event.source !== popup) { - return; - } - if (event.origin !== options.origin) { - return; - } - const { data } = event; - - if (data.type === 'config_info') { - targetOrigin = data.targetOrigin; - return; - } - - if (data.type !== 'authorization_response') { - return; - } - const authResult = data as AuthResult; - - if ('error' in authResult) { - const error = new Error(authResult.error.message); - error.name = authResult.error.name; - // TODO: proper error type - // error.extra = authResult.error.extra; - reject(error); - } else { - resolve(authResult.response); - } - done(); - }; - - const intervalId = setInterval(() => { - if (popup.closed) { - const errMessage = `Login failed, ${ - targetOrigin && targetOrigin !== window.location.origin - ? `Incorrect app origin, expected ${targetOrigin}` - : 'popup was closed' - }`; - const error = new Error(errMessage); - error.name = 'PopupClosedError'; - reject(error); - done(); - } - }, 100); - - function done() { - window.removeEventListener('message', messageListener); - clearInterval(intervalId); - } - - window.addEventListener('message', messageListener); - }); -} diff --git a/packages/core-api/src/lib/subjects.test.ts b/packages/core-api/src/lib/subjects.test.ts deleted file mode 100644 index 31ed26d97d..0000000000 --- a/packages/core-api/src/lib/subjects.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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 { PublishSubject, BehaviorSubject } from './subjects'; - -function observerSpy() { - return { - next: jest.fn(), - error: jest.fn(), - complete: jest.fn(), - }; -} - -describe('PublishSubject', () => { - it('should be completed', async () => { - const subj = new PublishSubject(); - subj.complete(); - - const spy = observerSpy(); - subj.subscribe(spy); - await 'a tick'; - - expect(spy.next).not.toHaveBeenCalled(); - expect(spy.error).not.toHaveBeenCalled(); - expect(spy.complete).toHaveBeenCalledTimes(1); - - expect(() => subj.next(1)).toThrow('PublishSubject is closed'); - expect(() => subj.error(new Error())).toThrow('PublishSubject is closed'); - expect(() => subj.complete()).toThrow('PublishSubject is closed'); - - expect(subj.closed).toBe(true); - }); - - it('should forward values to all subscribers', async () => { - const subj = new PublishSubject(); - - const spy1 = observerSpy(); - const spy2 = observerSpy(); - subj.subscribe(spy1); - const sub = subj.subscribe(spy2); - - subj.next(42); - - expect(spy1.next).toHaveBeenCalledTimes(1); - expect(spy1.next).toHaveBeenCalledWith(42); - expect(spy2.next).toHaveBeenCalledTimes(1); - expect(spy2.next).toHaveBeenCalledWith(42); - - sub.unsubscribe(); - - subj.next(1337); - - expect(spy1.next).toHaveBeenCalledTimes(2); - expect(spy1.next).toHaveBeenCalledWith(1337); - expect(spy2.next).toHaveBeenCalledTimes(1); - - expect(spy1.error).not.toHaveBeenCalled(); - expect(spy2.error).not.toHaveBeenCalled(); - expect(spy1.complete).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(false); - }); - - it('should forward errors', async () => { - const subj = new PublishSubject(); - - const spy1 = observerSpy(); - subj.subscribe(spy1); - - const error = new Error('NOPE'); - subj.error(error); - expect(spy1.error).toHaveBeenCalledWith(error); - expect(spy1.next).not.toHaveBeenCalled(); - expect(spy1.complete).not.toHaveBeenCalled(); - - const spy2 = observerSpy(); - subj.subscribe(spy2); - await 'a tick'; - expect(spy2.error).toHaveBeenCalledWith(error); - expect(spy2.next).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(true); - }); -}); - -describe('BehaviorSubject', () => { - it('should be completed', async () => { - const subj = new BehaviorSubject(0); - subj.complete(); - - const next = jest.fn(); - const complete = jest.fn(); - subj.subscribe({ next, complete }); - await 'a tick'; - - expect(complete).toHaveBeenCalledTimes(1); - - expect(() => subj.next(1)).toThrow('BehaviorSubject is closed'); - expect(() => subj.error(new Error())).toThrow('BehaviorSubject is closed'); - expect(() => subj.complete()).toThrow('BehaviorSubject is closed'); - - expect(subj.closed).toBe(true); - }); - - it('should forward values to all subscribers', async () => { - const subj = new BehaviorSubject(0); - - const obs1 = jest.fn(); - const obs2 = jest.fn(); - subj.subscribe(obs1); - const sub = subj.subscribe(obs2); - await 'a tick'; - - expect(obs1).toHaveBeenCalledTimes(1); - expect(obs1).toHaveBeenCalledWith(0); - expect(obs2).toHaveBeenCalledTimes(1); - expect(obs2).toHaveBeenCalledWith(0); - - subj.next(42); - - expect(obs1).toHaveBeenCalledTimes(2); - expect(obs1).toHaveBeenCalledWith(42); - expect(obs2).toHaveBeenCalledTimes(2); - expect(obs2).toHaveBeenCalledWith(42); - - sub.unsubscribe(); - - subj.next(1337); - - expect(obs1).toHaveBeenCalledTimes(3); - expect(obs1).toHaveBeenCalledWith(1337); - expect(obs2).toHaveBeenCalledTimes(2); - - expect(subj.closed).toBe(false); - }); - - it('should forward errors', async () => { - const subj = new BehaviorSubject(0); - - const spy1 = observerSpy(); - subj.subscribe(spy1); - await 'a tick'; - - expect(spy1.error).not.toHaveBeenCalled(); - expect(spy1.next).toHaveBeenCalledWith(0); - - const error = new Error('NOPE'); - subj.error(error); - expect(spy1.error).toHaveBeenCalledWith(error); - expect(spy1.next).toHaveBeenCalledWith(0); - expect(spy1.next).toHaveBeenCalledTimes(1); - expect(spy1.complete).not.toHaveBeenCalled(); - - const spy2 = observerSpy(); - subj.subscribe(spy2); - await 'a tick'; - expect(spy2.error).toHaveBeenCalledWith(error); - expect(spy2.next).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(true); - }); -}); diff --git a/packages/core-api/src/lib/subjects.ts b/packages/core-api/src/lib/subjects.ts deleted file mode 100644 index 5239e460af..0000000000 --- a/packages/core-api/src/lib/subjects.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 { Observable } from '../types'; -import ObservableImpl from 'zen-observable'; - -// TODO(Rugvip): These are stopgap and probably incomplete implementations of subjects. -// If we add a more complete Observables library they should be replaced. - -/** - * A basic implementation of ReactiveX publish subjects. - * - * A subject is a convenient way to create an observable when you want - * to fan out a single value to all subscribers. - * - * See http://reactivex.io/documentation/subject.html - */ -export class PublishSubject - implements Observable, ZenObservable.SubscriptionObserver { - private isClosed = false; - private terminatingError?: Error; - - private readonly observable = new ObservableImpl(subscriber => { - if (this.isClosed) { - if (this.terminatingError) { - subscriber.error(this.terminatingError); - } else { - subscriber.complete(); - } - return () => {}; - } - - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }); - - private readonly subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - [Symbol.observable]() { - return this; - } - - get closed() { - return this.isClosed; - } - - next(value: T) { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.subscribers.forEach(subscriber => subscriber.next(value)); - } - - error(error: Error) { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.isClosed = true; - this.terminatingError = error; - this.subscribers.forEach(subscriber => subscriber.error(error)); - } - - complete() { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.isClosed = true; - this.subscribers.forEach(subscriber => subscriber.complete()); - } - - subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; - subscribe( - onNext: (value: T) => void, - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription; - subscribe( - onNext: ZenObservable.Observer | ((value: T) => void), - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription { - const observer = - typeof onNext === 'function' - ? { - next: onNext, - error: onError, - complete: onComplete, - } - : onNext; - - return this.observable.subscribe(observer); - } -} - -/** - * A basic implementation of ReactiveX behavior subjects. - * - * A subject is a convenient way to create an observable when you want - * to fan out a single value to all subscribers. - * - * The BehaviorSubject will emit the most recently emitted value or error - * whenever a new observer subscribes to the subject. - * - * See http://reactivex.io/documentation/subject.html - */ -export class BehaviorSubject - implements Observable, ZenObservable.SubscriptionObserver { - private isClosed = false; - private currentValue: T; - private terminatingError?: Error; - - constructor(value: T) { - this.currentValue = value; - } - - private readonly observable = new ObservableImpl(subscriber => { - if (this.isClosed) { - if (this.terminatingError) { - subscriber.error(this.terminatingError); - } else { - subscriber.complete(); - } - return () => {}; - } - - subscriber.next(this.currentValue); - - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }); - - private readonly subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - [Symbol.observable]() { - return this; - } - - get closed() { - return this.isClosed; - } - - next(value: T) { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.currentValue = value; - this.subscribers.forEach(subscriber => subscriber.next(value)); - } - - error(error: Error) { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.isClosed = true; - this.terminatingError = error; - this.subscribers.forEach(subscriber => subscriber.error(error)); - } - - complete() { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.isClosed = true; - this.subscribers.forEach(subscriber => subscriber.complete()); - } - - subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; - subscribe( - onNext: (value: T) => void, - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription; - subscribe( - onNext: ZenObservable.Observer | ((value: T) => void), - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription { - const observer = - typeof onNext === 'function' - ? { - next: onNext, - error: onError, - complete: onComplete, - } - : onNext; - - return this.observable.subscribe(observer); - } -} diff --git a/packages/core-api/src/lib/versionedValues.test.ts b/packages/core-api/src/lib/versionedValues.test.ts deleted file mode 100644 index 6ba7db4970..0000000000 --- a/packages/core-api/src/lib/versionedValues.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 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 { createVersionedValueMap } from './versionedValues'; - -describe('createVersionedValueMap', () => { - it('should be empty', () => { - const map = createVersionedValueMap({}); - - // @ts-expect-error - expect(map.atVersion(1 as any)).toBe(undefined); - }); - - it('should access values by version', () => { - const map = createVersionedValueMap({ 1: 'v1', 2: 'v2' }); - - expect(map.atVersion(1)).toBe('v1'); - expect(map.atVersion(2)).toBe('v2'); - - // @ts-expect-error - expect(map.atVersion(0)).toBe(undefined); - // @ts-expect-error - expect(map.atVersion(NaN)).toBe(undefined); - // @ts-expect-error - expect(map.atVersion(Infinity)).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/versionedValues.ts b/packages/core-api/src/lib/versionedValues.ts deleted file mode 100644 index 88e4e90084..0000000000 --- a/packages/core-api/src/lib/versionedValues.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 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. - */ - -/** - * The versioned value interface is a container for a set of values that - * can be looked up by version. It is intended to be used as a container - * for values that can be versioned independently of package versions. - */ -export type VersionedValue = { - atVersion( - version: Version, - ): Versions[Version] | undefined; -}; - -/** - * Creates a container for a map of versioned values that implements VersionedValue. - */ -export function createVersionedValueMap< - Versions extends { [version: number]: any } ->(versions: Versions): VersionedValue { - Object.freeze(versions); - return { - atVersion(version) { - return versions[version]; - }, - }; -} diff --git a/packages/core-api/src/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx deleted file mode 100644 index cc168707be..0000000000 --- a/packages/core-api/src/plugin/Plugin.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { - PluginConfig, - PluginOutput, - BackstagePlugin, - Extension, - AnyRoutes, - AnyExternalRoutes, -} from './types'; -import { AnyApiFactory } from '../apis'; - -export class PluginImpl< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes -> implements BackstagePlugin { - private storedOutput?: PluginOutput[]; - - constructor(private readonly config: PluginConfig) {} - - getId(): string { - return this.config.id; - } - - getApis(): Iterable { - return this.config.apis ?? []; - } - - get routes(): Routes { - return this.config.routes ?? ({} as Routes); - } - - get externalRoutes(): ExternalRoutes { - return this.config.externalRoutes ?? ({} as ExternalRoutes); - } - - output(): PluginOutput[] { - if (this.storedOutput) { - return this.storedOutput; - } - if (!this.config.register) { - return []; - } - - const outputs = new Array(); - - this.config.register({ - router: { - addRoute(target, component, options) { - outputs.push({ - type: 'route', - target, - component, - options, - }); - }, - }, - featureFlags: { - register(name) { - outputs.push({ type: 'feature-flag', name }); - }, - }, - }); - - this.storedOutput = outputs; - return this.storedOutput; - } - - provide(extension: Extension): T { - return extension.expose(this); - } - - toString() { - return `plugin{${this.config.id}}`; - } -} - -export function createPlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} ->( - config: PluginConfig, -): BackstagePlugin { - return new PluginImpl(config); -} diff --git a/packages/core-api/src/plugin/collectors.test.tsx b/packages/core-api/src/plugin/collectors.test.tsx deleted file mode 100644 index 5baf2539ab..0000000000 --- a/packages/core-api/src/plugin/collectors.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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, { PropsWithChildren } from 'react'; -import { createRouteRef } from '../routing'; -import { createPlugin } from './Plugin'; -import { - createRoutableExtension, - createComponentExtension, -} from '../extensions'; -import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { - traverseElementTree, - childDiscoverer, - routeElementDiscoverer, -} from '../extensions/traversal'; -import { pluginCollector } from './collectors'; - -const mockConfig = () => ({ path: '/foo', title: 'Foo' }); -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const pluginA = createPlugin({ id: 'my-plugin-a' }); -const pluginB = createPlugin({ id: 'my-plugin-b' }); -const pluginC = createPlugin({ id: 'my-plugin-c' }); - -const ref1 = createRouteRef(mockConfig()); -const ref2 = createRouteRef(mockConfig()); - -const Extension1 = pluginA.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = pluginB.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref2, - }), -); -const Extension3 = pluginA.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); -const Extension4 = pluginB.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); -const Extension5 = pluginC.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); - -describe('collection', () => { - it('should collect the plugins', () => { - const root = ( - - - -
- -
-
- {[]} - Some text here shouldn't be a problem -
- {null} -
- -
- - {false} - {true} - {0} -
- -
- } /> -
- - - ); - - const { plugins } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - plugins: pluginCollector, - }, - }); - - expect(plugins).toEqual(new Set([pluginA, pluginB, pluginC])); - }); -}); diff --git a/packages/core-api/src/plugin/index.ts b/packages/core-api/src/plugin/index.ts deleted file mode 100644 index bbeeca4824..0000000000 --- a/packages/core-api/src/plugin/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -export { createPlugin } from './Plugin'; -export type { - BackstagePlugin, - Extension, - FeatureFlagOutput, - FeatureFlagsHooks, - LegacyRedirectRouteOutput, - LegacyRouteOutput, - PluginConfig, - PluginHooks, - PluginOutput, - RedirectRouteOutput, - RouteOptions, - RouteOutput, - RoutePath, - RouterHooks, -} from './types'; diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts deleted file mode 100644 index 2ad02b243d..0000000000 --- a/packages/core-api/src/plugin/types.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 { ComponentType } from 'react'; -import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; -import { AnyApiFactory } from '../apis/system'; - -export type RouteOptions = { - // Whether the route path must match exactly, defaults to true. - exact?: boolean; -}; - -export type RoutePath = string; - -// Replace with using RouteRefs -export type LegacyRouteOutput = { - type: 'legacy-route'; - path: RoutePath; - component: ComponentType<{}>; - options?: RouteOptions; -}; - -export type RouteOutput = { - type: 'route'; - target: RouteRef; - component: ComponentType<{}>; - options?: RouteOptions; -}; - -export type RedirectRouteOutput = { - type: 'redirect-route'; - from: RouteRef; - to: RouteRef; - options?: RouteOptions; -}; - -export type LegacyRedirectRouteOutput = { - type: 'legacy-redirect-route'; - path: RoutePath; - target: RoutePath; - options?: RouteOptions; -}; - -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - -export type PluginOutput = - | LegacyRouteOutput - | RouteOutput - | LegacyRedirectRouteOutput - | RedirectRouteOutput - | FeatureFlagOutput; - -export type Extension = { - expose(plugin: BackstagePlugin): T; -}; - -export type AnyRoutes = { [name: string]: RouteRef | SubRouteRef }; - -export type AnyExternalRoutes = { [name: string]: ExternalRouteRef }; - -export type BackstagePlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} -> = { - getId(): string; - output(): PluginOutput[]; - getApis(): Iterable; - provide(extension: Extension): T; - routes: Routes; - externalRoutes: ExternalRoutes; -}; - -export type PluginConfig< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes -> = { - id: string; - apis?: Iterable; - register?(hooks: PluginHooks): void; - routes?: Routes; - externalRoutes?: ExternalRoutes; -}; - -export type PluginHooks = { - /** - * @deprecated All router hooks have been deprecated - */ - router: RouterHooks; - featureFlags: FeatureFlagsHooks; -}; - -export type RouterHooks = { - /** - * @deprecated Use a routable extension instead, see https://backstage.io/docs/plugins/composability#porting-existing-plugins - */ - addRoute( - target: RouteRef, - Component: ComponentType, - options?: RouteOptions, - ): void; -}; - -export type FeatureFlagsHooks = { - register(name: string): void; -}; diff --git a/packages/core-api/src/private.ts b/packages/core-api/src/private.ts deleted file mode 100644 index 65462d8d51..0000000000 --- a/packages/core-api/src/private.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { PrivateAppImpl } from './app/App'; diff --git a/packages/core-api/src/public.ts b/packages/core-api/src/public.ts deleted file mode 100644 index f91d97c31d..0000000000 --- a/packages/core-api/src/public.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -export * from './apis'; -export * from './app'; -export * from './extensions'; -export * from './icons'; -export * from './plugin'; -export * from './routing'; -export * from './types'; diff --git a/packages/core-api/src/routing/ExternalRouteRef.test.ts b/packages/core-api/src/routing/ExternalRouteRef.test.ts deleted file mode 100644 index 785ad2732f..0000000000 --- a/packages/core-api/src/routing/ExternalRouteRef.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 { AnyParams, ExternalRouteRef } from './types'; -import { createExternalRouteRef, isExternalRouteRef } from './ExternalRouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isRouteRef } from './RouteRef'; - -describe('ExternalRouteRef', () => { - it('should be created', () => { - const routeRef: ExternalRouteRef = createExternalRouteRef({ - id: 'my-route-ref', - }); - expect(routeRef.params).toEqual([]); - expect(routeRef.optional).toBe(false); - expect(String(routeRef)).toBe('routeRef{type=external,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(false); - expect(isSubRouteRef(routeRef)).toBe(false); - expect(isExternalRouteRef(routeRef)).toBe(true); - - expect(isRouteRef({} as ExternalRouteRef)).toBe(false); - }); - - it('should be created as optional', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: [], - optional: true, - }); - expect(routeRef.params).toEqual([]); - expect(routeRef.optional).toEqual(true); - }); - - it('should be created with params', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - }); - expect(routeRef.params).toEqual(['x', 'y']); - expect(routeRef.optional).toEqual(false); - }); - - it('should be created as optional with params', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - optional: true, - }); - expect(routeRef.params).toEqual(['x', 'y']); - expect(routeRef.optional).toEqual(true); - }); - - it('should properly infer and validate parameter types and assignments', () => { - function validateType( - _ref: ExternalRouteRef, - ) {} - - const _1 = createExternalRouteRef({ id: '1', params: ['notX'] }); - // @ts-expect-error - validateType<{ x: string }, any>(_1); - validateType<{ notX: string }, any>(_1); - - const _2 = createExternalRouteRef({ - id: '2', - params: ['x'], - optional: true, - }); - // @ts-expect-error - validateType(_2); - validateType<{ x: string }, true>(_2); - - const _3 = createExternalRouteRef({ id: '3', params: ['x', 'y'] }); - // @ts-expect-error - validateType<{ x: string }, any>(_3); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }, any>(_3); - validateType<{ x: string; y: string }, false>(_3); - - const _4 = createExternalRouteRef({ id: '4', params: [] }); - // @ts-expect-error - validateType<{ x: string }, any>(_4); - validateType(_4); - - const _5 = createExternalRouteRef({ id: '5' }); - // @ts-expect-error - validateType<{ x: string }, any>(_5); - validateType(_5); - - const _6 = createExternalRouteRef({ id: '6', optional: true }); - // @ts-expect-error - validateType(_6); - validateType(_6); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4, _5, _6].join('')).toEqual(expect.any(String)); - }); -}); diff --git a/packages/core-api/src/routing/ExternalRouteRef.ts b/packages/core-api/src/routing/ExternalRouteRef.ts deleted file mode 100644 index af61ad8dfc..0000000000 --- a/packages/core-api/src/routing/ExternalRouteRef.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, - routeRefType, - AnyParams, -} from './types'; - -export { createExternalRouteRef } from '@backstage/core-plugin-api'; - -export function isExternalRouteRef< - Params extends AnyParams, - Optional extends boolean ->( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is ExternalRouteRef { - return routeRef[routeRefType] === 'external'; -} diff --git a/packages/core-api/src/routing/FlatRoutes.test.tsx b/packages/core-api/src/routing/FlatRoutes.test.tsx deleted file mode 100644 index a9b83d1016..0000000000 --- a/packages/core-api/src/routing/FlatRoutes.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 { render, RenderResult } from '@testing-library/react'; -import React, { ReactNode } from 'react'; -import { MemoryRouter, Route, Routes, useOutlet } from 'react-router-dom'; -import { AppContext } from '../app'; -import { AppContextProvider } from '../app/AppContext'; -import { FlatRoutes } from './FlatRoutes'; - -function makeRouteRenderer(node: ReactNode) { - let rendered: RenderResult | undefined = undefined; - return (path: string) => { - const content = ( - ({ - NotFoundErrorPage: () => <>Not Found, - }), - } as unknown) as AppContext - } - > - - - ); - if (rendered) { - rendered.unmount(); - rendered.rerender(content); - } else { - rendered = render(content); - } - return rendered; - }; -} - -describe('FlatRoutes', () => { - it('renders some routes', () => { - const renderRoute = makeRouteRenderer( - - a} /> - b} /> - , - ); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderRoute('/b').getByText('b')).toBeInTheDocument(); - expect(renderRoute('/c').getByText('Not Found')).toBeInTheDocument(); - expect(renderRoute('/b').queryByText('Not Found')).not.toBeInTheDocument(); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - }); - - it('is not sensitive to ordering and overlapping routes', () => { - // The '/*' suffixes here are intentional and will be ignored by FlatRoutes - const routes = ( - <> - a-1} /> - a} /> - a-2} /> - - ); - const renderRoute = makeRouteRenderer({routes}); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderRoute('/a-1').getByText('a-1')).toBeInTheDocument(); - expect(renderRoute('/a-2').getByText('a-2')).toBeInTheDocument(); - renderRoute('').unmount(); - - // This uses regular Routes from react-router, not that a-2 renders a, which is the behavior we're working around - const renderBadRoute = makeRouteRenderer({routes}); - expect(renderBadRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderBadRoute('/a-1').getByText('a-1')).toBeInTheDocument(); - expect(renderBadRoute('/a-2').getByText('a')).toBeInTheDocument(); - }); - - it('renders children straight as outlets', () => { - const MyPage = () => { - return <>Outlet: {useOutlet()}; - }; - - // The '/*' suffixes here are intentional and will be ignored by FlatRoutes - const routes = ( - <> - }> - a - - }> - a-b - - }> - b - - - ); - const renderRoute = makeRouteRenderer({routes}); - expect(renderRoute('/a').getByText('Outlet: a')).toBeInTheDocument(); - expect(renderRoute('/a/b').getByText('Outlet: a-b')).toBeInTheDocument(); - expect(renderRoute('/b').getByText('Outlet: b')).toBeInTheDocument(); - }); -}); diff --git a/packages/core-api/src/routing/FlatRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx deleted file mode 100644 index 5f2cfc150d..0000000000 --- a/packages/core-api/src/routing/FlatRoutes.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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, { ReactNode, Children, isValidElement, Fragment } from 'react'; -import { useRoutes } from 'react-router-dom'; -import { useApp } from '../app'; - -type RouteObject = { - path: string; - element: JSX.Element; - children?: RouteObject[]; -}; - -// Similar to the same function from react-router, this collects routes from the -// children, but only the first level of routes -function createRoutesFromChildren(childrenNode: ReactNode): RouteObject[] { - return Children.toArray(childrenNode).flatMap(child => { - if (!isValidElement(child)) { - return []; - } - - const { children } = child.props; - - if (child.type === Fragment) { - return createRoutesFromChildren(children); - } - - let path = child.props.path as string | undefined; - - // TODO(Rugvip): Work around plugins registering empty paths, remove once deprecated routes are gone - if (path === '') { - return []; - } - path = path?.replace(/\/\*$/, '') ?? '/'; - - return [ - { - path, - element: child, - children: children && [ - { - path: '/*', - element: children, - }, - ], - }, - ]; - }); -} - -type FlatRoutesProps = { - children: ReactNode; -}; - -export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { - const app = useApp(); - const { NotFoundErrorPage } = app.getComponents(); - const routes = createRoutesFromChildren(props.children) - // Routes are sorted to work around a bug where prefixes are unexpectedly matched - .sort((a, b) => b.path.localeCompare(a.path)) - // We make sure all routes have '/*' appended, except '/' - .map(obj => { - obj.path = obj.path === '/' ? '/' : `${obj.path}/*`; - return obj; - }); - - // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop - routes.push({ - element: , - path: '/*', - }); - - return useRoutes(routes); -}; diff --git a/packages/core-api/src/routing/RouteRef.test.ts b/packages/core-api/src/routing/RouteRef.test.ts deleted file mode 100644 index 279589a6c7..0000000000 --- a/packages/core-api/src/routing/RouteRef.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { AnyParams, RouteRef } from './types'; -import { createRouteRef, isRouteRef } from './RouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; -import MyIcon from '@material-ui/icons/AcUnit'; - -describe('RouteRef', () => { - it('should be created', () => { - const routeRef: RouteRef = createRouteRef({ - id: 'my-route-ref', - }); - expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(true); - expect(isSubRouteRef(routeRef)).toBe(false); - expect(isExternalRouteRef(routeRef)).toBe(false); - - expect(isRouteRef({} as RouteRef)).toBe(false); - }); - - it('should be created with params', () => { - const routeRef: RouteRef<{ - x: string; - y: string; - }> = createRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - }); - expect(routeRef.params).toEqual(['x', 'y']); - }); - - it('should properly infer and validate parameter types and assignments', () => { - function validateType(_ref: RouteRef) {} - - const _1 = createRouteRef({ id: '1', params: ['x'] }); - // @ts-expect-error - validateType<{ y: string }>(_1); - // @ts-expect-error - validateType(_1); - validateType<{ x: string }>(_1); - - const _2 = createRouteRef({ id: '2', params: ['x', 'y'] }); - // @ts-expect-error - validateType<{ x: string }>(_2); - // @ts-expect-error - validateType(_2); - // @ts-expect-error - validateType<{ x: string; z: string }>(_2); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }>(_2); - validateType<{ x: string; y: string }>(_2); - - const _3 = createRouteRef({ id: '3', params: [] }); - // @ts-expect-error - validateType<{ x: string }>(_3); - validateType(_3); - - const _4 = createRouteRef({ id: '4' }); - // @ts-expect-error - validateType<{ x: string }>(_4); - validateType(_4); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); - }); - - it('should support deprecated access', () => { - const routeRef = createRouteRef({ - title: 'My Ref', - path: '/my-path', - icon: MyIcon, - }); - expect(routeRef.title).toBe('My Ref'); - expect(routeRef.path).toBe('/my-path'); - expect(routeRef.icon).toBe(MyIcon); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=My Ref}'); - }); -}); diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts deleted file mode 100644 index a6a8d60326..0000000000 --- a/packages/core-api/src/routing/RouteRef.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, - routeRefType, - AnyParams, - ParamKeys, -} from './types'; -import { IconComponent } from '../icons/types'; - -export { createRouteRef } from '@backstage/core-plugin-api'; - -// TODO(Rugvip): Remove this in the next breaking release, it's exported but unused -export type RouteRefConfig = { - params?: ParamKeys; - path?: string; - icon?: IconComponent; - title: string; -}; - -export function isRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is RouteRef { - return routeRef[routeRefType] === 'absolute'; -} diff --git a/packages/core-api/src/routing/RouteResolver.test.ts b/packages/core-api/src/routing/RouteResolver.test.ts deleted file mode 100644 index b46c5fc42a..0000000000 --- a/packages/core-api/src/routing/RouteResolver.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -/* - * 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 { createRouteRef } from './RouteRef'; -import { createSubRouteRef } from './SubRouteRef'; -import { createExternalRouteRef } from './ExternalRouteRef'; -import { RouteResolver } from './RouteResolver'; -import { ExternalRouteRef, RouteRef, SubRouteRef } from './types'; -import { MATCH_ALL_ROUTE } from './collectors'; - -const element = () => null; -const rest = { element, caseSensitive: false, children: [MATCH_ALL_ROUTE] }; - -const ref1 = createRouteRef({ id: 'rr1' }); -const ref2 = createRouteRef({ id: 'rr2', params: ['x'] }); -const ref3 = createRouteRef({ id: 'rr3', params: ['y'] }); -const subRef1 = createSubRouteRef({ - id: 'srr1', - parent: ref1, - path: '/foo', -}); -const subRef2 = createSubRouteRef({ - id: 'srr2', - parent: ref1, - path: '/foo/:a', -}); -const subRef3 = createSubRouteRef({ - id: 'srr3', - parent: ref2, - path: '/bar', -}); -const subRef4 = createSubRouteRef({ - id: 'srr4', - parent: ref2, - path: '/bar/:a', -}); -const externalRef1 = createExternalRouteRef({ id: 'err1' }); -const externalRef2 = createExternalRouteRef({ - id: 'err2', - optional: true, -}); -const externalRef3 = createExternalRouteRef({ id: 'err3', params: ['x'] }); -const externalRef4 = createExternalRouteRef({ - id: 'err4', - optional: true, - params: ['x'], -}); - -describe('RouteResolver', () => { - it('should not resolve anything with an empty resolver', () => { - const r = new RouteResolver(new Map(), new Map(), [], new Map()); - - expect(r.resolve(ref1, '/')?.()).toBe(undefined); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe(undefined); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(undefined); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); - }); - - it('should resolve an absolute route', () => { - const r = new RouteResolver( - new Map([[ref1, '/my-route']]), - new Map(), - [{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }], - new Map(), - ); - - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); - }); - - it('should resolve an absolute route with a param and with a parent', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/my-route'], - [ref2, '/my-parent/:x'], - ]), - new Map([[ref2, ref1]]), - [ - { - routeRefs: new Set([ref2]), - path: '/my-parent/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { routeRefs: new Set([ref1]), path: '/my-route', ...rest }, - ], - }, - ], - new Map([ - [externalRef1, ref1], - [externalRef3, ref2], - [externalRef4, subRef3], - ]), - ); - - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/my-route/my-parent/1x'); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe( - '/my-route/my-parent/3x/bar', - ); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe( - '/my-route/my-parent/4x/bar/4a', - ); - expect(r.resolve(externalRef1, '/')?.()).toBe('/my-route'); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe( - '/my-route/my-parent/5x', - ); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe( - '/my-route/my-parent/6x/bar', - ); - }); - - it('should resolve the most specific match', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/deep'], - [ref2, '/root/:x'], - [ref3, '/sub/:y'], - ]), - new Map([ - [ref3, ref2], - [ref1, ref3], - ]), - [ - { - routeRefs: new Set([ref2]), - path: '/root/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref3]), - path: '/sub/:y', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref1]), - path: '/deep', - ...rest, - }, - ], - }, - ], - }, - ], - new Map(), - ); - - expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x'); - expect(r.resolve(ref3, '/root/x')?.({ y: 'y' })).toBe('/root/x/sub/y'); - - expect(() => r.resolve(ref1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(() => r.resolve(ref1, '/root/x')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(ref1, '/root/x/sub/y')?.()).toBe('/root/x/sub/y/deep'); - // Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here - expect(r.resolve(ref1, '/root/x/sub/y/any/nested/path/here')?.()).toBe( - '/root/x/sub/y/deep', - ); - }); - - it('should resolve an absolute route with multiple parents', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/my-route'], - [ref2, '/my-parent/:x'], - [ref3, '/my-grandparent/:y'], - ]), - new Map([ - [ref1, ref2], - [ref2, ref3], - ]), - [ - { - routeRefs: new Set([ref3]), - path: '/my-grandparent/:y', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref2]), - path: '/my-parent/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { routeRefs: new Set([ref1]), path: '/my-route', ...rest }, - ], - }, - ], - }, - ], - new Map([ - [externalRef1, ref1], - [externalRef3, ref2], - [externalRef4, subRef3], - ]), - ); - - const l = '/my-grandparent/my-y/my-parent/my-x'; - expect(r.resolve(ref1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route', - ); - expect(() => r.resolve(ref1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(ref2, l)?.({ x: '1x' })).toBe( - '/my-grandparent/my-y/my-parent/1x', - ); - expect(r.resolve(ref2, '/my-grandparent/my-y')?.({ x: '1x' })).toBe( - '/my-grandparent/my-y/my-parent/1x', - ); - expect(() => r.resolve(ref2, '/')?.({ x: '1x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route/foo', - ); - expect(() => r.resolve(subRef1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef2, l)?.({ a: '2a' })).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route/foo/2a', - ); - expect(() => r.resolve(subRef2, '/')?.({ a: '2a' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef3, l)?.({ x: '3x' })).toBe( - '/my-grandparent/my-y/my-parent/3x/bar', - ); - expect(r.resolve(subRef3, '/my-grandparent/my-y')?.({ x: '3x' })).toBe( - '/my-grandparent/my-y/my-parent/3x/bar', - ); - expect(r.resolve(subRef4, l)?.({ x: '4x', a: '4a' })).toBe( - '/my-grandparent/my-y/my-parent/4x/bar/4a', - ); - expect( - r.resolve(subRef4, '/my-grandparent/my-y')?.({ x: '4x', a: '4a' }), - ).toBe('/my-grandparent/my-y/my-parent/4x/bar/4a'); - expect(r.resolve(externalRef1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route', - ); - expect(() => r.resolve(externalRef1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(externalRef2, l)?.()).toBe(undefined); - expect(r.resolve(externalRef3, l)?.({ x: '5x' })).toBe( - '/my-grandparent/my-y/my-parent/5x', - ); - expect(() => r.resolve(externalRef3, '/')?.({ x: '5x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(externalRef4, l)?.({ x: '6x' })).toBe( - '/my-grandparent/my-y/my-parent/6x/bar', - ); - expect(() => r.resolve(externalRef4, '/')?.({ x: '6x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - }); -}); diff --git a/packages/core-api/src/routing/RouteResolver.ts b/packages/core-api/src/routing/RouteResolver.ts deleted file mode 100644 index fb44b8c486..0000000000 --- a/packages/core-api/src/routing/RouteResolver.ts +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 { generatePath, matchRoutes } from 'react-router-dom'; -import { - AnyRouteRef, - BackstageRouteObject, - RouteRef, - ExternalRouteRef, - AnyParams, - SubRouteRef, - routeRefType, - RouteFunc, -} from './types'; -import { isRouteRef } from './RouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; - -// Joins a list of paths together, avoiding trailing and duplicate slashes -function joinPaths(...paths: string[]): string { - const normalized = paths.join('/').replace(/\/\/+/g, '/'); - if (normalized !== '/' && normalized.endsWith('/')) { - return normalized.slice(0, -1); - } - return normalized; -} - -/** - * Resolves the absolute route ref that our target route ref is pointing pointing to, as well - * as the relative target path. - * - * Returns an undefined target ref if one could not be fully resolved. - */ -function resolveTargetRef( - anyRouteRef: AnyRouteRef, - routePaths: Map, - routeBindings: Map, -): readonly [RouteRef | undefined, string] { - // First we figure out which absolute route ref we're dealing with, an if there was an sub route path to append. - // For sub routes it will be the parent path, while for external routes it will be the bound route. - let targetRef: RouteRef; - let subRoutePath = ''; - if (isRouteRef(anyRouteRef)) { - targetRef = anyRouteRef; - } else if (isSubRouteRef(anyRouteRef)) { - targetRef = anyRouteRef.parent; - subRoutePath = anyRouteRef.path; - } else if (isExternalRouteRef(anyRouteRef)) { - const resolvedRoute = routeBindings.get(anyRouteRef); - if (!resolvedRoute) { - return [undefined, '']; - } - if (isRouteRef(resolvedRoute)) { - targetRef = resolvedRoute; - } else if (isSubRouteRef(resolvedRoute)) { - targetRef = resolvedRoute.parent; - subRoutePath = resolvedRoute.path; - } else { - throw new Error( - `ExternalRouteRef was bound to invalid target, ${resolvedRoute}`, - ); - } - } else if (anyRouteRef[routeRefType]) { - throw new Error( - `Unknown or invalid route ref type, ${anyRouteRef[routeRefType]}`, - ); - } else { - throw new Error(`Unknown object passed to useRouteRef, got ${anyRouteRef}`); - } - - // Bail if no absolute path could be resolved - if (!targetRef) { - return [undefined, '']; - } - - // Find the path that our target route is bound to - const resolvedPath = routePaths.get(targetRef); - if (!resolvedPath) { - return [undefined, '']; - } - - // SubRouteRefs join the path from the parent route with its own path - const targetPath = joinPaths(resolvedPath, subRoutePath); - return [targetRef, targetPath]; -} - -/** - * Resolves the complete base path for navigating to the target RouteRef. - */ -function resolveBasePath( - targetRef: RouteRef, - sourceLocation: Parameters[1], - routePaths: Map, - routeParents: Map, - routeObjects: BackstageRouteObject[], -) { - // While traversing the app element tree we build up the routeObjects structure - // used here. It is the same kind of structure that react-router creates, with the - // addition that associated route refs are stored throughout the tree. This lets - // us look up all route refs that can be reached from our source location. - // Because of the similar route object structure, we can use `matchRoutes` from - // react-router to do the lookup of our current location. - const match = matchRoutes(routeObjects, sourceLocation) ?? []; - - // While we search for a common routing root between our current location and - // the target route, we build a list of all route refs we find that we need - // to traverse to reach the target. - const refDiffList = Array(); - - let matchIndex = -1; - for ( - let targetSearchRef: RouteRef | undefined = targetRef; - targetSearchRef; - targetSearchRef = routeParents.get(targetSearchRef) - ) { - // The match contains a list of all ancestral route refs present at our current location - // Starting at the desired target ref and traversing back through its parents, we search - // for a target ref that is present in the match for our current location. When a match - // is found it means we have found a common base to resolve the route from. - matchIndex = match.findIndex(m => - (m.route as BackstageRouteObject).routeRefs.has(targetSearchRef!), - ); - if (matchIndex !== -1) { - break; - } - - // Every time we move a step up in the ancestry of the target ref, we add the current ref - // to the diff list, which ends up being the list of route refs to traverse form the common base - // in order to reach our target. - refDiffList.unshift(targetSearchRef); - } - - // If our target route is present in the initial match we need to construct the final path - // from the parent of the matched route segment. That's to allow the caller of the route - // function to supply their own params. - if (refDiffList.length === 0) { - matchIndex -= 1; - } - - // This is the part of the route tree that the target and source locations have in common. - // We re-use the existing pathname directly along with all params. - const parentPath = matchIndex === -1 ? '' : match[matchIndex].pathname; - - // This constructs the mid section of the path using paths resolved from all route refs - // we need to traverse to reach our target except for the very last one. None of these - // paths are allowed to require any parameters, as the caller would have no way of knowing - // what parameters those are. - const diffPath = joinPaths( - ...refDiffList.slice(0, -1).map(ref => { - const path = routePaths.get(ref); - if (!path) { - throw new Error(`No path for ${ref}`); - } - if (path.includes(':')) { - throw new Error( - `Cannot route to ${targetRef} with parent ${ref} as it has parameters`, - ); - } - return path; - }), - ); - - return parentPath + diffPath; -} - -export class RouteResolver { - constructor( - private readonly routePaths: Map, - private readonly routeParents: Map, - private readonly routeObjects: BackstageRouteObject[], - private readonly routeBindings: Map< - ExternalRouteRef, - RouteRef | SubRouteRef - >, - ) {} - - resolve( - anyRouteRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, - sourceLocation: Parameters[1], - ): RouteFunc | undefined { - // First figure out what our target absolute ref is, as well as our target path. - const [targetRef, targetPath] = resolveTargetRef( - anyRouteRef, - this.routePaths, - this.routeBindings, - ); - if (!targetRef) { - return undefined; - } - - // Next we figure out the base path, which is the combination of the common parent path - // between our current location and our target location, as well as the additional path - // that is the difference between the parent path and the base of our target location. - const basePath = resolveBasePath( - targetRef, - sourceLocation, - this.routePaths, - this.routeParents, - this.routeObjects, - ); - - const routeFunc: RouteFunc = (...[params]) => { - return basePath + generatePath(targetPath, params); - }; - return routeFunc; - } -} diff --git a/packages/core-api/src/routing/SubRouteRef.test.ts b/packages/core-api/src/routing/SubRouteRef.test.ts deleted file mode 100644 index 1c1a3c1b21..0000000000 --- a/packages/core-api/src/routing/SubRouteRef.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 { AnyParams, SubRouteRef } from './types'; -import { createSubRouteRef, isSubRouteRef } from './SubRouteRef'; -import { createRouteRef, isRouteRef } from './RouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; - -const parent = createRouteRef({ id: 'parent' }); -const parentX = createRouteRef({ id: 'parent-x', params: ['x'] }); - -describe('SubRouteRef', () => { - it('should be created', () => { - const routeRef: SubRouteRef = createSubRouteRef({ - parent, - id: 'my-route-ref', - path: '/foo', - }); - expect(routeRef.path).toBe('/foo'); - expect(routeRef.parent).toBe(parent); - expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=sub,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(false); - expect(isSubRouteRef(routeRef)).toBe(true); - expect(isExternalRouteRef(routeRef)).toBe(false); - - expect(isRouteRef({} as SubRouteRef)).toBe(false); - }); - - it('should be created with params', () => { - const routeRef: SubRouteRef<{ bar: string }> = createSubRouteRef({ - parent, - id: 'my-other-route-ref', - path: '/foo/:bar', - }); - expect(routeRef.path).toBe('/foo/:bar'); - expect(routeRef.parent).toBe(parent); - expect(routeRef.params).toEqual(['bar']); - }); - - it('should be created with merged params', () => { - const routeRef: SubRouteRef<{ - x: string; - y: string; - z: string; - }> = createSubRouteRef({ - parent: parentX, - id: 'my-other-route-ref', - path: '/foo/:y/:z', - }); - expect(routeRef.path).toBe('/foo/:y/:z'); - expect(routeRef.parent).toBe(parentX); - expect(routeRef.params).toEqual(['x', 'y', 'z']); - }); - - it('should be created with params from parent', () => { - const routeRef: SubRouteRef<{ x: string }> = createSubRouteRef({ - parent: parentX, - id: 'my-other-route-ref', - path: '/foo/bar', - }); - expect(routeRef.path).toBe('/foo/bar'); - expect(routeRef.parent).toBe(parentX); - expect(routeRef.params).toEqual(['x']); - }); - - it.each([ - ['foo', "SubRouteRef path must start with '/', got 'foo'"], - [':foo', "SubRouteRef path must start with '/', got ':foo'"], - ['', "SubRouteRef path must start with '/', got ''"], - ['/', "SubRouteRef path must not end with '/', got '/'"], - ['/foo/', "SubRouteRef path must not end with '/', got '/foo/'"], - ['/foo/:x', 'SubRouteRef may not have params that overlap with its parent'], - ['/:/foo', "SubRouteRef path has invalid param, got ''"], - ['/:inva:lid/foo', "SubRouteRef path has invalid param, got 'inva:lid'"], - ['/:inva=lid/foo', "SubRouteRef path has invalid param, got 'inva=lid'"], - ])('should throw if path is invalid, %s', (path, message) => { - expect(() => - createSubRouteRef({ path, parent: parentX, id: path }), - ).toThrow(message); - }); - - it('should properly infer and parse path parameters', () => { - function validateType(_ref: SubRouteRef) {} - - const _1 = createSubRouteRef({ id: '1', parent, path: '/foo/bar' }); - // @ts-expect-error - validateType<{ x: string }>(_1); - validateType(_1); - - const _2 = createSubRouteRef({ id: '2', parent, path: '/foo/:x/:y' }); - // @ts-expect-error - validateType(_2); - // @ts-expect-error - validateType<{ x: string; z: string }>(_2); - // @ts-expect-error - validateType<{ y: string }>(_2); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }>(_2); - validateType<{ x: string; y: string }>(_2); - - const _3 = createSubRouteRef({ id: '3', parent: parentX, path: '/foo' }); - // @ts-expect-error - validateType(_3); - // @ts-expect-error - validateType<{ y: string }>(_3); - validateType<{ x: string }>(_3); - - const _4 = createSubRouteRef({ id: '4', parent: parentX, path: '/foo/:y' }); - // @ts-expect-error - validateType(_4); - // @ts-expect-error - validateType<{ x: string; z: string }>(_4); - // @ts-expect-error - validateType<{ y: string }>(_4); - validateType<{ x: string; y: string }>(_4); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); - }); -}); diff --git a/packages/core-api/src/routing/SubRouteRef.ts b/packages/core-api/src/routing/SubRouteRef.ts deleted file mode 100644 index 61d3450e27..0000000000 --- a/packages/core-api/src/routing/SubRouteRef.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { - AnyParams, - ExternalRouteRef, - RouteRef, - routeRefType, - SubRouteRef, -} from './types'; - -export { createSubRouteRef } from '@backstage/core-plugin-api'; - -export function isSubRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is SubRouteRef { - return routeRef[routeRefType] === 'sub'; -} diff --git a/packages/core-api/src/routing/collectors.test.tsx b/packages/core-api/src/routing/collectors.test.tsx deleted file mode 100644 index 498d1dd9fc..0000000000 --- a/packages/core-api/src/routing/collectors.test.tsx +++ /dev/null @@ -1,374 +0,0 @@ -/* - * 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, { PropsWithChildren } from 'react'; -import { - routePathCollector, - routeParentCollector, - routeObjectCollector, -} from './collectors'; - -import { - traverseElementTree, - childDiscoverer, - routeElementDiscoverer, -} from '../extensions/traversal'; -import { createRouteRef } from './RouteRef'; -import { createPlugin } from '../plugin'; -import { attachComponentData, createRoutableExtension } from '../extensions'; -import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { RouteRef } from './types'; - -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const plugin = createPlugin({ id: 'my-plugin' }); - -const ref1 = createRouteRef({ path: '/foo1', title: 'Foo' }); -const ref2 = createRouteRef({ path: '/foo2', title: 'Foo' }); -const ref3 = createRouteRef({ path: '/foo3', title: 'Foo' }); -const ref4 = createRouteRef({ path: '/foo4', title: 'Foo' }); -const ref5 = createRouteRef({ path: '/foo5', title: 'Foo' }); -const refOrder = [ref1, ref2, ref3, ref4, ref5]; - -const Extension1 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref2, - }), -); -const Extension3 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref3, - }), -); -const Extension4 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref4, - }), -); -const Extension5 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref5, - }), -); - -const AggregationComponent = ({ - children, -}: PropsWithChildren<{ - path: string; -}>) => <>{children}; - -attachComponentData(AggregationComponent, 'core.gatherMountPoints', true); - -function sortedEntries(map: Map): [RouteRef, T][] { - return Array.from(map).sort( - ([a], [b]) => refOrder.indexOf(a) - refOrder.indexOf(b), - ); -} - -function routeObj( - path: string, - refs: RouteRef[], - children: any[] = [], - type: 'mounted' | 'gathered' = 'mounted', -) { - return { - path: path, - caseSensitive: false, - element: type, - routeRefs: new Set(refs), - children: [ - { - path: '/*', - caseSensitive: false, - element: 'match-all', - routeRefs: new Set(), - }, - ...children, - ], - }; -} - -describe('discovery', () => { - it('should collect routes', () => { - const list = [ -
, -
, -
- -
, - ]; - - const root = ( - - - -
- -
-
- Some text here shouldn't be a problem -
- {null} -
- -
- - {false} - {list} - {true} - {0} -
- -
- } /> -
- - - ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar/:id'], - [ref3, '/baz'], - [ref4, '/divsoup'], - [ref5, '/blop'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, ref2], - [ref4, undefined], - [ref5, ref1], - ]); - expect(routeObjects).toEqual([ - routeObj( - '/foo', - [ref1], - [ - routeObj('/bar/:id', [ref2], [routeObj('/baz', [ref3])]), - routeObj('/blop', [ref5]), - ], - ), - routeObj('/divsoup', [ref4]), - ]); - }); - - it('should handle all react router Route patterns', () => { - const root = ( - - - - - - - - } - /> - }> - } /> - - - - - ); - - const { routes, routeParents } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar/:id'], - [ref3, '/baz'], - [ref4, '/divsoup'], - [ref5, '/blop'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, undefined], - [ref4, ref3], - [ref5, ref3], - ]); - }); - - it('should use the route aggregator key to bind child routes to the same path', () => { - const root = ( - - - - -
- -
- HELLO -
- - - - - - - -
-
- ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/foo'], - [ref3, '/bar'], - [ref4, '/baz'], - [ref5, '/baz'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, undefined], - [ref3, undefined], - [ref4, ref3], - [ref5, ref3], - ]); - expect(routeObjects).toEqual([ - routeObj('/foo', [ref1, ref2], [], 'gathered'), - routeObj( - '/bar', - [ref3], - [routeObj('/baz', [ref4, ref5], [], 'gathered')], - ), - ]); - }); - - it('should use the route aggregator but stop when encountering explicit path', () => { - const root = ( - - - - - - - - - - - - - - - ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar'], - [ref3, '/baz'], - [ref4, '/blop'], - [ref5, '/bar'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, ref1], - [ref4, ref3], - [ref5, ref1], - ]); - expect(routeObjects).toEqual([ - routeObj( - '/foo', - [ref1], - [ - routeObj( - '/bar', - [ref2, ref5], - [routeObj('/baz', [ref3], [routeObj('/blop', [ref4])])], - 'gathered', - ), - ], - ), - ]); - }); - - it('should stop gathering mount points after encountering explicit path', () => { - const root = ( - - - - - - - - - - - - ); - - expect(() => { - traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - }, - }); - }).toThrow('Mounted routable extension must have a path'); - }); -}); diff --git a/packages/core-api/src/routing/collectors.tsx b/packages/core-api/src/routing/collectors.tsx deleted file mode 100644 index 9d651dbc0d..0000000000 --- a/packages/core-api/src/routing/collectors.tsx +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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 { isValidElement, ReactElement, ReactNode } from 'react'; -import { BackstageRouteObject, RouteRef } from '../routing/types'; -import { getComponentData } from '../extensions'; -import { createCollector } from '../extensions/traversal'; - -function getMountPoint(node: ReactElement): RouteRef | undefined { - const element: ReactNode = node.props?.element; - - let routeRef = getComponentData(node, 'core.mountPoint'); - if (!routeRef && isValidElement(element)) { - routeRef = getComponentData(element, 'core.mountPoint'); - } - - return routeRef; -} - -export const routePathCollector = createCollector( - () => new Map(), - (acc, node, parent, ctxPath: string | undefined) => { - // The context path is used during mount point gathering to assign the same path - // to all discovered mount points - let currentCtxPath = ctxPath; - - if (parent?.props.element === node) { - return currentCtxPath; - } - - // Start gathering mount points when we encounter a mount point gathering flag - if (getComponentData(node, 'core.gatherMountPoints')) { - const path: string | undefined = node.props?.path; - if (!path) { - throw new Error('Mount point gatherer must have a path'); - } - currentCtxPath = path; - } - - const routeRef = getMountPoint(node); - if (routeRef) { - let path: string | undefined = node.props?.path; - // If we're gathering mount points we use the context path as out path, unless - // the element has its own path, in which case we use that instead and stop gathering - if (currentCtxPath) { - if (path) { - currentCtxPath = undefined; - } else { - path = currentCtxPath; - } - } - if (!path) { - throw new Error('Mounted routable extension must have a path'); - } - acc.set(routeRef, path); - } - return currentCtxPath; - }, -); - -export const routeParentCollector = createCollector( - () => new Map(), - (acc, node, parent, parentRouteRef?: RouteRef | { sticky: RouteRef }) => { - if (parent?.props.element === node) { - return parentRouteRef; - } - - let nextParent = parentRouteRef; - - const routeRef = getMountPoint(node); - if (routeRef) { - // "sticky" route ref is when we've encountered a mount point gatherer, and we want a - // mount points beneath it to have the same parent, regardless of internal structure - if (parentRouteRef && 'sticky' in parentRouteRef) { - acc.set(routeRef, parentRouteRef.sticky); - - // When we encounter a mount point with an explicit path, we stop gathering - // mount points withing the children and remove the sticky state - if (node.props?.path) { - nextParent = routeRef; - } else { - nextParent = parentRouteRef; - } - } else { - acc.set(routeRef, parentRouteRef); - nextParent = routeRef; - } - } - - // Mount point gatherers are marked as "sticky" - if (getComponentData(node, 'core.gatherMountPoints')) { - return { sticky: nextParent }; - } - - return nextParent; - }, -); - -// We always add a child that matches all subroutes but without any route refs. This makes -// sure that we're always able to match each route no matter how deep the navigation goes. -// The route resolver then takes care of selecting the most specific match in order to find -// mount points that are as deep in the routing tree as possible. -export const MATCH_ALL_ROUTE: BackstageRouteObject = { - caseSensitive: false, - path: '/*', - element: 'match-all', // These elements aren't used, so we add in a bit of debug information - routeRefs: new Set(), -}; - -export const routeObjectCollector = createCollector( - () => Array(), - (acc, node, parent, parentObj: BackstageRouteObject | undefined) => { - const parentChildren = parentObj?.children ?? acc; - if (parent?.props.element === node) { - return parentObj; - } - - const path: string | undefined = node.props?.path; - const caseSensitive: boolean = Boolean(node.props?.caseSensitive); - - const routeRef = getMountPoint(node); - if (routeRef) { - if (path) { - const newObject: BackstageRouteObject = { - caseSensitive, - path, - element: 'mounted', - routeRefs: new Set([routeRef]), - children: [MATCH_ALL_ROUTE], - }; - parentChildren.push(newObject); - return newObject; - } - - parentObj?.routeRefs.add(routeRef); - } - - const isGatherer = getComponentData( - node, - 'core.gatherMountPoints', - ); - if (isGatherer) { - if (!path) { - throw new Error('Mount point gatherer must have a path'); - } - const newObject: BackstageRouteObject = { - caseSensitive, - path, - element: 'gathered', - routeRefs: new Set(), - children: [MATCH_ALL_ROUTE], - }; - parentChildren.push(newObject); - return newObject; - } - - return parentObj; - }, -); diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx deleted file mode 100644 index 8a9b70ed7d..0000000000 --- a/packages/core-api/src/routing/hooks.test.tsx +++ /dev/null @@ -1,405 +0,0 @@ -/* - * 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 { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; -import React, { - Context, - PropsWithChildren, - ReactElement, - useContext, -} from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { createRoutableExtension } from '../extensions'; -import { - childDiscoverer, - routeElementDiscoverer, - traverseElementTree, -} from '../extensions/traversal'; -import { getGlobalSingleton } from '../lib/globalObject'; -import { VersionedValue } from '../lib/versionedValues'; -import { createPlugin } from '../plugin'; -import { - routeObjectCollector, - routeParentCollector, - routePathCollector, -} from './collectors'; -import { createExternalRouteRef } from './ExternalRouteRef'; -import { RoutingProvider, useRouteRef, useRouteRefParams } from './hooks'; -import { createRouteRef, RouteRefConfig } from './RouteRef'; -import { RouteResolver } from './RouteResolver'; -import { AnyRouteRef, ExternalRouteRef, RouteFunc, RouteRef } from './types'; -import { validateRoutes } from './validation'; - -const mockConfig = (extra?: Partial>) => ({ - path: '/unused', - title: 'Unused', - ...extra, -}); -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const plugin = createPlugin({ id: 'my-plugin' }); - -const ref1 = createRouteRef(mockConfig({ path: '/wat1' })); -const ref2 = createRouteRef(mockConfig({ path: '/wat2' })); -const ref3 = createRouteRef(mockConfig({ path: '/wat3' })); -const ref4 = createRouteRef(mockConfig({ path: '/wat4' })); -const ref5 = createRouteRef({ - ...mockConfig({ path: '/wat5' }), - params: ['x'], -}); -const eRefA = createExternalRouteRef({ id: '1' }); -const eRefB = createExternalRouteRef({ id: '2' }); -const eRefC = createExternalRouteRef({ id: '3', params: ['y'] }); -const eRefD = createExternalRouteRef({ id: '4', optional: true }); -const eRefE = createExternalRouteRef({ - id: '5', - optional: true, - params: ['z'], -}); - -const MockRouteSource = (props: { - path?: string; - name: string; - routeRef: AnyRouteRef; - params?: T; -}) => { - try { - const routeFunc = useRouteRef(props.routeRef as any) as - | RouteFunc - | undefined; - return ( -
- Path at {props.name}: {routeFunc?.(props.params) ?? ''} -
- ); - } catch (ex) { - return ( -
- Error at {props.name}: {ex.message} -
- ); - } -}; - -const Extension1 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockRouteSource), - mountPoint: ref2, - }), -); -const Extension3 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref3, - }), -); -const Extension4 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockRouteSource), - mountPoint: ref4, - }), -); -const Extension5 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref5, - }), -); - -function withRoutingProvider( - root: ReactElement, - routeBindings: [ExternalRouteRef, RouteRef][] = [], -) { - const { routePaths, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - - return ( - - {root} - - ); -} - -describe('discovery', () => { - it('should handle simple routeRef path creation for routeRefs used in other parts of the app', async () => { - const root = ( - - - - - - - - - - - - - - - ); - - const rendered = render( - withRoutingProvider(root, [ - [eRefA, ref3], - [eRefB, ref1], - [eRefC, ref2], - [eRefD, ref1], - ]), - ); - - await expect( - rendered.findByText('Path at inside: /foo/bar'), - ).resolves.toBeInTheDocument(); - expect( - rendered.getByText('Path at insideExternal: /baz'), - ).toBeInTheDocument(); - expect(rendered.getByText('Path at outside: /foo/bar')).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal1: /foo'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal2: /foo/bar'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal3: /foo'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal4: '), - ).toBeInTheDocument(); - }); - - it('should handle routeRefs with parameters', async () => { - const root = ( - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - await expect( - rendered.findByText('Path at inside: /foo/bar/bleb'), - ).resolves.toBeInTheDocument(); - expect( - rendered.getByText('Path at outside: /foo/bar/blob'), - ).toBeInTheDocument(); - }); - - it('should handle relative routing within parameterized routePaths', async () => { - const root = ( - - - - - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - await expect( - rendered.findByText('Path at inside: /foo/blob/baz'), - ).resolves.toBeInTheDocument(); - }); - - it('should throw errors for routing to other routeRefs with unsupported parameters', () => { - const root = ( - - - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - expect( - rendered.getByText( - `Error at outsideWithParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`, - ), - ).toBeInTheDocument(); - expect( - rendered.getByText( - `Error at outsideNoParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`, - ), - ).toBeInTheDocument(); - }); - - it('should handle relative routing of parameterized routePaths with duplicate param names', () => { - const root = ( - - - - - - - - ); - - const { routePaths, routeParents } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - }, - }); - - expect(() => validateRoutes(routePaths, routeParents)).toThrow( - 'Parameter :id is duplicated in path /foo/:id/bar/:id', - ); - }); -}); - -describe('v1 consumer', () => { - const RoutingContext = getGlobalSingleton< - Context> - >('routing-context'); - - function useMockRouteRefV1( - routeRef: AnyRouteRef, - location: string, - ): RouteFunc | undefined { - const resolver = useContext(RoutingContext)?.atVersion(1); - if (!resolver) { - throw new Error('no impl'); - } - return resolver.resolve(routeRef, location); - } - - it('should resolve routes', () => { - const routeRef1 = createRouteRef({ id: 'ref1' }); - const routeRef2 = createRouteRef({ id: 'ref2' }); - const routeRef3 = createRouteRef({ id: 'ref3', params: ['x'] }); - - const renderedHook = renderHook( - ({ routeRef }) => useMockRouteRefV1(routeRef, '/'), - { - initialProps: { - routeRef: routeRef1 as AnyRouteRef, - }, - wrapper: ({ children }) => ( - , string>([ - [routeRef2, '/foo'], - [routeRef3, '/bar/:x'], - ]) - } - routeParents={new Map()} - routeObjects={[]} - routeBindings={new Map()} - children={children} - /> - ), - }, - ); - - expect(renderedHook.result.current).toBe(undefined); - renderedHook.rerender({ routeRef: routeRef2 }); - expect(renderedHook.result.current?.()).toBe('/foo'); - renderedHook.rerender({ routeRef: routeRef3 }); - expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/bar/my-x'); - }); -}); - -describe('useRouteRefParams', () => { - it('should provide types params', () => { - const routeRef = createRouteRef({ - id: 'ref1', - params: ['a', 'b'], - }); - - const Page = () => { - const params: { a: string; b: string } = useRouteRefParams(routeRef); - - return ( -
- {params.a} - {params.b} -
- ); - }; - - const { getByText } = render( - - - - - - - , - ); - - expect(getByText('foo')).toBeInTheDocument(); - expect(getByText('bar')).toBeInTheDocument(); - }); -}); diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx deleted file mode 100644 index 1867ac5c67..0000000000 --- a/packages/core-api/src/routing/hooks.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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, { - createContext, - ReactNode, - useContext, - useMemo, - Context, -} from 'react'; -import { useLocation, useParams } from 'react-router-dom'; -import { - BackstageRouteObject, - RouteRef, - ExternalRouteRef, - AnyParams, - SubRouteRef, - RouteFunc, -} from './types'; -import { RouteResolver } from './RouteResolver'; -import { - VersionedValue, - createVersionedValueMap, -} from '../lib/versionedValues'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../lib/globalObject'; - -type RoutingContextType = VersionedValue<{ 1: RouteResolver }> | undefined; -const RoutingContext = getOrCreateGlobalSingleton('routing-context', () => - createContext(undefined), -); - -export function useRouteRef( - routeRef: ExternalRouteRef, -): Optional extends true ? RouteFunc | undefined : RouteFunc; -export function useRouteRef( - routeRef: RouteRef | SubRouteRef, -): RouteFunc; -export function useRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): RouteFunc | undefined { - const sourceLocation = useLocation(); - const versionedContext = useContext( - getGlobalSingleton>('routing-context'), - ); - const resolver = versionedContext?.atVersion(1); - const routeFunc = useMemo( - () => resolver && resolver.resolve(routeRef, sourceLocation), - [resolver, routeRef, sourceLocation], - ); - - if (!versionedContext) { - throw new Error('useRouteRef used outside of routing context'); - } - if (!resolver) { - throw new Error('RoutingContext v1 not available'); - } - - const isOptional = 'optional' in routeRef && routeRef.optional; - if (!routeFunc && !isOptional) { - throw new Error(`No path for ${routeRef}`); - } - - return routeFunc; -} - -type ProviderProps = { - routePaths: Map; - routeParents: Map; - routeObjects: BackstageRouteObject[]; - routeBindings: Map; - children: ReactNode; -}; - -export const RoutingProvider = ({ - routePaths, - routeParents, - routeObjects, - routeBindings, - children, -}: ProviderProps) => { - const resolver = new RouteResolver( - routePaths, - routeParents, - routeObjects, - routeBindings, - ); - - const versionedValue = createVersionedValueMap({ 1: resolver }); - return ( - - {children} - - ); -}; - -export function useRouteRefParams( - _routeRef: RouteRef | SubRouteRef, -): Params { - return useParams() as Params; -} diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts deleted file mode 100644 index 0c01e74f3f..0000000000 --- a/packages/core-api/src/routing/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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. - */ - -export { createExternalRouteRef } from './ExternalRouteRef'; -export { FlatRoutes } from './FlatRoutes'; -export { useRouteRef, useRouteRefParams } from './hooks'; -export { createRouteRef } from './RouteRef'; -export type { RouteRefConfig } from './RouteRef'; -export { createSubRouteRef } from './SubRouteRef'; -export type { - AbsoluteRouteRef, - ConcreteRoute, - ExternalRouteRef, - MutableRouteRef, - RouteRef, - SubRouteRef, -} from './types'; diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts deleted file mode 100644 index 0e5a786770..0000000000 --- a/packages/core-api/src/routing/types.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, -} from '@backstage/core-plugin-api'; -import { getOrCreateGlobalSingleton } from '../lib/globalObject'; - -export type { RouteRef, SubRouteRef, ExternalRouteRef }; - -export type AnyParams = { [param in string]: string } | undefined; -export type ParamKeys = keyof Params extends never - ? [] - : (keyof Params)[]; -export type OptionalParams< - Params extends { [param in string]: string } -> = Params[keyof Params] extends never ? undefined : Params; - -// The extra TS magic here is to require a single params argument if the RouteRef -// had at least one param defined, but require 0 arguments if there are no params defined. -// Without this we'd have to pass in empty object to all parameter-less RouteRefs -// just to make TypeScript happy, or we would have to make the argument optional in -// which case you might forget to pass it in when it is actually required. -export type RouteFunc = ( - ...[params]: Params extends undefined ? readonly [] : readonly [Params] -) => string; - -type RouteRefType = Exclude< - keyof RouteRef, - 'params' | 'path' | 'title' | 'icon' ->; -export const routeRefType: RouteRefType = getOrCreateGlobalSingleton( - 'route-ref-type', - () => Symbol('route-ref-type'), -); - -export type AnyRouteRef = - | RouteRef - | SubRouteRef - | ExternalRouteRef; - -// TODO(Rugvip): None of these should be found in the wild anymore, remove in next minor release -/** @deprecated */ -export type ConcreteRoute = {}; -/** @deprecated */ -export type AbsoluteRouteRef = RouteRef<{}>; -/** @deprecated */ -export type MutableRouteRef = RouteRef<{}>; - -// A duplicate of the react-router RouteObject, but with routeRef added -export interface BackstageRouteObject { - caseSensitive: boolean; - children?: BackstageRouteObject[]; - element: React.ReactNode; - path: string; - routeRefs: Set; -} diff --git a/packages/core-api/src/routing/validation.ts b/packages/core-api/src/routing/validation.ts deleted file mode 100644 index 2d32471a14..0000000000 --- a/packages/core-api/src/routing/validation.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 { AnyRouteRef } from './types'; - -export function validateRoutes( - routePaths: Map, - routeParents: Map, -) { - const notLeafRoutes = new Set(routeParents.values()); - notLeafRoutes.delete(undefined); - - for (const route of routeParents.keys()) { - if (notLeafRoutes.has(route)) { - continue; - } - - let currentRouteRef: AnyRouteRef | undefined = route; - - let fullPath = ''; - while (currentRouteRef) { - const path = routePaths.get(currentRouteRef); - if (!path) { - throw new Error(`No path for ${currentRouteRef}`); - } - fullPath = `${path}${fullPath}`; - currentRouteRef = routeParents.get(currentRouteRef); - } - - const params = fullPath.match(/:(\w+)/g); - if (params) { - for (let j = 0; j < params.length; j++) { - for (let i = j + 1; i < params.length; i++) { - if (params[i] === params[j]) { - throw new Error( - `Parameter ${params[i]} is duplicated in path ${fullPath}`, - ); - } - } - } - } - } -} diff --git a/packages/core-api/src/setupTests.ts b/packages/core-api/src/setupTests.ts deleted file mode 100644 index aea2220869..0000000000 --- a/packages/core-api/src/setupTests.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import 'cross-fetch/polyfill'; diff --git a/packages/core-api/src/types.ts b/packages/core-api/src/types.ts deleted file mode 100644 index ab0aa56a1b..0000000000 --- a/packages/core-api/src/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -/** - * This file contains non-react related core types used throughout Backstage. - */ - -/** - * Observer interface for consuming an Observer, see TC39. - */ -export type Observer = { - next?(value: T): void; - error?(error: Error): void; - complete?(): void; -}; - -/** - * Subscription returned when subscribing to an Observable, see TC39. - */ -export type Subscription = { - /** - * Cancels the subscription - */ - unsubscribe(): void; - - /** - * Value indicating whether the subscription is closed. - */ - 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. - * - * https://github.com/tc39/proposal-observable - * - * This is used as a common return type for observable values and can be created - * using many different observable implementations, such as zen-observable or RxJS 5. - */ -export type Observable = { - [Symbol.observable](): Observable; - - /** - * Subscribes to this observable to start receiving new values. - */ - subscribe(observer: Observer): Subscription; - subscribe( - onNext?: (value: T) => void, - onError?: (error: Error) => void, - onComplete?: () => void, - ): Subscription; -}; diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 4d7b7541d0..4f09f231c3 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/core-app-api +## 0.1.4 + +### Patch Changes + +- 62abffee4: Reintroduce export of `defaultConfigLoader`. +- Updated dependencies + - @backstage/core-components@0.1.4 + +## 0.1.3 + +### Patch Changes + +- dc3e7ce68: Introducing new UnhandledErrorForwarder installed by default. For catching unhandled promise rejections, you can override the API to align with general error handling. +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.2 ### Patch Changes diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index b85c9392d7..af86f042a0 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { AlertApi } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; @@ -57,72 +56,97 @@ import { SubRouteRef } from '@backstage/core-plugin-api'; // @public export class AlertApiForwarder implements AlertApi { - // (undocumented) - alert$(): Observable; - // (undocumented) - post(alert: AlertMessage): void; - } + // (undocumented) + alert$(): Observable; + // (undocumented) + post(alert: AlertMessage): void; +} // @public (undocumented) export type ApiFactoryHolder = { - get(api: ApiRef): ApiFactory | undefined; + get( + api: ApiRef, + ): + | ApiFactory< + T, + T, + { + [key in string]: unknown; + } + > + | undefined; }; // @public export class ApiFactoryRegistry implements ApiFactoryHolder { - // (undocumented) - get(api: ApiRef): ApiFactory | undefined; - // (undocumented) - getAllApis(): Set; - register(scope: ApiFactoryScope, factory: ApiFactory): boolean; + // (undocumented) + get( + api: ApiRef, + ): + | ApiFactory< + T, + T, + { + [x: string]: unknown; + } + > + | undefined; + // (undocumented) + getAllApis(): Set; + register< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + } + >(scope: ApiFactoryScope, factory: ApiFactory): boolean; } // @public (undocumented) export const ApiProvider: { - ({ apis, children, }: PropsWithChildren): JSX.Element; - propTypes: { - apis: PropTypes.Validator any>; - }>>; - children: PropTypes.Requireable; - }; + ({ apis, children }: PropsWithChildren): JSX.Element; + propTypes: { + apis: PropTypes.Validator< + PropTypes.InferProps<{ + get: PropTypes.Validator<(...args: any[]) => any>; + }> + >; + children: PropTypes.Requireable; + }; }; // @public (undocumented) export class ApiRegistry implements ApiHolder { - constructor(apis: Map); - // (undocumented) - static builder(): ApiRegistryBuilder; - // (undocumented) - static from(apis: ApiImpl[]): ApiRegistry; - // (undocumented) - get(api: ApiRef): T | undefined; - static with(api: ApiRef, impl: T): ApiRegistry; - with(api: ApiRef, impl: T): ApiRegistry; + constructor(apis: Map); + // (undocumented) + static builder(): ApiRegistryBuilder; + // (undocumented) + static from(apis: ApiImpl[]): ApiRegistry; + // (undocumented) + get(api: ApiRef): T | undefined; + static with(api: ApiRef, impl: T): ApiRegistry; + with(api: ApiRef, impl: T): ApiRegistry; } // @public (undocumented) export class ApiResolver implements ApiHolder { - constructor(factories: ApiFactoryHolder); - // (undocumented) - get(ref: ApiRef): T | undefined; - static validateFactories(factories: ApiFactoryHolder, apis: Iterable): void; + constructor(factories: ApiFactoryHolder); + // (undocumented) + get(ref: ApiRef): T | undefined; + static validateFactories( + factories: ApiFactoryHolder, + apis: Iterable, + ): void; } // @public (undocumented) export type AppComponents = { - NotFoundErrorPage: ComponentType<{}>; - BootErrorPage: ComponentType; - Progress: ComponentType<{}>; - Router: ComponentType<{}>; - ErrorBoundaryFallback: ComponentType; - SignInPage?: ComponentType; + NotFoundErrorPage: ComponentType<{}>; + BootErrorPage: ComponentType; + Progress: ComponentType<{}>; + Router: ComponentType<{}>; + ErrorBoundaryFallback: ComponentType; + SignInPage?: ComponentType; }; // @public @@ -130,283 +154,382 @@ export type AppConfigLoader = () => Promise; // @public (undocumented) export type AppContext = { - getPlugins(): BackstagePlugin[]; - getSystemIcon(key: string): IconComponent | undefined; - getComponents(): AppComponents; + getPlugins(): BackstagePlugin[]; + getSystemIcon(key: string): IconComponent | undefined; + getComponents(): AppComponents; }; // @public (undocumented) export type AppOptions = { - apis?: Iterable; - icons?: Partial & { - [key in string]: IconComponent; + apis?: Iterable; + icons?: Partial & + { + [key in string]: IconComponent; }; - plugins?: BackstagePluginWithAnyOutput[]; - components?: Partial; - themes?: AppTheme[]; - configLoader?: AppConfigLoader; - bindRoutes?(context: { - bind: AppRouteBinder; - }): void; + plugins?: BackstagePluginWithAnyOutput[]; + components?: Partial; + themes?: AppTheme[]; + configLoader?: AppConfigLoader; + bindRoutes?(context: { bind: AppRouteBinder }): void; }; // @public (undocumented) -export type AppRouteBinder = (externalRoutes: ExternalRoutes, targetRoutes: PartialKeys, KeysWithType>>) => void; + } +>( + externalRoutes: ExternalRoutes, + targetRoutes: PartialKeys< + TargetRouteMap, + KeysWithType> + >, +) => void; // @public (undocumented) export class AppThemeSelector implements AppThemeApi { - constructor(themes: AppTheme[]); - // (undocumented) - activeThemeId$(): Observable; - // (undocumented) - static createWithStorage(themes: AppTheme[]): AppThemeSelector; - // (undocumented) - getActiveThemeId(): string | undefined; - // (undocumented) - getInstalledThemes(): AppTheme[]; - // (undocumented) - setActiveThemeId(themeId?: string): void; - } + constructor(themes: AppTheme[]); + // (undocumented) + activeThemeId$(): Observable; + // (undocumented) + static createWithStorage(themes: AppTheme[]): AppThemeSelector; + // (undocumented) + getActiveThemeId(): string | undefined; + // (undocumented) + getInstalledThemes(): AppTheme[]; + // (undocumented) + setActiveThemeId(themeId?: string): void; +} // @public (undocumented) export class Auth0Auth { - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, defaultScopes, }: OAuthApiCreateOptions): typeof auth0AuthApiRef.T; + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + defaultScopes, + }: OAuthApiCreateOptions): typeof auth0AuthApiRef.T; } // @public (undocumented) export type BackstageApp = { - getPlugins(): BackstagePlugin[]; - getSystemIcon(key: string): IconComponent | undefined; - getProvider(): ComponentType<{}>; - getRouter(): ComponentType<{}>; + getPlugins(): BackstagePlugin[]; + getSystemIcon(key: string): IconComponent | undefined; + getProvider(): ComponentType<{}>; + getRouter(): ComponentType<{}>; }; // @public (undocumented) -export type BackstagePluginWithAnyOutput = Omit, 'output'> & { - output(): (PluginOutput | UnknownPluginOutput)[]; +export type BackstagePluginWithAnyOutput = Omit< + BackstagePlugin, + 'output' +> & { + output(): (PluginOutput | UnknownPluginOutput)[]; }; // @public (undocumented) export type BootErrorPageProps = { - step: 'load-config' | 'load-chunk'; - error: Error; + step: 'load-config' | 'load-chunk'; + error: Error; }; -export { ConfigReader } +export { ConfigReader }; // @public export function createApp(options?: AppOptions): PrivateAppImpl; +// @public +export const defaultConfigLoader: AppConfigLoader; + // @public export class ErrorAlerter implements ErrorApi { - constructor(alertApi: AlertApi, errorApi: ErrorApi); - // (undocumented) - error$(): Observable<{ - error: { - name: string; - message: string; - stack?: string | undefined; - }; - context?: ErrorContext | undefined; - }>; - // (undocumented) - post(error: Error, context?: ErrorContext): void; + constructor(alertApi: AlertApi, errorApi: ErrorApi); + // (undocumented) + error$(): Observable<{ + error: { + name: string; + message: string; + stack?: string | undefined; + }; + context?: ErrorContext | undefined; + }>; + // (undocumented) + post(error: Error, context?: ErrorContext): void; } // @public export class ErrorApiForwarder implements ErrorApi { - // (undocumented) - error$(): Observable<{ - error: Error; - context?: ErrorContext; - }>; - // (undocumented) - post(error: Error, context?: ErrorContext): void; - } + // (undocumented) + error$(): Observable<{ + error: Error; + context?: ErrorContext; + }>; + // (undocumented) + post(error: Error, context?: ErrorContext): void; +} // @public (undocumented) export type ErrorBoundaryFallbackProps = { - plugin?: BackstagePlugin; - error: Error; - resetError: () => void; + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; }; +// @public (undocumented) +export const FeatureFlagged: (props: FeatureFlaggedProps) => JSX.Element; + +// @public (undocumented) +export type FeatureFlaggedProps = { + children: ReactNode; +} & ( + | { + with: string; + } + | { + without: string; + } +); + // @public (undocumented) export const FlatRoutes: (props: FlatRoutesProps) => JSX.Element | null; // @public (undocumented) export class GithubAuth implements OAuthApi, SessionApi { - constructor(sessionManager: SessionManager); - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, defaultScopes, }: OAuthApiCreateOptions): GithubAuth; - // (undocumented) - getAccessToken(scope?: string, options?: AuthRequestOptions): Promise; - // (undocumented) - getBackstageIdentity(options?: AuthRequestOptions): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; - // (undocumented) - static normalizeScope(scope?: string): Set; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; + constructor(sessionManager: SessionManager); + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + defaultScopes, + }: OAuthApiCreateOptions): GithubAuth; + // (undocumented) + getAccessToken(scope?: string, options?: AuthRequestOptions): Promise; + // (undocumented) + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; + // (undocumented) + getProfile(options?: AuthRequestOptions): Promise; + // (undocumented) + static normalizeScope(scope?: string): Set; + // (undocumented) + sessionState$(): Observable; + // (undocumented) + signIn(): Promise; + // (undocumented) + signOut(): Promise; } // @public (undocumented) export type GithubSession = { - providerInfo: { - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; + providerInfo: { + accessToken: string; + scopes: Set; + expiresAt: Date; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; }; // @public (undocumented) export class GitlabAuth { - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, defaultScopes, }: OAuthApiCreateOptions): typeof gitlabAuthApiRef.T; + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + defaultScopes, + }: OAuthApiCreateOptions): typeof gitlabAuthApiRef.T; } // @public (undocumented) export class GoogleAuth { - // (undocumented) - static create({ discoveryApi, oauthRequestApi, environment, provider, defaultScopes, }: OAuthApiCreateOptions): typeof googleAuthApiRef.T; + // (undocumented) + static create({ + discoveryApi, + oauthRequestApi, + environment, + provider, + defaultScopes, + }: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { - // (undocumented) - getRegisteredFlags(): FeatureFlag[]; - // (undocumented) - isActive(name: string): boolean; - // (undocumented) - registerFlag(flag: FeatureFlag): void; - // (undocumented) - save(options: FeatureFlagsSaveOptions): void; + // (undocumented) + getRegisteredFlags(): FeatureFlag[]; + // (undocumented) + isActive(name: string): boolean; + // (undocumented) + registerFlag(flag: FeatureFlag): void; + // (undocumented) + save(options: FeatureFlagsSaveOptions): void; } // @public (undocumented) export class MicrosoftAuth { - // (undocumented) - static create({ environment, provider, oauthRequestApi, discoveryApi, defaultScopes, }: OAuthApiCreateOptions): typeof microsoftAuthApiRef.T; + // (undocumented) + static create({ + environment, + provider, + oauthRequestApi, + discoveryApi, + defaultScopes, + }: OAuthApiCreateOptions): typeof microsoftAuthApiRef.T; } // @public (undocumented) -export class OAuth2 implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, BackstageIdentityApi, SessionApi { - constructor(options: Options); - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, defaultScopes, scopeTransform, }: CreateOptions): OAuth2; - // (undocumented) - getAccessToken(scope?: string | string[], options?: AuthRequestOptions): Promise; - // (undocumented) - getBackstageIdentity(options?: AuthRequestOptions): Promise; - // (undocumented) - getIdToken(options?: AuthRequestOptions): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; +export class OAuth2 + implements + OAuthApi, + OpenIdConnectApi, + ProfileInfoApi, + BackstageIdentityApi, + SessionApi { + constructor(options: Options); + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + defaultScopes, + scopeTransform, + }: CreateOptions): OAuth2; + // (undocumented) + getAccessToken( + scope?: string | string[], + options?: AuthRequestOptions, + ): Promise; + // (undocumented) + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; + // (undocumented) + getIdToken(options?: AuthRequestOptions): Promise; + // (undocumented) + getProfile(options?: AuthRequestOptions): Promise; + // (undocumented) + sessionState$(): Observable; + // (undocumented) + signIn(): Promise; + // (undocumented) + signOut(): Promise; } // @public (undocumented) export type OAuth2Session = { - providerInfo: { - idToken: string; - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; + providerInfo: { + idToken: string; + accessToken: string; + scopes: Set; + expiresAt: Date; + }; + profile: ProfileInfo; + backstageIdentity: BackstageIdentity; }; // @public export class OAuthRequestManager implements OAuthRequestApi { - // (undocumented) - authRequest$(): Observable; - // (undocumented) - createAuthRequester(options: AuthRequesterOptions): AuthRequester; - } + // (undocumented) + authRequest$(): Observable; + // (undocumented) + createAuthRequester(options: AuthRequesterOptions): AuthRequester; +} // @public (undocumented) export class OktaAuth { - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, defaultScopes, }: OAuthApiCreateOptions): typeof oktaAuthApiRef.T; + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + defaultScopes, + }: OAuthApiCreateOptions): typeof oktaAuthApiRef.T; } // @public (undocumented) export class OneLoginAuth { - // (undocumented) - static create({ discoveryApi, environment, provider, oauthRequestApi, }: CreateOptions_2): typeof oneloginAuthApiRef.T; + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + oauthRequestApi, + }: CreateOptions_2): typeof oneloginAuthApiRef.T; } // @public (undocumented) -export class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { - constructor(sessionManager: SessionManager); - // (undocumented) - static create({ discoveryApi, environment, provider, }: AuthApiCreateOptions): SamlAuth; - // (undocumented) - getBackstageIdentity(options?: AuthRequestOptions): Promise; - // (undocumented) - getProfile(options?: AuthRequestOptions): Promise; - // (undocumented) - sessionState$(): Observable; - // (undocumented) - signIn(): Promise; - // (undocumented) - signOut(): Promise; +export class SamlAuth + implements ProfileInfoApi, BackstageIdentityApi, SessionApi { + constructor(sessionManager: SessionManager); + // (undocumented) + static create({ + discoveryApi, + environment, + provider, + }: AuthApiCreateOptions): SamlAuth; + // (undocumented) + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; + // (undocumented) + getProfile(options?: AuthRequestOptions): Promise; + // (undocumented) + sessionState$(): Observable; + // (undocumented) + signIn(): Promise; + // (undocumented) + signOut(): Promise; } // @public (undocumented) export type SignInPageProps = { - onResult(result: SignInResult): void; + onResult(result: SignInResult): void; }; // @public (undocumented) export type SignInResult = { - userId: string; - profile: ProfileInfo; - getIdToken?: () => Promise; - signOut?: () => Promise; + userId: string; + profile: ProfileInfo; + getIdToken?: () => Promise; + signOut?: () => Promise; }; +// @public (undocumented) +export class UnhandledErrorForwarder { + static forward(errorApi: ErrorApi, errorContext: ErrorContext): void; +} + // @public export class UrlPatternDiscovery implements DiscoveryApi { - static compile(pattern: string): UrlPatternDiscovery; - // (undocumented) - getBaseUrl(pluginId: string): Promise; - } + static compile(pattern: string): UrlPatternDiscovery; + // (undocumented) + getBaseUrl(pluginId: string): Promise; +} // @public (undocumented) export class WebStorage implements StorageApi { - constructor(namespace: string, errorApi: ErrorApi); - // (undocumented) - static create(options: CreateStorageApiOptions): WebStorage; - // (undocumented) - forBucket(name: string): WebStorage; - // (undocumented) - get(key: string): T | undefined; - // (undocumented) - observe$(key: string): Observable>; - // (undocumented) - remove(key: string): Promise; - // (undocumented) - set(key: string, data: T): Promise; - } - + constructor(namespace: string, errorApi: ErrorApi); + // (undocumented) + static create(options: CreateStorageApiOptions): WebStorage; + // (undocumented) + forBucket(name: string): WebStorage; + // (undocumented) + get(key: string): T | undefined; + // (undocumented) + observe$(key: string): Observable>; + // (undocumented) + remove(key: string): Promise; + // (undocumented) + set(key: string, data: T): Promise; +} // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts index 0e1d531226..d88c818d11 100644 --- a/packages/core-app-api/config.d.ts +++ b/packages/core-app-api/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 9298a53ea4..0f6773497b 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.2", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.1.2", + "@backstage/core-components": "^0.1.4", "@backstage/config": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,8 +44,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.3", + "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", @@ -55,7 +55,7 @@ "@types/node": "^14.14.32", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.3" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts index 28cb5bc068..0ba275ea4d 100644 --- a/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts +++ b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/AlertApi/index.ts b/packages/core-app-api/src/apis/implementations/AlertApi/index.ts index 12ab8bc60c..d572845809 100644 --- a/packages/core-app-api/src/apis/implementations/AlertApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/AlertApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts index 8d36e6b10e..cab4ec8a88 100644 --- a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts +++ b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts index 3bd4d764ea..4dceed4749 100644 --- a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts +++ b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/AppThemeApi/index.ts b/packages/core-app-api/src/apis/implementations/AppThemeApi/index.ts index cb42c0f875..b0a314303b 100644 --- a/packages/core-app-api/src/apis/implementations/AppThemeApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/AppThemeApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/ConfigApi/index.ts b/packages/core-app-api/src/apis/implementations/ConfigApi/index.ts index 7c7f88a3e5..708c1d4573 100644 --- a/packages/core-app-api/src/apis/implementations/ConfigApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/ConfigApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts index 9597443b98..9cd666e8a3 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts index a9decd9ef9..81cc5a26b1 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts index 24468fdcb6..184346401f 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts index 73d5042c11..57f1ffab66 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts index 875d07c0a3..9c9a6f20f1 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-api/src/plugin/collectors.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts similarity index 56% rename from packages/core-api/src/plugin/collectors.ts rename to packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts index bbcca88e98..b859148e8e 100644 --- a/packages/core-api/src/plugin/collectors.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/UnhandledErrorForwarder.ts @@ -1,3 +1,5 @@ +import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api'; + /* * Copyright 2020 Spotify AB * @@ -14,19 +16,16 @@ * limitations under the License. */ -import { BackstagePlugin } from './types'; -import { getComponentData } from '../extensions'; -import { createCollector } from '../extensions/traversal'; - -export const pluginCollector = createCollector( - () => new Set>(), - (acc, node) => { - const plugin = getComponentData>( - node, - 'core.plugin', +export class UnhandledErrorForwarder { + /** + * Add event listener, such that unhandled errors can be forwarded using an given `ErrorApi` instance + */ + static forward(errorApi: ErrorApi, errorContext: ErrorContext) { + window.addEventListener( + 'unhandledrejection', + (e: PromiseRejectionEvent) => { + errorApi.post(e.reason as Error, errorContext); + }, ); - if (plugin) { - acc.add(plugin); - } - }, -); + } +} diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/index.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/index.ts index 757dfd0d8f..0d82894060 100644 --- a/packages/core-app-api/src/apis/implementations/ErrorApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/ErrorApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,3 +16,4 @@ export { ErrorAlerter } from './ErrorAlerter'; export { ErrorApiForwarder } from './ErrorApiForwarder'; +export { UnhandledErrorForwarder } from './UnhandledErrorForwarder'; diff --git a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx index a100f01a52..afc25b2e8c 100644 --- a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx +++ b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx index 800ae98f24..3ee62b3d6e 100644 --- a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx +++ b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/index.ts b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/index.ts index 33990584f3..fc806c9ae1 100644 --- a/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/FeatureFlagsApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts index 32170acc6f..d0f137f0be 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts index 46d620c329..4a0a07ef34 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts index 280321daa0..20b0cbba67 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts index 370ba9a3a1..d91cb0ebb0 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts index 46a5362f35..d8faf90229 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts index e50c18d271..6c00cd85cd 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/index.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/index.ts index 0fe9bfae0f..72e42872fd 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.test.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.test.ts index da5b2509db..6ae43b6100 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.test.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts index 3f49b644f6..d1f712e72a 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/index.ts b/packages/core-app-api/src/apis/implementations/StorageApi/index.ts index 33b0094551..2f941381fd 100644 --- a/packages/core-app-api/src/apis/implementations/StorageApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/StorageApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts b/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts index 391a709935..5f074252bf 100644 --- a/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/auth0/index.ts b/packages/core-app-api/src/apis/implementations/auth/auth0/index.ts index dda27d0fa3..9daed7d13e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/auth0/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/auth0/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts index 3ff29cc6ef..04bfff028d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts index 2ab0e5390f..21ebc5699a 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/github/index.ts b/packages/core-app-api/src/apis/implementations/auth/github/index.ts index 9e1722f4a4..ee4334f6fc 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/github/types.ts b/packages/core-app-api/src/apis/implementations/auth/github/types.ts index 95beef3668..88df25b49d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts index 6a592c7fbe..3346af9b7c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts index fbb9509e7d..642600558c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/index.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/index.ts index 42d7210551..935a11dd54 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts index 9e8569c5cf..f8a1c3a1f5 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts index 1074a192ef..39e4a896e4 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/google/index.ts b/packages/core-app-api/src/apis/implementations/auth/google/index.ts index 2521d46046..96a8699eab 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/index.ts b/packages/core-app-api/src/apis/implementations/auth/index.ts index 7ef78d19cf..9889a33878 100644 --- a/packages/core-app-api/src/apis/implementations/auth/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts index 490e74b532..b0f7f42a10 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/index.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/index.ts index 77328d8557..44bca3d37c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts index 93db2c732d..4f030d1c64 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index c6c251a4f5..8c27e0aa63 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/index.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/index.ts index 52bcb1df2c..793be515b7 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index ade0f1a6c6..be0fbf38a2 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts index d6b1a07d9d..6489b326ff 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts index a86e3f272d..5ef9bf04a0 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/index.ts b/packages/core-app-api/src/apis/implementations/auth/okta/index.ts index 4cc774b26b..c20ce4e16b 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index 2e8c7ac2e8..bb0aebae94 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts index 1d163207db..e1826f17dd 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts index 7f747d6977..ae4f80c9b1 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/index.ts b/packages/core-app-api/src/apis/implementations/auth/saml/index.ts index c2436ab435..930e6cb115 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/index.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/types.ts b/packages/core-app-api/src/apis/implementations/auth/saml/types.ts index b62826b2ea..70cbf41ee4 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/auth/types.ts b/packages/core-app-api/src/apis/implementations/auth/types.ts index a752e68eba..89343e9e06 100644 --- a/packages/core-app-api/src/apis/implementations/auth/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/implementations/index.ts b/packages/core-app-api/src/apis/implementations/index.ts index d0df2760ab..31f01f2bff 100644 --- a/packages/core-app-api/src/apis/implementations/index.ts +++ b/packages/core-app-api/src/apis/implementations/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/index.ts b/packages/core-app-api/src/apis/index.ts index 5652dadf79..088eba3c4c 100644 --- a/packages/core-app-api/src/apis/index.ts +++ b/packages/core-app-api/src/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiAggregator.test.ts b/packages/core-app-api/src/apis/system/ApiAggregator.test.ts index c38aa08fad..c2754878f2 100644 --- a/packages/core-app-api/src/apis/system/ApiAggregator.test.ts +++ b/packages/core-app-api/src/apis/system/ApiAggregator.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiAggregator.ts b/packages/core-app-api/src/apis/system/ApiAggregator.ts index 1299e38da0..a6b4471f8c 100644 --- a/packages/core-app-api/src/apis/system/ApiAggregator.ts +++ b/packages/core-app-api/src/apis/system/ApiAggregator.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.test.ts b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.test.ts index 160019604e..e859e8a491 100644 --- a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.test.ts +++ b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts index fe17f5a600..28ed9f85a1 100644 --- a/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiFactoryRegistry.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx index 7d38ec3cf9..5afb39a2ec 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiProvider.tsx b/packages/core-app-api/src/apis/system/ApiProvider.tsx index 93d20b12fc..ce6c388087 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiRegistry.test.ts b/packages/core-app-api/src/apis/system/ApiRegistry.test.ts index 0931e2c103..c0be9f8d02 100644 --- a/packages/core-app-api/src/apis/system/ApiRegistry.test.ts +++ b/packages/core-app-api/src/apis/system/ApiRegistry.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiRegistry.ts b/packages/core-app-api/src/apis/system/ApiRegistry.ts index 4571d52cce..7669810a1e 100644 --- a/packages/core-app-api/src/apis/system/ApiRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiRegistry.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiResolver.test.ts b/packages/core-app-api/src/apis/system/ApiResolver.test.ts index af8dbc28c6..01491684f4 100644 --- a/packages/core-app-api/src/apis/system/ApiResolver.test.ts +++ b/packages/core-app-api/src/apis/system/ApiResolver.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/ApiResolver.ts b/packages/core-app-api/src/apis/system/ApiResolver.ts index 1fbce4871a..dd29ff5010 100644 --- a/packages/core-app-api/src/apis/system/ApiResolver.ts +++ b/packages/core-app-api/src/apis/system/ApiResolver.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/index.ts b/packages/core-app-api/src/apis/system/index.ts index dd7c081f62..23e1a9a4b8 100644 --- a/packages/core-app-api/src/apis/system/index.ts +++ b/packages/core-app-api/src/apis/system/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/apis/system/types.ts b/packages/core-app-api/src/apis/system/types.ts index f1ba82c21e..bb0b9d42ea 100644 --- a/packages/core-app-api/src/apis/system/types.ts +++ b/packages/core-app-api/src/apis/system/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/App.test.tsx b/packages/core-app-api/src/app/App.test.tsx index 0539c2146c..14ec71cb36 100644 --- a/packages/core-app-api/src/app/App.test.tsx +++ b/packages/core-app-api/src/app/App.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/App.tsx b/packages/core-app-api/src/app/App.tsx index a5ba5c4bca..a95554709a 100644 --- a/packages/core-app-api/src/app/App.tsx +++ b/packages/core-app-api/src/app/App.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ import { } from '../extensions/traversal'; import { pluginCollector } from '../plugins/collectors'; import { + featureFlagCollector, routeObjectCollector, routeParentCollector, routePathCollector, @@ -215,7 +216,12 @@ export class PrivateAppImpl implements BackstageApp { [], ); - const { routePaths, routeParents, routeObjects } = useMemo(() => { + const { + routePaths, + routeParents, + routeObjects, + featureFlags, + } = useMemo(() => { const result = traverseElementTree({ root: children, discoverers: [childDiscoverer, routeElementDiscoverer], @@ -224,6 +230,7 @@ export class PrivateAppImpl implements BackstageApp { routeParents: routeParentCollector, routeObjects: routeObjectCollector, collectedPlugins: pluginCollector, + featureFlags: featureFlagCollector, }, }); @@ -238,7 +245,6 @@ export class PrivateAppImpl implements BackstageApp { // Initialize APIs once all plugins are available this.getApiHolder(); - return result; }, [children]); @@ -273,8 +279,14 @@ export class PrivateAppImpl implements BackstageApp { } } } + + // Go through the featureFlags returned from the traversal and + // register those now the configApi has been loaded + for (const name of featureFlags) { + featureFlagsApi.registerFlag({ name, pluginId: '' }); + } } - }, [hasConfigApi, loadedConfig]); + }, [hasConfigApi, loadedConfig, featureFlags]); if ('node' in loadedConfig) { // Loading or error diff --git a/packages/core-app-api/src/app/AppContext.test.tsx b/packages/core-app-api/src/app/AppContext.test.tsx index 224499b148..d87bf7d228 100644 --- a/packages/core-app-api/src/app/AppContext.test.tsx +++ b/packages/core-app-api/src/app/AppContext.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/AppContext.tsx b/packages/core-app-api/src/app/AppContext.tsx index 9cdd4cdc13..c583478ec2 100644 --- a/packages/core-app-api/src/app/AppContext.tsx +++ b/packages/core-app-api/src/app/AppContext.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/AppIdentity.ts b/packages/core-app-api/src/app/AppIdentity.ts index b2b4a0bcdc..64e698102f 100644 --- a/packages/core-app-api/src/app/AppIdentity.ts +++ b/packages/core-app-api/src/app/AppIdentity.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/AppThemeProvider.tsx b/packages/core-app-api/src/app/AppThemeProvider.tsx index d297b05ced..4e283a1d00 100644 --- a/packages/core-app-api/src/app/AppThemeProvider.tsx +++ b/packages/core-app-api/src/app/AppThemeProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/createApp.test.tsx b/packages/core-app-api/src/app/createApp.test.tsx index a800c11ea6..8c16dfe45e 100644 --- a/packages/core-app-api/src/app/createApp.test.tsx +++ b/packages/core-app-api/src/app/createApp.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/createApp.tsx b/packages/core-app-api/src/app/createApp.tsx index 1670978791..b7c49939e2 100644 --- a/packages/core-app-api/src/app/createApp.tsx +++ b/packages/core-app-api/src/app/createApp.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/defaultApis.ts b/packages/core-app-api/src/app/defaultApis.ts index 5d84e27f89..2322f72e8d 100644 --- a/packages/core-app-api/src/app/defaultApis.ts +++ b/packages/core-app-api/src/app/defaultApis.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import { UrlPatternDiscovery, SamlAuth, OneLoginAuth, + UnhandledErrorForwarder, } from '../apis'; import { @@ -67,8 +68,11 @@ export const defaultApis = [ createApiFactory({ api: errorApiRef, deps: { alertApi: alertApiRef }, - factory: ({ alertApi }) => - new ErrorAlerter(alertApi, new ErrorApiForwarder()), + factory: ({ alertApi }) => { + const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); + UnhandledErrorForwarder.forward(errorApi, { hidden: false }); + return errorApi; + }, }), createApiFactory({ api: storageApiRef, diff --git a/packages/core-app-api/src/app/icons.tsx b/packages/core-app-api/src/app/icons.tsx index 9ec278ba7f..bf45155096 100644 --- a/packages/core-app-api/src/app/icons.tsx +++ b/packages/core-app-api/src/app/icons.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/app/index.ts b/packages/core-app-api/src/app/index.ts index a7cdf22a43..ab82774cad 100644 --- a/packages/core-app-api/src/app/index.ts +++ b/packages/core-app-api/src/app/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,5 +14,5 @@ * limitations under the License. */ -export { createApp } from './createApp'; +export { createApp, defaultConfigLoader } from './createApp'; export * from './types'; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index cf5811beae..836f1b7fb0 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/componentData.test.tsx b/packages/core-app-api/src/extensions/componentData.test.tsx index 417fe07414..808ab08cf1 100644 --- a/packages/core-app-api/src/extensions/componentData.test.tsx +++ b/packages/core-app-api/src/extensions/componentData.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/componentData.tsx b/packages/core-app-api/src/extensions/componentData.tsx index fc8594039c..d4975d9eef 100644 --- a/packages/core-app-api/src/extensions/componentData.tsx +++ b/packages/core-app-api/src/extensions/componentData.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/extensions.tsx b/packages/core-app-api/src/extensions/extensions.tsx index a9121c4d15..063f34c659 100644 --- a/packages/core-app-api/src/extensions/extensions.tsx +++ b/packages/core-app-api/src/extensions/extensions.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/index.ts b/packages/core-app-api/src/extensions/index.ts index 26a0c597b1..914d76aebf 100644 --- a/packages/core-app-api/src/extensions/index.ts +++ b/packages/core-app-api/src/extensions/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/traversal.test.tsx b/packages/core-app-api/src/extensions/traversal.test.tsx index 38571fdcc7..0acc51fb27 100644 --- a/packages/core-app-api/src/extensions/traversal.test.tsx +++ b/packages/core-app-api/src/extensions/traversal.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/extensions/traversal.ts b/packages/core-app-api/src/extensions/traversal.ts index 4431bbd24c..79f44523b3 100644 --- a/packages/core-app-api/src/extensions/traversal.ts +++ b/packages/core-app-api/src/extensions/traversal.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/index.test.ts b/packages/core-app-api/src/index.test.ts deleted file mode 100644 index bec176fda8..0000000000 --- a/packages/core-app-api/src/index.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 * as index from '.'; - -describe('index', () => { - it('exports the app api', () => { - expect(index).toEqual({ - // Public API - createApp: expect.any(Function), - ApiProvider: expect.any(Function), - // TODO(Rugvip): Figure out if we need these - ApiFactoryRegistry: expect.any(Function), - ApiResolver: expect.any(Function), - ApiRegistry: expect.any(Function), - - // Components - FlatRoutes: expect.any(Function), - - // Utility API Implementations - AlertApiForwarder: expect.any(Function), - AppThemeSelector: expect.any(Function), - Auth0Auth: expect.any(Function), - ConfigReader: expect.any(Function), - ErrorAlerter: expect.any(Function), - ErrorApiForwarder: expect.any(Function), - GithubAuth: expect.any(Function), - GitlabAuth: expect.any(Function), - GoogleAuth: expect.any(Function), - LocalStorageFeatureFlags: expect.any(Function), - MicrosoftAuth: expect.any(Function), - OAuth2: expect.any(Function), - OAuthRequestManager: expect.any(Function), - OktaAuth: expect.any(Function), - OneLoginAuth: expect.any(Function), - SamlAuth: expect.any(Function), - UrlPatternDiscovery: expect.any(Function), - WebStorage: expect.any(Function), - }); - }); -}); diff --git a/packages/core-app-api/src/index.ts b/packages/core-app-api/src/index.ts index 816e788a2b..f522bf3a0c 100644 --- a/packages/core-app-api/src/index.ts +++ b/packages/core-app-api/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 391b86952d..524a0c5709 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 1498657281..81da04010c 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts index e5b3ca1219..61fdd825a2 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.test.ts index cd7986ffd0..0758c0ba29 100644 --- a/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.ts index 9134fd0773..db978e7018 100644 --- a/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/MockAuthConnector.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/index.ts b/packages/core-app-api/src/lib/AuthConnector/index.ts index 388619e2c1..047583f3ac 100644 --- a/packages/core-app-api/src/lib/AuthConnector/index.ts +++ b/packages/core-app-api/src/lib/AuthConnector/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthConnector/types.ts b/packages/core-app-api/src/lib/AuthConnector/types.ts index 46175a265f..464a2ed627 100644 --- a/packages/core-app-api/src/lib/AuthConnector/types.ts +++ b/packages/core-app-api/src/lib/AuthConnector/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts index 5c960f7876..4ceadd51ba 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.ts index 224036d283..057a70e58d 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts index ce273847cd..41347d0106 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts index a098b384f9..d31f5e29bf 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts b/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts index 72ca789cc9..b70b4bf4ca 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts index 6280750875..26f489a1b4 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts index e4f144b0a9..b7940d88c4 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/common.ts b/packages/core-app-api/src/lib/AuthSessionManager/common.ts index ff2897535d..002b6c616b 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/common.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/common.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/index.ts b/packages/core-app-api/src/lib/AuthSessionManager/index.ts index 5f4dde8662..85ef2013e9 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/index.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/AuthSessionManager/types.ts b/packages/core-app-api/src/lib/AuthSessionManager/types.ts index 0bed895fba..43655b8395 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/types.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/globalObject.test.ts b/packages/core-app-api/src/lib/globalObject.test.ts index e72f027b46..a658b253a6 100644 --- a/packages/core-app-api/src/lib/globalObject.test.ts +++ b/packages/core-app-api/src/lib/globalObject.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/globalObject.ts b/packages/core-app-api/src/lib/globalObject.ts index 87be58499d..ad70a61110 100644 --- a/packages/core-app-api/src/lib/globalObject.ts +++ b/packages/core-app-api/src/lib/globalObject.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/index.ts b/packages/core-app-api/src/lib/index.ts index 10f213b50f..1327aab4c2 100644 --- a/packages/core-app-api/src/lib/index.ts +++ b/packages/core-app-api/src/lib/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/loginPopup.test.ts b/packages/core-app-api/src/lib/loginPopup.test.ts index 98541c268e..1eb7c3f8b9 100644 --- a/packages/core-app-api/src/lib/loginPopup.test.ts +++ b/packages/core-app-api/src/lib/loginPopup.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/loginPopup.ts b/packages/core-app-api/src/lib/loginPopup.ts index 716c4f9651..b6c14d60c9 100644 --- a/packages/core-app-api/src/lib/loginPopup.ts +++ b/packages/core-app-api/src/lib/loginPopup.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/subjects.test.ts b/packages/core-app-api/src/lib/subjects.test.ts index 31ed26d97d..eab5757898 100644 --- a/packages/core-app-api/src/lib/subjects.test.ts +++ b/packages/core-app-api/src/lib/subjects.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/subjects.ts b/packages/core-app-api/src/lib/subjects.ts index 52870538f0..0391050bda 100644 --- a/packages/core-app-api/src/lib/subjects.ts +++ b/packages/core-app-api/src/lib/subjects.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/versionedValues.test.ts b/packages/core-app-api/src/lib/versionedValues.test.ts index 6ba7db4970..19f9c8f349 100644 --- a/packages/core-app-api/src/lib/versionedValues.test.ts +++ b/packages/core-app-api/src/lib/versionedValues.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/lib/versionedValues.ts b/packages/core-app-api/src/lib/versionedValues.ts index 88e4e90084..3d0a4a41ae 100644 --- a/packages/core-app-api/src/lib/versionedValues.ts +++ b/packages/core-app-api/src/lib/versionedValues.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/plugins/collectors.test.tsx b/packages/core-app-api/src/plugins/collectors.test.tsx index 46c19ec7bf..acbe337e44 100644 --- a/packages/core-app-api/src/plugins/collectors.test.tsx +++ b/packages/core-app-api/src/plugins/collectors.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/plugins/collectors.ts b/packages/core-app-api/src/plugins/collectors.ts index 8e5b623164..11e5cd51b3 100644 --- a/packages/core-app-api/src/plugins/collectors.ts +++ b/packages/core-app-api/src/plugins/collectors.ts @@ -1,20 +1,5 @@ /* - * 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. - */ -/* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/plugins/index.ts b/packages/core-app-api/src/plugins/index.ts index 95794a7487..820697f8bb 100644 --- a/packages/core-app-api/src/plugins/index.ts +++ b/packages/core-app-api/src/plugins/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/FeatureFlagged.test.tsx b/packages/core-app-api/src/routing/FeatureFlagged.test.tsx new file mode 100644 index 0000000000..2b05c8f61d --- /dev/null +++ b/packages/core-app-api/src/routing/FeatureFlagged.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { FeatureFlagged } from './FeatureFlagged'; +import { render } from '@testing-library/react'; +import { ApiProvider, ApiRegistry, LocalStorageFeatureFlags } from '../apis'; +import { featureFlagsApiRef } from '@backstage/core-plugin-api'; + +const mockFeatureFlagsApi = new LocalStorageFeatureFlags(); +const Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + +); + +describe('FeatureFlagged', () => { + describe('with', () => { + it('should render contents when the feature flag is enabled', async () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => true); + + const { queryByText } = render( + +
+ +

BACKSTAGE!

+
+
+
, + ); + + expect(await queryByText('BACKSTAGE!')).toBeInTheDocument(); + }); + it('should not render contents when the feature flag is disabled', async () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => false); + + const { queryByText } = render( + +
+ +

BACKSTAGE!

+
+
+
, + ); + + expect(await queryByText('BACKSTAGE!')).not.toBeInTheDocument(); + }); + }); + describe('without', () => { + it('should not render contents when the feature flag is enabled', async () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => true); + + const { queryByText } = render( + +
+ +

BACKSTAGE!

+
+
+
, + ); + + expect(await queryByText('BACKSTAGE!')).not.toBeInTheDocument(); + }); + it('should render contents when the feature flag is disabled', async () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => false); + + const { queryByText } = render( + +
+ +

BACKSTAGE!

+
+
+
, + ); + + expect(await queryByText('BACKSTAGE!')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/core-app-api/src/routing/FeatureFlagged.tsx b/packages/core-app-api/src/routing/FeatureFlagged.tsx new file mode 100644 index 0000000000..47561d4135 --- /dev/null +++ b/packages/core-app-api/src/routing/FeatureFlagged.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + featureFlagsApiRef, + useApi, + attachComponentData, +} from '@backstage/core-plugin-api'; +import React, { ReactNode } from 'react'; + +export type FeatureFlaggedProps = { children: ReactNode } & ( + | { with: string } + | { without: string } +); + +export const FeatureFlagged = (props: FeatureFlaggedProps) => { + const { children } = props; + const featureFlagApi = useApi(featureFlagsApiRef); + const isEnabled = + 'with' in props + ? featureFlagApi.isActive(props.with) + : !featureFlagApi.isActive(props.without); + return <>{isEnabled ? children : null}; +}; + +attachComponentData(FeatureFlagged, 'core.featureFlagged', true); diff --git a/packages/core-app-api/src/routing/FlatRoutes.test.tsx b/packages/core-app-api/src/routing/FlatRoutes.test.tsx index a9b83d1016..a73534a272 100644 --- a/packages/core-app-api/src/routing/FlatRoutes.test.tsx +++ b/packages/core-app-api/src/routing/FlatRoutes.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,25 +17,36 @@ import { render, RenderResult } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter, Route, Routes, useOutlet } from 'react-router-dom'; +import { ApiProvider, ApiRegistry, LocalStorageFeatureFlags } from '../apis'; +import { featureFlagsApiRef } from '@backstage/core-plugin-api'; import { AppContext } from '../app'; import { AppContextProvider } from '../app/AppContext'; import { FlatRoutes } from './FlatRoutes'; +const mockFeatureFlagsApi = new LocalStorageFeatureFlags(); +const Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + +); + function makeRouteRenderer(node: ReactNode) { let rendered: RenderResult | undefined = undefined; return (path: string) => { const content = ( - ({ - NotFoundErrorPage: () => <>Not Found, - }), - } as unknown) as AppContext - } - > - - + + ({ + NotFoundErrorPage: () => <>Not Found, + }), + } as unknown) as AppContext + } + > + + + ); if (rendered) { rendered.unmount(); diff --git a/packages/core-app-api/src/routing/FlatRoutes.tsx b/packages/core-app-api/src/routing/FlatRoutes.tsx index 54ef092965..6ba82203fd 100644 --- a/packages/core-app-api/src/routing/FlatRoutes.tsx +++ b/packages/core-app-api/src/routing/FlatRoutes.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,53 +14,16 @@ * limitations under the License. */ -import React, { ReactNode, Children, isValidElement, Fragment } from 'react'; +import React, { ReactNode } from 'react'; import { useRoutes } from 'react-router-dom'; -import { useApp } from '@backstage/core-plugin-api'; +import { useApp, useElementFilter } from '@backstage/core-plugin-api'; type RouteObject = { path: string; - element: JSX.Element; + element: ReactNode; children?: RouteObject[]; }; -// Similar to the same function from react-router, this collects routes from the -// children, but only the first level of routes -function createRoutesFromChildren(childrenNode: ReactNode): RouteObject[] { - return Children.toArray(childrenNode).flatMap(child => { - if (!isValidElement(child)) { - return []; - } - - const { children } = child.props; - - if (child.type === Fragment) { - return createRoutesFromChildren(children); - } - - let path = child.props.path as string | undefined; - - // TODO(Rugvip): Work around plugins registering empty paths, remove once deprecated routes are gone - if (path === '') { - return []; - } - path = path?.replace(/\/\*$/, '') ?? '/'; - - return [ - { - path, - element: child, - children: children && [ - { - path: '/*', - element: children, - }, - ], - }, - ]; - }); -} - type FlatRoutesProps = { children: ReactNode; }; @@ -68,14 +31,41 @@ type FlatRoutesProps = { export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { const app = useApp(); const { NotFoundErrorPage } = app.getComponents(); - const routes = createRoutesFromChildren(props.children) - // Routes are sorted to work around a bug where prefixes are unexpectedly matched - .sort((a, b) => b.path.localeCompare(a.path)) - // We make sure all routes have '/*' appended, except '/' - .map(obj => { - obj.path = obj.path === '/' ? '/' : `${obj.path}/*`; - return obj; - }); + const routes = useElementFilter(props.children, elements => + elements + .getElements<{ path?: string; children: ReactNode }>() + .flatMap(child => { + let path = child.props.path; + + // TODO(Rugvip): Work around plugins registering empty paths, remove once deprecated routes are gone + if (path === '') { + return []; + } + path = path?.replace(/\/\*$/, '') ?? '/'; + + return [ + { + path, + element: child, + children: child.props.children + ? [ + { + path: '/*', + element: child.props.children, + }, + ] + : undefined, + }, + ]; + }) + // Routes are sorted to work around a bug where prefixes are unexpectedly matched + .sort((a, b) => b.path.localeCompare(a.path)) + // We make sure all routes have '/*' appended, except '/' + .map(obj => { + obj.path = obj.path === '/' ? '/' : `${obj.path}/*`; + return obj; + }), + ); // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop routes.push({ diff --git a/packages/core-app-api/src/routing/RouteResolver.test.ts b/packages/core-app-api/src/routing/RouteResolver.test.ts index f75617e7a5..b47a18ecce 100644 --- a/packages/core-app-api/src/routing/RouteResolver.test.ts +++ b/packages/core-app-api/src/routing/RouteResolver.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/RouteResolver.ts b/packages/core-app-api/src/routing/RouteResolver.ts index 38158f750e..e09da28df8 100644 --- a/packages/core-app-api/src/routing/RouteResolver.ts +++ b/packages/core-app-api/src/routing/RouteResolver.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/RoutingProvider.test.tsx b/packages/core-app-api/src/routing/RoutingProvider.test.tsx index b75e68e7f0..499b81a743 100644 --- a/packages/core-app-api/src/routing/RoutingProvider.test.tsx +++ b/packages/core-app-api/src/routing/RoutingProvider.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/RoutingProvider.tsx b/packages/core-app-api/src/routing/RoutingProvider.tsx index 86507d2ff4..ea8e8f76f8 100644 --- a/packages/core-app-api/src/routing/RoutingProvider.tsx +++ b/packages/core-app-api/src/routing/RoutingProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/collectors.test.tsx b/packages/core-app-api/src/routing/collectors.test.tsx index 50c8ec1772..d37b5e3016 100644 --- a/packages/core-app-api/src/routing/collectors.test.tsx +++ b/packages/core-app-api/src/routing/collectors.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/collectors.tsx b/packages/core-app-api/src/routing/collectors.tsx index e940a2cada..fcbe3ca923 100644 --- a/packages/core-app-api/src/routing/collectors.tsx +++ b/packages/core-app-api/src/routing/collectors.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; import { BackstageRouteObject } from './types'; import { getComponentData } from '../extensions'; import { createCollector } from '../extensions/traversal'; +import { FeatureFlagged, FeatureFlaggedProps } from './FeatureFlagged'; function getMountPoint(node: ReactElement): RouteRef | undefined { const element: ReactNode = node.props?.element; @@ -171,3 +172,13 @@ export const routeObjectCollector = createCollector( return parentObj; }, ); + +export const featureFlagCollector = createCollector( + () => new Set(), + (acc, node) => { + if (node.type === FeatureFlagged) { + const props = node.props as FeatureFlaggedProps; + acc.add('with' in props ? props.with : props.without); + } + }, +); diff --git a/packages/core-app-api/src/routing/index.ts b/packages/core-app-api/src/routing/index.ts index 7982333f4b..4169fffc88 100644 --- a/packages/core-app-api/src/routing/index.ts +++ b/packages/core-app-api/src/routing/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,3 +15,5 @@ */ export { FlatRoutes } from './FlatRoutes'; +export { FeatureFlagged } from './FeatureFlagged'; +export type { FeatureFlaggedProps } from './FeatureFlagged'; diff --git a/packages/core-app-api/src/routing/types.ts b/packages/core-app-api/src/routing/types.ts index 53128642f9..6561a0da70 100644 --- a/packages/core-app-api/src/routing/types.ts +++ b/packages/core-app-api/src/routing/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/routing/validation.ts b/packages/core-app-api/src/routing/validation.ts index 2d32471a14..51078a0130 100644 --- a/packages/core-app-api/src/routing/validation.ts +++ b/packages/core-app-api/src/routing/validation.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-app-api/src/setupTests.ts b/packages/core-app-api/src/setupTests.ts index aea2220869..c1d649f2ad 100644 --- a/packages/core-app-api/src/setupTests.ts +++ b/packages/core-app-api/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 83e5bad9f5..1536cf3fb2 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/core-components +## 0.1.5 + +### Patch Changes + +- a446bffdb: Improve UX of the Sidebar by adding SidebarScrollWrapper component allowing the user to scroll through Plugins & Shortcuts on smaller screens. Prevent the Sidebar from opening on click on small devices +- f11e50ea7: - Enhanced core `Button` component to open external links in new tab. + - Replaced the use of `Button` component from material by `core-components` in tools card. +- 76bb7aeda: Show scroll bar of the sidebar wrapper only on hover +- 2a13aa1b7: Handle empty code blocks in markdown files so they don't fail rendering +- 47748c7e6: Fix error in error panel, and console warnings about DOM nesting pre inside p +- 34352a79c: Add edit button to Group Profile Card +- 612e25fd7: Add custom styles to scroll bar of the sidebar wrapper to fix flaky behaviour + +## 0.1.4 + +### Patch Changes + +- f423891ee: Fixed sizing of the System diagram when the rendered graph was wider than the container. +- 3db266fe4: Make `ErrorBoundary` display more helpful information about the error that + occurred. + + The `slackChannel` (optional) prop can now be passed as an object on the form + `{ name: string; href?: string; }` in addition to the old string form. If you + are using the error boundary like + + ```tsx + + + + ``` + + you may like to migrate it to + + ```tsx + const support = { + name: '#support', + href: 'https://slack.com/channels/your-channel', + }; + + + + + ``` + + Also deprecated the prop `slackChannel` on `TabbedCard` and `InfoCard`, while + adding the prop `errorBoundaryProps` to replace it. + +- e8c65b068: Clear the previously selected sign-in provider on failure + +## 0.1.3 + +### Patch Changes + +- d2c31b132: Add title prop in SupportButton component +- d4644f592: Use the Backstage `Link` component in the `Button` + ## 0.1.2 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md new file mode 100644 index 0000000000..3d8e60d4fb --- /dev/null +++ b/packages/core-components/api-report.md @@ -0,0 +1,2411 @@ +## API Report File for "@backstage/core-components" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { BackstageIdentityApi } from '@backstage/core-plugin-api'; +import { Breadcrumbs as Breadcrumbs_2 } from '@material-ui/core'; +import { ButtonProps } from '@material-ui/core'; +import { ButtonTypeMap } from '@material-ui/core'; +import { CardHeaderProps } from '@material-ui/core'; +import { Column } from 'material-table'; +import { CommonProps } from '@material-ui/core/OverridableComponent'; +import { ComponentClass } from 'react'; +import { ComponentProps } from 'react'; +import { ComponentType } from 'react'; +import { Context } from 'react'; +import { default as CSS_2 } from 'csstype'; +import { CSSProperties } from 'react'; +import { default as dagre_2 } from 'dagre'; +import { ElementType } from 'react'; +import { ErrorInfo } from 'react'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { LinearProgressProps } from '@material-ui/core'; +import { LinkProps as LinkProps_2 } from '@material-ui/core'; +import { LinkProps as LinkProps_3 } from 'react-router-dom'; +import { MaterialTableProps } from 'material-table'; +import { NavLinkProps } from 'react-router-dom'; +import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { PropsWithChildren } from 'react'; +import PropTypes from 'prop-types'; +import { default as React_2 } from 'react'; +import * as React_3 from 'react'; +import { ReactElement } from 'react'; +import { ReactNode } from 'react'; +import { SessionApi } from '@backstage/core-plugin-api'; +import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SparklinesLineProps } from 'react-sparklines'; +import { SparklinesProps } from 'react-sparklines'; +import { StyledComponentProps } from '@material-ui/core'; +import { StyleRules } from '@material-ui/styles'; +import { TabProps } from '@material-ui/core'; +import { TextTruncateProps } from 'react-text-truncate'; +import { Theme } from '@material-ui/core'; +import { TooltipProps } from '@material-ui/core'; +import { WithStyles } from '@material-ui/core'; + +// @public (undocumented) +export const AlertDisplay: () => JSX.Element | null; + +// @public (undocumented) +enum Alignment { + // (undocumented) + DOWN_LEFT = 'DL', + // (undocumented) + DOWN_RIGHT = 'DR', + // (undocumented) + UP_LEFT = 'UL', + // (undocumented) + UP_RIGHT = 'UR', +} + +// @public (undocumented) +export const Avatar: ({ + displayName, + picture, + customStyles, +}: AvatarProps) => JSX.Element; + +// @public (undocumented) +export const Breadcrumbs: ({ children, ...props }: Props_25) => JSX.Element; + +// @public (undocumented) +export const BrokenImageIcon: IconComponent; + +// @public +export const Button: React_2.ForwardRefExoticComponent< + Pick< + Props, + | 'replace' + | 'media' + | 'hidden' + | 'dir' + | 'form' + | 'slot' + | 'title' + | 'disabled' + | 'color' + | 'size' + | 'underline' + | 'display' + | 'translate' + | 'prefix' + | 'children' + | 'key' + | 'value' + | 'id' + | 'name' + | 'action' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | 'component' + | 'variant' + | 'download' + | 'href' + | 'hrefLang' + | 'ping' + | 'rel' + | 'target' + | 'type' + | 'referrerPolicy' + | 'disableElevation' + | 'fullWidth' + | 'startIcon' + | 'endIcon' + | 'noWrap' + | 'gutterBottom' + | 'paragraph' + | 'autoFocus' + | 'formAction' + | 'formEncType' + | 'formMethod' + | 'formNoValidate' + | 'formTarget' + | 'disableFocusRipple' + | 'buttonRef' + | 'centerRipple' + | 'disableRipple' + | 'disableTouchRipple' + | 'focusRipple' + | 'focusVisibleClassName' + | 'onFocusVisible' + | 'TouchRippleProps' + | 'align' + | 'variantMapping' + | 'to' + | 'state' + | 'TypographyClasses' + | keyof CommonProps> + > & + React_2.RefAttributes +>; + +// @public (undocumented) +export const CardTab: ({ + children, + ...props +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const CatalogIcon: IconComponent; + +// @public (undocumented) +export const ChatIcon: IconComponent; + +// @public (undocumented) +export const CodeSnippet: ({ + text, + language, + showLineNumbers, + showCopyCodeButton, + highlightedNumbers, + customStyle, +}: Props_2) => JSX.Element; + +// @public (undocumented) +export const Content: ({ + className, + stretch, + noPadding, + children, + ...props +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const ContentHeader: ({ + description, + title, + titleComponent: TitleComponent, + children, + textAlign, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const CopyTextButton: { + (props: Props_3): JSX.Element; + propTypes: { + text: PropTypes.Validator; + tooltipDelay: PropTypes.Requireable; + tooltipText: PropTypes.Requireable; + }; +}; + +// @public (undocumented) +export const DashboardIcon: IconComponent; + +// @public (undocumented) +type DependencyEdge = T & { + from: string; + to: string; + label?: string; +}; + +// @public (undocumented) +export function DependencyGraph({ + edges, + nodes, + renderNode, + direction, + align, + nodeMargin, + edgeMargin, + rankMargin, + paddingX, + paddingY, + acyclicer, + ranker, + labelPosition, + labelOffset, + edgeRanks, + edgeWeight, + renderLabel, + defs, + ...svgProps +}: DependencyGraphProps): JSX.Element; + +declare namespace DependencyGraphTypes { + export { + DependencyEdge, + GraphEdge, + RenderLabelProps, + RenderLabelFunction, + DependencyNode, + GraphNode, + RenderNodeProps, + RenderNodeFunction, + EdgeProperties, + Direction, + Alignment, + Ranker, + LabelPosition, + }; +} +export { DependencyGraphTypes }; + +// @public (undocumented) +type DependencyNode = T & { + id: string; +}; + +// @public (undocumented) +enum Direction { + // (undocumented) + BOTTOM_TOP = 'BT', + // (undocumented) + LEFT_RIGHT = 'LR', + // (undocumented) + RIGHT_LEFT = 'RL', + // (undocumented) + TOP_BOTTOM = 'TB', +} + +// @public (undocumented) +export const DismissableBanner: ({ + variant, + message, + id, + fixed, +}: Props_4) => JSX.Element; + +// @public (undocumented) +export const DocsIcon: IconComponent; + +// @public (undocumented) +type EdgeProperties = { + label?: string; + width?: number; + height?: number; + labeloffset?: number; + labelpos?: LabelPosition; + minlen?: number; + weight?: number; + [customKey: string]: any; +}; + +// @public (undocumented) +export const EmailIcon: IconComponent; + +// @public (undocumented) +export const EmptyState: ({ + title, + description, + missing, + action, +}: Props_5) => JSX.Element; + +// @public (undocumented) +export const ErrorBoundary: ComponentClass; + +// @public (undocumented) +export type ErrorBoundaryProps = { + slackChannel?: string | SlackChannel; + onError?: (error: Error, errorInfo: string) => null; +}; + +// @public (undocumented) +export const ErrorPage: ({ + status, + statusMessage, + additionalInfo, +}: IErrorPageProps) => JSX.Element; + +// @public +export const ErrorPanel: ({ + title, + error, + defaultExpanded, + children, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export type ErrorPanelProps = { + error: Error; + defaultExpanded?: boolean; + title?: string; +}; + +// @public (undocumented) +export const FeatureCalloutCircular: ({ + featureId, + title, + description, + children, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const Gauge: (props: Props_14) => JSX.Element; + +// @public (undocumented) +export const GaugeCard: (props: Props_13) => JSX.Element; + +// @public (undocumented) +export const GitHubIcon: IconComponent; + +// @public (undocumented) +type GraphEdge = DependencyEdge & + dagre_2.GraphEdge & + EdgeProperties; + +// @public (undocumented) +type GraphNode = dagre_2.Node>; + +// @public (undocumented) +export const GroupIcon: IconComponent; + +// @public (undocumented) +export const Header: ({ + children, + pageTitleOverride, + style, + subtitle, + title, + tooltip, + type, + typeLink, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const HeaderIconLinkRow: ({ links }: Props_8) => JSX.Element; + +// @public (undocumented) +export const HeaderLabel: ({ + label, + value, + url, +}: HeaderLabelProps) => JSX.Element; + +// @public (undocumented) +export const HeaderTabs: ({ + tabs, + onChange, + selectedIndex, +}: HeaderTabsProps) => JSX.Element; + +// @public (undocumented) +export const HelpIcon: IconComponent; + +// @public (undocumented) +export const HomepageTimer: () => JSX.Element | null; + +// @public (undocumented) +export const HorizontalScrollGrid: ( + props: PropsWithChildren, +) => JSX.Element; + +// @public (undocumented) +export type IconLinkVerticalProps = { + color?: 'primary' | 'secondary'; + disabled?: boolean; + href?: string; + icon?: React_2.ReactNode; + label: string; + onClick?: React_2.MouseEventHandler; + title?: string; +}; + +// @public (undocumented) +export const InfoCard: ({ + title, + subheader, + divider, + deepLink, + slackChannel, + errorBoundaryProps, + variant, + children, + headerStyle, + headerProps, + action, + actionsClassName, + actions, + cardClassName, + actionsTopRight, + className, + noPadding, + titleTypographyProps, +}: Props_20) => JSX.Element; + +// @public (undocumented) +export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; + +// @public (undocumented) +export const IntroCard: (props: IntroCardProps) => JSX.Element; + +// @public @deprecated +export const ItemCard: ({ + description, + tags, + title, + type, + subtitle, + label, + onClick, + href, +}: ItemCardProps) => JSX.Element; + +// @public +export const ItemCardGrid: (props: ItemCardGridProps) => JSX.Element; + +// @public (undocumented) +export type ItemCardGridProps = Partial> & { + children?: React_2.ReactNode; +}; + +// @public +export const ItemCardHeader: (props: ItemCardHeaderProps) => JSX.Element; + +// @public (undocumented) +export type ItemCardHeaderProps = Partial> & { + title?: React_2.ReactNode; + subtitle?: React_2.ReactNode; + children?: React_2.ReactNode; +}; + +// @public (undocumented) +enum LabelPosition { + // (undocumented) + CENTER = 'c', + // (undocumented) + LEFT = 'l', + // (undocumented) + RIGHT = 'r', +} + +// @public (undocumented) +export const Lifecycle: (props: Props_10) => JSX.Element; + +// @public (undocumented) +export const LinearGauge: ({ value }: Props_15) => JSX.Element | null; + +// @public +export const Link: React_2.ForwardRefExoticComponent< + Pick< + LinkProps, + | 'replace' + | 'media' + | 'hidden' + | 'dir' + | 'slot' + | 'style' + | 'title' + | 'color' + | 'underline' + | 'display' + | 'translate' + | 'prefix' + | 'children' + | 'key' + | 'id' + | 'classes' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'className' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | 'component' + | 'variant' + | 'innerRef' + | 'download' + | 'href' + | 'hrefLang' + | 'ping' + | 'rel' + | 'target' + | 'type' + | 'referrerPolicy' + | 'noWrap' + | 'gutterBottom' + | 'paragraph' + | 'align' + | 'variantMapping' + | 'to' + | 'state' + | 'TypographyClasses' + > & + React_2.RefAttributes +>; + +// @public (undocumented) +export type LinkProps = LinkProps_2 & + LinkProps_3 & { + component?: ElementType; + }; + +// @public +export const MarkdownContent: ({ content, dialect }: Props_11) => JSX.Element; + +// @public (undocumented) +export const MissingAnnotationEmptyState: ({ + annotation, +}: Props_6) => JSX.Element; + +// @public (undocumented) +export const OAuthRequestDialog: () => JSX.Element; + +// @public (undocumented) +export const OverflowTooltip: (props: Props_12) => JSX.Element; + +// @public (undocumented) +export const Page: ({ + themeId, + children, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const Progress: ( + props: PropsWithChildren, +) => JSX.Element; + +// @public (undocumented) +enum Ranker { + // (undocumented) + LONGEST_PATH = 'longest-path', + // (undocumented) + NETWORK_SIMPLEX = 'network-simplex', + // (undocumented) + TIGHT_TREE = 'tight-tree', +} + +// @public (undocumented) +type RenderLabelFunction = (props: RenderLabelProps) => React.ReactNode; + +// @public (undocumented) +type RenderLabelProps = { + edge: DependencyEdge; +}; + +// @public (undocumented) +type RenderNodeFunction = (props: RenderNodeProps) => React.ReactNode; + +// @public (undocumented) +type RenderNodeProps = { + node: DependencyNode; +}; + +// @public +export const ResponseErrorPanel: ({ + title, + error, + defaultExpanded, +}: ErrorPanelProps) => JSX.Element; + +// @public (undocumented) +export const RoutedTabs: ({ routes }: { routes: SubRoute_2[] }) => JSX.Element; + +// @public (undocumented) +export const Select: ({ + multiple, + items, + label, + placeholder, + selected, + onChange, + triggerReset, +}: SelectProps) => JSX.Element; + +// @public (undocumented) +export const Sidebar: ({ + openDelayMs, + closeDelayMs, + children, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const SIDEBAR_INTRO_LOCAL_STORAGE = + '@backstage/core/sidebar-intro-dismissed'; + +// @public (undocumented) +export const sidebarConfig: { + drawerWidthClosed: number; + drawerWidthOpen: number; + defaultOpenDelayMs: number; + defaultCloseDelayMs: number; + defaultFadeDuration: number; + logoHeight: number; + iconContainerWidth: number; + iconSize: number; + iconPadding: number; + selectedIndicatorWidth: number; + userBadgePadding: number; + userBadgeDiameter: number; +}; + +// @public (undocumented) +export const SidebarContext: Context; + +// @public (undocumented) +export type SidebarContextType = { + isOpen: boolean; +}; + +// @public (undocumented) +export const SidebarDivider: React_2.ComponentType< + Pick< + React_2.DetailedHTMLProps< + React_2.HTMLAttributes, + HTMLHRElement + >, + | 'hidden' + | 'dir' + | 'slot' + | 'style' + | 'title' + | 'color' + | 'translate' + | 'prefix' + | 'children' + | 'id' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | keyof React_2.ClassAttributes + > & + StyledComponentProps<'root'> & { + className?: string | undefined; + } +>; + +// @public (undocumented) +export const SidebarIntro: () => JSX.Element | null; + +// @public (undocumented) +export const SidebarItem: React_2.ForwardRefExoticComponent< + SidebarItemProps & React_2.RefAttributes +>; + +// @public (undocumented) +export const SidebarPage: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const SidebarPinStateContext: React_2.Context; + +// @public (undocumented) +export type SidebarPinStateContextType = { + isPinned: boolean; + toggleSidebarPinState: () => any; +}; + +// @public (undocumented) +export const SidebarScrollWrapper: React_2.ComponentType< + Pick< + React_2.DetailedHTMLProps< + React_2.HTMLAttributes, + HTMLDivElement + >, + | 'hidden' + | 'dir' + | 'slot' + | 'style' + | 'title' + | 'color' + | 'translate' + | 'prefix' + | 'children' + | 'id' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | keyof React_2.ClassAttributes + > & + StyledComponentProps<'root'> & { + className?: string | undefined; + } +>; + +// @public (undocumented) +export const SidebarSearchField: ( + props: SidebarSearchFieldProps, +) => JSX.Element; + +// @public (undocumented) +export const SidebarSpace: React_2.ComponentType< + Pick< + React_2.DetailedHTMLProps< + React_2.HTMLAttributes, + HTMLDivElement + >, + | 'hidden' + | 'dir' + | 'slot' + | 'style' + | 'title' + | 'color' + | 'translate' + | 'prefix' + | 'children' + | 'id' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | keyof React_2.ClassAttributes + > & + StyledComponentProps<'root'> & { + className?: string | undefined; + } +>; + +// @public (undocumented) +export const SidebarSpacer: React_2.ComponentType< + Pick< + React_2.DetailedHTMLProps< + React_2.HTMLAttributes, + HTMLDivElement + >, + | 'hidden' + | 'dir' + | 'slot' + | 'style' + | 'title' + | 'color' + | 'translate' + | 'prefix' + | 'children' + | 'id' + | 'defaultChecked' + | 'defaultValue' + | 'suppressContentEditableWarning' + | 'suppressHydrationWarning' + | 'accessKey' + | 'contentEditable' + | 'contextMenu' + | 'draggable' + | 'lang' + | 'placeholder' + | 'spellCheck' + | 'tabIndex' + | 'radioGroup' + | 'role' + | 'about' + | 'datatype' + | 'inlist' + | 'property' + | 'resource' + | 'typeof' + | 'vocab' + | 'autoCapitalize' + | 'autoCorrect' + | 'autoSave' + | 'itemProp' + | 'itemScope' + | 'itemType' + | 'itemID' + | 'itemRef' + | 'results' + | 'security' + | 'unselectable' + | 'inputMode' + | 'is' + | 'aria-activedescendant' + | 'aria-atomic' + | 'aria-autocomplete' + | 'aria-busy' + | 'aria-checked' + | 'aria-colcount' + | 'aria-colindex' + | 'aria-colspan' + | 'aria-controls' + | 'aria-current' + | 'aria-describedby' + | 'aria-details' + | 'aria-disabled' + | 'aria-dropeffect' + | 'aria-errormessage' + | 'aria-expanded' + | 'aria-flowto' + | 'aria-grabbed' + | 'aria-haspopup' + | 'aria-hidden' + | 'aria-invalid' + | 'aria-keyshortcuts' + | 'aria-label' + | 'aria-labelledby' + | 'aria-level' + | 'aria-live' + | 'aria-modal' + | 'aria-multiline' + | 'aria-multiselectable' + | 'aria-orientation' + | 'aria-owns' + | 'aria-placeholder' + | 'aria-posinset' + | 'aria-pressed' + | 'aria-readonly' + | 'aria-relevant' + | 'aria-required' + | 'aria-roledescription' + | 'aria-rowcount' + | 'aria-rowindex' + | 'aria-rowspan' + | 'aria-selected' + | 'aria-setsize' + | 'aria-sort' + | 'aria-valuemax' + | 'aria-valuemin' + | 'aria-valuenow' + | 'aria-valuetext' + | 'dangerouslySetInnerHTML' + | 'onCopy' + | 'onCopyCapture' + | 'onCut' + | 'onCutCapture' + | 'onPaste' + | 'onPasteCapture' + | 'onCompositionEnd' + | 'onCompositionEndCapture' + | 'onCompositionStart' + | 'onCompositionStartCapture' + | 'onCompositionUpdate' + | 'onCompositionUpdateCapture' + | 'onFocus' + | 'onFocusCapture' + | 'onBlur' + | 'onBlurCapture' + | 'onChange' + | 'onChangeCapture' + | 'onBeforeInput' + | 'onBeforeInputCapture' + | 'onInput' + | 'onInputCapture' + | 'onReset' + | 'onResetCapture' + | 'onSubmit' + | 'onSubmitCapture' + | 'onInvalid' + | 'onInvalidCapture' + | 'onLoad' + | 'onLoadCapture' + | 'onError' + | 'onErrorCapture' + | 'onKeyDown' + | 'onKeyDownCapture' + | 'onKeyPress' + | 'onKeyPressCapture' + | 'onKeyUp' + | 'onKeyUpCapture' + | 'onAbort' + | 'onAbortCapture' + | 'onCanPlay' + | 'onCanPlayCapture' + | 'onCanPlayThrough' + | 'onCanPlayThroughCapture' + | 'onDurationChange' + | 'onDurationChangeCapture' + | 'onEmptied' + | 'onEmptiedCapture' + | 'onEncrypted' + | 'onEncryptedCapture' + | 'onEnded' + | 'onEndedCapture' + | 'onLoadedData' + | 'onLoadedDataCapture' + | 'onLoadedMetadata' + | 'onLoadedMetadataCapture' + | 'onLoadStart' + | 'onLoadStartCapture' + | 'onPause' + | 'onPauseCapture' + | 'onPlay' + | 'onPlayCapture' + | 'onPlaying' + | 'onPlayingCapture' + | 'onProgress' + | 'onProgressCapture' + | 'onRateChange' + | 'onRateChangeCapture' + | 'onSeeked' + | 'onSeekedCapture' + | 'onSeeking' + | 'onSeekingCapture' + | 'onStalled' + | 'onStalledCapture' + | 'onSuspend' + | 'onSuspendCapture' + | 'onTimeUpdate' + | 'onTimeUpdateCapture' + | 'onVolumeChange' + | 'onVolumeChangeCapture' + | 'onWaiting' + | 'onWaitingCapture' + | 'onAuxClick' + | 'onAuxClickCapture' + | 'onClick' + | 'onClickCapture' + | 'onContextMenu' + | 'onContextMenuCapture' + | 'onDoubleClick' + | 'onDoubleClickCapture' + | 'onDrag' + | 'onDragCapture' + | 'onDragEnd' + | 'onDragEndCapture' + | 'onDragEnter' + | 'onDragEnterCapture' + | 'onDragExit' + | 'onDragExitCapture' + | 'onDragLeave' + | 'onDragLeaveCapture' + | 'onDragOver' + | 'onDragOverCapture' + | 'onDragStart' + | 'onDragStartCapture' + | 'onDrop' + | 'onDropCapture' + | 'onMouseDown' + | 'onMouseDownCapture' + | 'onMouseEnter' + | 'onMouseLeave' + | 'onMouseMove' + | 'onMouseMoveCapture' + | 'onMouseOut' + | 'onMouseOutCapture' + | 'onMouseOver' + | 'onMouseOverCapture' + | 'onMouseUp' + | 'onMouseUpCapture' + | 'onSelect' + | 'onSelectCapture' + | 'onTouchCancel' + | 'onTouchCancelCapture' + | 'onTouchEnd' + | 'onTouchEndCapture' + | 'onTouchMove' + | 'onTouchMoveCapture' + | 'onTouchStart' + | 'onTouchStartCapture' + | 'onPointerDown' + | 'onPointerDownCapture' + | 'onPointerMove' + | 'onPointerMoveCapture' + | 'onPointerUp' + | 'onPointerUpCapture' + | 'onPointerCancel' + | 'onPointerCancelCapture' + | 'onPointerEnter' + | 'onPointerEnterCapture' + | 'onPointerLeave' + | 'onPointerLeaveCapture' + | 'onPointerOver' + | 'onPointerOverCapture' + | 'onPointerOut' + | 'onPointerOutCapture' + | 'onGotPointerCapture' + | 'onGotPointerCaptureCapture' + | 'onLostPointerCapture' + | 'onLostPointerCaptureCapture' + | 'onScroll' + | 'onScrollCapture' + | 'onWheel' + | 'onWheelCapture' + | 'onAnimationStart' + | 'onAnimationStartCapture' + | 'onAnimationEnd' + | 'onAnimationEndCapture' + | 'onAnimationIteration' + | 'onAnimationIterationCapture' + | 'onTransitionEnd' + | 'onTransitionEndCapture' + | keyof React_2.ClassAttributes + > & + StyledComponentProps<'root'> & { + className?: string | undefined; + } +>; + +// @public (undocumented) +export const SignInPage: (props: Props_23) => JSX.Element; + +// @public (undocumented) +export type SignInProviderConfig = { + id: string; + title: string; + message: string; + apiRef: ApiRef; +}; + +// @public (undocumented) +export const SimpleStepper: ({ + children, + elevated, + onStepChange, + activeStep, +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const SimpleStepperStep: ({ + title, + children, + end, + actions, + ...muiProps +}: PropsWithChildren) => JSX.Element; + +// @public (undocumented) +export const StatusAborted: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StatusError: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StatusOK: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StatusPending: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StatusRunning: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StatusWarning: (props: PropsWithChildren<{}>) => JSX.Element; + +// @public (undocumented) +export const StructuredMetadataTable: ({ + metadata, + dense, + options, +}: Props_16) => JSX.Element; + +// @public (undocumented) +export const SubvalueCell: ({ + value, + subvalue, +}: SubvalueCellProps) => JSX.Element; + +// @public (undocumented) +export const SupportButton: ({ + title, + children, +}: SupportButtonProps) => JSX.Element; + +// @public (undocumented) +export type SupportConfig = { + url: string; + items: SupportItem[]; +}; + +// @public (undocumented) +export type SupportItem = { + title: string; + icon?: string; + links: SupportItemLink[]; +}; + +// @public (undocumented) +export type SupportItemLink = { + url: string; + title: string; +}; + +// @public (undocumented) +export type Tab = { + id: string; + label: string; + tabProps?: TabProps< + React_2.ElementType, + { + component?: React_2.ElementType; + } + >; +}; + +// @public (undocumented) +export const TabbedCard: ({ + slackChannel, + errorBoundaryProps, + children, + title, + deepLink, + value, + onChange, +}: PropsWithChildren) => JSX.Element; + +// @public +export const TabbedLayout: { + ({ children }: PropsWithChildren<{}>): JSX.Element; + Route: (props: SubRoute) => null; +}; + +// @public (undocumented) +export function Table({ + columns, + options, + title, + subtitle, + filters, + initialState, + emptyContent, + onStateChange, + ...props +}: TableProps): JSX.Element; + +// @public (undocumented) +export interface TableColumn extends Column { + // (undocumented) + highlight?: boolean; + // (undocumented) + width?: string; +} + +// @public (undocumented) +export type TableFilter = { + column: string; + type: 'select' | 'multiple-select' | 'checkbox-tree'; +}; + +// @public (undocumented) +export interface TableProps + extends MaterialTableProps { + // (undocumented) + columns: TableColumn[]; + // (undocumented) + emptyContent?: ReactNode; + // (undocumented) + filters?: TableFilter[]; + // (undocumented) + initialState?: TableState; + // (undocumented) + onStateChange?: (state: TableState) => any; + // (undocumented) + subtitle?: string; +} + +// @public (undocumented) +export type TableState = { + search?: string; + filtersOpen?: boolean; + filters?: SelectedFilters; +}; + +// @public (undocumented) +export const Tabs: ({ tabs }: TabsProps) => JSX.Element; + +// @public (undocumented) +export const TrendLine: ( + props: SparklinesProps & + Pick & { + title?: string; + }, +) => JSX.Element | null; + +// @public (undocumented) +export function useQueryParamState( + stateName: string, + debounceTime?: number, +): [T | undefined, SetQueryParams]; + +// @public (undocumented) +export const UserIcon: IconComponent; + +// @public (undocumented) +export function useSupportConfig(): SupportConfig; + +// @public (undocumented) +export const WarningIcon: IconComponent; + +// @public +export const WarningPanel: (props: Props_17) => JSX.Element; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 42a98b859a..b9d514d074 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.1.2", + "version": "0.1.5", "private": false, "publishConfig": { "access": "public", @@ -70,8 +70,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.2", - "@backstage/cli": "^0.7.0", + "@backstage/core-app-api": "^0.1.4", + "@backstage/cli": "^0.7.3", "@backstage/test-utils": "^0.1.13", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx index 624337c49b..d6bf990f8a 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx index c841d234f6..495e2ea14c 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/AlertDisplay/index.ts b/packages/core-components/src/components/AlertDisplay/index.ts index 72aa1c5ad8..34b2dfaabf 100644 --- a/packages/core-components/src/components/AlertDisplay/index.ts +++ b/packages/core-components/src/components/AlertDisplay/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/Avatar.stories.tsx b/packages/core-components/src/components/Avatar/Avatar.stories.tsx index 5ac628d72b..59f2fb6cfa 100644 --- a/packages/core-components/src/components/Avatar/Avatar.stories.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/Avatar.test.tsx b/packages/core-components/src/components/Avatar/Avatar.test.tsx index da6ca8f42e..1c08d57679 100644 --- a/packages/core-components/src/components/Avatar/Avatar.test.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/Avatar.tsx b/packages/core-components/src/components/Avatar/Avatar.tsx index 95aa4a8ced..df3c46fba7 100644 --- a/packages/core-components/src/components/Avatar/Avatar.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/index.ts b/packages/core-components/src/components/Avatar/index.ts index 962414634e..a58b47eba9 100644 --- a/packages/core-components/src/components/Avatar/index.ts +++ b/packages/core-components/src/components/Avatar/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/util.test.ts b/packages/core-components/src/components/Avatar/util.test.ts index 94de957e8e..4d2b5417c0 100644 --- a/packages/core-components/src/components/Avatar/util.test.ts +++ b/packages/core-components/src/components/Avatar/util.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Avatar/utils.ts b/packages/core-components/src/components/Avatar/utils.ts index 5990a72955..98ce01664a 100644 --- a/packages/core-components/src/components/Avatar/utils.ts +++ b/packages/core-components/src/components/Avatar/utils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Button/Button.stories.tsx b/packages/core-components/src/components/Button/Button.stories.tsx index 0fee0300ab..28deb1ad33 100644 --- a/packages/core-components/src/components/Button/Button.stories.tsx +++ b/packages/core-components/src/components/Button/Button.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Button/Button.test.tsx b/packages/core-components/src/components/Button/Button.test.tsx index 8bae5f2767..e3a1074e6f 100644 --- a/packages/core-components/src/components/Button/Button.test.tsx +++ b/packages/core-components/src/components/Button/Button.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Button/Button.tsx b/packages/core-components/src/components/Button/Button.tsx index ca45b3da7f..6dd593d776 100644 --- a/packages/core-components/src/components/Button/Button.tsx +++ b/packages/core-components/src/components/Button/Button.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,19 @@ * limitations under the License. */ -import React, { ComponentProps } from 'react'; -import { Button as MaterialButton } from '@material-ui/core'; -import { Link as RouterLink } from 'react-router-dom'; +import { + Button as MaterialButton, + ButtonProps as MaterialButtonProps, +} from '@material-ui/core'; +import React from 'react'; +import { Link, LinkProps } from '../Link'; -type Props = ComponentProps & - ComponentProps; +type Props = MaterialButtonProps & Omit; /** * Thin wrapper on top of material-ui's Button component * Makes the Button to utilise react-router */ export const Button = React.forwardRef((props, ref) => ( - + )); diff --git a/packages/core-components/src/components/Button/index.ts b/packages/core-components/src/components/Button/index.ts index 7b584ed799..e2aa3aff98 100644 --- a/packages/core-components/src/components/Button/index.ts +++ b/packages/core-components/src/components/Button/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx index 3416e94260..48da8b92b2 100644 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx +++ b/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx index 0750867ff3..6c84a311d2 100644 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx +++ b/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx index d6d3410913..7e7155143b 100644 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx +++ b/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CheckboxTree/index.tsx b/packages/core-components/src/components/CheckboxTree/index.tsx index d8e62460ab..e1ceb98ee4 100644 --- a/packages/core-components/src/components/CheckboxTree/index.tsx +++ b/packages/core-components/src/components/CheckboxTree/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Chip/Chip.stories.tsx b/packages/core-components/src/components/Chip/Chip.stories.tsx index 87d904ad0e..ff52a81c16 100644 --- a/packages/core-components/src/components/Chip/Chip.stories.tsx +++ b/packages/core-components/src/components/Chip/Chip.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx index 6df7e5bb6d..a266c8c6cb 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.test.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.test.tsx index 7d5d4de087..298a8d5780 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.test.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx index 84b5aa403e..a3badaad07 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CodeSnippet/index.tsx b/packages/core-components/src/components/CodeSnippet/index.tsx index 11ca1ecde2..bafcf145e9 100644 --- a/packages/core-components/src/components/CodeSnippet/index.tsx +++ b/packages/core-components/src/components/CodeSnippet/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx index 745812a219..c1834091b5 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.test.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.test.tsx index 39d88d07ce..acd0fa809c 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.test.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx index 7ccb0371f4..b5bc516d1a 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/CopyTextButton/index.tsx b/packages/core-components/src/components/CopyTextButton/index.tsx index adde10a927..a90975fa77 100644 --- a/packages/core-components/src/components/CopyTextButton/index.tsx +++ b/packages/core-components/src/components/CopyTextButton/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx b/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx index 0679d1dae0..74f0a97a2b 100644 --- a/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx +++ b/packages/core-components/src/components/DependencyGraph/DefaultLabel.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx index 9656c860ae..c93651e3e0 100644 --- a/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx +++ b/packages/core-components/src/components/DependencyGraph/DefaultNode.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx index e39ecfc5da..0b97059754 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.test.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.test.tsx index 526dc6d7dd..2020c2ba65 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index ced0bdf276..89f7421d2a 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -262,7 +262,7 @@ export function DependencyGraph({ diff --git a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx index b651c17bf0..323a445546 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index 5e67aca63f..4e4c5ca5f7 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/Node.test.tsx b/packages/core-components/src/components/DependencyGraph/Node.test.tsx index 9f9f7c693a..ebd6478db9 100644 --- a/packages/core-components/src/components/DependencyGraph/Node.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/Node.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/Node.tsx b/packages/core-components/src/components/DependencyGraph/Node.tsx index 4d64e31335..d9e8cbfb78 100644 --- a/packages/core-components/src/components/DependencyGraph/Node.tsx +++ b/packages/core-components/src/components/DependencyGraph/Node.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/constants.ts b/packages/core-components/src/components/DependencyGraph/constants.ts index 412a677f87..155fd4e82a 100644 --- a/packages/core-components/src/components/DependencyGraph/constants.ts +++ b/packages/core-components/src/components/DependencyGraph/constants.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/index.ts b/packages/core-components/src/components/DependencyGraph/index.ts index 9a4f0071e3..5f5e7a5439 100644 --- a/packages/core-components/src/components/DependencyGraph/index.ts +++ b/packages/core-components/src/components/DependencyGraph/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DependencyGraph/types.ts b/packages/core-components/src/components/DependencyGraph/types.ts index 19173db4b3..ed22007f9a 100644 --- a/packages/core-components/src/components/DependencyGraph/types.ts +++ b/packages/core-components/src/components/DependencyGraph/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Dialog/Dialog.stories.tsx b/packages/core-components/src/components/Dialog/Dialog.stories.tsx index c9388dcbec..b4c0a7da57 100644 --- a/packages/core-components/src/components/Dialog/Dialog.stories.tsx +++ b/packages/core-components/src/components/Dialog/Dialog.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx index 3a2c9532a5..583413eb35 100644 --- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx +++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.test.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.test.tsx index ed8e4f0fff..ddc119369b 100644 --- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.test.tsx +++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx index 8f222768a8..6c4e43811d 100644 --- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx +++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/DismissableBanner/index.ts b/packages/core-components/src/components/DismissableBanner/index.ts index c1d69cd95e..4390d44903 100644 --- a/packages/core-components/src/components/DismissableBanner/index.ts +++ b/packages/core-components/src/components/DismissableBanner/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Drawer/Drawer.stories.tsx b/packages/core-components/src/components/Drawer/Drawer.stories.tsx index b399cfed8e..2538af855e 100644 --- a/packages/core-components/src/components/Drawer/Drawer.stories.tsx +++ b/packages/core-components/src/components/Drawer/Drawer.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx b/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx index dcfba73227..ed4dbe2101 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/EmptyState.test.tsx b/packages/core-components/src/components/EmptyState/EmptyState.test.tsx index 32e71f044d..2f4e4e883d 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.test.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/EmptyState.tsx b/packages/core-components/src/components/EmptyState/EmptyState.tsx index fdd9738735..1d8d5a798f 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/EmptyStateImage.test.tsx b/packages/core-components/src/components/EmptyState/EmptyStateImage.test.tsx index 258eee943d..904f5afc63 100644 --- a/packages/core-components/src/components/EmptyState/EmptyStateImage.test.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyStateImage.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx b/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx index 1973ff9a23..a76f0863f7 100644 --- a/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyStateImage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 6c04b34a90..d477494964 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import React from 'react'; import { Button, makeStyles, Typography } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; +import { Link } from '../Link'; import { EmptyState } from './EmptyState'; import { CodeSnippet } from '../CodeSnippet'; @@ -73,9 +74,9 @@ export const MissingAnnotationEmptyState = ({ annotation }: Props) => { />
diff --git a/packages/core-components/src/components/EmptyState/index.ts b/packages/core-components/src/components/EmptyState/index.ts index 2e2a88be94..95e12a014e 100644 --- a/packages/core-components/src/components/EmptyState/index.ts +++ b/packages/core-components/src/components/EmptyState/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ErrorPanel/ErrorPanel.tsx b/packages/core-components/src/components/ErrorPanel/ErrorPanel.tsx index 1f7d2a4be7..14b5d50325 100644 --- a/packages/core-components/src/components/ErrorPanel/ErrorPanel.tsx +++ b/packages/core-components/src/components/ErrorPanel/ErrorPanel.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ErrorPanel/index.ts b/packages/core-components/src/components/ErrorPanel/index.ts index 7103266eab..1da4a76959 100644 --- a/packages/core-components/src/components/ErrorPanel/index.ts +++ b/packages/core-components/src/components/ErrorPanel/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx index 83a31f198d..35ea30ffa6 100644 --- a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx +++ b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx index 64722c24b0..9af6c30c3b 100644 --- a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx +++ b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/FeatureDiscovery/index.ts b/packages/core-components/src/components/FeatureDiscovery/index.ts index 57e35e3855..ed9ab28b17 100644 --- a/packages/core-components/src/components/FeatureDiscovery/index.ts +++ b/packages/core-components/src/components/FeatureDiscovery/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/FeatureDiscovery/lib/usePortal.ts b/packages/core-components/src/components/FeatureDiscovery/lib/usePortal.ts index d5fd2c23c9..c1377b6060 100644 --- a/packages/core-components/src/components/FeatureDiscovery/lib/usePortal.ts +++ b/packages/core-components/src/components/FeatureDiscovery/lib/usePortal.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts b/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts index 0bbcf3b8ec..617f1324ed 100644 --- a/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts +++ b/packages/core-components/src/components/FeatureDiscovery/lib/useShowCallout.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx index a2f86d133b..afceda165e 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx index 425bed3490..df26bc07b8 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HeaderIconLinkRow/index.ts b/packages/core-components/src/components/HeaderIconLinkRow/index.ts index fb25f9b7ed..fb732644e4 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/index.ts +++ b/packages/core-components/src/components/HeaderIconLinkRow/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx index 9718393427..55fff2430a 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx index 0d07d8ec84..080e48919b 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx index 073fb960e1..f5c45d3eb0 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/HorizontalScrollGrid/index.tsx b/packages/core-components/src/components/HorizontalScrollGrid/index.tsx index cb1253cae2..bbf545dab3 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/index.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx b/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx index ccce7fba25..8640dd28e9 100644 --- a/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx +++ b/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Lifecycle/Lifecycle.test.tsx b/packages/core-components/src/components/Lifecycle/Lifecycle.test.tsx index b5567b2b5a..05b0f650dd 100644 --- a/packages/core-components/src/components/Lifecycle/Lifecycle.test.tsx +++ b/packages/core-components/src/components/Lifecycle/Lifecycle.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Lifecycle/Lifecycle.tsx b/packages/core-components/src/components/Lifecycle/Lifecycle.tsx index 585f7a5c18..c72ea46f1b 100644 --- a/packages/core-components/src/components/Lifecycle/Lifecycle.tsx +++ b/packages/core-components/src/components/Lifecycle/Lifecycle.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Lifecycle/index.ts b/packages/core-components/src/components/Lifecycle/index.ts index 8854c04396..d52e79e08b 100644 --- a/packages/core-components/src/components/Lifecycle/index.ts +++ b/packages/core-components/src/components/Lifecycle/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Link/Link.stories.tsx b/packages/core-components/src/components/Link/Link.stories.tsx index ec5278133e..516a5adcff 100644 --- a/packages/core-components/src/components/Link/Link.stories.tsx +++ b/packages/core-components/src/components/Link/Link.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index 46504e9adc..97a71c5760 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 31800ddaf1..7853c4f52f 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,9 +37,16 @@ export type LinkProps = MaterialLinkProps & */ export const Link = React.forwardRef((props, ref) => { const to = String(props.to); - return isExternalUri(to) ? ( + const external = isExternalUri(to); + const newWindow = external && !!/^https?:/.exec(to); + return external ? ( // External links - + ) : ( // Interact with React Router for internal links diff --git a/packages/core-components/src/components/Link/index.ts b/packages/core-components/src/components/Link/index.ts index 9be779feb7..2160508451 100644 --- a/packages/core-components/src/components/Link/index.ts +++ b/packages/core-components/src/components/Link/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx index eb15aa92d3..b16eea150c 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx index dcbe421dcb..d39e92c75e 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 0000e54a5f..296df22ecd 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,8 +65,8 @@ type Props = { }; const renderers = { - code: ({ language, value }: { language: string; value: string }) => { - return ; + code: ({ language, value }: { language: string; value?: string }) => { + return ; }, }; diff --git a/packages/core-components/src/components/MarkdownContent/index.ts b/packages/core-components/src/components/MarkdownContent/index.ts index 7267ff191c..9218d9fcde 100644 --- a/packages/core-components/src/components/MarkdownContent/index.ts +++ b/packages/core-components/src/components/MarkdownContent/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index cbb3705bd6..e0102564fd 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 0eb9ac49a9..687e4211bf 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OAuthRequestDialog/index.ts b/packages/core-components/src/components/OAuthRequestDialog/index.ts index 45f87ece1d..d2a48eac06 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/index.ts +++ b/packages/core-components/src/components/OAuthRequestDialog/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index e0cbe7c814..0882a63e9c 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx index 52f44bb4d0..c61069f810 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index b7bad6f377..1be7ebc7ed 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/OverflowTooltip/index.ts b/packages/core-components/src/components/OverflowTooltip/index.ts index fe51e8267f..f7258b5364 100644 --- a/packages/core-components/src/components/OverflowTooltip/index.ts +++ b/packages/core-components/src/components/OverflowTooltip/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Progress/Progress.stories.tsx b/packages/core-components/src/components/Progress/Progress.stories.tsx index 807ad159d8..af24019a69 100644 --- a/packages/core-components/src/components/Progress/Progress.stories.tsx +++ b/packages/core-components/src/components/Progress/Progress.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Progress/Progress.test.tsx b/packages/core-components/src/components/Progress/Progress.test.tsx index 46a162e8b3..4f4e74ce08 100644 --- a/packages/core-components/src/components/Progress/Progress.test.tsx +++ b/packages/core-components/src/components/Progress/Progress.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Progress/Progress.tsx b/packages/core-components/src/components/Progress/Progress.tsx index aacdf8821b..68dcb91785 100644 --- a/packages/core-components/src/components/Progress/Progress.tsx +++ b/packages/core-components/src/components/Progress/Progress.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Progress/index.ts b/packages/core-components/src/components/Progress/index.ts index 6598103ab1..c7b3d202cf 100644 --- a/packages/core-components/src/components/Progress/index.ts +++ b/packages/core-components/src/components/Progress/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx index ab9c263f05..3481c48cef 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/Gauge.test.tsx b/packages/core-components/src/components/ProgressBars/Gauge.test.tsx index 00cf0fd009..066e1233a0 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.test.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index ca6a3a66ab..46948d4af6 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 5d54e5eda6..da81100a99 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.test.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.test.tsx index db112fa2ab..ea29a288e6 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.test.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 8dcec129c5..39248b1786 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx index fa3c7c00f0..ee2ed2eae6 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.test.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.test.tsx index fc04b642fb..f7260a9a65 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.test.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx index 9bb7b34c09..d733cb2eae 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ProgressBars/index.ts b/packages/core-components/src/components/ProgressBars/index.ts index 4463aea29b..01cab20f27 100644 --- a/packages/core-components/src/components/ProgressBars/index.ts +++ b/packages/core-components/src/components/ProgressBars/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx index 33da6261ec..ccfb706bf9 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ export const ResponseErrorPanel = ({ @@ -87,10 +87,9 @@ export const ResponseErrorPanel = ({ } /> - + ); diff --git a/packages/core-components/src/components/ResponseErrorPanel/index.ts b/packages/core-components/src/components/ResponseErrorPanel/index.ts index 1fc6221a44..c93bd9eade 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/index.ts +++ b/packages/core-components/src/components/ResponseErrorPanel/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/Select.stories.tsx b/packages/core-components/src/components/Select/Select.stories.tsx index 07b9e38bd5..c48f36c2e5 100644 --- a/packages/core-components/src/components/Select/Select.stories.tsx +++ b/packages/core-components/src/components/Select/Select.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/Select.test.tsx b/packages/core-components/src/components/Select/Select.test.tsx index 89ae2b1883..41e787a5b4 100644 --- a/packages/core-components/src/components/Select/Select.test.tsx +++ b/packages/core-components/src/components/Select/Select.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/Select.tsx b/packages/core-components/src/components/Select/Select.tsx index 1e6dba76e0..b4512b3562 100644 --- a/packages/core-components/src/components/Select/Select.tsx +++ b/packages/core-components/src/components/Select/Select.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/index.tsx b/packages/core-components/src/components/Select/index.tsx index 977ebc88eb..0c35cc1378 100644 --- a/packages/core-components/src/components/Select/index.tsx +++ b/packages/core-components/src/components/Select/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/static/ClosedDropdown.tsx b/packages/core-components/src/components/Select/static/ClosedDropdown.tsx index 235a91963b..41155bb268 100644 --- a/packages/core-components/src/components/Select/static/ClosedDropdown.tsx +++ b/packages/core-components/src/components/Select/static/ClosedDropdown.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Select/static/OpenedDropdown.tsx b/packages/core-components/src/components/Select/static/OpenedDropdown.tsx index e4a8021017..2c91dc6989 100644 --- a/packages/core-components/src/components/Select/static/OpenedDropdown.tsx +++ b/packages/core-components/src/components/Select/static/OpenedDropdown.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx index 11bb06c723..9d958f766d 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx index 527160c4ff..5897054ed5 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx index ff65534843..46b2ce551a 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx index 750ee55c10..b126b5ba75 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx index bdf3e8c59d..51fcee1f71 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/index.ts b/packages/core-components/src/components/SimpleStepper/index.ts index 392a66affa..ddb6d2537a 100644 --- a/packages/core-components/src/components/SimpleStepper/index.ts +++ b/packages/core-components/src/components/SimpleStepper/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SimpleStepper/types.ts b/packages/core-components/src/components/SimpleStepper/types.ts index 0a488ff356..ef0fc46a84 100644 --- a/packages/core-components/src/components/SimpleStepper/types.ts +++ b/packages/core-components/src/components/SimpleStepper/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Status/Status.stories.tsx b/packages/core-components/src/components/Status/Status.stories.tsx index 205645e1ec..26a6fd2219 100644 --- a/packages/core-components/src/components/Status/Status.stories.tsx +++ b/packages/core-components/src/components/Status/Status.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Status/Status.test.tsx b/packages/core-components/src/components/Status/Status.test.tsx index 9a8daecd33..c1e1d42af2 100644 --- a/packages/core-components/src/components/Status/Status.test.tsx +++ b/packages/core-components/src/components/Status/Status.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Status/Status.tsx b/packages/core-components/src/components/Status/Status.tsx index f12f886b68..3d79c38581 100644 --- a/packages/core-components/src/components/Status/Status.tsx +++ b/packages/core-components/src/components/Status/Status.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/Status/index.ts b/packages/core-components/src/components/Status/index.ts index 4c0fd6322b..2e34890482 100644 --- a/packages/core-components/src/components/Status/index.ts +++ b/packages/core-components/src/components/Status/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx b/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx index b34e535a00..b7790e38da 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/MetadataTable.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx index 7f07018f6f..458c431b27 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx index 0844710876..7ebe15c931 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx index 916259042c..af56cb5b89 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/StructuredMetadataTable/index.tsx b/packages/core-components/src/components/StructuredMetadataTable/index.tsx index 628f3395db..32ba94edd6 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/index.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/components/SupportButton/SupportButton.test.tsx b/packages/core-components/src/components/SupportButton/SupportButton.test.tsx index 6d6b9fd477..341ed145a6 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.test.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import { RenderResult, waitFor, fireEvent, + screen, } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; +import { wrapInTestApp, renderInTestApp } from '@backstage/test-utils'; import { SupportButton } from './SupportButton'; const SUPPORT_BUTTON_ID = 'support-button'; @@ -41,6 +42,12 @@ describe('', () => { ); }); + it('supports passing a title', async () => { + await renderInTestApp(); + fireEvent.click(screen.getByTestId(SUPPORT_BUTTON_ID)); + expect(screen.getByText('Custom title')).toBeInTheDocument(); + }); + it('shows popover on click', async () => { let renderResult: RenderResult; diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 6c6de8abe3..2ffef89878 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,19 +24,18 @@ import { ListItem, ListItemIcon, ListItemText, + Typography, makeStyles, Popover, } from '@material-ui/core'; -import React, { - Fragment, - MouseEventHandler, - PropsWithChildren, - useState, -} from 'react'; +import React, { Fragment, MouseEventHandler, useState } from 'react'; import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; import { Link } from '../Link'; -type Props = {}; +type SupportButtonProps = { + title?: string; + children?: React.ReactNode; +}; const useStyles = makeStyles({ popoverList: { @@ -78,7 +77,7 @@ const SupportListItem = ({ item }: { item: SupportItem }) => { ); }; -export const SupportButton = ({ children }: PropsWithChildren) => { +export const SupportButton = ({ title, children }: SupportButtonProps) => { const { items } = useSupportConfig(); const [popoverOpen, setPopoverOpen] = useState(false); @@ -121,13 +120,20 @@ export const SupportButton = ({ children }: PropsWithChildren) => { onClose={popoverCloseHandler} > + {title && ( + + {title} + + )} {React.Children.map(children, (child, i) => ( - + {child} ))} {items && - items.map((item, i) => )} + items.map((item, i) => ( + + ))} ); }; export const ErrorBoundary: ComponentClass< - Props, + ErrorBoundaryProps, State -> = class ErrorBoundary extends Component { - constructor(props: Props) { +> = class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { super(props); - this.state = { error: undefined, errorInfo: undefined, @@ -61,13 +70,17 @@ export const ErrorBoundary: ComponentClass< } render() { - const { slackChannel } = this.props; - const { error, errorInfo } = this.state; + const { slackChannel, children } = this.props; + const { error } = this.state; - if (!errorInfo) { - return this.props.children; + if (!error) { + return children; } - return ; + return ( + + + + ); } }; diff --git a/packages/core-components/src/layout/ErrorBoundary/index.ts b/packages/core-components/src/layout/ErrorBoundary/index.ts index 607634e89a..a0640bd5ef 100644 --- a/packages/core-components/src/layout/ErrorBoundary/index.ts +++ b/packages/core-components/src/layout/ErrorBoundary/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,5 @@ * limitations under the License. */ +export type { ErrorBoundaryProps } from './ErrorBoundary'; export { ErrorBoundary } from './ErrorBoundary'; diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx index 23b42c0a11..a314128bce 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index 466c2f46b6..cca88147c4 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/ErrorPage/MicDrop.tsx b/packages/core-components/src/layout/ErrorPage/MicDrop.tsx index aa8aaaf5e7..666eea4f09 100644 --- a/packages/core-components/src/layout/ErrorPage/MicDrop.tsx +++ b/packages/core-components/src/layout/ErrorPage/MicDrop.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/ErrorPage/index.ts b/packages/core-components/src/layout/ErrorPage/index.ts index 506ed1f815..a9a5fc9b2a 100644 --- a/packages/core-components/src/layout/ErrorPage/index.ts +++ b/packages/core-components/src/layout/ErrorPage/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Header/Header.stories.tsx b/packages/core-components/src/layout/Header/Header.stories.tsx index db4ddac8ec..9fee6313e9 100644 --- a/packages/core-components/src/layout/Header/Header.stories.tsx +++ b/packages/core-components/src/layout/Header/Header.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Header/Header.test.tsx b/packages/core-components/src/layout/Header/Header.test.tsx index a833f26939..69d46cd102 100644 --- a/packages/core-components/src/layout/Header/Header.test.tsx +++ b/packages/core-components/src/layout/Header/Header.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Header/Header.tsx b/packages/core-components/src/layout/Header/Header.tsx index 1eadaddaf5..96d024f524 100644 --- a/packages/core-components/src/layout/Header/Header.tsx +++ b/packages/core-components/src/layout/Header/Header.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Header/index.ts b/packages/core-components/src/layout/Header/index.ts index e0860413c9..2c322fe9c1 100644 --- a/packages/core-components/src/layout/Header/index.ts +++ b/packages/core-components/src/layout/Header/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx index 76ede5398d..b1bd398d15 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.tsx index 6f0b337b54..06a9b2b701 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderActionMenu/VerticalMenuIcon.tsx b/packages/core-components/src/layout/HeaderActionMenu/VerticalMenuIcon.tsx index c9fde8cea2..a6a1d3a3d7 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/VerticalMenuIcon.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/VerticalMenuIcon.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderActionMenu/index.ts b/packages/core-components/src/layout/HeaderActionMenu/index.ts index bb7fa80d59..22182a6a0e 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/index.ts +++ b/packages/core-components/src/layout/HeaderActionMenu/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.test.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.test.tsx index acadb22525..3dff1f51be 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.test.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx index 914be06cf2..1534656f24 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderLabel/index.ts b/packages/core-components/src/layout/HeaderLabel/index.ts index 683ec59784..cc803e7fde 100644 --- a/packages/core-components/src/layout/HeaderLabel/index.ts +++ b/packages/core-components/src/layout/HeaderLabel/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx index 763dc3ceee..e6762de90e 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx index 1fd4a18ba3..46fe9f1569 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HeaderTabs/index.tsx b/packages/core-components/src/layout/HeaderTabs/index.tsx index 1097698265..e706f19bf6 100644 --- a/packages/core-components/src/layout/HeaderTabs/index.tsx +++ b/packages/core-components/src/layout/HeaderTabs/index.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx index e3ca55667c..eda877884e 100644 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx +++ b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx index d93d178486..04073cca48 100644 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx +++ b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/HomepageTimer/index.ts b/packages/core-components/src/layout/HomepageTimer/index.ts index facee1e982..bc004fc757 100644 --- a/packages/core-components/src/layout/HomepageTimer/index.ts +++ b/packages/core-components/src/layout/HomepageTimer/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx index 047f0f9321..cf012a129a 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.test.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.test.tsx index f82d51fe06..479efe4fd4 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.test.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 5bb3e19362..cc15bbd9f4 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import { makeStyles, } from '@material-ui/core'; import classNames from 'classnames'; -import { ErrorBoundary } from '../ErrorBoundary'; +import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary'; import { BottomLink, BottomLinkProps } from '../BottomLink'; const useStyles = makeStyles(theme => ({ @@ -37,7 +37,6 @@ const useStyles = makeStyles(theme => ({ }, }, header: { - display: 'inline-block', padding: theme.spacing(2, 2, 2, 2.5), }, headerTitle: { @@ -95,8 +94,8 @@ export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; * You can custom style an InfoCard with the 'style' (outer container) and 'cardStyle' (inner container) * styles. * - * The InfoCard serves as an error boundary. As a result, if you provide a 'slackChannel' property this - * specifies the channel to display in the error component that is displayed if an error occurs + * The InfoCard serves as an error boundary. As a result, if you provide an 'errorBoundaryProps' property this + * specifies the extra information to display in the error component that is displayed if an error occurs * in any descendent components. * * By default the InfoCard has no custom layout of its children, but is treated as a block element. A @@ -112,13 +111,16 @@ type Props = { subheader?: ReactNode; divider?: boolean; deepLink?: BottomLinkProps; + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; variant?: InfoCardVariants; style?: object; cardStyle?: object; children?: ReactNode; headerStyle?: object; headerProps?: CardHeaderProps; + action?: ReactNode; actionsClassName?: string; actions?: ReactNode; cardClassName?: string; @@ -133,11 +135,13 @@ export const InfoCard = ({ subheader, divider = true, deepLink, - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, variant, children, headerStyle, headerProps, + action, actionsClassName, actions, cardClassName, @@ -169,9 +173,12 @@ export const InfoCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && ( ({ "'pageHeader pageHeader pageHeader' 'pageSubheader pageSubheader pageSubheader' 'pageNav pageContent pageSidebar'", gridTemplateRows: 'auto auto 1fr', gridTemplateColumns: 'auto 1fr auto', - minHeight: '100vh', + height: '100vh', + overflowY: 'auto', }, })); diff --git a/packages/core-components/src/layout/Page/index.ts b/packages/core-components/src/layout/Page/index.ts index 987aee5cdb..d2523e8467 100644 --- a/packages/core-components/src/layout/Page/index.ts +++ b/packages/core-components/src/layout/Page/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 260593de95..65da4d2d75 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core'; +import { makeStyles, useMediaQuery } from '@material-ui/core'; import clsx from 'clsx'; import React, { useRef, useState, useContext, PropsWithChildren } from 'react'; import { sidebarConfig, SidebarContext } from './config'; @@ -82,6 +82,9 @@ export const Sidebar = ({ children, }: PropsWithChildren) => { const classes = useStyles(); + const isSmallScreen = useMediaQuery(theme => + theme.breakpoints.down('md'), + ); const [state, setState] = useState(State.Closed); const hoverTimerRef = useRef(); const { isPinned } = useContext(SidebarPinStateContext); @@ -94,7 +97,7 @@ export const Sidebar = ({ clearTimeout(hoverTimerRef.current); hoverTimerRef.current = undefined; } - if (state !== State.Open) { + if (state !== State.Open && !isSmallScreen) { hoverTimerRef.current = window.setTimeout(() => { hoverTimerRef.current = undefined; setState(State.Open); @@ -122,6 +125,8 @@ export const Sidebar = ({ } }; + const isOpen = (state === State.Open && !isSmallScreen) || isPinned; + return (
{children} diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index b95719c05f..1d46239865 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/Items.test.tsx b/packages/core-components/src/layout/Sidebar/Items.test.tsx index fb0ee174f8..89d2a2c294 100644 --- a/packages/core-components/src/layout/Sidebar/Items.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index f839db56de..28abbce53c 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,10 @@ import { makeStyles, styled, TextField, + Theme, Typography, } from '@material-ui/core'; +import { CreateCSSProperties } from '@material-ui/core/styles/withStyles'; import SearchIcon from '@material-ui/icons/Search'; import clsx from 'clsx'; import React, { @@ -290,3 +292,32 @@ export const SidebarDivider = styled('hr')({ border: 'none', margin: '12px 0px', }); + +const styledScrollbar = (theme: Theme): CreateCSSProperties => ({ + overflowY: 'auto', + '&::-webkit-scrollbar': { + backgroundColor: theme.palette.background.default, + width: '5px', + borderRadius: '5px', + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: theme.palette.text.hint, + borderRadius: '5px', + }, +}); + +export const SidebarScrollWrapper = styled('div')(({ theme }) => { + const scrollbarStyles = styledScrollbar(theme); + return { + flex: '0 1 auto', + overflowX: 'hidden', + // 5px space to the right of the scrollbar + width: 'calc(100% - 5px)', + // Display at least one item in the container + // Question: Can this be a config/theme variable - if so, which? :/ + minHeight: '48px', + overflowY: 'hidden', + '@media (hover: none)': scrollbarStyles, + '&:hover': scrollbarStyles, + }; +}); diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx index 25717cb053..0ace7468ef 100644 --- a/packages/core-components/src/layout/Sidebar/Page.tsx +++ b/packages/core-components/src/layout/Sidebar/Page.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index d4f1a7a96e..40921a8159 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index 8ea6cc94f9..3d574d03bb 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index 803306478d..58e193dbaf 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ export { SidebarSearchField, SidebarSpace, SidebarSpacer, + SidebarScrollWrapper, } from './Items'; export { IntroCard, SidebarIntro } from './Intro'; export { diff --git a/packages/core-components/src/layout/Sidebar/localStorage.test.ts b/packages/core-components/src/layout/Sidebar/localStorage.test.ts index 0cfcac73c5..c115fa4e99 100644 --- a/packages/core-components/src/layout/Sidebar/localStorage.test.ts +++ b/packages/core-components/src/layout/Sidebar/localStorage.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/Sidebar/localStorage.ts b/packages/core-components/src/layout/Sidebar/localStorage.ts index 665c97423a..0e904ee319 100644 --- a/packages/core-components/src/layout/Sidebar/localStorage.ts +++ b/packages/core-components/src/layout/Sidebar/localStorage.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 3aec8997aa..1ccce80e59 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx index c0c0ba8e09..f105cd4716 100644 --- a/packages/core-components/src/layout/SignInPage/auth0Provider.tsx +++ b/packages/core-components/src/layout/SignInPage/auth0Provider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 389d8caa98..bd46e6fa58 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index 09ffcda10a..e8f7d03a1d 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index f4854311ac..8673c17ffa 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/index.ts b/packages/core-components/src/layout/SignInPage/index.ts index b10ea7ae33..2e5502d7ea 100644 --- a/packages/core-components/src/layout/SignInPage/index.ts +++ b/packages/core-components/src/layout/SignInPage/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/providers.tsx b/packages/core-components/src/layout/SignInPage/providers.tsx index ab9daf373b..19915e6aa9 100644 --- a/packages/core-components/src/layout/SignInPage/providers.tsx +++ b/packages/core-components/src/layout/SignInPage/providers.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,6 +143,7 @@ export const useSignInProviders = ( if (didCancel) { return; } + localStorage.removeItem(PROVIDER_STORAGE_KEY); errorApi.post(error); setLoading(false); }); diff --git a/packages/core-components/src/layout/SignInPage/styles.tsx b/packages/core-components/src/layout/SignInPage/styles.tsx index 0d8bd245c5..96559a4e78 100644 --- a/packages/core-components/src/layout/SignInPage/styles.tsx +++ b/packages/core-components/src/layout/SignInPage/styles.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index 54385d05a3..1e4e4a2997 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx index 0a68b0b47b..3214d26653 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx index 097d81d570..5e0014c400 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx index c9ae2eebc8..41b39984a5 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import { TabProps, } from '@material-ui/core'; import { BottomLink, BottomLinkProps } from '../BottomLink'; -import { ErrorBoundary } from '../ErrorBoundary'; +import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary'; const useTabsStyles = makeStyles(theme => ({ root: { @@ -52,7 +52,9 @@ const BoldHeader = withStyles(theme => ({ }))(CardHeader); type Props = { + /** @deprecated Use errorBoundaryProps instead */ slackChannel?: string; + errorBoundaryProps?: ErrorBoundaryProps; children?: ReactElement[]; onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void; title?: string; @@ -61,7 +63,8 @@ type Props = { }; const TabbedCard = ({ - slackChannel = '#backstage', + slackChannel, + errorBoundaryProps, children, title, deepLink, @@ -87,9 +90,12 @@ const TabbedCard = ({ }); } + const errProps: ErrorBoundaryProps = + errorBoundaryProps || (slackChannel ? { slackChannel } : {}); + return ( - + {title && } Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// import { BackstageTheme } from '@backstage/theme'; import { ComponentType } from 'react'; import { Config } from '@backstage/config'; import { default as React_2 } from 'react'; +import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { SvgIconProps } from '@material-ui/core'; // @public export type AlertApi = { - post(alert: AlertMessage): void; - alert$(): Observable; + post(alert: AlertMessage): void; + alert$(): Observable; }; // @public (undocumented) @@ -22,43 +24,53 @@ export const alertApiRef: ApiRef; // @public (undocumented) export type AlertMessage = { - message: string; - severity?: 'success' | 'info' | 'warning' | 'error'; + message: string; + severity?: 'success' | 'info' | 'warning' | 'error'; }; // @public (undocumented) -export type AnyApiFactory = ApiFactory; + } +>; // @public (undocumented) export type AnyApiRef = ApiRef; // @public (undocumented) -export type ApiFactory = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; + } +> = { + api: ApiRef; + deps: TypesToApiRefs; + factory(deps: Deps): Impl; }; // @public (undocumented) export type ApiHolder = { - get(api: ApiRef): T | undefined; + get(api: ApiRef): T | undefined; }; // @public (undocumented) export type ApiRef = { - id: string; - T: T; + id: string; + T: T; }; // @public (undocumented) -export type ApiRefsToTypes; -}> = { - [key in keyof T]: ApiRefType; + } +> = { + [key in keyof T]: ApiRefType; }; // @public (undocumented) @@ -66,93 +78,106 @@ export type ApiRefType = T extends ApiRef ? U : never; // @public (undocumented) export type AppComponents = { - NotFoundErrorPage: ComponentType<{}>; - BootErrorPage: ComponentType; - Progress: ComponentType<{}>; - Router: ComponentType<{}>; - ErrorBoundaryFallback: ComponentType; - SignInPage?: ComponentType; + NotFoundErrorPage: ComponentType<{}>; + BootErrorPage: ComponentType; + Progress: ComponentType<{}>; + Router: ComponentType<{}>; + ErrorBoundaryFallback: ComponentType; + SignInPage?: ComponentType; }; // @public (undocumented) export type AppContext = { - getPlugins(): BackstagePlugin[]; - getSystemIcon(key: string): IconComponent | undefined; - getComponents(): AppComponents; + getPlugins(): BackstagePlugin[]; + getSystemIcon(key: string): IconComponent | undefined; + getComponents(): AppComponents; }; // @public export type AppTheme = { - id: string; - title: string; - variant: 'light' | 'dark'; - theme: BackstageTheme; - icon?: React.ReactElement; + id: string; + title: string; + variant: 'light' | 'dark'; + theme: BackstageTheme; + icon?: React.ReactElement; }; // @public export type AppThemeApi = { - getInstalledThemes(): AppTheme[]; - activeThemeId$(): Observable; - getActiveThemeId(): string | undefined; - setActiveThemeId(themeId?: string): void; + getInstalledThemes(): AppTheme[]; + activeThemeId$(): Observable; + getActiveThemeId(): string | undefined; + setActiveThemeId(themeId?: string): void; }; // @public (undocumented) export const appThemeApiRef: ApiRef; // @public (undocumented) -export function attachComponentData

(component: ComponentType

, type: string, data: unknown): void; +export function attachComponentData

( + component: ComponentType

, + type: string, + data: unknown, +): void; // @public -export const auth0AuthApiRef: ApiRef; +export const auth0AuthApiRef: ApiRef< + OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; // @public export type AuthProvider = { - title: string; - icon: IconComponent; + title: string; + icon: IconComponent; }; // @public -export type AuthRequester = (scopes: Set) => Promise; +export type AuthRequester = ( + scopes: Set, +) => Promise; // @public export type AuthRequesterOptions = { - provider: AuthProvider; - onAuthRequest(scopes: Set): Promise; + provider: AuthProvider; + onAuthRequest(scopes: Set): Promise; }; // @public (undocumented) export type AuthRequestOptions = { - optional?: boolean; - instantPopup?: boolean; + optional?: boolean; + instantPopup?: boolean; }; // @public (undocumented) export type BackstageIdentity = { - id: string; - idToken: string; + id: string; + idToken: string; }; // @public export type BackstageIdentityApi = { - getBackstageIdentity(options?: AuthRequestOptions): Promise; + getBackstageIdentity( + options?: AuthRequestOptions, + ): Promise; }; // @public (undocumented) -export type BackstagePlugin = { - getId(): string; - output(): PluginOutput[]; - getApis(): Iterable; - provide(extension: Extension): T; - routes: Routes; - externalRoutes: ExternalRoutes; +export type BackstagePlugin< + Routes extends AnyRoutes = {}, + ExternalRoutes extends AnyExternalRoutes = {} +> = { + getId(): string; + output(): PluginOutput[]; + getApis(): Iterable; + provide(extension: Extension): T; + routes: Routes; + externalRoutes: ExternalRoutes; }; // @public (undocumented) export type BootErrorPageProps = { - step: 'load-config' | 'load-chunk'; - error: Error; + step: 'load-config' | 'load-chunk'; + error: Error; }; // @public @@ -162,78 +187,115 @@ export type ConfigApi = Config; export const configApiRef: ApiRef; // @public -export function createApiFactory(factory: ApiFactory): ApiFactory; + } +>(factory: ApiFactory): ApiFactory; // @public (undocumented) -export function createApiFactory(api: ApiRef, instance: Impl): ApiFactory; +export function createApiFactory( + api: ApiRef, + instance: Impl, +): ApiFactory; // @public (undocumented) export function createApiRef(config: ApiRefConfig): ApiRef; // @public (undocumented) -export function createComponentExtension JSX.Element | null>(options: { - component: ComponentLoader; -}): Extension; +export function createComponentExtension< + T extends (props: any) => JSX.Element | null +>(options: { component: ComponentLoader }): Extension; // @public (undocumented) -export function createExternalRouteRef(options: { - id: string; - params?: ParamKey[]; - optional?: Optional; + }, + Optional extends boolean = false, + ParamKey extends string = never +>(options: { + id: string; + params?: ParamKey[]; + optional?: Optional; }): ExternalRouteRef, Optional>; // @public (undocumented) -export function createPlugin(config: PluginConfig): BackstagePlugin; +export function createPlugin< + Routes extends AnyRoutes = {}, + ExternalRoutes extends AnyExternalRoutes = {} +>( + config: PluginConfig, +): BackstagePlugin; // @public (undocumented) -export function createReactExtension JSX.Element | null>(options: { - component: ComponentLoader; - data?: Record; +export function createReactExtension< + T extends (props: any) => JSX.Element | null +>(options: { + component: ComponentLoader; + data?: Record; }): Extension; // @public (undocumented) -export function createRoutableExtension JSX.Element | null>(options: { - component: () => Promise; - mountPoint: RouteRef; -}): Extension; +export function createRoutableExtension< + T extends (props: any) => JSX.Element | null +>(options: { component: () => Promise; mountPoint: RouteRef }): Extension; // @public (undocumented) -export function createRouteRef(config: { - id?: string; - params?: ParamKey[]; - path?: string; - icon?: OldIconComponent; - title?: string; + }, + ParamKey extends string = never +>(config: { + id?: string; + params?: ParamKey[]; + path?: string; + icon?: OldIconComponent; + title?: string; }): RouteRef>; // @public (undocumented) -export function createSubRouteRef(config: { - id: string; - path: Path; - parent: RouteRef; +export function createSubRouteRef< + Path extends string, + ParentParams extends AnyParams = never +>(config: { + id: string; + path: Path; + parent: RouteRef; }): MakeSubRouteRef, ParentParams>; // @public export type DiscoveryApi = { - getBaseUrl(pluginId: string): Promise; + getBaseUrl(pluginId: string): Promise; }; // @public (undocumented) export const discoveryApiRef: ApiRef; +// @public +export interface ElementCollection { + findComponentData(query: { key: string }): T[]; + getElements< + Props extends { + [name: string]: unknown; + } + >(): Array>; + selectByComponentData(query: { + key: string; + withStrictError?: string; + }): ElementCollection; +} + // @public export type ErrorApi = { - post(error: Error_2, context?: ErrorContext): void; - error$(): Observable<{ - error: Error_2; - context?: ErrorContext; - }>; + post(error: Error_2, context?: ErrorContext): void; + error$(): Observable<{ + error: Error_2; + context?: ErrorContext; + }>; }; // @public (undocumented) @@ -241,46 +303,49 @@ export const errorApiRef: ApiRef; // @public (undocumented) export type ErrorBoundaryFallbackProps = { - plugin?: BackstagePlugin; - error: Error; - resetError: () => void; + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; }; // @public export type ErrorContext = { - hidden?: boolean; + hidden?: boolean; }; // @public (undocumented) export type Extension = { - expose(plugin: BackstagePlugin): T; + expose(plugin: BackstagePlugin): T; }; // @public (undocumented) -export type ExternalRouteRef = { - readonly [routeRefType]: 'external'; - params: ParamKeys; - optional?: Optional; +export type ExternalRouteRef< + Params extends AnyParams = any, + Optional extends boolean = any +> = { + readonly [routeRefType]: 'external'; + params: ParamKeys; + optional?: Optional; }; // @public export type FeatureFlag = { - name: string; - pluginId: string; + name: string; + pluginId: string; }; // @public (undocumented) export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; + type: 'feature-flag'; + name: string; }; // @public (undocumented) export interface FeatureFlagsApi { - getRegisteredFlags(): FeatureFlag[]; - isActive(name: string): boolean; - registerFlag(flag: FeatureFlag): void; - save(options: FeatureFlagsSaveOptions): void; + getRegisteredFlags(): FeatureFlag[]; + isActive(name: string): boolean; + registerFlag(flag: FeatureFlag): void; + save(options: FeatureFlagsSaveOptions): void; } // @public (undocumented) @@ -288,66 +353,96 @@ export const featureFlagsApiRef: ApiRef; // @public (undocumented) export type FeatureFlagsHooks = { - register(name: string): void; + register(name: string): void; }; // @public export type FeatureFlagsSaveOptions = { - states: Record; - merge?: boolean; + states: Record; + merge?: boolean; }; // @public (undocumented) export enum FeatureFlagState { - // (undocumented) - Active = 1, - // (undocumented) - None = 0 + // (undocumented) + Active = 1, + // (undocumented) + None = 0, } // @public (undocumented) -export function getComponentData(node: ReactNode, type: string): T | undefined; +export function getComponentData( + node: ReactNode, + type: string, +): T | undefined; // @public -export const githubAuthApiRef: ApiRef; +export const githubAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; // @public -export const gitlabAuthApiRef: ApiRef; +export const gitlabAuthApiRef: ApiRef< + OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +>; // @public -export const googleAuthApiRef: ApiRef; +export const googleAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public export type IconComponent = ComponentType<{ - fontSize?: 'default' | 'small' | 'large'; + fontSize?: 'default' | 'small' | 'large'; }>; // @public export type IdentityApi = { - getUserId(): string; - getProfile(): ProfileInfo; - getIdToken(): Promise; - signOut(): Promise; + getUserId(): string; + getProfile(): ProfileInfo; + getIdToken(): Promise; + signOut(): Promise; }; // @public (undocumented) export const identityApiRef: ApiRef; // @public -export const microsoftAuthApiRef: ApiRef; +export const microsoftAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public -export const oauth2ApiRef: ApiRef; +export const oauth2ApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public export type OAuthApi = { - getAccessToken(scope?: OAuthScope, options?: AuthRequestOptions): Promise; + getAccessToken( + scope?: OAuthScope, + options?: AuthRequestOptions, + ): Promise; }; // @public export type OAuthRequestApi = { - createAuthRequester(options: AuthRequesterOptions): AuthRequester; - authRequest$(): Observable; + createAuthRequester( + options: AuthRequesterOptions, + ): AuthRequester; + authRequest$(): Observable; }; // @public (undocumented) @@ -358,51 +453,76 @@ export type OAuthScope = string | string[]; // @public export type Observable = { - [Symbol.observable](): Observable; - subscribe(observer: Observer): Subscription; - subscribe(onNext?: (value: T) => void, onError?: (error: Error) => void, onComplete?: () => void): Subscription; + [Symbol.observable](): Observable; + subscribe(observer: Observer): Subscription; + subscribe( + onNext?: (value: T) => void, + onError?: (error: Error) => void, + onComplete?: () => void, + ): Subscription; }; // @public export type Observer = { - next?(value: T): void; - error?(error: Error): void; - complete?(): void; + next?(value: T): void; + error?(error: Error): void; + complete?(): void; }; // @public -export const oidcAuthApiRef: ApiRef; +export const oidcAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public -export const oktaAuthApiRef: ApiRef; +export const oktaAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public (undocumented) -export const oneloginAuthApiRef: ApiRef; +export const oneloginAuthApiRef: ApiRef< + OAuthApi & + OpenIdConnectApi & + ProfileInfoApi & + BackstageIdentityApi & + SessionApi +>; // @public export type OpenIdConnectApi = { - getIdToken(options?: AuthRequestOptions): Promise; + getIdToken(options?: AuthRequestOptions): Promise; }; // @public export type PendingAuthRequest = { - provider: AuthProvider; - reject: () => void; - trigger(): Promise; + provider: AuthProvider; + reject: () => void; + trigger(): Promise; }; // @public (undocumented) -export type PluginConfig = { - id: string; - apis?: Iterable; - register?(hooks: PluginHooks): void; - routes?: Routes; - externalRoutes?: ExternalRoutes; +export type PluginConfig< + Routes extends AnyRoutes, + ExternalRoutes extends AnyExternalRoutes +> = { + id: string; + apis?: Iterable; + register?(hooks: PluginHooks): void; + routes?: Routes; + externalRoutes?: ExternalRoutes; }; // @public (undocumented) export type PluginHooks = { - featureFlags: FeatureFlagsHooks; + featureFlags: FeatureFlagsHooks; }; // @public (undocumented) @@ -410,19 +530,19 @@ export type PluginOutput = FeatureFlagOutput; // @public export type ProfileInfo = { - email?: string; - displayName?: string; - picture?: string; + email?: string; + displayName?: string; + picture?: string; }; // @public export type ProfileInfoApi = { - getProfile(options?: AuthRequestOptions): Promise; + getProfile(options?: AuthRequestOptions): Promise; }; // @public (undocumented) export type RouteOptions = { - exact?: boolean; + exact?: boolean; }; // @public (undocumented) @@ -430,51 +550,53 @@ export type RoutePath = string; // @public (undocumented) export type RouteRef = { - readonly [routeRefType]: 'absolute'; - params: ParamKeys; - path: string; - icon?: OldIconComponent; - title?: string; + readonly [routeRefType]: 'absolute'; + params: ParamKeys; + path: string; + icon?: OldIconComponent; + title?: string; }; // @public -export const samlAuthApiRef: ApiRef; +export const samlAuthApiRef: ApiRef< + ProfileInfoApi & BackstageIdentityApi & SessionApi +>; // @public export type SessionApi = { - signIn(): Promise; - signOut(): Promise; - sessionState$(): Observable; + signIn(): Promise; + signOut(): Promise; + sessionState$(): Observable; }; // @public export enum SessionState { - // (undocumented) - SignedIn = "SignedIn", - // (undocumented) - SignedOut = "SignedOut" + // (undocumented) + SignedIn = 'SignedIn', + // (undocumented) + SignedOut = 'SignedOut', } // @public (undocumented) export type SignInPageProps = { - onResult(result: SignInResult): void; + onResult(result: SignInResult): void; }; // @public (undocumented) export type SignInResult = { - userId: string; - profile: ProfileInfo; - getIdToken?: () => Promise; - signOut?: () => Promise; + userId: string; + profile: ProfileInfo; + getIdToken?: () => Promise; + signOut?: () => Promise; }; // @public (undocumented) export interface StorageApi { - forBucket(name: string): StorageApi; - get(key: string): T | undefined; - observe$(key: string): Observable>; - remove(key: string): Promise; - set(key: string, data: any): Promise; + forBucket(name: string): StorageApi; + get(key: string): T | undefined; + observe$(key: string): Observable>; + remove(key: string): Promise; + set(key: string, data: any): Promise; } // @public (undocumented) @@ -482,27 +604,27 @@ export const storageApiRef: ApiRef; // @public (undocumented) export type StorageValueChange = { - key: string; - newValue?: T; + key: string; + newValue?: T; }; // @public (undocumented) export type SubRouteRef = { - readonly [routeRefType]: 'sub'; - parent: RouteRef; - path: string; - params: ParamKeys; + readonly [routeRefType]: 'sub'; + parent: RouteRef; + path: string; + params: ParamKeys; }; // @public export type Subscription = { - unsubscribe(): void; - readonly closed: boolean; + unsubscribe(): void; + readonly closed: boolean; }; // @public (undocumented) export type TypesToApiRefs = { - [key in keyof T]: ApiRef; + [key in keyof T]: ApiRef; }; // @public (undocumented) @@ -514,25 +636,40 @@ export function useApiHolder(): ApiHolder; // @public (undocumented) export const useApp: () => AppContext; +// @public +export function useElementFilter( + node: ReactNode, + filterFn: (arg: ElementCollection) => T, + dependencies?: any[], +): T; + // @public (undocumented) export type UserFlags = {}; // @public (undocumented) -export function useRouteRef(routeRef: ExternalRouteRef): Optional extends true ? RouteFunc | undefined : RouteFunc; +export function useRouteRef( + routeRef: ExternalRouteRef, +): Optional extends true ? RouteFunc | undefined : RouteFunc; // @public (undocumented) -export function useRouteRef(routeRef: RouteRef | SubRouteRef): RouteFunc; +export function useRouteRef( + routeRef: RouteRef | SubRouteRef, +): RouteFunc; // @public (undocumented) -export function useRouteRefParams(_routeRef: RouteRef | SubRouteRef): Params; +export function useRouteRefParams( + _routeRef: RouteRef | SubRouteRef, +): Params; // @public (undocumented) -export function withApis(apis: TypesToApiRefs):

(WrappedComponent: React_2.ComponentType

) => { - (props: React_2.PropsWithChildren>): JSX.Element; - displayName: string; +export function withApis( + apis: TypesToApiRefs, +):

( + WrappedComponent: React_2.ComponentType

, +) => { + (props: React_2.PropsWithChildren>): JSX.Element; + displayName: string; }; - // (No @packageDocumentation comment for this package) - ``` diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 816727ed9e..7b2aa3deea 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", @@ -41,8 +41,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", @@ -53,7 +54,7 @@ "@types/prop-types": "^15.7.3", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.3" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts index 36bc1b5596..aac2283973 100644 --- a/packages/core-plugin-api/src/apis/definitions/AlertApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AlertApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts index df58c3977e..3ff9ddce98 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts index 6fdd180a6f..08b9f91f57 100644 --- a/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ConfigApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts index 6e11c06ce5..7082cda9dc 100644 --- a/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/DiscoveryApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts index ffbfa67924..dc63e28e1a 100644 --- a/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/ErrorApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index 66a39aa1b1..07797bf3f1 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts index a0cfde537a..9d7131fed6 100644 --- a/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/IdentityApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts index 8b33ee394b..3fe7f26bc0 100644 --- a/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/OAuthRequestApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts index 404de3697b..9332c80f0f 100644 --- a/packages/core-plugin-api/src/apis/definitions/StorageApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/StorageApi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/auth.ts b/packages/core-plugin-api/src/apis/definitions/auth.ts index 14e402a3c0..c786b43fc3 100644 --- a/packages/core-plugin-api/src/apis/definitions/auth.ts +++ b/packages/core-plugin-api/src/apis/definitions/auth.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/definitions/index.ts b/packages/core-plugin-api/src/apis/definitions/index.ts index e29d1022c4..d4350ddbf6 100644 --- a/packages/core-plugin-api/src/apis/definitions/index.ts +++ b/packages/core-plugin-api/src/apis/definitions/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/index.ts b/packages/core-plugin-api/src/apis/index.ts index 3cce4b982a..15058e2c49 100644 --- a/packages/core-plugin-api/src/apis/index.ts +++ b/packages/core-plugin-api/src/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.test.ts b/packages/core-plugin-api/src/apis/system/ApiRef.test.ts index b0535e954c..dab872236f 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.test.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/ApiRef.ts b/packages/core-plugin-api/src/apis/system/ApiRef.ts index 397402cdc3..37678ed079 100644 --- a/packages/core-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/core-plugin-api/src/apis/system/ApiRef.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/helpers.ts b/packages/core-plugin-api/src/apis/system/helpers.ts index cabff73060..8e84dd6c09 100644 --- a/packages/core-plugin-api/src/apis/system/helpers.ts +++ b/packages/core-plugin-api/src/apis/system/helpers.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/index.ts b/packages/core-plugin-api/src/apis/system/index.ts index 8b8dfff80b..ceada1ebd7 100644 --- a/packages/core-plugin-api/src/apis/system/index.ts +++ b/packages/core-plugin-api/src/apis/system/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/types.ts b/packages/core-plugin-api/src/apis/system/types.ts index 17f3fe276b..e805b6f255 100644 --- a/packages/core-plugin-api/src/apis/system/types.ts +++ b/packages/core-plugin-api/src/apis/system/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/useApi.test.tsx b/packages/core-plugin-api/src/apis/system/useApi.test.tsx index 563ae8bc29..b23e44b815 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.test.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/apis/system/useApi.tsx b/packages/core-plugin-api/src/apis/system/useApi.tsx index a0a40085cd..eae6b98654 100644 --- a/packages/core-plugin-api/src/apis/system/useApi.tsx +++ b/packages/core-plugin-api/src/apis/system/useApi.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/app/index.ts b/packages/core-plugin-api/src/app/index.ts index 3079aef415..bd806910ae 100644 --- a/packages/core-plugin-api/src/app/index.ts +++ b/packages/core-plugin-api/src/app/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/app/types.ts b/packages/core-plugin-api/src/app/types.ts index 26e264983c..8b07d699ee 100644 --- a/packages/core-plugin-api/src/app/types.ts +++ b/packages/core-plugin-api/src/app/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/app/useApp.test.tsx b/packages/core-plugin-api/src/app/useApp.test.tsx index 037963670c..12b315ae87 100644 --- a/packages/core-plugin-api/src/app/useApp.test.tsx +++ b/packages/core-plugin-api/src/app/useApp.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/app/useApp.tsx b/packages/core-plugin-api/src/app/useApp.tsx index 8979155366..1d17fb2d0f 100644 --- a/packages/core-plugin-api/src/app/useApp.tsx +++ b/packages/core-plugin-api/src/app/useApp.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/PluginErrorBoundary.tsx b/packages/core-plugin-api/src/extensions/PluginErrorBoundary.tsx index e33d9dcb43..215ebc8470 100644 --- a/packages/core-plugin-api/src/extensions/PluginErrorBoundary.tsx +++ b/packages/core-plugin-api/src/extensions/PluginErrorBoundary.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/componentData.test.tsx b/packages/core-plugin-api/src/extensions/componentData.test.tsx index 6c4abda40f..49a1d59cc4 100644 --- a/packages/core-plugin-api/src/extensions/componentData.test.tsx +++ b/packages/core-plugin-api/src/extensions/componentData.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/componentData.tsx b/packages/core-plugin-api/src/extensions/componentData.tsx index f835ad38a8..85527950ab 100644 --- a/packages/core-plugin-api/src/extensions/componentData.tsx +++ b/packages/core-plugin-api/src/extensions/componentData.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx index 798d3c8fc2..e8b9d9c534 100644 --- a/packages/core-plugin-api/src/extensions/extensions.test.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx index 349f44726f..5142dccc55 100644 --- a/packages/core-plugin-api/src/extensions/extensions.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/extensions/index.ts b/packages/core-plugin-api/src/extensions/index.ts index 26a0c597b1..39db17311a 100644 --- a/packages/core-plugin-api/src/extensions/index.ts +++ b/packages/core-plugin-api/src/extensions/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,3 +20,5 @@ export { createRoutableExtension, createComponentExtension, } from './extensions'; +export { useElementFilter } from './useElementFilter'; +export type { ElementCollection } from './useElementFilter'; diff --git a/packages/core-plugin-api/src/extensions/useElementFilter.test.tsx b/packages/core-plugin-api/src/extensions/useElementFilter.test.tsx new file mode 100644 index 0000000000..dc5e0046f1 --- /dev/null +++ b/packages/core-plugin-api/src/extensions/useElementFilter.test.tsx @@ -0,0 +1,377 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { ReactNode } from 'react'; +import { useElementFilter } from './useElementFilter'; +import { renderHook } from '@testing-library/react-hooks'; +import { attachComponentData } from './componentData'; +import { featureFlagsApiRef } from '../apis'; +import { + ApiProvider, + ApiRegistry, + LocalStorageFeatureFlags, +} from '@backstage/core-app-api'; + +const WRAPPING_COMPONENT_KEY = 'core.blob.testing'; +const INNER_COMPONENT_KEY = 'core.blob2.testing'; + +const WrappingComponent = (_props: { children: ReactNode }) => null; +attachComponentData(WrappingComponent, WRAPPING_COMPONENT_KEY, { + message: 'hey! im wrapping component data', +}); +const InnerComponent = () => null; +attachComponentData(InnerComponent, INNER_COMPONENT_KEY, { + message: 'hey! im the inner component', +}); +const MockComponent = (_props: { children: ReactNode }) => null; + +const FeatureFlagComponent = (_props: { + children: ReactNode; + with?: string; + without?: string; +}) => null; +attachComponentData(FeatureFlagComponent, 'core.featureFlagged', true); +const mockFeatureFlagsApi = new LocalStorageFeatureFlags(); +const Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + +); + +describe('useElementFilter', () => { + it('should select elements based on a component data key', () => { + const tree = ( + + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + expect(result.current[0].key).toBe('.$.$first'); + expect(result.current[1].key).toBe('.$.$second'); + }); + + it('should find componentData', () => { + const tree = ( + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements.findComponentData({ key: WRAPPING_COMPONENT_KEY }), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + expect(result.current[0]).toEqual({ + message: 'hey! im wrapping component data', + }); + expect(result.current[1]).toEqual({ + message: 'hey! im wrapping component data', + }); + }); + + it('can be combined to together to filter the selection', () => { + const tree = ( + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .findComponentData({ key: INNER_COMPONENT_KEY }), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + expect(result.current[0]).toEqual({ + message: 'hey! im the inner component', + }); + expect(result.current[1]).toEqual({ + message: 'hey! im the inner component', + }); + }); + + describe('FeatureFlags', () => { + describe('with', () => { + it('should not discover deeper than the feature gate if the feature flag is disabled', () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => false); + const tree = ( + + + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(1); + expect(result.current[0].key).toContain('second'); + }); + + it('should discover components behind a feature flag if the flag is enabled', () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => true); + const tree = ( + + + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + }); + }); + + describe('without', () => { + it('should discover deeper than the feature gate if the feature flag is disabled', () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => false); + const tree = ( + + + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + }); + + it('should not discover components behind a feature flag if the flag is enabled', () => { + jest + .spyOn(mockFeatureFlagsApi, 'isActive') + .mockImplementation(() => true); + const tree = ( + + + + + + + + + + + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(1); + }); + }); + }); + + it('should reject when strict mode is enabled with the correct string', () => { + const tree = ( + +

Hello

+ + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ + key: WRAPPING_COMPONENT_KEY, + withStrictError: 'Could not find component', + }) + .findComponentData({ key: INNER_COMPONENT_KEY }), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.error.message).toEqual('Could not find component'); + }); + + it('should support fragments and text node iteration', () => { + jest.spyOn(mockFeatureFlagsApi, 'isActive').mockImplementation(() => true); + const tree = ( + <> + + <> + + + + + + + + hello my name + <> + + + + + + is text + + + + ); + + const { result } = renderHook( + props => + useElementFilter(props.tree, elements => + elements + .selectByComponentData({ key: WRAPPING_COMPONENT_KEY }) + .getElements(), + ), + { + initialProps: { tree }, + wrapper: Wrapper, + }, + ); + + expect(result.current.length).toBe(2); + }); +}); diff --git a/packages/core-plugin-api/src/extensions/useElementFilter.tsx b/packages/core-plugin-api/src/extensions/useElementFilter.tsx new file mode 100644 index 0000000000..7efdd949b2 --- /dev/null +++ b/packages/core-plugin-api/src/extensions/useElementFilter.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Children, + Fragment, + isValidElement, + ReactNode, + ReactElement, + useMemo, +} from 'react'; +import { getComponentData } from './componentData'; +import { useApi, FeatureFlagsApi, featureFlagsApiRef } from '../apis'; + +function selectChildren( + rootNode: ReactNode, + featureFlagsApi: FeatureFlagsApi, + selector?: (element: ReactElement) => boolean, + strictError?: string, +): Array> { + return Children.toArray(rootNode).flatMap(node => { + if (!isValidElement(node)) { + return []; + } + + if (node.type === Fragment) { + return selectChildren( + node.props.children, + featureFlagsApi, + selector, + strictError, + ); + } + + if (getComponentData(node, 'core.featureFlagged')) { + const props = node.props as { with: string } | { without: string }; + const isEnabled = + 'with' in props + ? featureFlagsApi.isActive(props.with) + : !featureFlagsApi.isActive(props.without); + if (isEnabled) { + return selectChildren( + node.props.children, + featureFlagsApi, + selector, + strictError, + ); + } + return []; + } + + if (selector === undefined || selector(node)) { + return [node]; + } + + if (strictError) { + throw new Error(strictError); + } + + return selectChildren( + node.props.children, + featureFlagsApi, + selector, + strictError, + ); + }); +} + +/** + * A querying interface tailored to traversing a set of selected React elements + * and extracting data. + * + * Methods prefixed with `selectBy` are used to narrow the set of selected elements. + * + * Methods prefixed with `find` return concrete data using a deep traversal of the set. + * + * Methods prefixed with `get` return concrete data using a shallow traversal of the set. + */ +export interface ElementCollection { + /** + * Narrows the set of selected components by doing a deep traversal and + * only including those that have defined component data for the given `key`. + * + * Whether an element in the tree has component data set for the given key + * is determined by whether `getComponentData` returns undefined. + * + * The traversal does not continue deeper past elements that match the criteria, + * and it also includes the root children in the selection, meaning that if the, + * of all the currently selected elements contain data for the given key, this + * method is a no-op. + * + * If `withStrictError` is set, the resulting selection must be a full match, meaning + * there may be no elements that were excluded in the selection. If the selection + * is not a clean match, an error will be throw with `withStrictError` as the message. + */ + selectByComponentData(query: { + key: string; + withStrictError?: string; + }): ElementCollection; + + /** + * Finds all elements using the same criteria as `selectByComponentData`, but + * returns the actual component data of each of those elements instead. + */ + findComponentData(query: { key: string }): T[]; + + /** + * Returns all of the elements currently selected by this collection. + */ + getElements(): Array< + ReactElement + >; +} + +class Collection implements ElementCollection { + constructor( + private readonly node: ReactNode, + private readonly featureFlagsApi: FeatureFlagsApi, + ) {} + + selectByComponentData(query: { key: string; withStrictError?: string }) { + const selection = selectChildren( + this.node, + this.featureFlagsApi, + node => getComponentData(node, query.key) !== undefined, + query.withStrictError, + ); + return new Collection(selection, this.featureFlagsApi); + } + + findComponentData(query: { key: string }): T[] { + const selection = selectChildren( + this.node, + this.featureFlagsApi, + node => getComponentData(node, query.key) !== undefined, + ); + return selection + .map(node => getComponentData(node, query.key)) + .filter((data: T | undefined): data is T => data !== undefined); + } + + getElements(): Array< + ReactElement + > { + return selectChildren(this.node, this.featureFlagsApi) as Array< + ReactElement + >; + } +} + +/** + * useElementFilter is a utility that helps you narrow down and retrieve data + * from a React element tree, typically operating on the `children` property + * passed in to a component. A common use-case is to construct declarative APIs + * where a React component defines its behavior based on its children, such as + * the relationship between `Routes` and `Route` in `react-router`. + * + * The purpose of this hook is similar to `React.Children.map`, and it expands upon + * it to also handle traversal of fragments and Backstage specific things like the + * `FeatureFlagged` component. + * + * The return value of the hook is computed by the provided filter function, but + * with added memoization based on the input `node`. If further memoization + * dependencies are used in the filter function, they should be added to the + * third `dependencies` argument, just like `useMemo`, `useEffect`, etc. + */ +export function useElementFilter( + node: ReactNode, + filterFn: (arg: ElementCollection) => T, + dependencies: any[] = [], +) { + const featureFlagsApi = useApi(featureFlagsApiRef); + const elements = new Collection(node, featureFlagsApi); + // eslint-disable-next-line react-hooks/exhaustive-deps + return useMemo(() => filterFn(elements), [node, ...dependencies]); +} diff --git a/packages/core-plugin-api/src/icons/index.ts b/packages/core-plugin-api/src/icons/index.ts index 953c6ef1ed..9c9e45e54c 100644 --- a/packages/core-plugin-api/src/icons/index.ts +++ b/packages/core-plugin-api/src/icons/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts index 53743edc59..ccc1c337e7 100644 --- a/packages/core-plugin-api/src/icons/types.ts +++ b/packages/core-plugin-api/src/icons/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/index.test.ts b/packages/core-plugin-api/src/index.test.ts deleted file mode 100644 index 9414a6b730..0000000000 --- a/packages/core-plugin-api/src/index.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 * as index from '.'; -import { createApiRef } from '.'; - -describe('index', () => { - const ApiRef = Object.getPrototypeOf(createApiRef({ id: 'x' })).constructor; - - it('exports the plugin api', () => { - expect(index).toEqual({ - FeatureFlagState: { - 0: 'None', - 1: 'Active', - Active: 1, - None: 0, - }, - SessionState: expect.any(Object), - attachComponentData: expect.any(Function), - createApiFactory: expect.any(Function), - createComponentExtension: expect.any(Function), - createPlugin: expect.any(Function), - createReactExtension: expect.any(Function), - createRoutableExtension: expect.any(Function), - - getComponentData: expect.any(Function), - useApi: expect.any(Function), - useApiHolder: expect.any(Function), - useApp: expect.any(Function), - useRouteRef: expect.any(Function), - useRouteRefParams: expect.any(Function), - withApis: expect.any(Function), - - createApiRef: expect.any(Function), - createExternalRouteRef: expect.any(Function), - createRouteRef: expect.any(Function), - createSubRouteRef: expect.any(Function), - - alertApiRef: expect.any(ApiRef), - appThemeApiRef: expect.any(ApiRef), - auth0AuthApiRef: expect.any(ApiRef), - configApiRef: expect.any(ApiRef), - discoveryApiRef: expect.any(ApiRef), - errorApiRef: expect.any(ApiRef), - featureFlagsApiRef: expect.any(ApiRef), - githubAuthApiRef: expect.any(ApiRef), - gitlabAuthApiRef: expect.any(ApiRef), - googleAuthApiRef: expect.any(ApiRef), - identityApiRef: expect.any(ApiRef), - microsoftAuthApiRef: expect.any(ApiRef), - oauth2ApiRef: expect.any(ApiRef), - oauthRequestApiRef: expect.any(ApiRef), - oidcAuthApiRef: expect.any(ApiRef), - oktaAuthApiRef: expect.any(ApiRef), - oneloginAuthApiRef: expect.any(ApiRef), - samlAuthApiRef: expect.any(ApiRef), - storageApiRef: expect.any(ApiRef), - }); - }); -}); diff --git a/packages/core-plugin-api/src/index.ts b/packages/core-plugin-api/src/index.ts index f91d97c31d..28b078357d 100644 --- a/packages/core-plugin-api/src/index.ts +++ b/packages/core-plugin-api/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/lib/globalObject.test.ts b/packages/core-plugin-api/src/lib/globalObject.test.ts index 2bf07b3142..13634f1f08 100644 --- a/packages/core-plugin-api/src/lib/globalObject.test.ts +++ b/packages/core-plugin-api/src/lib/globalObject.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/lib/globalObject.ts b/packages/core-plugin-api/src/lib/globalObject.ts index 5bd1809147..7a400148fb 100644 --- a/packages/core-plugin-api/src/lib/globalObject.ts +++ b/packages/core-plugin-api/src/lib/globalObject.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/lib/versionedValues.ts b/packages/core-plugin-api/src/lib/versionedValues.ts index bc80425edd..a7b7d8e3b7 100644 --- a/packages/core-plugin-api/src/lib/versionedValues.ts +++ b/packages/core-plugin-api/src/lib/versionedValues.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx index 428d9d9d48..abf4bfa35c 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts index 860aad3d1d..321ae6998a 100644 --- a/packages/core-plugin-api/src/plugin/index.ts +++ b/packages/core-plugin-api/src/plugin/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 55cac1cc6d..23ff4594a5 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts index 68dbc531c4..f6e072f760 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts index c2b1fd2a03..348c78220f 100644 --- a/packages/core-plugin-api/src/routing/ExternalRouteRef.ts +++ b/packages/core-plugin-api/src/routing/ExternalRouteRef.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/RouteRef.test.ts b/packages/core-plugin-api/src/routing/RouteRef.test.ts index db42f71730..5314957a25 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts index fa4c936bff..0239b57c25 100644 --- a/packages/core-plugin-api/src/routing/RouteRef.ts +++ b/packages/core-plugin-api/src/routing/RouteRef.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts index 2c2eb2ac7e..cec00de025 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.test.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index ca5d060164..f7e5cf0bc5 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/index.ts b/packages/core-plugin-api/src/routing/index.ts index 4e272910ba..79158d2cdc 100644 --- a/packages/core-plugin-api/src/routing/index.ts +++ b/packages/core-plugin-api/src/routing/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts index 0e1c817a39..9ea1e3ff0d 100644 --- a/packages/core-plugin-api/src/routing/types.ts +++ b/packages/core-plugin-api/src/routing/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx index 2fa9bb3137..ace2d1a58a 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.test.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/useRouteRef.tsx b/packages/core-plugin-api/src/routing/useRouteRef.tsx index 8cd4082e70..4447759b99 100644 --- a/packages/core-plugin-api/src/routing/useRouteRef.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRef.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/useRouteRefParams.test.tsx b/packages/core-plugin-api/src/routing/useRouteRefParams.test.tsx index 7c847f95c6..430dc746a5 100644 --- a/packages/core-plugin-api/src/routing/useRouteRefParams.test.tsx +++ b/packages/core-plugin-api/src/routing/useRouteRefParams.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/routing/useRouteRefParams.ts b/packages/core-plugin-api/src/routing/useRouteRefParams.ts index 7df44eedff..a61df97cf5 100644 --- a/packages/core-plugin-api/src/routing/useRouteRefParams.ts +++ b/packages/core-plugin-api/src/routing/useRouteRefParams.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 Spotify AB + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/setupTests.ts b/packages/core-plugin-api/src/setupTests.ts index aea2220869..c1d649f2ad 100644 --- a/packages/core-plugin-api/src/setupTests.ts +++ b/packages/core-plugin-api/src/setupTests.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core-plugin-api/src/types.ts b/packages/core-plugin-api/src/types.ts index ab0aa56a1b..fff4cb1515 100644 --- a/packages/core-plugin-api/src/types.ts +++ b/packages/core-plugin-api/src/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2020 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js deleted file mode 100644 index d592a653c8..0000000000 --- a/packages/core/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], - rules: { - // TODO: add prop types to JS and remove - 'react/prop-types': 0, - 'jest/expect-expect': 0, - }, -}; diff --git a/packages/core/.npmrc b/packages/core/.npmrc deleted file mode 100644 index 214c29d139..0000000000 --- a/packages/core/.npmrc +++ /dev/null @@ -1 +0,0 @@ -registry=https://registry.npmjs.org/ diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md deleted file mode 100644 index d06dbab3d5..0000000000 --- a/packages/core/CHANGELOG.md +++ /dev/null @@ -1,502 +0,0 @@ -# @backstage/core - -## 0.7.12 - -### Patch Changes - -- 1cf1d351f: Export `CheckboxTree` as we have a storybook for it -- Updated dependencies [e7c5e4b30] -- Updated dependencies [0160678b1] - - @backstage/theme@0.2.8 - - @backstage/core-api@0.2.21 - -## 0.7.11 - -### Patch Changes - -- cc592248b: SignInPage: Show login page while pop-up is being displayed when `auto` prop is set. -- Updated dependencies [d597a50c6] - - @backstage/core-api@0.2.20 - -## 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 - -- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 -- 889d89b6e: Fix state persisted in the URL make search input in the table toolbar lose their - focus. -- 3f988cb63: Add count of older messages when multiple messages exist in AlertDisplay -- 675a569a9: chore: bump `react-use` dependency in all packages -- Updated dependencies [062bbf90f] -- Updated dependencies [675a569a9] - - @backstage/core-api@0.2.18 - -## 0.7.8 - -### Patch Changes - -- f65adcde7: Fix some transitive dependency warnings in yarn -- 80888659b: Bump react-hook-form version to be the same for the entire project. -- Updated dependencies [7b8272fb7] -- Updated dependencies [d8b81fd28] - - @backstage/theme@0.2.7 - - @backstage/config@0.1.5 - -## 0.7.7 - -### Patch Changes - -- 9afcac5af: Allow passing NavLinkProps to SidebarItem component to use in NavLink -- e0c9ed759: Add `if` prop to `EntityLayout.Route` to conditionally render tabs -- 6eaecbd81: Improve owner example value in `MissingAnnotationEmptyState`. - -## 0.7.6 - -### Patch Changes - -- 94da20976: Sort the table filter options by name. -- d8cc7e67a: Exposing Material UI extension point for tabs to be able to add additional information to them -- 99fbef232: Adding Headings for Accessibility on the Scaffolder Plugin -- ab07d77f6: Add support for discovering plugins through the app element tree, removing the need to register them explicitly. -- 937ed39ce: Exported SignInProviderConfig to strongly type SignInPage providers -- 9a9e7a42f: Adding close button on support menu -- 50ce875a0: Fixed a potentially confusing error being thrown about misuse of routable extensions where the error was actually something different. -- Updated dependencies [ab07d77f6] -- Updated dependencies [931b21a12] -- Updated dependencies [50ce875a0] - - @backstage/core-api@0.2.17 - - @backstage/theme@0.2.6 - -## 0.7.5 - -### Patch Changes - -- d0d1c2f7b: Pass `inverse` prop to Gauge from GaugeCard -- 5cafcf452: add debounce time attribute for apis-docs for search, giving more time to the users when they are typing. -- 86a95ba67: exposes undocumented `PageTheme` -- e27cb6c45: Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`. - -## 0.7.4 - -### Patch Changes - -- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading - errors visible to the user. -- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. -- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. -- 98dd5da71: Add support for multiple links to post-scaffold task summary page -- Updated dependencies [1279a3325] -- Updated dependencies [4a4681b1b] -- Updated dependencies [b051e770c] - - @backstage/core-api@0.2.16 - -## 0.7.3 - -### Patch Changes - -- fcc3ada24: Reuse ResponseErrorList for non ResponseErrors -- 4618774ff: Changed color for Add Item, Support & Choose buttons with low contrast/readability in dark mode -- df59930b3: Fix PropTypes error with OverflowTooltip component -- Updated dependencies [76deafd31] -- Updated dependencies [01ccef4c7] -- Updated dependencies [4618774ff] - - @backstage/core-api@0.2.15 - - @backstage/theme@0.2.5 - -## 0.7.2 - -### Patch Changes - -- 8686eb38c: Add a `ResponseErrorPanel` to render `ResponseError` from `@backstage/errors` -- 9ca0e4009: use local version of lowerCase and upperCase methods -- 34ff49b0f: Allow extension components to also return `null` in addition to a `JSX.Element`. -- Updated dependencies [a51dc0006] -- Updated dependencies [e7f9b9435] -- Updated dependencies [0434853a5] -- Updated dependencies [34ff49b0f] -- Updated dependencies [d88dd219e] -- Updated dependencies [c8b54c370] - - @backstage/core-api@0.2.14 - - @backstage/config@0.1.4 - -## 0.7.1 - -### Patch Changes - -- ff4d666ab: Add support for passing a fetch function instead of data to Table `data` prop. -- 2089de76b: Deprecated `ItemCard`. Added `ItemCardGrid` and `ItemCardHeader` instead, that can be used to compose functionality around regular Material-UI `Card` components instead. -- dc1fc92c8: Add support for non external URI's in the Link component to `to` prop. For example `Slack -- Updated dependencies [13524b80b] -- Updated dependencies [e74b07578] -- Updated dependencies [6fb4258a8] -- Updated dependencies [2089de76b] -- Updated dependencies [395885905] - - @backstage/core-api@0.2.13 - - @backstage/theme@0.2.4 - -## 0.7.0 - -### Minor Changes - -- 4c049a1a1: - Adds onClick and other props to IconLinkVertical; - - - Allows TriggerButton component to render when pager duty key is missing; - - Refactors TriggerButton and PagerDutyCard not to have shared state; - - Removes the `action` prop of the IconLinkVertical component while adding `onClick`. - - Instead of having an action including a button with onClick, now the whole component can be clickable making it easier to implement and having a better UX. - - Before: - - ```ts - const myLink: IconLinkVerticalProps = { - label: 'Click me', - action: - - - - Secondary Button: - Used for actions that cancel, skip, and in general perform negative - functions, etc. -
-
color="secondary" variant="contained"
-
- - -
- - - Tertiary Button: - Used commonly in a ButtonGroup and when the button function itself is - not a primary function on a page. -
-
color="default" variant="outlined"
-
- - -
- - ); -}; - -export const ButtonLinks = () => { - const routeRef = createRouteRef({ - path: '/hello', - title: 'Hi there!', - }); - - const handleClick = () => { - return 'Your click worked!'; - }; - - return ( - <> - - { - // TODO: Refactor to use new routing mechanisms - } - - -   has props for both Material-UI's component as well as for - react-router-dom's Route object. - - - - -   links to a statically defined route. In general, this should be - avoided. - - - - - View URL - -   links to a defined URL using Material-UI's Button. - - - - - Trigger Event - -   triggers an onClick event using Material-UI's Button. - - - - ); -}; diff --git a/packages/core/src/components/Button/Button.test.tsx b/packages/core/src/components/Button/Button.test.tsx deleted file mode 100644 index 8bae5f2767..0000000000 --- a/packages/core/src/components/Button/Button.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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'; -import { render, fireEvent, act } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { Button } from './Button'; -import { Route, Routes } from 'react-router'; - -describe(' - , - ), - ); - - expect(() => getByText(testString)).toThrow(); - await act(async () => { - fireEvent.click(getByText(buttonLabel)); - }); - expect(getByText(testString)).toBeInTheDocument(); - }); -}); diff --git a/packages/core/src/components/Button/Button.tsx b/packages/core/src/components/Button/Button.tsx deleted file mode 100644 index ca45b3da7f..0000000000 --- a/packages/core/src/components/Button/Button.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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, { ComponentProps } from 'react'; -import { Button as MaterialButton } from '@material-ui/core'; -import { Link as RouterLink } from 'react-router-dom'; - -type Props = ComponentProps & - ComponentProps; - -/** - * Thin wrapper on top of material-ui's Button component - * Makes the Button to utilise react-router - */ -export const Button = React.forwardRef((props, ref) => ( - -)); diff --git a/packages/core/src/components/Button/index.ts b/packages/core/src/components/Button/index.ts deleted file mode 100644 index 7b584ed799..0000000000 --- a/packages/core/src/components/Button/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { Button } from './Button'; diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx deleted file mode 100644 index 3416e94260..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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, { useState } from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 2', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 3', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -export default { - title: 'Inputs/CheckboxTree', - component: CheckboxTree, -}; - -export const Default = () => ( - {}} - label="default" - subCategories={CHECKBOX_TREE_ITEMS} - /> -); - -export const DynamicTree = () => { - function generateTree(showMore: boolean = false) { - const t = [ - { - label: 'Show more', - options: [], - }, - ]; - - if (showMore) { - t.push({ - label: 'More', - options: [], - }); - } - - return t; - } - - const [tree, setTree] = useState(generateTree()); - - return ( - { - setTree(generateTree(state.some(c => c.category === 'Show more'))); - }} - label="default" - subCategories={tree} - /> - ); -}; diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx deleted file mode 100644 index 0750867ff3..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -const minProps = { - onChange: jest.fn(), - label: 'Default', - subCategories: CHECKBOX_TREE_ITEMS, -}; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText, getByTestId } = render(); - - expect(getByText('Generic subcategory name 1')).toBeInTheDocument(); - const checkbox = await getByTestId('expandable'); - - // Simulate click on expandable arrow - fireEvent.click(checkbox); - - // Simulate click on option - const option = getByText('Option 1'); - expect(getByText('Option 1')).toBeInTheDocument(); - fireEvent.click(option); - expect(minProps.onChange).toHaveBeenCalled(); - }); -}); diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.tsx deleted file mode 100644 index d6d3410913..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.tsx +++ /dev/null @@ -1,358 +0,0 @@ -/* - * 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. - */ -/* eslint-disable guard-for-in */ -import { - Checkbox, - Collapse, - List, - ListItem, - ListItemIcon, - ListItemText, - Typography, -} from '@material-ui/core'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; -import produce from 'immer'; -import { isEqual } from 'lodash'; -import React, { useEffect, useReducer } from 'react'; -import { usePrevious } from 'react-use'; - -type IndexedObject = { - [key: string]: T; -}; - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - minWidth: 10, - maxWidth: 360, - backgroundColor: 'transparent', - '&:hover': { - backgroundColor: 'transparent', - }, - '&:active': { - animation: 'none', - transform: 'none', - }, - }, - nested: { - paddingLeft: theme.spacing(5), - height: '32px', - '&:hover': { - backgroundColor: 'transparent', - }, - }, - listItemIcon: { - minWidth: 10, - }, - listItem: { - '&:hover': { - backgroundColor: 'transparent', - }, - }, - text: { - '& span, & svg': { - fontWeight: 'normal', - fontSize: 14, - }, - }, - }), -); - -/* SUB_CATEGORY */ - -type SubCategory = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options?: Option[]; -}; - -type SubCategoryWithIndexedOptions = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options: IndexedObject