diff --git a/.changeset/bright-buttons-rescue.md b/.changeset/bright-buttons-rescue.md new file mode 100644 index 0000000000..71a6b76fa2 --- /dev/null +++ b/.changeset/bright-buttons-rescue.md @@ -0,0 +1,27 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +**BREAKING** Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + +These changes are **required** to `packages/backend/src/plugins/auth.ts`: + +```diff +export default async function createPlugin({ + logger, + database, + config, + discovery, ++ tokenManager, +}: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, ++ tokenManager, + }); +} +``` + +**BREAKING** The `CatalogIdentityClient` constructor now expects a `TokenManager` instead of a `TokenIssuer`. The `TokenManager` interface is used to generate a server token when [resolving a user's identity and membership through the catalog](https://backstage.io/docs/auth/identity-resolver). Using server tokens for these requests allows the auth-backend to bypass authorization checks when permissions are enabled for Backstage. This change will break apps that rely on the user tokens that were previously used by the client. Refer to the ["Backend-to-backend Authentication" tutorial](https://backstage.io/docs/tutorials/backend-to-backend-auth) for more information on server token usage. diff --git a/.changeset/dependabot-9ec400d.md b/.changeset/dependabot-9ec400d.md new file mode 100644 index 0000000000..1fd5a85477 --- /dev/null +++ b/.changeset/dependabot-9ec400d.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +chore(deps): bump `passport` from 0.4.1 to 0.5.2 diff --git a/.changeset/dependabot-f969614.md b/.changeset/dependabot-f969614.md new file mode 100644 index 0000000000..c3c8890ec9 --- /dev/null +++ b/.changeset/dependabot-f969614.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +chore(deps): bump `prom-client` from 13.2.0 to 14.0.1 diff --git a/.changeset/gold-queens-clap.md b/.changeset/gold-queens-clap.md new file mode 100644 index 0000000000..252fbb1626 --- /dev/null +++ b/.changeset/gold-queens-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fix an issue where changes related to the `MobileSidebar` prevented scrolling pages. Additionally improve the menu of the `MobileSidebar` to not overlay the `BottomNavigation`. diff --git a/.changeset/sour-chairs-double.md b/.changeset/sour-chairs-double.md new file mode 100644 index 0000000000..4f481e20e7 --- /dev/null +++ b/.changeset/sour-chairs-double.md @@ -0,0 +1,25 @@ +--- +'@backstage/create-app': patch +--- + +Added `tokenManager` as a required property for the auth-backend `createRouter` function. This dependency is used to issue server tokens that are used by the `CatalogIdentityClient` when looking up users and their group membership during authentication. + +These changes are **required** to `packages/backend/src/plugins/auth.ts`: + +```diff +export default async function createPlugin({ + logger, + database, + config, + discovery, ++ tokenManager, +}: PluginEnvironment): Promise { + return await createRouter({ + logger, + config, + database, + discovery, ++ tokenManager, + }); +} +``` diff --git a/.changeset/tame-ads-exercise.md b/.changeset/tame-ads-exercise.md new file mode 100644 index 0000000000..a2e11393a6 --- /dev/null +++ b/.changeset/tame-ads-exercise.md @@ -0,0 +1,34 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +Make filename, branch name and examples URLs used in catalog import customizable. + +Catalog backend ingestion loop can be already configured to fetch targets with custom catalog filename (other than `catalog-info.yaml`). It's now possible to customize said filename and branch name used in pull requests created by catalog import flow too. This allows organizations to further customize Backstage experience and to better reflect their branding. + +Filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: + +```yaml +// app-config.yaml + +catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration +``` + +Following React components have also been updated to accept optional props for providing example entity and repository paths: + +```tsx + +``` + +```tsx + +``` diff --git a/.changeset/witty-lamps-laugh.md b/.changeset/witty-lamps-laugh.md new file mode 100644 index 0000000000..1ca4f4e4dc --- /dev/null +++ b/.changeset/witty-lamps-laugh.md @@ -0,0 +1,13 @@ +--- +'@backstage/create-app': patch +--- + +Switched the `app` dependency in the backend to use a file target rather than version. + +To apply this change to an existing app, make the following change to `packages/backend/package.json`: + +```diff + "dependencies": { +- "app": "0.0.0", ++ "app": "file:../app", +``` diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index e8a50904d0..4fc5d20708 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -9,24 +9,39 @@ jobs: create-new-version: runs-on: ubuntu-latest steps: + # Setup node & install deps before checkout, keeping install quick + - uses: actions/setup-node@v2 + with: + node-version: 16 + - name: Install dependencies + # Just the deps for the assemble manifest script + run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 + - name: Checkout uses: actions/checkout@v2 with: + path: backstage ref: ${{ github.event.inputs.version }} - - name: Install dependencies - run: yarn install + + # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions uses: actions/checkout@v2 with: repository: backstage/versions - path: versions + path: backstage/versions token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + - name: Configure Git run: | git config --global user.email noreply@backstage.io git config --global user.name 'Github versions workflow' + - name: Create release + # This grabs the scripts from master in order to support backfills run: | + cd backstage + mkdir -p scripts + wget -O scripts/assemble-manifest.js https://raw.githubusercontent.com/backstage/backstage/master/scripts/assemble-manifest.js node scripts/assemble-manifest.js ${{ github.event.inputs.version }} cd versions git add . diff --git a/app-config.yaml b/app-config.yaml index b43a229ae9..b84b5de2f2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -194,6 +194,9 @@ integrations: secretAccessKey: ${AWS_SECRET_ACCESS_KEY} catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration rules: - allow: - Component diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 1885c1a961..863b4b3ecb 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -496,6 +496,13 @@ plugins: - kroki ``` +> Note: you will very likely want to set a `kroki` `ServerURL` configuration in your +> `mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If +> you have sensitive information in your organization's diagrams, you should set +> up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it +> instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config) +> for more plugin configuration details. + 4. **Add mermaid code into techdocs:** ````md diff --git a/microsite/blog/2022-01-25-backstage-homepage-templates.md b/microsite/blog/2022-01-25-backstage-homepage-templates.md index 97a7b68869..7ed5ee92e3 100644 --- a/microsite/blog/2022-01-25-backstage-homepage-templates.md +++ b/microsite/blog/2022-01-25-backstage-homepage-templates.md @@ -9,7 +9,7 @@ _This blog post is co-authored by Emma Indal ([@emmaindal](https://github.com/em ![homepage template](assets/22-01-25/homepage-template-blog-post-header.png) -_TLDR;_ We’re excited to announce the arrival of homepage templates for Backstage. It can be intimidating to build up your own instance of Backstage, so we decided to make your life a bit easier by giving you an out-of-the-box homepage to hit the ground running. This template can be found in our [Backstage Storybook](https://backstage.io/storybook/), as well as future templates that we build. +_TLDR;_ We’re excited to announce the arrival of homepage templates for Backstage. It can be intimidating to build up your own instance of Backstage, so we decided to make your life a bit easier by giving you an out-of-the-box homepage to hit the ground running. This template can be found in our [Backstage Storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates--default-template), as well as future templates that we build. @@ -23,7 +23,7 @@ But we also know that all organizations look different and have different needs. ## What are homepage templates? -A homepage template is just a template of what a homepage can look like. You can either copy it as a whole, or pick different components from it to use. The homepage templates live in our [Backstage Storybook](https://backstage.io/storybook/) and we hope to see this collection grow with contributions from you all. If you don’t want to use one of the templates, you can pick and choose from our collection of [homepage components](https://backstage.io/storybook/?path=/story/plugins-home-components) that you can use right out of the box and compose your own homepage. We’ve included the [SearchBar](https://backstage.io/storybook/?path=/story/plugins-home-components-searchbar--custom-styles), [CompanyLogo](https://backstage.io/storybook/?path=/story/plugins-home-components-companylogo--custom-logo) and [Toolkit](https://backstage.io/storybook/?path=/story/plugins-home-components-toolkit--default) components in our Backstage Storybook as of today. In the future, we plan on open sourcing the widely popular QuickAccess card, which includes Favorites, Frequently Visited, and Recently Visited options. +A homepage template is just a template of what a homepage can look like. You can either copy it as a whole, or pick different components from it to use. The homepage templates live in our [Backstage Storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates--default-template) and we hope to see this collection grow with contributions from you all. If you don’t want to use one of the templates, you can pick and choose from our collection of [homepage components](https://backstage.io/storybook/?path=/story/plugins-home-components) that you can use right out of the box and compose your own homepage. We’ve included the [SearchBar](https://backstage.io/storybook/?path=/story/plugins-home-components-searchbar--custom-styles), [CompanyLogo](https://backstage.io/storybook/?path=/story/plugins-home-components-companylogo--custom-logo) and [Toolkit](https://backstage.io/storybook/?path=/story/plugins-home-components-toolkit--default) components in our Backstage Storybook as of today. In the future, we plan on open sourcing the widely popular QuickAccess card, which includes Favorites, Frequently Visited, and Recently Visited options. ![Composed Backstage homepage](assets/22-01-25/composed-backstage-homepage.png) _Composed homepage for the app using the default homepage template_ @@ -40,7 +40,7 @@ _Example of the homepage components in the Backstage UI Kit in Figma_ ## What’s next? -Some future templates that we have in mind are more dashboard-esque ones that display a variety of relevant data for your organization. We will also open source more homepage components such as Starred Entities ([[Home] Starred Entities homepage component #6906](https://github.com/backstage/backstage/issues/6906)) that you can use when you are composing your components. +Some future templates that we have in mind are more dashboard-esque ones that display a variety of relevant data for your organization. We will also open source more homepage components such as [Starred Entities](https://github.com/backstage/backstage/issues/6906) that you can use when you are composing your components. ## How to contribute diff --git a/packages/backend/package.json b/packages/backend/package.json index c56a3d8ee0..44544703d8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -60,14 +60,14 @@ "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^11.0.1", "dockerode": "^3.3.1", - "example-app": "^0.2.62", + "example-app": "file:../app", "express": "^4.17.1", "express-promise-router": "^4.1.0", "express-prom-bundle": "^6.3.6", "knex": "^0.95.1", "pg": "^8.3.0", "pg-connection-string": "^2.3.0", - "prom-client": "^13.2.0", + "prom-client": "^14.0.1", "sqlite3": "^5.0.1", "winston": "^3.2.1" }, diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index 4e51518bc1..7f2b950c3c 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -23,6 +23,13 @@ export default async function createPlugin({ database, config, discovery, + tokenManager, }: PluginEnvironment): Promise { - return await createRouter({ logger, config, database, discovery }); + return await createRouter({ + logger, + config, + database, + discovery, + tokenManager, + }); } diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 6046113dd2..88e7f4efab 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -739,7 +739,7 @@ export type OverflowTooltipClassKey = 'container'; // Warning: (ae-missing-release-tag) "Page" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function Page(props: PropsWithChildren): JSX.Element; +export function Page(props: Props_16): JSX.Element; // Warning: (ae-missing-release-tag) "PageClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/src/layout/Page/Page.tsx b/packages/core-components/src/layout/Page/Page.tsx index b013affee3..0b35a59d8f 100644 --- a/packages/core-components/src/layout/Page/Page.tsx +++ b/packages/core-components/src/layout/Page/Page.tsx @@ -14,34 +14,37 @@ * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; +import React, { useContext } from 'react'; import { BackstageTheme } from '@backstage/theme'; import { makeStyles, ThemeProvider } from '@material-ui/core/styles'; +import { SidebarPinStateContext } from '../Sidebar/Page'; export type PageClassKey = 'root'; -const useStyles = makeStyles( +const useStyles = makeStyles( () => ({ - root: { + root: ({ isMobile }) => ({ display: 'grid', gridTemplateAreas: "'pageHeader pageHeader pageHeader' 'pageSubheader pageSubheader pageSubheader' 'pageNav pageContent pageSidebar'", gridTemplateRows: 'max-content auto 1fr', gridTemplateColumns: 'auto 1fr auto', - height: '100%', + height: isMobile ? '100%' : '100vh', overflowY: 'auto', - }, + }), }), { name: 'BackstagePage' }, ); type Props = { themeId: string; + children?: React.ReactNode; }; -export function Page(props: PropsWithChildren) { +export function Page(props: Props) { const { themeId, children } = props; - const classes = useStyles(); + const { isMobile } = useContext(SidebarPinStateContext); + const classes = useStyles({ isMobile }); return ( ({ diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index 95947f9274..6734d4b53a 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -93,6 +93,10 @@ const useStyles = makeStyles(theme => ({ overlayHeaderClose: { color: theme.palette.bursts.fontColor, }, + + marginMobileSidebar: { + marginBottom: `${sidebarConfig.mobileSidebarHeight}px`, + }, })); const sortSidebarGroupsForPriority = (children: React.ReactElement[]) => @@ -117,7 +121,13 @@ const OverlayMenu = ({ anchor="bottom" open={open} onClose={onClose} - classes={{ paperAnchorBottom: classes.overlay }} + ModalProps={{ + BackdropProps: { classes: { root: classes.marginMobileSidebar } }, + }} + classes={{ + root: classes.marginMobileSidebar, + paperAnchorBottom: classes.overlay, + }} > {label} diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index c804b4b561..46e2ecbb05 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -80,6 +80,9 @@ scaffolder: # see https://backstage.io/docs/features/software-templates/configuration for software template options catalog: + import: + entityFilename: catalog-info.yaml + pullRequestBranchName: backstage-integration rules: - allow: [Component, System, API, Group, User, Resource, Location] locations: diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 96dc724a1e..2288b475a9 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -14,7 +14,7 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "app": "0.0.0", + "app": "file:../app", "@backstage/backend-common": "^{{version '@backstage/backend-common'}}", "@backstage/backend-tasks": "^{{version '@backstage/backend-tasks'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts index 52165104a2..015c86466f 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/auth.ts @@ -7,6 +7,13 @@ export default async function createPlugin({ database, config, discovery, + tokenManager, }: PluginEnvironment): Promise { - return await createRouter({ logger, config, database, discovery }); + return await createRouter({ + logger, + config, + database, + discovery, + tokenManager, + }); } diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 3490ea317b..7cdc33f0e2 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -15,6 +15,7 @@ import { Logger as Logger_2 } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Profile } from 'passport'; +import { TokenManager } from '@backstage/backend-common'; import { TokenSet } from 'openid-client'; import { UserEntity } from '@backstage/catalog-model'; import { UserinfoResponse } from 'openid-client'; @@ -85,6 +86,7 @@ export type AuthProviderFactoryOptions = { globalConfig: AuthProviderConfig; config: Config; logger: Logger_2; + tokenManager: TokenManager; tokenIssuer: TokenIssuer; discovery: PluginEndpointDiscovery; catalogApi: CatalogApi; @@ -205,7 +207,7 @@ export const bitbucketUsernameSignInResolver: SignInResolver; // Warning: (ae-forgotten-export) The symbol "MemberClaimQuery" needs to be exported by the entry point index.d.ts @@ -671,6 +673,8 @@ export interface RouterOptions { // // (undocumented) providerFactories?: ProviderFactories; + // (undocumented) + tokenManager: TokenManager; } // @public (undocumented) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 799ab4c414..5b4e4b22ba 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -58,7 +58,7 @@ "node-fetch": "^2.6.1", "node-cache": "^5.1.2", "openid-client": "^4.2.1", - "passport": "^0.4.1", + "passport": "^0.5.2", "passport-bitbucket-oauth2": "^0.1.2", "passport-github2": "^0.1.12", "passport-gitlab2": "^5.0.0", diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index 0ddcaa6d2c..897233294f 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -14,13 +14,13 @@ * limitations under the License. */ +import { TokenManager } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { RELATION_MEMBER_OF, UserEntity, UserEntityV1alpha1, } from '@backstage/catalog-model'; -import { TokenIssuer } from '../../identity'; import { CatalogIdentityClient } from './CatalogIdentityClient'; describe('CatalogIdentityClient', () => { @@ -36,19 +36,19 @@ describe('CatalogIdentityClient', () => { refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), }; - const tokenIssuer: jest.Mocked = { - issueToken: jest.fn(), - listPublicKeys: jest.fn(), + const tokenManager: jest.Mocked = { + getToken: jest.fn(), + authenticate: jest.fn(), }; afterEach(() => jest.resetAllMocks()); it('findUser passes through the correct search params', async () => { catalogApi.getEntities.mockResolvedValueOnce({ items: [{} as UserEntity] }); - tokenIssuer.issueToken.mockResolvedValue('my-token'); + tokenManager.getToken.mockResolvedValue({ token: 'my-token' }); const client = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); await client.findUser({ annotations: { key: 'value' } }); @@ -62,11 +62,7 @@ describe('CatalogIdentityClient', () => { }, { token: 'my-token' }, ); - expect(tokenIssuer.issueToken).toHaveBeenCalledWith({ - claims: { - sub: 'backstage.io/auth-backend', - }, - }); + expect(tokenManager.getToken).toHaveBeenCalledWith(); }); it('resolveCatalogMembership resolves membership', async () => { @@ -114,35 +110,39 @@ describe('CatalogIdentityClient', () => { }, ]; catalogApi.getEntities.mockResolvedValueOnce({ items: mockUsers }); + tokenManager.getToken.mockResolvedValue({ token: 'my-token' }); const client = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const claims = await client.resolveCatalogMembership({ entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'], }); - expect(catalogApi.getEntities).toHaveBeenCalledWith({ - filter: [ - { - kind: 'user', - 'metadata.namespace': 'default', - 'metadata.name': 'inigom', - }, - { - kind: 'user', - 'metadata.namespace': 'default', - 'metadata.name': 'imontoya', - }, - { - kind: 'user', - 'metadata.namespace': 'reality', - 'metadata.name': 'mpatinkin', - }, - ], - }); + expect(catalogApi.getEntities).toHaveBeenCalledWith( + { + filter: [ + { + kind: 'user', + 'metadata.namespace': 'default', + 'metadata.name': 'inigom', + }, + { + kind: 'user', + 'metadata.namespace': 'default', + 'metadata.name': 'imontoya', + }, + { + kind: 'user', + 'metadata.namespace': 'reality', + 'metadata.name': 'mpatinkin', + }, + ], + }, + { token: 'my-token' }, + ); expect(claims).toMatchObject([ 'user:default/inigom', diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index a1f915d152..aecf018975 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -24,7 +24,7 @@ import { stringifyEntityRef, UserEntity, } from '@backstage/catalog-model'; -import { TokenIssuer } from '../../identity'; +import { TokenManager } from '@backstage/backend-common'; type UserQuery = { annotations: Record; @@ -40,11 +40,11 @@ type MemberClaimQuery = { */ export class CatalogIdentityClient { private readonly catalogApi: CatalogApi; - private readonly tokenIssuer: TokenIssuer; + private readonly tokenManager: TokenManager; - constructor(options: { catalogApi: CatalogApi; tokenIssuer: TokenIssuer }) { + constructor(options: { catalogApi: CatalogApi; tokenManager: TokenManager }) { this.catalogApi = options.catalogApi; - this.tokenIssuer = options.tokenIssuer; + this.tokenManager = options.tokenManager; } /** @@ -60,10 +60,7 @@ export class CatalogIdentityClient { filter[`metadata.annotations.${key}`] = value; } - // TODO(Rugvip): cache the token - const token = await this.tokenIssuer.issueToken({ - claims: { sub: 'backstage.io/auth-backend' }, - }); + const { token } = await this.tokenManager.getToken(); const { items } = await this.catalogApi.getEntities({ filter }, { token }); if (items.length !== 1) { @@ -106,8 +103,9 @@ export class CatalogIdentityClient { 'metadata.namespace': ref.namespace, 'metadata.name': ref.name, })); + const { token } = await this.tokenManager.getToken(); const entities = await this.catalogApi - .getEntities({ filter }) + .getEntities({ filter }, { token }) .then(r => r.items); if (entityRefs.length !== entities.length) { diff --git a/plugins/auth-backend/src/providers/atlassian/provider.ts b/plugins/auth-backend/src/providers/atlassian/provider.ts index 9987a88407..2696fd233f 100644 --- a/plugins/auth-backend/src/providers/atlassian/provider.ts +++ b/plugins/auth-backend/src/providers/atlassian/provider.ts @@ -197,6 +197,7 @@ export const createAtlassianProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -208,7 +209,7 @@ export const createAtlassianProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index dbd6924804..15578117ea 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -220,6 +220,7 @@ export const createAuth0Provider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -231,7 +232,7 @@ export const createAuth0Provider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.ts b/plugins/auth-backend/src/providers/aws-alb/provider.ts index cc111b4aaf..12f7c7f4b4 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.ts @@ -241,7 +241,7 @@ export type AwsAlbProviderOptions = { export const createAwsAlbProvider = ( options?: AwsAlbProviderOptions, ): AuthProviderFactory => { - return ({ config, tokenIssuer, catalogApi, logger }) => { + return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => { const region = config.getString('region'); const issuer = config.getOptionalString('iss'); @@ -253,7 +253,7 @@ export const createAwsAlbProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts index ff203e0a9d..4d5bdcf6f6 100644 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucket/provider.ts @@ -273,6 +273,7 @@ export const createBitbucketProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -283,7 +284,7 @@ export const createBitbucketProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = diff --git a/plugins/auth-backend/src/providers/gcp-iap/provider.ts b/plugins/auth-backend/src/providers/gcp-iap/provider.ts index a54c7e9fb2..7816f68b76 100644 --- a/plugins/auth-backend/src/providers/gcp-iap/provider.ts +++ b/plugins/auth-backend/src/providers/gcp-iap/provider.ts @@ -102,7 +102,7 @@ export class GcpIapProvider implements AuthProviderRouteHandlers { export function createGcpIapProvider( options: GcpIapProviderOptions, ): AuthProviderFactory { - return ({ config, tokenIssuer, catalogApi, logger }) => { + return ({ config, tokenIssuer, catalogApi, logger, tokenManager }) => { const audience = config.getString('audience'); const authHandler = options.authHandler ?? defaultAuthHandler; @@ -111,7 +111,7 @@ export function createGcpIapProvider( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); return new GcpIapProvider({ diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index bef1a87b0a..9dc70cf60e 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -245,6 +245,7 @@ export const createGithubProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -270,7 +271,7 @@ export const createGithubProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 5454065847..da816f2fd2 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -227,6 +227,7 @@ export const createGitlabProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -239,7 +240,7 @@ export const createGitlabProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 44c22f757e..13c5093aa5 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -259,6 +259,7 @@ export const createGoogleProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -269,7 +270,7 @@ export const createGoogleProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index b4ef30f491..9249f643dc 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -267,6 +267,7 @@ export const createMicrosoftProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -281,7 +282,7 @@ export const createMicrosoftProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts index eceebc5924..24bb4d0362 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts @@ -182,12 +182,12 @@ export const createOauth2ProxyProvider = ( options: Oauth2ProxyProviderOptions, ): AuthProviderFactory => - ({ catalogApi, logger, tokenIssuer }) => { + ({ catalogApi, logger, tokenIssuer, tokenManager }) => { const signInResolver = options.signIn.resolver; const authHandler = options.authHandler; const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); return new Oauth2ProxyAuthProvider({ logger, diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index ec777c231b..8e69a3ee1d 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -233,6 +233,7 @@ export const createOAuth2Provider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -249,7 +250,7 @@ export const createOAuth2Provider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 0c820648e4..fa46294784 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -257,6 +257,7 @@ export const createOidcProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -272,7 +273,7 @@ export const createOidcProvider = ( const prompt = envConfig.getOptionalString('prompt'); const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 30dd295d12..def0da694e 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -268,6 +268,7 @@ export const createOktaProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -286,7 +287,7 @@ export const createOktaProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = _options?.authHandler diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index ea84b402d5..a5ab4f658b 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -219,6 +219,7 @@ export const createOneLoginProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => @@ -230,7 +231,7 @@ export const createOneLoginProvider = ( const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index b9c8d89f5b..1ca8cf0d5d 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -187,12 +187,13 @@ export const createSamlProvider = ( globalConfig, config, tokenIssuer, + tokenManager, catalogApi, logger, }) => { const catalogIdentityClient = new CatalogIdentityClient({ catalogApi, - tokenIssuer, + tokenManager, }); const authHandler: AuthHandler = options?.authHandler diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 179d536f3c..b02e8d74b8 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; @@ -124,6 +127,7 @@ export type AuthProviderFactoryOptions = { globalConfig: AuthProviderConfig; config: Config; logger: Logger; + tokenManager: TokenManager; tokenIssuer: TokenIssuer; discovery: PluginEndpointDiscovery; catalogApi: CatalogApi; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 6fdd768498..bdb68929b1 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -25,6 +25,7 @@ import { import { PluginDatabaseManager, PluginEndpointDiscovery, + TokenManager, } from '@backstage/backend-common'; import { assertError, NotFoundError } from '@backstage/errors'; import { CatalogClient } from '@backstage/catalog-client'; @@ -41,13 +42,21 @@ export interface RouterOptions { database: PluginDatabaseManager; config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; providerFactories?: ProviderFactories; } export async function createRouter( options: RouterOptions, ): Promise { - const { logger, config, discovery, database, providerFactories } = options; + const { + logger, + config, + discovery, + database, + tokenManager, + providerFactories, + } = options; const router = Router(); const appUrl = config.getString('app.baseUrl'); @@ -105,6 +114,7 @@ export async function createRouter( globalConfig: { baseUrl: authUrl, appUrl, isOriginAllowed }, config: providersConfig.getConfig(providerId), logger, + tokenManager, tokenIssuer, discovery, catalogApi, diff --git a/plugins/auth-backend/src/service/standaloneServer.ts b/plugins/auth-backend/src/service/standaloneServer.ts index 15ffe1d053..4e416e8dc7 100644 --- a/plugins/auth-backend/src/service/standaloneServer.ts +++ b/plugins/auth-backend/src/service/standaloneServer.ts @@ -17,6 +17,7 @@ import { createServiceBuilder, loadBackendConfig, + ServerTokenManager, SingleHostDiscovery, useHotMemoize, } from '@backstage/backend-common'; @@ -58,6 +59,7 @@ export async function startStandaloneServer( }, }, discovery, + tokenManager: ServerTokenManager.noop(), }); const service = createServiceBuilder(module) diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d1137d599d..748ff77592 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -57,7 +57,7 @@ "luxon": "^2.0.2", "node-fetch": "^2.6.1", "p-limit": "^3.0.2", - "prom-client": "^13.2.0", + "prom-client": "^14.0.1", "uuid": "^8.0.0", "winston": "^3.2.1", "yaml": "^1.9.2", diff --git a/plugins/catalog-import/README.md b/plugins/catalog-import/README.md index bad48a8486..86f3aecba9 100644 --- a/plugins/catalog-import/README.md +++ b/plugins/catalog-import/README.md @@ -35,39 +35,73 @@ import { CatalogImportPage } from '@backstage/plugin-catalog-import'; ## Customizations -### Disable the creation of Pull Requests +### Custom layout -The pull request feature can be disabled by options that are passed to the `CatalogImportPage`: +A custom layout can be passed to the import page, as it's already +supported by the search page. If no custom layout is passed, the default layout +is used. + +```typescript +}> + +
+ + + + Start tracking your component in Backstage by adding it to the + software catalog. + + + + + + Hello World + + + + + + + + + +``` + +Previously it was possible to disable and customize the automatic pull request +feature by passing options to `` (`pullRequest.disable` and +`pullRequest.preparePullRequest`). This functionality is moved to the +`CatalogImportApi` which now provides an optional `preparePullRequest()` +function. The function can either be overridden to generate a different content +for the pull request, or removed to disable this feature. + +### Entity filename and branch name + +Entity filename (default: `catalog-info.yaml`) and branch name (default: `backstage-integration`) used in pull requests can be configured in `app-config.yaml` as follows: + +```yaml +// app-config.yaml + +catalog: + import: + entityFilename: anvil.yaml + pullRequestBranchName: anvil-integration +``` + +### Entity examples + +Following React components accept optional props for providing custom example entity and repository paths: ```tsx -// packages/app/src/App.tsx - -} + ``` -### Customize the title and body of the Pull Request - -The pull request form is filled with a default title and body. -This can be configured by options that are passed to the `CatalogImportPage`: - ```tsx -// packages/app/src/App.tsx - - ({ - title: 'My title', - body: 'My **markdown** body', - }), - }} - /> - } + ``` diff --git a/plugins/catalog-import/api-report.md b/plugins/catalog-import/api-report.md index a1ad76f323..a85777c563 100644 --- a/plugins/catalog-import/api-report.md +++ b/plugins/catalog-import/api-report.md @@ -60,7 +60,7 @@ export const AutocompleteTextField: ({ helperText, errorHelperText, textFieldProps, -}: Props_4) => JSX.Element; +}: Props_5) => JSX.Element; // Warning: (ae-missing-release-tag) "CatalogImportApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -171,10 +171,14 @@ export const EntityListComponent: ({ withLinks, }: Props) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ImportInfoCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ImportInfoCard: () => JSX.Element; +export const ImportInfoCard: ({ + exampleLocationUrl, + exampleRepositoryUrl, +}: Props_2) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ImportStepper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -184,7 +188,7 @@ export const ImportStepper: ({ initialUrl, generateStepper, variant, -}: Props_2) => JSX.Element; +}: Props_3) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreparePullRequestForm" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -196,7 +200,7 @@ export const PreparePullRequestForm: < defaultValues, onSubmit, render, -}: Props_5) => JSX.Element; +}: Props_6) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreviewCatalogInfoComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -206,7 +210,7 @@ export const PreviewCatalogInfoComponent: ({ repositoryUrl, entities, classes, -}: Props_6) => JSX.Element; +}: Props_7) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PreviewPullRequestComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -216,7 +220,7 @@ export const PreviewPullRequestComponent: ({ title, description, classes, -}: Props_7) => JSX.Element; +}: Props_8) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "StepInitAnalyzeUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -226,7 +230,8 @@ export const StepInitAnalyzeUrl: ({ onAnalysis, analysisUrl, disablePullRequest, -}: Props_3) => JSX.Element; + exampleLocationUrl, +}: Props_4) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "StepPrepareCreatePullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -237,7 +242,7 @@ export const StepPrepareCreatePullRequest: ({ onPrepare, onGoBack, renderFormFields, -}: Props_8) => JSX.Element; +}: Props_9) => JSX.Element; // Warnings were encountered during analysis: // diff --git a/plugins/catalog-import/config.d.ts b/plugins/catalog-import/config.d.ts new file mode 100644 index 0000000000..0cc49e418b --- /dev/null +++ b/plugins/catalog-import/config.d.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** + * Configuration options for the catalog plugin. + */ + catalog?: { + /** + * List of import flow specific options and attributes + */ + import?: { + /** + * Catalog entity descriptor filename, defaults to "catalog-info.yaml" + * @visibility frontend + */ + entityFilename?: string; + /** + * A branch name used in pull request when registering existing component via UI + * Valid git refname required, see: https://git-scm.com/docs/git-check-ref-format + * Defaults to "backstage-integration" + * @visibility frontend + */ + pullRequestBranchName?: string; + }; + }; +} diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ff9fcb24c2..2508543541 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -34,6 +34,7 @@ "@backstage/catalog-client": "^0.5.5", "@backstage/catalog-model": "^0.9.10", "@backstage/core-components": "^0.8.6", + "@backstage/config": "^0.1.13", "@backstage/core-plugin-api": "^0.6.0", "@backstage/errors": "^0.2.0", "@backstage/integration": "^0.7.2", @@ -69,6 +70,8 @@ "msw": "^0.35.0" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 202a7aba85..fa63825af1 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -396,6 +396,84 @@ describe('CatalogImportClient', () => { ], }); }); + + it('should find location with custom catalog filename', async () => { + const repositoryUrl = 'https://github.com/acme-corp/our-awesome-api'; + const entityFilename = 'anvil.yaml'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + }, + }, + }), + }); + + (new Octokit().search.code as any as jest.Mock).mockImplementationOnce( + async params => ({ + data: { + total_count: 1, + items: [{ path: params.q.split('+filename:').slice(-1)[0] }], + }, + }), + ); + + catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ + location: { + id: 'id-0', + type: type ?? 'url', + target, + }, + entities: [ + { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + ], + })); + + await expect( + catalogImportClient.analyzeUrl(repositoryUrl), + ).resolves.toEqual({ + locations: [ + { + entities: [ + { + kind: 'Location', + namespace: 'my-namespace', + name: 'my-entity', + }, + { + kind: 'Component', + namespace: 'my-namespace', + name: 'my-entity', + }, + ], + target: `${repositoryUrl}/blob/main/${entityFilename}`, + }, + ], + type: 'locations', + }); + }); }); describe('submitPullRequest', () => { @@ -443,6 +521,67 @@ describe('CatalogImportClient', () => { base: 'main', }); }); + + it('should create GitHub pull request with custom filename and branch name', async () => { + const entityFilename = 'anvil.yaml'; + const pullRequestBranchName = 'anvil-integration'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + pullRequestBranchName, + }, + }, + }), + }); + + await expect( + catalogImportClient.submitPullRequest({ + repositoryUrl: 'https://github.com/acme-corp/our-awesome-api', + fileContent: '', + title: `Add ${entityFilename} config file`, + body: `Add ${entityFilename} config file`, + }), + ).resolves.toEqual( + expect.objectContaining({ + link: 'http://pull/request/0', + location: `https://github.com/acme-corp/our-awesome-api/blob/main/${entityFilename}`, + }), + ); + + expect( + (new Octokit().git.createRef as any as jest.Mock).mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + ref: `refs/heads/${pullRequestBranchName}`, + }), + ); + + expect( + (new Octokit().repos.createOrUpdateFileContents as any as jest.Mock) + .mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + path: entityFilename, + branch: pullRequestBranchName, + }), + ); + + expect( + (new Octokit().pulls.create as any as jest.Mock).mock.calls[0][0], + ).toEqual( + expect.objectContaining({ + head: pullRequestBranchName, + }), + ); + }); }); describe('preparePullRequest', () => { @@ -452,5 +591,34 @@ describe('CatalogImportClient', () => { body: expect.any(String), }); }); + + test('should prepare pull request details with custom filename', async () => { + const entityFilename = 'anvil.yaml'; + const pullRequestBranchName = 'anvil-integration'; + + catalogImportClient = new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + identityApi, + catalogApi, + configApi: new ConfigReader({ + catalog: { + import: { + entityFilename, + pullRequestBranchName, + }, + }, + app: { + baseUrl: 'https://demo.backstage.io/', + }, + }), + }); + + await expect(catalogImportClient.preparePullRequest()).resolves.toEqual({ + title: `Add ${entityFilename} config file`, + body: expect.any(String), + }); + }); }); }); diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index bc59657cad..60745923b4 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -32,6 +32,7 @@ import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { trimEnd } from 'lodash'; +import { getBranchName, getCatalogFilename } from '../components/helpers'; export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; @@ -87,13 +88,15 @@ export class CatalogImportClient implements CatalogImportApi { const ghConfig = getGithubIntegrationConfig(this.scmIntegrationsApi, url); if (!ghConfig) { const other = this.scmIntegrationsApi.byUrl(url); + const catalogFilename = getCatalogFilename(this.configApi); + if (other) { throw new Error( - `The ${other.title} integration only supports full URLs to catalog-info.yaml files. Did you try to pass in the URL of a directory instead?`, + `The ${other.title} integration only supports full URLs to ${catalogFilename} files. Did you try to pass in the URL of a directory instead?`, ); } throw new Error( - 'This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a catalog-info.yaml file instead.', + `This URL was not recognized as a valid GitHub URL because there was no configured integration that matched the given host name. You could try to paste the full URL to a ${catalogFilename} file instead.`, ); } @@ -127,9 +130,10 @@ export class CatalogImportClient implements CatalogImportApi { const appTitle = this.configApi.getOptionalString('app.title') ?? 'Backstage'; const appBaseUrl = this.configApi.getString('app.baseUrl'); + const catalogFilename = getCatalogFilename(this.configApi); return { - title: 'Add catalog-info.yaml config file', + title: `Add ${catalogFilename} config file`, body: `This pull request adds a **Backstage entity metadata file** \ to this repository so that the component can be added to the \ [${appTitle} software catalog](${appBaseUrl}).\n\nAfter this pull request is merged, \ @@ -221,8 +225,8 @@ the component will become available.\n\nFor more information, read an \ auth: token, baseUrl: githubIntegrationConfig.apiBaseUrl, }); - const catalogFileName = 'catalog-info.yaml'; - const query = `repo:${owner}/${repo}+filename:${catalogFileName}`; + const catalogFilename = getCatalogFilename(this.configApi); + const query = `repo:${owner}/${repo}+filename:${catalogFilename}`; const searchResult = await octo.search.code({ q: query }).catch(e => { throw new Error( @@ -294,8 +298,8 @@ the component will become available.\n\nFor more information, read an \ baseUrl: githubIntegrationConfig.apiBaseUrl, }); - const branchName = 'backstage-integration'; - const fileName = 'catalog-info.yaml'; + const branchName = getBranchName(this.configApi); + const fileName = getCatalogFilename(this.configApi); const repoData = await octo.repos .get({ diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index 2fab223f20..3e66e137eb 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -19,8 +19,17 @@ import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { Chip, Typography } from '@material-ui/core'; import React from 'react'; import { catalogImportApiRef } from '../../api'; +import { useCatalogFilename } from '../../hooks'; -export const ImportInfoCard = () => { +type Props = { + exampleLocationUrl?: string; + exampleRepositoryUrl?: string; +}; + +export const ImportInfoCard = ({ + exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + exampleRepositoryUrl = 'https://github.com/backstage/backstage', +}: Props) => { const configApi = useApi(configApiRef); const appTitle = configApi.getOptional('app.title') || 'Backstage'; const catalogImportApi = useApi(catalogImportApiRef); @@ -28,6 +37,8 @@ export const ImportInfoCard = () => { const integrations = configApi.getConfig('integrations'); const hasGithubIntegration = integrations.has('github'); + const catalogFilename = useCatalogFilename(); + return ( { Link to an existing entity file - Example:{' '} - - https://github.com/backstage/backstage/blob/master/catalog-info.yaml - + Example: {exampleLocationUrl} The wizard analyzes the file, previews the entities, and adds them to @@ -57,17 +65,17 @@ export const ImportInfoCard = () => { - Example: https://github.com/backstage/backstage + Example: {exampleRepositoryUrl} - The wizard discovers all catalog-info.yaml files in the + The wizard discovers all {catalogFilename} files in the repository, previews the entities, and adds them to the {appTitle}{' '} catalog. {catalogImportApi.preparePullRequest && ( If no entities are found, the wizard will prepare a Pull Request - that adds an example catalog-info.yaml and prepares + that adds an example {catalogFilename} and prepares the {appTitle} catalog to load all entities as soon as the Pull Request is merged. diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 63f3c10fe3..83377d6b2f 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -36,6 +36,7 @@ type Props = { ) => void; disablePullRequest?: boolean; analysisUrl?: string; + exampleLocationUrl?: string; }; /** @@ -49,6 +50,7 @@ export const StepInitAnalyzeUrl = ({ onAnalysis, analysisUrl = '', disablePullRequest = false, + exampleLocationUrl = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', }: Props) => { const errorApi = useApi(errorApiRef); const catalogImportApi = useApi(catalogImportApiRef); @@ -138,7 +140,7 @@ export const StepInitAnalyzeUrl = ({ fullWidth id="url" label="Repository URL" - placeholder="https://github.com/backstage/backstage/blob/master/catalog-info.yaml" + placeholder={exampleLocationUrl} helperText="Enter the full path to your entity file to start tracking your component" margin="normal" variant="outlined" diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx index fe5e76c968..0a6960cdc7 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.test.tsx @@ -15,6 +15,8 @@ */ import { Entity } from '@backstage/catalog-model'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { MockConfigApi, TestApiProvider } from '@backstage/test-utils'; import { makeStyles } from '@material-ui/core'; import { render, screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; @@ -44,13 +46,18 @@ const entities: Entity[] = [ }, ]; +const mockConfigApi = new MockConfigApi({}); +const apis = [[configApiRef, mockConfigApi]] as const; + describe('', () => { it('renders without exploding', () => { render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -67,11 +74,13 @@ describe('', () => { const { result } = renderHook(() => useStyles()); render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -88,11 +97,13 @@ describe('', () => { const { result } = renderHook(() => useStyles()); render( - , + + + , ); const repositoryUrl = screen.getByText( @@ -104,4 +115,34 @@ describe('', () => { expect(kindText).toBeInTheDocument(); expect(kindText).not.toBeVisible(); }); + + it('renders with custom catalog filename', () => { + render( + + + , + ); + + const repositoryUrl = screen.getByText( + 'http://acme-corp/awesome-api/anvil.yaml', + ); + expect(repositoryUrl).toBeInTheDocument(); + expect(repositoryUrl).toBeVisible(); + }); }); diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx index d0f29c00e2..0f1dc33d2e 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewCatalogInfoComponent.tsx @@ -20,6 +20,7 @@ import React from 'react'; import YAML from 'yaml'; import { CodeSnippet } from '@backstage/core-components'; import { trimEnd } from 'lodash'; +import { useCatalogFilename } from '../../hooks'; type Props = { repositoryUrl: string; @@ -32,11 +33,13 @@ export const PreviewCatalogInfoComponent = ({ entities, classes, }: Props) => { + const catalogFilename = useCatalogFilename(); + return ( {`${trimEnd(repositoryUrl, '/')}/catalog-info.yaml`} + {`${trimEnd(repositoryUrl, '/')}/${catalogFilename}`} } /> diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 3b6f353155..8d26657bb3 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import { errorApiRef } from '@backstage/core-plugin-api'; +import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TestApiProvider } from '@backstage/test-utils'; +import { TestApiProvider, MockConfigApi } from '@backstage/test-utils'; import { TextField } from '@material-ui/core'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -53,12 +53,15 @@ describe('', () => { post: jest.fn(), }; + const configApi = new MockConfigApi({}); + const Wrapper = ({ children }: { children?: React.ReactNode }) => ( {children} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 077d99c4ba..a79caaa0b9 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -28,6 +28,7 @@ import { UnpackNestedValue, UseFormReturn } from 'react-hook-form'; import useAsync from 'react-use/lib/useAsync'; import YAML from 'yaml'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; +import { useCatalogFilename } from '../../hooks'; import { PartialEntity } from '../../types'; import { BackButton, NextButton } from '../Buttons'; import { PrepareResult } from '../useImportState'; @@ -106,6 +107,8 @@ export const StepPrepareCreatePullRequest = ({ const [submitted, setSubmitted] = useState(false); const [error, setError] = useState(); + const catalogFilename = useCatalogFilename(); + const { loading: prDefaultsLoading, value: prDefaults, @@ -193,7 +196,7 @@ export const StepPrepareCreatePullRequest = ({ <> You entered a link to a {analyzeResult.integrationType} repository but a{' '} - catalog-info.yaml could not be found. Use this form to open + {catalogFilename} could not be found. Use this form to open a Pull Request that creates one. diff --git a/plugins/catalog-import/src/components/helpers.ts b/plugins/catalog-import/src/components/helpers.ts index 4240186459..b134a8726a 100644 --- a/plugins/catalog-import/src/components/helpers.ts +++ b/plugins/catalog-import/src/components/helpers.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type { Config } from '@backstage/config'; import { UseFormRegisterReturn } from 'react-hook-form'; /** @@ -31,3 +32,17 @@ export function asInputRef(renderResult: UseFormRegisterReturn) { ...rest, }; } + +export function getCatalogFilename(config: Config): string { + return ( + config.getOptionalString('catalog.import.entityFilename') ?? + 'catalog-info.yaml' + ); +} + +export function getBranchName(config: Config): string { + return ( + config.getOptionalString('catalog.import.pullRequestBranchName') ?? + 'backstage-integration' + ); +} diff --git a/plugins/catalog-import/src/hooks/index.ts b/plugins/catalog-import/src/hooks/index.ts new file mode 100644 index 0000000000..afdfb0cbe2 --- /dev/null +++ b/plugins/catalog-import/src/hooks/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { useCatalogFilename } from './useCatalogFilename'; diff --git a/plugins/catalog-import/src/hooks/useCatalogFilename.ts b/plugins/catalog-import/src/hooks/useCatalogFilename.ts new file mode 100644 index 0000000000..0e2d770d3e --- /dev/null +++ b/plugins/catalog-import/src/hooks/useCatalogFilename.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { getCatalogFilename } from '../components/helpers'; + +export function useCatalogFilename(): string { + const config = useApi(configApiRef); + + return getCatalogFilename(config); +} diff --git a/scripts/assemble-manifest.js b/scripts/assemble-manifest.js index 03b6f51516..f68a83447d 100755 --- a/scripts/assemble-manifest.js +++ b/scripts/assemble-manifest.js @@ -58,6 +58,8 @@ async function main() { ); const tag = version.includes('next') ? 'next' : 'main'; const tagPath = path.resolve('versions', 'v1', 'tags', tag); + + // Check if there's an existing version for the tag, and that it's not newer than the one we're adding if (await fs.pathExists(tagPath)) { const currentTag = await fs.readJSON( path.resolve(tagPath, 'manifest.json'), @@ -66,9 +68,11 @@ async function main() { console.log( `Skipping update of ${tagPath} since current current ${tag} version is ${currentTag.releaseVersion}`, ); - process.exit(1); + return; } } + + // Switch the tag to our new version await fs.remove(tagPath); await fs.ensureSymlink(path.join('..', 'releases', version), tagPath); } diff --git a/yarn.lock b/yarn.lock index 1e5adac439..ee92b9f917 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3683,9 +3683,9 @@ integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== "@microsoft/microsoft-graph-types@^2.6.0": - version "2.11.0" - resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.11.0.tgz#0e1d3a0795855fc726e08836b1d3c4a72a8bcd00" - integrity sha512-v4Wuxp+kbcxeJGmb2UHbcukNr05XItFYXL+U3ReignI3Vl8tp1vfq0hkqP35Fun2QpqHJiu8Rkxj1MUF8d82ag== + version "2.13.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.13.0.tgz#aa584e4897665df5a9c8869a226264cd6ec5882b" + integrity sha512-63FfWBLcyNo8tMP4oPcdqHQvk4ehuWpiUMjVLD7zJXPENIowpdwudP969AALkKzlwsjWImamdivGKd2Zc8Z1Uw== "@microsoft/tsdoc-config@~0.15.2": version "0.15.2" @@ -11401,6 +11401,73 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +"example-app@file:packages/app": + version "0.2.62" + dependencies: + "@backstage/app-defaults" "^0.1.5" + "@backstage/catalog-model" "^0.9.10" + "@backstage/cli" "^0.13.0" + "@backstage/core-app-api" "^0.5.1" + "@backstage/core-components" "^0.8.6" + "@backstage/core-plugin-api" "^0.6.0" + "@backstage/integration-react" "^0.1.19" + "@backstage/plugin-airbrake" "^0.1.1" + "@backstage/plugin-apache-airflow" "^0.1.4" + "@backstage/plugin-api-docs" "^0.7.0" + "@backstage/plugin-azure-devops" "^0.1.12" + "@backstage/plugin-badges" "^0.2.20" + "@backstage/plugin-catalog" "^0.7.10" + "@backstage/plugin-catalog-graph" "^0.2.8" + "@backstage/plugin-catalog-import" "^0.7.10" + "@backstage/plugin-catalog-react" "^0.6.12" + "@backstage/plugin-circleci" "^0.2.35" + "@backstage/plugin-cloudbuild" "^0.2.33" + "@backstage/plugin-code-coverage" "^0.1.23" + "@backstage/plugin-cost-insights" "^0.11.18" + "@backstage/plugin-explore" "^0.3.27" + "@backstage/plugin-gcp-projects" "^0.3.15" + "@backstage/plugin-github-actions" "^0.4.33" + "@backstage/plugin-gocd" "^0.1.2" + "@backstage/plugin-graphiql" "^0.2.28" + "@backstage/plugin-home" "^0.4.12" + "@backstage/plugin-jenkins" "^0.5.18" + "@backstage/plugin-kafka" "^0.2.26" + "@backstage/plugin-kubernetes" "^0.5.5" + "@backstage/plugin-lighthouse" "^0.2.35" + "@backstage/plugin-newrelic" "^0.3.14" + "@backstage/plugin-newrelic-dashboard" "^0.1.4" + "@backstage/plugin-org" "^0.4.0" + "@backstage/plugin-pagerduty" "0.3.23" + "@backstage/plugin-rollbar" "^0.3.24" + "@backstage/plugin-scaffolder" "^0.12.0" + "@backstage/plugin-search" "^0.6.0" + "@backstage/plugin-sentry" "^0.3.34" + "@backstage/plugin-shortcuts" "^0.1.20" + "@backstage/plugin-tech-insights" "^0.1.6" + "@backstage/plugin-tech-radar" "^0.5.3" + "@backstage/plugin-techdocs" "^0.13.1" + "@backstage/plugin-todo" "^0.1.20" + "@backstage/plugin-user-settings" "^0.3.17" + "@backstage/search-common" "^0.2.2" + "@backstage/theme" "^0.2.14" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + "@octokit/rest" "^18.5.3" + "@roadiehq/backstage-plugin-buildkite" "^1.3.4" + "@roadiehq/backstage-plugin-github-insights" "^1.4.2" + "@roadiehq/backstage-plugin-github-pull-requests" "^1.3.2" + "@roadiehq/backstage-plugin-travis-ci" "^1.3.2" + history "^5.0.0" + prop-types "^15.7.2" + react "^17.0.2" + react-dom "^17.0.2" + react-hot-loader "^4.13.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-use "^17.2.4" + zen-observable "^0.8.15" + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -12879,9 +12946,9 @@ graphql@^15.5.1: integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== graphql@^16.0.0: - version "16.0.1" - resolved "https://registry.npmjs.org/graphql/-/graphql-16.0.1.tgz#93a13cd4e0e38ca8d0832e79614c8578bfd34f10" - integrity sha512-oPvCuu6dlLdiz8gZupJ47o1clgb72r1u8NDBcQYjcV6G/iEdmE11B1bBlkhXRvV0LisP/SXRFP7tT6AgaTjpzg== + version "16.3.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" + integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== grouped-queue@^2.0.0: version "2.0.0" @@ -18832,10 +18899,10 @@ passport-strategy@1.x.x, passport-strategy@^1.0.0: resolved "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= -passport@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/passport/-/passport-0.4.1.tgz#941446a21cb92fc688d97a0861c38ce9f738f270" - integrity sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg== +passport@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/passport/-/passport-0.5.2.tgz#0cb38dd8a71552c8390dfa6a9a6f7f3909954bcf" + integrity sha512-w9n/Ot5I7orGD4y+7V3EFJCQEznE5RxHamUxcqLT2QoJY0f2JdN8GyHonYFvN0Vz+L6lUJfVhrk2aZz2LbuREw== dependencies: passport-strategy "1.x.x" pause "0.0.1" @@ -19613,10 +19680,10 @@ progress@2.0.3: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -prom-client@^13.2.0: - version "13.2.0" - resolved "https://registry.npmjs.org/prom-client/-/prom-client-13.2.0.tgz#99d13357912dd400f8911b77df19f7b328a93e92" - integrity sha512-wGr5mlNNdRNzEhRYXgboUU2LxHWIojxscJKmtG3R8f4/KiWqyYgXTLHs0+Ted7tG3zFT7pgHJbtomzZ1L0ARaQ== +prom-client@^14.0.1: + version "14.0.1" + resolved "https://registry.npmjs.org/prom-client/-/prom-client-14.0.1.tgz#bdd9583e02ec95429677c0e013712d42ef1f86a8" + integrity sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w== dependencies: tdigest "^0.1.1" @@ -19671,9 +19738,9 @@ promzard@^0.3.0: read "1" prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: - version "15.8.0" - resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz#d237e624c45a9846e469f5f31117f970017ff588" - integrity sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g== + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" object-assign "^4.1.1"