diff --git a/.changeset/afraid-boats-check.md b/.changeset/afraid-boats-check.md new file mode 100644 index 0000000000..d36fda4e03 --- /dev/null +++ b/.changeset/afraid-boats-check.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Specify type of `visibilityPermission` property on collators and collator factories. diff --git a/.changeset/angry-pens-begin.md b/.changeset/angry-pens-begin.md new file mode 100644 index 0000000000..ed5bbf4c4e --- /dev/null +++ b/.changeset/angry-pens-begin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Override default commit message and author details in GitLab action diff --git a/.changeset/beige-avocados-matter.md b/.changeset/beige-avocados-matter.md new file mode 100644 index 0000000000..d6c1e3b2a5 --- /dev/null +++ b/.changeset/beige-avocados-matter.md @@ -0,0 +1,28 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +**BREAKING (alpha api):** Replace `createCatalogPolicyDecision` export with `createCatalogConditionalDecision`, which accepts a permission parameter of type `ResourcePermission<'catalog-entity'>` along with conditions. The permission passed is expected to be the handled permission in `PermissionPolicy#handle`, whose type must first be narrowed using methods like `isPermission` and `isResourcePermission`: + +```typescript +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + _user?: BackstageIdentityResponse, + ): Promise { + if ( + // Narrow type of `request.permission` to `ResourcePermission<'catalog-entity'> + isResourcePermission(request.permission, RESOURCE_TYPE_CATALOG_ENTITY) + ) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner( + _user?.identity.ownershipEntityRefs ?? [], + ), + ); + } + + return { + result: AuthorizeResult.ALLOW, + }; +``` diff --git a/.changeset/beige-lamps-cry.md b/.changeset/beige-lamps-cry.md deleted file mode 100644 index c77d5689c5..0000000000 --- a/.changeset/beige-lamps-cry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -support advanced querying capabilities using the config option `queryMode` diff --git a/.changeset/beige-suits-tell.md b/.changeset/beige-suits-tell.md deleted file mode 100644 index e9e8c919a3..0000000000 --- a/.changeset/beige-suits-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Applied the fix from version `0.15.3` of this package, which is part of the `v0.71.1` release of Backstage. diff --git a/.changeset/big-buses-clap.md b/.changeset/big-buses-clap.md new file mode 100644 index 0000000000..503aa314bc --- /dev/null +++ b/.changeset/big-buses-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bazaar': patch +--- + +Pass authorization header with Backstage token to backend requests. diff --git a/.changeset/big-mayflies-sin.md b/.changeset/big-mayflies-sin.md new file mode 100644 index 0000000000..6aa18fc9c5 --- /dev/null +++ b/.changeset/big-mayflies-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Check for non-resource permissions when authorizing result-by-result in AuthorizedSearchEngine. diff --git a/.changeset/big-planets-train.md b/.changeset/big-planets-train.md deleted file mode 100644 index 3639c3546c..0000000000 --- a/.changeset/big-planets-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-gcalendar': minor ---- - -Added Google calendar widget diff --git a/.changeset/blue-beers-kiss.md b/.changeset/blue-beers-kiss.md new file mode 100644 index 0000000000..15ea58efb4 --- /dev/null +++ b/.changeset/blue-beers-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-react': minor +--- + +**BREAKING**: More restrictive typing for `usePermission` hook and `PermissionedRoute` component. It's no longer possible to pass a `resourceRef` unless the permission is of type `ResourcePermission`. diff --git a/.changeset/brave-chairs-stare.md b/.changeset/brave-chairs-stare.md new file mode 100644 index 0000000000..8a2dd91e91 --- /dev/null +++ b/.changeset/brave-chairs-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Refactored the internal `TaskWorker` class to make it easier to test. diff --git a/.changeset/calm-walls-heal.md b/.changeset/calm-walls-heal.md new file mode 100644 index 0000000000..f47ce4a0e9 --- /dev/null +++ b/.changeset/calm-walls-heal.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-common': minor +--- + +Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances. diff --git a/.changeset/clever-donuts-teach.md b/.changeset/clever-donuts-teach.md new file mode 100644 index 0000000000..2304de1137 --- /dev/null +++ b/.changeset/clever-donuts-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Prevent permissions with types other than `ResourcePermission<'catalog-entity'>` from being used with the `useEntityPermission` hook. diff --git a/.changeset/cuddly-bags-rescue.md b/.changeset/cuddly-bags-rescue.md deleted file mode 100644 index b67926dd69..0000000000 --- a/.changeset/cuddly-bags-rescue.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@backstage/backend-test-utils': patch -'@backstage/create-app': patch -'@backstage/plugin-airbrake': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-azure-devops-backend': patch -'@backstage/plugin-badges-backend': patch -'@backstage/plugin-bazaar-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-jenkins-backend': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-scaffolder-backend-module-yeoman': patch -'@backstage/plugin-tech-insights-backend': patch -'@backstage/plugin-tech-insights-backend-module-jsonfc': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-todo-backend': patch ---- - -Minor README update diff --git a/.changeset/dependabot-05b19b9.md b/.changeset/dependabot-05b19b9.md new file mode 100644 index 0000000000..97845bb77d --- /dev/null +++ b/.changeset/dependabot-05b19b9.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +build(deps): bump `eslint-plugin-jest` from 25.3.4 to 26.1.2 diff --git a/.changeset/dependabot-2b68456.md b/.changeset/dependabot-2b68456.md new file mode 100644 index 0000000000..f54dd17d55 --- /dev/null +++ b/.changeset/dependabot-2b68456.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-model': patch +'@backstage/config-loader': patch +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch +--- + +build(deps): bump `ajv` from 7.0.3 to 8.10.0 diff --git a/.changeset/dependabot-3e359a1.md b/.changeset/dependabot-3e359a1.md deleted file mode 100644 index b50122a481..0000000000 --- a/.changeset/dependabot-3e359a1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -build(deps): bump `typescript-json-schema` from 0.52.0 to 0.53.0 diff --git a/.changeset/dependabot-6754062.md b/.changeset/dependabot-6754062.md new file mode 100644 index 0000000000..14d9648071 --- /dev/null +++ b/.changeset/dependabot-6754062.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +build(deps): bump `eslint-webpack-plugin` from 2.6.0 to 3.1.1 diff --git a/.changeset/dependabot-6ef69ed.md b/.changeset/dependabot-6ef69ed.md deleted file mode 100644 index 825e9e9964..0000000000 --- a/.changeset/dependabot-6ef69ed.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -build(deps): bump `@google-cloud/container` from 2.3.0 to 3.0.0 diff --git a/.changeset/dependabot-84caed8.md b/.changeset/dependabot-84caed8.md new file mode 100644 index 0000000000..d4806431a0 --- /dev/null +++ b/.changeset/dependabot-84caed8.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +build(deps): bump `@spotify/eslint-config-base` from 12.0.0 to 13.0.0 diff --git a/.changeset/dependabot-afa578f.md b/.changeset/dependabot-afa578f.md deleted file mode 100644 index be438c0149..0000000000 --- a/.changeset/dependabot-afa578f.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-airbrake': patch ---- - -build(deps): bump `object-hash` from 2.2.0 to 3.0.0 diff --git a/.changeset/dependabot-bf11310.md b/.changeset/dependabot-bf11310.md deleted file mode 100644 index 9b3034eb11..0000000000 --- a/.changeset/dependabot-bf11310.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -'@backstage/app-defaults': patch -'@backstage/core-app-api': patch -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch -'@backstage/create-app': patch -'@backstage/dev-utils': patch -'@backstage/integration-react': patch -'@backstage/test-utils': patch -'@backstage/version-bridge': patch -'@backstage/plugin-airbrake': patch -'@backstage/plugin-allure': patch -'@backstage/plugin-analytics-module-ga': patch -'@backstage/plugin-apache-airflow': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-azure-devops': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-bitrise': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-code-climate': patch -'@backstage/plugin-code-coverage': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore-react': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-firehydrant': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-gcp-projects': patch -'@backstage/plugin-git-release-manager': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-gitops-profiles': patch -'@backstage/plugin-gocd': patch -'@backstage/plugin-graphiql': patch -'@backstage/plugin-home': patch -'@backstage/plugin-ilert': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-newrelic': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-periskop': patch -'@backstage/plugin-permission-react': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-shortcuts': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-tech-insights': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-xcmetrics': patch ---- - -chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 diff --git a/.changeset/dependabot-c1fe92f.md b/.changeset/dependabot-c1fe92f.md new file mode 100644 index 0000000000..8d106e3962 --- /dev/null +++ b/.changeset/dependabot-c1fe92f.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +build(deps): bump `@spotify/eslint-config-typescript` diff --git a/.changeset/dry-fans-arrive.md b/.changeset/dry-fans-arrive.md new file mode 100644 index 0000000000..6947703e7f --- /dev/null +++ b/.changeset/dry-fans-arrive.md @@ -0,0 +1,63 @@ +--- +'@backstage/plugin-permission-node': minor +--- + +**BREAKING**: Stronger typing in `PermissionPolicy` 🎉. + +Previously, it was entirely the responsibility of the `PermissionPolicy` author to only return `CONDITIONAL` decisions for permissions that are associated with a resource, and to return the correct kind of `PermissionCondition` instances inside the decision. Now, the policy authoring helpers provided in this package now ensure that the decision and permission match. + +**For policy authors**: rename and adjust api of `createConditionExports`. Previously, the function returned a factory for creating conditional decisions named `createPolicyDecision`, which had a couple of drawbacks: + +1. The function always creates a _conditional_ policy decision, but this was not reflected in the name. +2. Conditional decisions should only ever be returned from `PermissionPolicy#handle` for resource permissions, but there was nothing in the API that encoded this constraint. + +This change addresses the drawbacks above by making the following changes for policy authors: + +- The `createPolicyDecision` method has been renamed to `createConditionalDecision`. +- Along with conditions, the method now accepts a permission, which must be a `ResourcePermission`. This is expected to be the handled permission in `PermissionPolicy#handle`, whose type must first be narrowed using methods like `isPermission` and `isResourcePermission`: + +```typescript +class TestPermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + _user?: BackstageIdentityResponse, + ): Promise { + if ( + // Narrow type of `request.permission` to `ResourcePermission<'catalog-entity'> + isResourcePermission(request.permission, RESOURCE_TYPE_CATALOG_ENTITY) + ) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner( + _user?.identity.ownershipEntityRefs ?? [], + ), + ); + } + + return { + result: AuthorizeResult.ALLOW, + }; +``` + +**BREAKING**: when creating `PermissionRule`s, provide a `resourceType`. + +```diff +export const isEntityOwner = createCatalogPermissionRule({ + name: 'IS_ENTITY_OWNER', + description: 'Allow entities owned by the current user', ++ resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + apply: (resource: Entity, claims: string[]) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === RELATION_OWNED_BY) + .some(relation => claims.includes(relation.targetRef)); + }, + toQuery: (claims: string[]) => ({ + key: 'relations.ownedBy', + values: claims, + }), +}); +``` diff --git a/.changeset/eleven-days-brush.md b/.changeset/eleven-days-brush.md new file mode 100644 index 0000000000..d628b068fc --- /dev/null +++ b/.changeset/eleven-days-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': minor +--- + +Adds the ability to manually trigger tasks which are registered diff --git a/.changeset/eleven-frogs-promise.md b/.changeset/eleven-frogs-promise.md new file mode 100644 index 0000000000..1868bb9df5 --- /dev/null +++ b/.changeset/eleven-frogs-promise.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-permission-common': minor +--- + +Refactor api types into more specific, decoupled names. + +- **BREAKING:** + - Renamed `AuthorizeDecision` to `EvaluatePermissionResponse` + - Renamed `AuthorizeQuery` to `EvaluatePermissionRequest` + - Renamed `AuthorizeRequest` to `EvaluatePermissionRequestBatch` + - Renamed `AuthorizeResponse` to `EvaluatePermissionResponseBatch` + - Renamed `Identified` to `IdentifiedPermissionMessage` +- Add `PermissionMessageBatch` helper type +- Add `ConditionalPolicyDecision`, `DefinitivePolicyDecision`, and `PolicyDecision` types from `@backstage/plugin-permission-node` diff --git a/.changeset/eleven-pens-collect.md b/.changeset/eleven-pens-collect.md new file mode 100644 index 0000000000..0df1bb0400 --- /dev/null +++ b/.changeset/eleven-pens-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Corrected replacements for depreciated FilteredEntityLayout items diff --git a/.changeset/empty-pens-invent.md b/.changeset/empty-pens-invent.md new file mode 100644 index 0000000000..d6937afc84 --- /dev/null +++ b/.changeset/empty-pens-invent.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': minor +--- + +**BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents. diff --git a/.changeset/fair-dingos-glow.md b/.changeset/fair-dingos-glow.md new file mode 100644 index 0000000000..569f7a3fab --- /dev/null +++ b/.changeset/fair-dingos-glow.md @@ -0,0 +1,19 @@ +--- +'@backstage/create-app': patch +--- + +Remove the `knex` package that is installed in the `packages/backend` as it's provided by the `@backstage/*` packages for you automatically. You can make the following change in your `packages/backend/package.json` if you wish to apply this change. + +```diff + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", +- "migrate:create": "knex migrate:make -x ts" +``` + +```diff + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", + "pg": "^8.3.0", +``` diff --git a/.changeset/fair-lamps-leave.md b/.changeset/fair-lamps-leave.md new file mode 100644 index 0000000000..9e902b51f2 --- /dev/null +++ b/.changeset/fair-lamps-leave.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Wired up the OneLogin provider to be visible in the Settings UI when configured correctly. + +Previously it wasn't visible at all. diff --git a/.changeset/fast-cheetahs-grow.md b/.changeset/fast-cheetahs-grow.md new file mode 100644 index 0000000000..5567436b5e --- /dev/null +++ b/.changeset/fast-cheetahs-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': minor +--- + +**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. diff --git a/.changeset/few-mayflies-divide.md b/.changeset/few-mayflies-divide.md new file mode 100644 index 0000000000..e8744c47da --- /dev/null +++ b/.changeset/few-mayflies-divide.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-permission-node': minor +--- + +**BREAKING:** + +- Rename `PolicyAuthorizeQuery` to `PolicyQuery` +- Remove `PolicyDecision`, `DefinitivePolicyDecision`, and `ConditionalPolicyDecision`. These types are now exported from `@backstage/plugin-permission-common` diff --git a/.changeset/forty-poets-tie.md b/.changeset/forty-poets-tie.md new file mode 100644 index 0000000000..9118100934 --- /dev/null +++ b/.changeset/forty-poets-tie.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Decouple tags picker from backend entities + +`EntityTagPicker` fetches all the tags independently and it doesn't require all the entities to be available client side. diff --git a/.changeset/four-birds-peel.md b/.changeset/four-birds-peel.md new file mode 100644 index 0000000000..e90f631858 --- /dev/null +++ b/.changeset/four-birds-peel.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-home': patch +--- + +- Adds new `HomePageStackOverflowQuestions` component which renders a list of stack overflow questions on your homepage. + +- Exports `ComponentRenderer` type. diff --git a/.changeset/fresh-cobras-admire.md b/.changeset/fresh-cobras-admire.md new file mode 100644 index 0000000000..c73d4ab5d5 --- /dev/null +++ b/.changeset/fresh-cobras-admire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +export kubernetes components diff --git a/.changeset/fresh-dodos-rush.md b/.changeset/fresh-dodos-rush.md new file mode 100644 index 0000000000..b71c2ad055 --- /dev/null +++ b/.changeset/fresh-dodos-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-node': patch +--- + +Adds an optional timeout to fact retriever registrations to stop a task if it runs too long. diff --git a/.changeset/great-pots-fetch.md b/.changeset/great-pots-fetch.md deleted file mode 100644 index 34785dfb7f..0000000000 --- a/.changeset/great-pots-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': minor ---- - -**BREAKING**: The provided Jest configuration now only matches files with a `.test.` infix, rather than any files that is suffixed with `test.`. In particular this means that files named just `test.ts` will no longer be considered a test file. diff --git a/.changeset/happy-foxes-arrive.md b/.changeset/happy-foxes-arrive.md new file mode 100644 index 0000000000..53e59c8d77 --- /dev/null +++ b/.changeset/happy-foxes-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added an experimental `package fix` command which applies automated fixes to the target package. The initial fix that is available is to add missing monorepo dependencies to the target package. diff --git a/.changeset/happy-mugs-camp.md b/.changeset/happy-mugs-camp.md new file mode 100644 index 0000000000..22d13d80d1 --- /dev/null +++ b/.changeset/happy-mugs-camp.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-search-common': patch +--- + +- Introduce `SearchDocument` type. This type contains the subset of `IndexableDocument` properties relevant to the frontend, and is intended to be used for documents returned to the frontend from the search API. +- `SearchResultSet` is now a wrapper for documents of type `SearchDocument`, and is intended to be used in the frontend. This isn't a breaking change, since `IndexableDocument`s are valid `SearchDocument`s, so the old and new types are compatible. +- Introduce `IndexableResultSet` type, which wraps `IndexableDocument` instances in the same way as `SearchResultSet`. diff --git a/.changeset/heavy-kangaroos-confess.md b/.changeset/heavy-kangaroos-confess.md deleted file mode 100644 index 6d39e09db1..0000000000 --- a/.changeset/heavy-kangaroos-confess.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/backend-tasks': patch -'@backstage/backend-test-utils': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-bazaar-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-code-coverage-backend': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Use `better-sqlite3` instead of `@vscode/sqlite3` diff --git a/.changeset/hip-poems-breathe.md b/.changeset/hip-poems-breathe.md new file mode 100644 index 0000000000..f8a507b2a7 --- /dev/null +++ b/.changeset/hip-poems-breathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Stop logging "Stopped watcher" when shutting down the development backend. diff --git a/.changeset/khaki-pears-march.md b/.changeset/khaki-pears-march.md new file mode 100644 index 0000000000..5626bf3ea5 --- /dev/null +++ b/.changeset/khaki-pears-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +**BREAKING**: All sign-in resolvers must now return a `token` in their sign-in result. Returning an `id` is no longer supported. diff --git a/.changeset/large-bottles-cheer.md b/.changeset/large-bottles-cheer.md deleted file mode 100644 index edd62567f5..0000000000 --- a/.changeset/large-bottles-cheer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: Removed the deprecated `metadata.generation` field entirely. It is no longer present in TS types, nor in the REST API output. Entities that have not yet been re-stitched may still have the field present for some time, but it will get phased out gradually by your catalog instance. diff --git a/.changeset/large-coins-arrive.md b/.changeset/large-coins-arrive.md new file mode 100644 index 0000000000..3c0b3b9f97 --- /dev/null +++ b/.changeset/large-coins-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Adjust the error messages when entities fail validation, to clearly state what entity that failed it diff --git a/.changeset/light-drinks-rule.md b/.changeset/light-drinks-rule.md new file mode 100644 index 0000000000..2b99f5988c --- /dev/null +++ b/.changeset/light-drinks-rule.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-search-backend': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-pg': patch +--- + +Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. diff --git a/.changeset/lovely-feet-do.md b/.changeset/lovely-feet-do.md deleted file mode 100644 index 637603aed0..0000000000 --- a/.changeset/lovely-feet-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fix for `overrides` not being properly forwarded from the extra configuration passed to `@backstage/cli/config/eslint-factory`. diff --git a/.changeset/mean-pumas-search.md b/.changeset/mean-pumas-search.md new file mode 100644 index 0000000000..68a3aebc85 --- /dev/null +++ b/.changeset/mean-pumas-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +fixed empty body issue for POST requests using FetchAPI with 'plugin://' prefix diff --git a/.changeset/mean-rabbits-tell.md b/.changeset/mean-rabbits-tell.md new file mode 100644 index 0000000000..88d853d9b3 --- /dev/null +++ b/.changeset/mean-rabbits-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow-backend': minor +--- + +Add stack overflow backend plugin diff --git a/.changeset/mean-tomatoes-shout.md b/.changeset/mean-tomatoes-shout.md new file mode 100644 index 0000000000..446e66d386 --- /dev/null +++ b/.changeset/mean-tomatoes-shout.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-permission-react': patch +'@backstage/plugin-search-backend': patch +'@backstage/test-utils': patch +--- + +Use updated types from `@backstage/plugin-permission-common` diff --git a/.changeset/metal-beans-teach.md b/.changeset/metal-beans-teach.md deleted file mode 100644 index f3971a2f29..0000000000 --- a/.changeset/metal-beans-teach.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -**BREAKING**: Removed `EntityName`, use `CompoundEntityRef` type instead. - -**BREAKING**: Removed `getEntityName`, use `getCompoundEntityRef` instead. diff --git a/.changeset/metal-queens-cheat.md b/.changeset/metal-queens-cheat.md new file mode 100644 index 0000000000..b2d152b3a9 --- /dev/null +++ b/.changeset/metal-queens-cheat.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-common': patch +'@backstage/plugin-jenkins-common': patch +--- + +Use `createPermission` helper when creating permissions. diff --git a/.changeset/mighty-suns-drop.md b/.changeset/mighty-suns-drop.md new file mode 100644 index 0000000000..f8bb686aaf --- /dev/null +++ b/.changeset/mighty-suns-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +add aggregated ownership type for kind group in OwnershipCard diff --git a/.changeset/modern-actors-notice.md b/.changeset/modern-actors-notice.md new file mode 100644 index 0000000000..20dde7f42d --- /dev/null +++ b/.changeset/modern-actors-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +**BREAKING:** Mark CatalogBuilder#addPermissionRules as @alpha. diff --git a/.changeset/modern-pumas-join.md b/.changeset/modern-pumas-join.md new file mode 100644 index 0000000000..d2995af986 --- /dev/null +++ b/.changeset/modern-pumas-join.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Enable internal batching of very large deletions, to not run into SQL binding limits diff --git a/.changeset/moody-pigs-rush.md b/.changeset/moody-pigs-rush.md new file mode 100644 index 0000000000..37ae171382 --- /dev/null +++ b/.changeset/moody-pigs-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Applied the fix from version 0.12.3 of this package, which is part of the v1.0.1 release of Backstage. diff --git a/.changeset/moody-stingrays-tap.md b/.changeset/moody-stingrays-tap.md deleted file mode 100644 index de86127eba..0000000000 --- a/.changeset/moody-stingrays-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: Removed the previously deprecated `results` export. Please use `processingResult` instead. diff --git a/.changeset/nervous-spiders-join.md b/.changeset/nervous-spiders-join.md deleted file mode 100644 index fcbce29177..0000000000 --- a/.changeset/nervous-spiders-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': minor ---- - -**BREAKING**: Removed the deprecated `favoriteEntityTooltip` and `favoriteEntityIcon` functions. diff --git a/.changeset/new-lions-run.md b/.changeset/new-lions-run.md new file mode 100644 index 0000000000..8e830b33fa --- /dev/null +++ b/.changeset/new-lions-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Added renderNode and renderLabel property to EntityRelationsGraph to support customization using CustomNode and CustomLabel components diff --git a/.changeset/new-maps-complain.md b/.changeset/new-maps-complain.md deleted file mode 100644 index 199a0c3280..0000000000 --- a/.changeset/new-maps-complain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Added an integration for Gerrit diff --git a/.changeset/nine-grapes-turn.md b/.changeset/nine-grapes-turn.md new file mode 100644 index 0000000000..263618722c --- /dev/null +++ b/.changeset/nine-grapes-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added the GerritUrlReader that implements "readUrl". diff --git a/.changeset/ninety-fishes-vanish.md b/.changeset/ninety-fishes-vanish.md new file mode 100644 index 0000000000..b0d3ac370f --- /dev/null +++ b/.changeset/ninety-fishes-vanish.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Switch to `SearchDocument` type in `DefaultResultListItem` props diff --git a/.changeset/odd-spoons-design.md b/.changeset/odd-spoons-design.md deleted file mode 100644 index 1d0ca96015..0000000000 --- a/.changeset/odd-spoons-design.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: Removed the following deprecated symbols: - -- `catalogBuilder.setRefreshInterval`, use `catalogBuilder.setProcessingInterval` instead. -- `catalogBuilder.setRefreshIntervalSeconds`, use `catalogBuilder.setProcessingIntervalSeconds` instead. -- `createRandomRefreshInterval`, use `createRandomProcessingInterval` instead. -- `RefreshIntervalFunction`, use `ProcessingIntervalFunction` instead. diff --git a/.changeset/olive-geese-chew.md b/.changeset/olive-geese-chew.md new file mode 100644 index 0000000000..a08b73b4a7 --- /dev/null +++ b/.changeset/olive-geese-chew.md @@ -0,0 +1,10 @@ +--- +'@backstage/backend-common': patch +'@backstage/integration': patch +--- + +Support external ID when assuming roles in S3 integration + +In order to assume a role created by a 3rd party as external +ID is needed. This change adds an optional field to the s3 +integration configuration and consumes that in the AwsS3UrlReader. diff --git a/.changeset/perfect-phones-roll.md b/.changeset/perfect-phones-roll.md new file mode 100644 index 0000000000..c1f9ecdbc1 --- /dev/null +++ b/.changeset/perfect-phones-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Add helpful README.md files in the original `packages` and `plugins` folders diff --git a/.changeset/polite-melons-clap.md b/.changeset/polite-melons-clap.md deleted file mode 100644 index a4356f8d21..0000000000 --- a/.changeset/polite-melons-clap.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: Removed the export of the `RecursivePartial` utility type. If you relied on this type it can be redefined like this: - -```ts -type RecursivePartial = { - [P in keyof T]?: T[P] extends (infer U)[] - ? RecursivePartial[] - : T[P] extends object - ? RecursivePartial - : T[P]; -}; -``` diff --git a/.changeset/poor-ads-grab.md b/.changeset/poor-ads-grab.md deleted file mode 100644 index 75a56bf974..0000000000 --- a/.changeset/poor-ads-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/codemods': patch ---- - -Inlined the table of symbols used by the `core-imports` codemod so that future updates to the core packages don't break the codemod. An entry for has also been added to direct imports of `createApp` to `@backstage/app-defaults`. diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000000..063685ce82 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,172 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "example-app": "0.2.69", + "@backstage/app-defaults": "1.0.0", + "example-backend": "0.2.69", + "@backstage/backend-common": "0.13.1", + "@backstage/backend-tasks": "0.2.1", + "@backstage/backend-test-utils": "0.1.22", + "@backstage/catalog-client": "1.0.0", + "@backstage/catalog-model": "1.0.0", + "@backstage/cli": "0.16.0", + "@backstage/cli-common": "0.1.8", + "@backstage/codemods": "0.1.36", + "@backstage/config": "1.0.0", + "@backstage/config-loader": "1.0.0", + "@backstage/core-app-api": "1.0.0", + "@backstage/core-components": "0.9.2", + "@backstage/core-plugin-api": "1.0.0", + "@backstage/create-app": "0.4.24", + "@backstage/dev-utils": "1.0.0", + "e2e-test": "0.2.0", + "@backstage/errors": "1.0.0", + "@backstage/integration": "1.0.0", + "@backstage/integration-react": "1.0.0", + "@backstage/release-manifests": "0.0.2", + "@backstage/search-common": "0.3.2", + "@techdocs/cli": "1.0.0", + "techdocs-cli-embedded-app": "0.2.68", + "@backstage/techdocs-common": "0.11.13", + "@backstage/test-utils": "1.0.0", + "@backstage/theme": "0.2.15", + "@backstage/types": "1.0.0", + "@backstage/version-bridge": "1.0.0", + "@backstage/plugin-airbrake": "0.3.3", + "@backstage/plugin-airbrake-backend": "0.2.3", + "@backstage/plugin-allure": "0.1.19", + "@backstage/plugin-analytics-module-ga": "0.1.14", + "@backstage/plugin-apache-airflow": "0.1.11", + "@backstage/plugin-api-docs": "0.8.3", + "@backstage/plugin-app-backend": "0.3.30", + "@backstage/plugin-auth-backend": "0.12.2", + "@backstage/plugin-auth-node": "0.1.6", + "@backstage/plugin-azure-devops": "0.1.19", + "@backstage/plugin-azure-devops-backend": "0.3.9", + "@backstage/plugin-azure-devops-common": "0.2.2", + "@backstage/plugin-badges": "0.2.27", + "@backstage/plugin-badges-backend": "0.1.24", + "@backstage/plugin-bazaar": "0.1.18", + "@backstage/plugin-bazaar-backend": "0.1.14", + "@backstage/plugin-bitrise": "0.1.30", + "@backstage/plugin-catalog": "1.0.0", + "@backstage/plugin-catalog-backend": "1.0.0", + "@backstage/plugin-catalog-backend-module-aws": "0.1.3", + "@backstage/plugin-catalog-backend-module-azure": "0.1.1", + "@backstage/plugin-catalog-backend-module-bitbucket": "0.1.1", + "@backstage/plugin-catalog-backend-module-github": "0.1.1", + "@backstage/plugin-catalog-backend-module-gitlab": "0.1.1", + "@backstage/plugin-catalog-backend-module-ldap": "0.4.1", + "@backstage/plugin-catalog-backend-module-msgraph": "0.3.0", + "@backstage/plugin-catalog-common": "1.0.0", + "@backstage/plugin-catalog-graph": "0.2.15", + "@backstage/plugin-catalog-graphql": "0.3.7", + "@backstage/plugin-catalog-import": "0.8.6", + "@backstage/plugin-catalog-react": "1.0.0", + "@backstage/plugin-cicd-statistics": "0.1.5", + "@backstage/plugin-circleci": "0.3.3", + "@backstage/plugin-cloudbuild": "0.3.3", + "@backstage/plugin-code-climate": "0.1.3", + "@backstage/plugin-code-coverage": "0.1.30", + "@backstage/plugin-code-coverage-backend": "0.1.28", + "@backstage/plugin-config-schema": "0.1.26", + "@backstage/plugin-cost-insights": "0.11.25", + "@backstage/plugin-explore": "0.3.34", + "@backstage/plugin-explore-react": "0.0.15", + "@backstage/plugin-firehydrant": "0.1.20", + "@backstage/plugin-fossa": "0.2.35", + "@backstage/plugin-gcalendar": "0.2.0", + "@backstage/plugin-gcp-projects": "0.3.22", + "@backstage/plugin-git-release-manager": "0.3.16", + "@backstage/plugin-github-actions": "0.5.3", + "@backstage/plugin-github-deployments": "0.1.34", + "@backstage/plugin-gitops-profiles": "0.3.21", + "@backstage/plugin-gocd": "0.1.9", + "@backstage/plugin-graphiql": "0.2.35", + "@backstage/plugin-graphql-backend": "0.1.20", + "@backstage/plugin-home": "0.4.19", + "@backstage/plugin-ilert": "0.1.29", + "@backstage/plugin-jenkins": "0.7.2", + "@backstage/plugin-jenkins-backend": "0.1.19", + "@backstage/plugin-jenkins-common": "0.1.2", + "@backstage/plugin-kafka": "0.3.3", + "@backstage/plugin-kafka-backend": "0.2.23", + "@backstage/plugin-kubernetes": "0.6.3", + "@backstage/plugin-kubernetes-backend": "0.4.13", + "@backstage/plugin-kubernetes-common": "0.2.8", + "@backstage/plugin-lighthouse": "0.3.3", + "@backstage/plugin-newrelic": "0.3.21", + "@backstage/plugin-newrelic-dashboard": "0.1.11", + "@backstage/plugin-org": "0.5.3", + "@backstage/plugin-pagerduty": "0.3.30", + "@backstage/plugin-periskop": "0.1.1", + "@backstage/plugin-periskop-backend": "0.1.1", + "@backstage/plugin-permission-backend": "0.5.5", + "@backstage/plugin-permission-common": "0.5.3", + "@backstage/plugin-permission-node": "0.5.5", + "@backstage/plugin-permission-react": "0.3.4", + "@backstage/plugin-proxy-backend": "0.2.24", + "@backstage/plugin-rollbar": "0.4.3", + "@backstage/plugin-rollbar-backend": "0.1.27", + "@backstage/plugin-scaffolder": "1.0.0", + "@backstage/plugin-scaffolder-backend": "1.0.0", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.5", + "@backstage/plugin-scaffolder-backend-module-rails": "0.3.5", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.3", + "@backstage/plugin-scaffolder-common": "1.0.0", + "@backstage/plugin-search": "0.7.4", + "@backstage/plugin-search-backend": "0.4.8", + "@backstage/plugin-search-backend-module-elasticsearch": "0.1.2", + "@backstage/plugin-search-backend-module-pg": "0.3.1", + "@backstage/plugin-search-backend-node": "0.5.2", + "@backstage/plugin-search-common": "0.3.2", + "@backstage/plugin-sentry": "0.3.41", + "@backstage/plugin-shortcuts": "0.2.4", + "@backstage/plugin-sonarqube": "0.3.3", + "@backstage/plugin-splunk-on-call": "0.3.27", + "@backstage/plugin-tech-insights": "0.1.13", + "@backstage/plugin-tech-insights-backend": "0.2.10", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.14", + "@backstage/plugin-tech-insights-common": "0.2.4", + "@backstage/plugin-tech-insights-node": "0.2.8", + "@backstage/plugin-tech-radar": "0.5.10", + "@backstage/plugin-techdocs": "1.0.0", + "@backstage/plugin-techdocs-backend": "1.0.0", + "@backstage/plugin-techdocs-node": "1.0.0", + "@backstage/plugin-todo": "0.2.5", + "@backstage/plugin-todo-backend": "0.1.27", + "@backstage/plugin-user-settings": "0.4.2", + "@backstage/plugin-xcmetrics": "0.2.23" + }, + "changesets": [ + "dependabot-2b68456", + "eleven-pens-collect", + "empty-pens-invent", + "fair-lamps-leave", + "fast-cheetahs-grow", + "happy-foxes-arrive", + "happy-mugs-camp", + "khaki-pears-march", + "large-coins-arrive", + "light-drinks-rule", + "mean-pumas-search", + "modern-pumas-join", + "moody-pigs-rush", + "ninety-fishes-vanish", + "olive-geese-chew", + "purple-boats-punch", + "red-snakes-float", + "rich-maps-hear", + "silent-bobcats-matter", + "silver-pots-call", + "smart-phones-exist", + "techdocs-coats-obey", + "techdocs-head-shoulders-knees-toes", + "thick-comics-fold", + "tidy-emus-stare", + "wicked-beds-return", + "wicked-feet-clap", + "yellow-hats-remember" + ] +} diff --git a/.changeset/pretty-ghosts-scream.md b/.changeset/pretty-ghosts-scream.md new file mode 100644 index 0000000000..7a604d75b6 --- /dev/null +++ b/.changeset/pretty-ghosts-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +`TestDatabases.create` will no longer set up an `afterAll` test handler if no databases are supported. diff --git a/.changeset/purple-boats-punch.md b/.changeset/purple-boats-punch.md new file mode 100644 index 0000000000..817f0a79bc --- /dev/null +++ b/.changeset/purple-boats-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +**DEPRECATION**: The `tokenIssuer` option for `OAuthAdapter` is no longer needed and has been deprecated. diff --git a/.changeset/purple-laws-give.md b/.changeset/purple-laws-give.md new file mode 100644 index 0000000000..7e985092e2 --- /dev/null +++ b/.changeset/purple-laws-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Removed broken link from Labels section of entity inspector. diff --git a/.changeset/rare-waves-hammer.md b/.changeset/rare-waves-hammer.md new file mode 100644 index 0000000000..5b9f7c845d --- /dev/null +++ b/.changeset/rare-waves-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-common': patch +--- + +Add `@alpha` `CatalogEntityPermission` convenience type, available for import from `@backstage/plugin-catalog-common/alpha`. diff --git a/.changeset/red-snakes-float.md b/.changeset/red-snakes-float.md new file mode 100644 index 0000000000..f1bd16d84c --- /dev/null +++ b/.changeset/red-snakes-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Add support for string refs to the `EntityRefLinks` component diff --git a/.changeset/rich-bobcats-behave.md b/.changeset/rich-bobcats-behave.md deleted file mode 100644 index d873017624..0000000000 --- a/.changeset/rich-bobcats-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graphql': patch ---- - -Do not use `metadata.generation` from entity data diff --git a/.changeset/rich-maps-hear.md b/.changeset/rich-maps-hear.md new file mode 100644 index 0000000000..390da8b6bb --- /dev/null +++ b/.changeset/rich-maps-hear.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Added a new `NextScaffolderRouter` which will eventually replace the exiting router diff --git a/.changeset/rotten-bears-vanish.md b/.changeset/rotten-bears-vanish.md deleted file mode 100644 index b988d061ca..0000000000 --- a/.changeset/rotten-bears-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-azure-devops': patch ---- - -Updated readme diff --git a/.changeset/rotten-planes-watch.md b/.changeset/rotten-planes-watch.md new file mode 100644 index 0000000000..25aa6b83df --- /dev/null +++ b/.changeset/rotten-planes-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Handle trailing slashes on GitHub `enterpriseInstanceUrl` settings diff --git a/.changeset/search-pour-one-out-common.md b/.changeset/search-pour-one-out-common.md deleted file mode 100644 index e4bc2b4f6a..0000000000 --- a/.changeset/search-pour-one-out-common.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/search-common': patch ---- - -This package is no longer maintained. Use `@backstage/plugin-search-common`, going forward. diff --git a/.changeset/seven-apricots-sell.md b/.changeset/seven-apricots-sell.md new file mode 100644 index 0000000000..977bdd7d49 --- /dev/null +++ b/.changeset/seven-apricots-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-backend-module-jsonfc': patch +--- + +Removes node-cron from tech-insights to utilize backend-tasks diff --git a/.changeset/shaggy-beers-unite.md b/.changeset/shaggy-beers-unite.md deleted file mode 100644 index 7649203f52..0000000000 --- a/.changeset/shaggy-beers-unite.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog-react': minor ---- - -**BREAKING**: The following deprecated annotation reading helper functions were removed: - -- `getEntityMetadataViewUrl`, use `entity.metadata.annotations?.[ANNOTATION_VIEW_URL]` instead. -- `getEntityMetadataEditUrl`, use `entity.metadata.annotations?.[ANNOTATION_EDIT_URL]` instead. diff --git a/.changeset/shiny-seas-yell.md b/.changeset/shiny-seas-yell.md new file mode 100644 index 0000000000..9a939701d8 --- /dev/null +++ b/.changeset/shiny-seas-yell.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-tech-insights-backend': minor +--- + +This backend now uses the `@backstage/backend-tasks` package facilities for scheduling fact retrievers. + +**BREAKING**: The `buildTechInsightsContext` function now takes an additional field in its options argument: `scheduler`. This is an instance of `PluginTaskScheduler`, which can be found in your backend initialization code's `env`. + +```diff + const builder = buildTechInsightsContext({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, ++ scheduler: env.scheduler, + factRetrievers: [ /* ... */ ], + }); +``` diff --git a/.changeset/silent-bobcats-matter.md b/.changeset/silent-bobcats-matter.md new file mode 100644 index 0000000000..f67f2d3ede --- /dev/null +++ b/.changeset/silent-bobcats-matter.md @@ -0,0 +1,9 @@ +--- +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/core-app-api': patch +'@backstage/plugin-catalog-backend-module-bitbucket': patch +'@backstage/plugin-tech-insights-backend': patch +--- + +Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. diff --git a/.changeset/silent-fishes-dream.md b/.changeset/silent-fishes-dream.md deleted file mode 100644 index ea4b8c33c0..0000000000 --- a/.changeset/silent-fishes-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: The deprecated `CatalogEntityDocument` export has been removed, it can be imported from `@backstage/plugin-catalog-common` instead. diff --git a/.changeset/silly-llamas-relax.md b/.changeset/silly-llamas-relax.md new file mode 100644 index 0000000000..f5a002d46d --- /dev/null +++ b/.changeset/silly-llamas-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Fix an issue where the Radar is not updated when switching between different radars diff --git a/.changeset/silver-pots-call.md b/.changeset/silver-pots-call.md new file mode 100644 index 0000000000..42b3016545 --- /dev/null +++ b/.changeset/silver-pots-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +fix support config ref to use backstage/backstage diff --git a/.changeset/smart-phones-exist.md b/.changeset/smart-phones-exist.md new file mode 100644 index 0000000000..eb1c3d041a --- /dev/null +++ b/.changeset/smart-phones-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added a new experimental `repo list-deprecations` command, which scans the entire project for usage of deprecated APIs. diff --git a/.changeset/smart-snails-switch.md b/.changeset/smart-snails-switch.md deleted file mode 100644 index 59bc948f8b..0000000000 --- a/.changeset/smart-snails-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Add config support for LDAP search options. diff --git a/.changeset/sour-cameras-deliver.md b/.changeset/sour-cameras-deliver.md new file mode 100644 index 0000000000..21e23e7833 --- /dev/null +++ b/.changeset/sour-cameras-deliver.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Handle changes to @alpha permission-related types. + +- All exported permission rules and conditions now have a `resourceType`. +- `createCatalogConditionalDecision` now expects supplied conditions to have the appropriate `resourceType`. +- `createCatalogPermissionRule` now expects `resourceType` as part of the supplied rule object. +- Introduce new `CatalogPermissionRule` convenience type. diff --git a/.changeset/sour-lobsters-sniff.md b/.changeset/sour-lobsters-sniff.md new file mode 100644 index 0000000000..fb8d1f2076 --- /dev/null +++ b/.changeset/sour-lobsters-sniff.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': patch +--- + +Fix signature of permission rule in test suites diff --git a/.changeset/stale-carrots-smile.md b/.changeset/stale-carrots-smile.md new file mode 100644 index 0000000000..20b77dd834 --- /dev/null +++ b/.changeset/stale-carrots-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-common': patch +--- + +Add `isPermission` helper method. diff --git a/.changeset/strange-wasps-whisper.md b/.changeset/strange-wasps-whisper.md new file mode 100644 index 0000000000..bef72e276a --- /dev/null +++ b/.changeset/strange-wasps-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +The logger returned from `getVoidLogger` is now uses a silenced console transport instead. diff --git a/.changeset/strong-tomatoes-kneel.md b/.changeset/strong-tomatoes-kneel.md deleted file mode 100644 index bdddb91ae2..0000000000 --- a/.changeset/strong-tomatoes-kneel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Updated to no longer rely on the `RecursivePartial` export from `@backstage/plugin-catalog-backend`. diff --git a/.changeset/tasty-emus-hunt.md b/.changeset/tasty-emus-hunt.md new file mode 100644 index 0000000000..d3b9430bf4 --- /dev/null +++ b/.changeset/tasty-emus-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Expose 'initalFilter' through initialKind prop on Catalog Page. diff --git a/.changeset/techdocs-coats-obey.md b/.changeset/techdocs-coats-obey.md new file mode 100644 index 0000000000..0c2c2b5ac2 --- /dev/null +++ b/.changeset/techdocs-coats-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix permalink scrolling for anchors where the id starts with a number. diff --git a/.changeset/techdocs-head-shoulders-knees-toes.md b/.changeset/techdocs-head-shoulders-knees-toes.md new file mode 100644 index 0000000000..f96b624897 --- /dev/null +++ b/.changeset/techdocs-head-shoulders-knees-toes.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Fixed a bug affecting those with cache enabled that would result in empty content being cached if the first attempt to load a static asset from storage were made via a `HEAD` request, rather than a `GET` request. diff --git a/.changeset/techdocs-pour-one-out-common.md b/.changeset/techdocs-pour-one-out-common.md deleted file mode 100644 index ed2940f62a..0000000000 --- a/.changeset/techdocs-pour-one-out-common.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -This package is no longer maintained. Use `@backstage/plugin-techdocs-node`, going forward. diff --git a/.changeset/techdocs-slow-teachers-sleep.md b/.changeset/techdocs-slow-teachers-sleep.md deleted file mode 100644 index 5e50d92d2b..0000000000 --- a/.changeset/techdocs-slow-teachers-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Long sidebars will no longer overflow the footer and will properly show a scrollbar when needed. diff --git a/.changeset/thick-comics-fold.md b/.changeset/thick-comics-fold.md new file mode 100644 index 0000000000..5faf2fd399 --- /dev/null +++ b/.changeset/thick-comics-fold.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-tech-insights': patch +'@backstage/plugin-tech-insights-backend': patch +--- + +Improved the Tech-Insights documentation: + +- `lifecycle` examples used `ttl` when it should be `timeToLive` +- Added list of included FactRetrievers +- Added full backend example using all included FactRetrievers +- Added boolean scorecard example image showing results of backend example diff --git a/.changeset/thin-lizards-battle.md b/.changeset/thin-lizards-battle.md new file mode 100644 index 0000000000..de3658f042 --- /dev/null +++ b/.changeset/thin-lizards-battle.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Update `usePermission` usage. diff --git a/.changeset/tidy-days-warn.md b/.changeset/tidy-days-warn.md new file mode 100644 index 0000000000..e84d563f01 --- /dev/null +++ b/.changeset/tidy-days-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +Gerrit integration: Added an optional configuration to set the Gitiles base url. diff --git a/.changeset/four-schools-shake.md b/.changeset/tidy-emus-stare.md similarity index 53% rename from .changeset/four-schools-shake.md rename to .changeset/tidy-emus-stare.md index 9fd81b5888..93f3dccde7 100644 --- a/.changeset/four-schools-shake.md +++ b/.changeset/tidy-emus-stare.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Pass in auth token to ancestry endpoint +add gitlab to AnnotateScmSlugEntityProcessor diff --git a/.changeset/tricky-months-sort.md b/.changeset/tricky-months-sort.md deleted file mode 100644 index 43956932f7..0000000000 --- a/.changeset/tricky-months-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': minor ---- - -**BREAKING**: Removed the deprecated `formatEntityRefTitle`, use `humanizeEntityRef` instead. diff --git a/.changeset/twelve-buses-nail.md b/.changeset/twelve-buses-nail.md new file mode 100644 index 0000000000..ddeb7f1413 --- /dev/null +++ b/.changeset/twelve-buses-nail.md @@ -0,0 +1,9 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: Bump the version range of `jest` from `^26.0.1` to `^27.5.1`. You can find the complete list of breaking changes [here](https://github.com/facebook/jest/releases/tag/v27.0.0). + +We strongly recommend to have completed the [package role migration](https://backstage.io/docs/tutorials/package-role-migration) before upgrading to this version, as the package roles are used to automatically determine the testing environment for each package. If you instead want to set an explicit test environment for each package, you can do so for example in the `"jest"` section in `package.json`. The default test environment for all packages is now `node`, which is also the new Jest default. + +Note that one of the breaking changes of Jest 27 is that the `jsdom` environment no longer includes `setImmediate` and `clearImmediate`, which means you might need to update some of your frontend packages. Another notable change is that `jest.useFakeTimers` now defaults to the `'modern'` implementation, which also mocks microtasks. diff --git a/.changeset/unlucky-schools-heal.md b/.changeset/unlucky-schools-heal.md new file mode 100644 index 0000000000..55daf4e65c --- /dev/null +++ b/.changeset/unlucky-schools-heal.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-permission-backend': patch +--- + +- Add more specific check for policies which return conditional decisions for non-resource permissions. +- Refine permission validation in authorize endpoint to differentiate between `BasicPermission` and `ResourcePermission` instances. diff --git a/.changeset/unlucky-snakes-turn.md b/.changeset/unlucky-snakes-turn.md new file mode 100644 index 0000000000..bcb31feb40 --- /dev/null +++ b/.changeset/unlucky-snakes-turn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +Fixed possible type error if jenkins response contains null values diff --git a/.changeset/warm-impalas-march.md b/.changeset/warm-impalas-march.md deleted file mode 100644 index 6db9900173..0000000000 --- a/.changeset/warm-impalas-march.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -The main repo has switched from `@vscode/sqlite3` to `better-sqlite3` as its preferred SQLite installation. This decision was triggered by a number of issues with the former that arose because it needs build infrastructure in place and functional in order to be installed. The main drawback of this is that the new package uses the database client ID `better-sqlite3` instead of the plain `sqlite3`. - -If you want to perform the same switch in your own repository, - -- Replace all of your `package.json` dependencies on `@vscode/sqlite3` with the latest version of `better-sqlite3` instead - - ```diff - "dependencies": { - - "@vscode/sqlite3": "^5.0.7", - + "better-sqlite3": "^7.5.0", - ``` - -- In your app-config and tests, wherever you supply `client: 'sqlite3'`, instead supply `client: 'better-sqlite3` - - ```diff - backend: - database: - - client: sqlite3 - + client: better-sqlite3 - ``` diff --git a/.changeset/warm-mangos-compete.md b/.changeset/warm-mangos-compete.md new file mode 100644 index 0000000000..3215b36d22 --- /dev/null +++ b/.changeset/warm-mangos-compete.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-permission-common': patch +--- + +- Add more specific `Permission` types. +- Add `createPermission` helper to infer the appropriate type for some permission input. +- Add `isResourcePermission` helper to refine Permissions to ResourcePermissions. diff --git a/.changeset/wicked-beds-return.md b/.changeset/wicked-beds-return.md new file mode 100644 index 0000000000..add952ba0a --- /dev/null +++ b/.changeset/wicked-beds-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Fix division by zero in currentToDeclaredResourceToPerc when pod resources weren't set diff --git a/.changeset/wicked-feet-clap.md b/.changeset/wicked-feet-clap.md new file mode 100644 index 0000000000..e27f641820 --- /dev/null +++ b/.changeset/wicked-feet-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Updated the visibility of database connection fields in config to be secret diff --git a/.changeset/wicked-tools-return.md b/.changeset/wicked-tools-return.md deleted file mode 100644 index e2183c0e73..0000000000 --- a/.changeset/wicked-tools-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Fix handling of bucket names with dots, in `AwsS3UrlReader` diff --git a/.changeset/witty-years-sniff.md b/.changeset/witty-years-sniff.md new file mode 100644 index 0000000000..f8d8acf5c1 --- /dev/null +++ b/.changeset/witty-years-sniff.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow': minor +--- + +Add stack overflow plugin diff --git a/.changeset/yellow-hats-remember.md b/.changeset/yellow-hats-remember.md new file mode 100644 index 0000000000..1a9064f94e --- /dev/null +++ b/.changeset/yellow-hats-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +Make `resourceRef` required in `JenkinsApi` to match usage. diff --git a/.changeset/yellow-shirts-study.md b/.changeset/yellow-shirts-study.md deleted file mode 100644 index 7e464f1c26..0000000000 --- a/.changeset/yellow-shirts-study.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-catalog-backend': minor ---- - -**BREAKING**: Removed the `target` property from `EntityRelation`. This field has been replaced by `targetRef`. -This means that `target: { name: 'team-a', kind: 'group', namespace: 'default' }` is now replaced with `targetRef: 'group:default/team-a'` in entity relations. - -The entities API endpoint still return the old `target` field for to ease transitions, however the future removal of this field will be considered non breaking. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 44c61f9348..cfe405b7d3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,8 @@ /plugins/scaffolder-backend-module-yeoman @backstage/reviewers @pawelmitka /plugins/search @backstage/reviewers @backstage/techdocs-core /plugins/search-* @backstage/reviewers @backstage/techdocs-core +/plugins/stack-overflow @backstage/reviewers @backstage/techdocs-core +/plugins/stack-overflow-backend @backstage/reviewers @backstage/techdocs-core /plugins/sonarqube @backstage/reviewers @backstage/sda-se-reviewers /plugins/techdocs @backstage/reviewers @backstage/techdocs-core /plugins/techdocs-* @backstage/reviewers @backstage/techdocs-core diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000000..47b10068cc --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,4 @@ +{ + labels: ['dependencies'], + extends: ['config:base', ':disableDependencyDashboard', ':gitSignOff'], +} diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index b8e432dd98..4070e5600a 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -27,6 +27,7 @@ Billett bitbucket Bitrise Blackbox +Brex bool boolean builtins @@ -59,6 +60,7 @@ configmaps configs const cookiecutter +Corti cron cronjobs crontab @@ -102,9 +104,11 @@ FireHydrant Firekube Firestore Fiverr +Francesco Gerrit gitbeaker github +Gitiles gitlab GitLab Gource @@ -133,6 +137,7 @@ inlinehilite interop JaCoCo JavaScript +jenkins Jira jq js @@ -151,6 +156,7 @@ Leasot lerna Lerna LocalStack +lockdown lockfile lunr Luxon @@ -165,6 +171,7 @@ memoized microservice microservices microsite +microtasks middleware minikube Minikube @@ -268,6 +275,7 @@ seb semlas semver Serverless +shoutout siloed Sinon Snyk diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml new file mode 100644 index 0000000000..efcbaf210b --- /dev/null +++ b/.github/workflows/automate_changeset_feedback.yml @@ -0,0 +1,95 @@ +name: Automate changeset feedback +on: + pull_request_target: + +permissions: + pull-requests: write + actions: none + checks: none + contents: none + deployments: none + issues: none + packages: none + pages: none + repository-projects: none + security-events: none + statuses: none + +jobs: + feedback: + # prevent running towards forks and version packages + if: github.repository == 'backstage/backstage' && github.event.pull_request.user.login != 'backstage-service' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # Fetch the commit that's merged into the base rather than the target ref + # This will let us diff only the contents of the PR, without fetching more history + ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' + + - name: fetch base + run: git fetch --depth 1 origin ${{ github.base_ref }} + + # We avoid using the in-source script since this workflow has elevated permissions that we don't want to expose + - name: Generate Feedback + id: generate-feedback + run: | + rm -f generate.js + wget -O generate.js https://raw.githubusercontent.com/backstage/backstage/master/scripts/generate-changeset-feedback.js 1>&2 + node generate.js FETCH_HEAD > feedback.txt + + - name: Post Feedback + uses: actions/github-script@v5 + env: + ISSUE_NUMBER: ${{ github.event.pull_request.number }} + with: + script: | + const owner = "backstage"; + const repo = "backstage"; + const marker = ""; + const feedback = require('fs').readFileSync('feedback.txt', 'utf8'); + const issue_number = Number(process.env.ISSUE_NUMBER); + const body = feedback.trim() ? feedback + marker : undefined + + const existingComments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number, + }); + + const existingComment = existingComments.find((c) => + c.user.login === "github-actions[bot]" && + c.body.includes(marker) + ); + + if (existingComment) { + if (body) { + if (existingComment.body !== body) { + console.log(`updating existing comment in #${issue_number}`); + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existingComment.id, + body, + }); + } else { + console.log(`skipped update of identical comment in #${issue_number}`); + } + } else { + console.log(`removing comment from #${issue_number}`); + await github.rest.issues.deleteComment({ + owner, + repo, + comment_id: existingComment.id, + body, + }); + } + } else if (body) { + console.log(`creating comment for #${issue_number}`); + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } diff --git a/.github/workflows/sync_issue-labels.yml b/.github/workflows/sync_issue-labels.yml new file mode 100644 index 0000000000..6a1c33348a --- /dev/null +++ b/.github/workflows/sync_issue-labels.yml @@ -0,0 +1,37 @@ +name: Sync Issue Labels +on: + issues: + types: [opened] +jobs: + label-issue: + runs-on: ubuntu-latest + steps: + - name: View context attributes + uses: actions/github-script@v6 + with: + script: | + const keywords = { + 'techdocs|tech-docs|tech docs': 'docs-like-code', + 'search': 'search', + 'catalog': 'catalog', + 'scaffolder': 'scaffolder', + }; + + const labels = Object.entries(keywords) + .map(([regexp, label]) => { + if (new RegExp(regexp, 'gi').test(context.payload.issue.title)) { + return label; + } + }) + .filter(Boolean); + + if(!labels.length) { + return; + } + + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels + }); diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml new file mode 100644 index 0000000000..780972bd0f --- /dev/null +++ b/.github/workflows/sync_renovate-changesets.yml @@ -0,0 +1,97 @@ +name: Sync Renovate changeset +on: + pull_request_target: + paths: + - '.github/workflows/sync_renovate-changesets.yml' + - '**/yarn.lock' + +jobs: + generate-changeset: + runs-on: ubuntu-latest + if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 2 + ref: ${{ github.head_ref }} + token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'Github changeset workflow' + - name: Generate changeset + uses: actions/github-script@v5 + with: + script: | + const { promises: fs } = require("fs"); + // Parses package.json files and returns the package names + async function getPackagesNames(files) { + const names = []; + for (const file of files) { + const data = JSON.parse(await fs.readFile(file, "utf8")); + if (!data.private) { + names.push(data.name); + } + } + return names; + } + + async function createChangeset(fileName, packageBumps, packages) { + let message = ""; + for (const [pkg, bump] of packageBumps) { + message = message + `Updated dependency \`${pkg}\` to \`${bump}\`.\n`; + } + + const pkgs = packages.map((pkg) => `'${pkg}': patch`).join("\n"); + const body = `---\n${pkgs}\n---\n\n${message}\n`; + await fs.writeFile(fileName, body); + } + + async function getBumps(files) { + const bumps = new Map(); + for (const file of files) { + const { stdout: changes } = await exec.getExecOutput("git", [ + "show", + file, + ]); + for (const change of changes.split("\n")) { + if (!change.startsWith("+ ")) { + continue; + } + const match = change.match(/"(.*?)"/g); + bumps.set(match[0].replace(/"/g, ""), match[1].replace(/"/g, "")); + } + } + return bumps; + } + + const branch = await exec.getExecOutput("git branch --show-current"); + if (!branch.stdout.startsWith("renovate/")) { + console.log("Not a renovate branch, skipping"); + return; + } + const diffOutput = await exec.getExecOutput("git diff --name-only HEAD~1"); + const diffFiles = diffOutput.stdout.split("\n"); + if (diffFiles.find((f) => f.startsWith(".changeset"))) { + console.log("Changeset already exists, skipping"); + return; + } + const files = diffFiles + .filter((file) => file !== "package.json") // skip root package.json + .filter((file) => file.includes("package.json")); + const packageNames = await getPackagesNames(files); + if (!packageNames.length) { + console.log("No package.json changes to published packages, skipping"); + return; + } + const { stdout: shortHash } = await exec.getExecOutput( + "git rev-parse --short HEAD" + ); + const fileName = `.changeset/renovate-${shortHash.trim()}.md`; + + const packageBumps = await getBumps(files); + await createChangeset(fileName, packageBumps, packageNames); + await exec.exec("git", ["add", fileName]); + await exec.exec("git commit -C HEAD --amend --no-edit"); + await exec.exec("git push --force"); diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 7caf880366..b9dfdc23eb 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -1,11 +1,12 @@ name: E2E Test Techdocs on: pull_request: - paths-ignore: - - '.changeset/**' - - 'contrib/**' - - 'docs/**' - - 'microsite/**' + paths: + - 'yarn.lock' + - '.github/workflows/verify_e2e-techdocs.yml' + - 'packages/techdocs-cli/**' + - 'packages/techdocs-cli-embedded-app/**' + - 'plugins/techdocs/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index bc6bc8a698..8dc621ebf7 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -5,6 +5,7 @@ name: E2E Test Windows on: pull_request: paths: + - 'yarn.lock' - '.github/workflows/verify_e2e-windows.yml' - 'packages/cli/**' - 'packages/e2e-test/**' diff --git a/ADOPTERS.md b/ADOPTERS.md index 35a0e4ccc3..ac9139a7ed 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -47,7 +47,7 @@ _If you're using Backstage in your organization, please try to add your company | [creditas.com](https://creditas.com/) | [@aureliosaraiva](https://github.com/aureliosaraiva) [@Creditas](https://github.com/creditas) | Centralization of all services, standards, documentation, etc. We started the deployment process. | | [Prisjakt](https://www.prisjakt.nu) / [PriceSpy](https://pricespy.co.uk) | [@kennylindahl](https://github.com/kennylindahl) | Internal developer portal - Documentation, scaffolding, software catalog, TechRadar, Gitlab org data integration | | [Powerspike](https://powerspike.tv/) | [@trelore](https://github.com/trelore) | Developer portal for documentation of core libraries and repositories. | -| [2U](https://2u.com) | [Andrew Thal](https://github.com/athal7) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | +| [2U](https://2u.com) | [@danielleEriksen](https://github.com/danielleEriksen), [@sbhatia](https://github.com/sbhatia) | Development team home-base, promoting service discoverability, resource dependencies, and tech radar | | [Taxfix](https://taxfix.de/) | [Sami Ur Rehman](https://github.com/samiurrehman92) | Developer's portal with software catalog at it's core. Hosts API Specs, Tech Docs, Tech Radar and some custom plugins. | | [Busuu](https://busuu.com/) | [Adam Tester](https://github.com/adamtester) | Developer portal with service catalog, API docs, Event docs, service templating, and cost insights. | | [Loadsmart](https://loadsmart.com/) | [Loadsmart](https://github.com/loadsmart) | Improve services visibility and operations for service owners and developers. | @@ -71,7 +71,7 @@ _If you're using Backstage in your organization, please try to add your company | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | | [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | -| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | +| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services through templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | | [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! | | [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. | | [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 | @@ -104,3 +104,9 @@ _If you're using Backstage in your organization, please try to add your company | [Stilingue](https://www.stilingue.com.br/) | [@stilingue-inteligencia-artificial](https://github.com/Stilingue-IA), [@bbviana](https://github.com/bbviana) | Developer portal, services catalog and centralization of metrics from Grafana, Sentry and GCP. Furthermore, centralization of documentation and infra details like DNS, Network services, SSL and so on. | | [TUI Group](https://www.tuigroup.com/) | [Simon Stamm](https://github.com/simonstamm), [Christian Rudolph](https://github.com/ChrisRu82) | Developer portal for all engineer to provide discoverability to all internal components, APIs, documentation and to scaffold templates with integrations to our internal tools extended by plugins from our community. | | [Alliander](https://www.alliander.com/) | [@leon-vg](https://github.com/leon-vg), [@gieljl](https://github.com/gieljl), [@niekteg](https://github.com/niekteg) | Developer portal - software catalog, technical documentation, software templates, tech radar and exploration of used tools/services | +| [VIA](https://www.via.com.br) | [@vagnerguedes](https://github.com/vagnerguedes) | Centralized Developer Experience portal - Software catalog and documentation platform, software templates, techdocs, scaffolding, self-service infrastructure | +| [Surevine](https://www.surevine.com/) | [@DJDANNY123](https://github.com/djdanny123) | Developer portal for software catalog, discovery and a view of the technologies we are using across the organisation, we are looking to explore how we can enrich our entities in Backstage by integrating a software bill of materials. | +| [Bonial International GmbH](https://www.bonial.com/) | [@pjungermann](https://github.com/pjungermann) | Centralized developer portal with software catalog, tech docs, templates, and more. | +| [Beez Innovation Labs Pvt. Ltd](https://www.beezlabs.com/) | [Karthikeyan Venkatesan](https://github.com/karthikeyan23) | Developer portal with software catalog, scaffolding, tech docs, templates, and infra. | +| [Agorapulse](https://www.agorapulse.com/) | [@jvdrean](https://github.com/jvdrean) | Developer portal with software catalog, documentation, monitoring, runbooks, tech radar and more. | + diff --git a/README.md b/README.md index 1078631926..a13f9d5247 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # [Backstage](https://backstage.io) [![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) +[![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-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) [![Discord](https://img.shields.io/discord/687207715902193673)](https://discord.gg/EBHEGzX) ![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) @@ -25,7 +25,7 @@ Out of the box, Backstage includes: - [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 -Backstage was created by Spotify but is now hosted by the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io) as a Sandbox level project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox). +Backstage was created by Spotify but is now hosted by the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io) as an Incubation level project. Read the announcement [here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). ## Project roadmap diff --git a/STYLE.md b/STYLE.md index d74616a88c..391b31ce50 100644 --- a/STYLE.md +++ b/STYLE.md @@ -100,6 +100,15 @@ This section describes guidelines for designing public APIs. It can also be appl } ``` +1. Prefer common prefixes over suffixes when naming constants. + + ```ts + // May be tempting to use `GITHUB_WIDGET_LABEL` instead. + const WIDGET_LABEL_GITHUB = 'github'; + const WIDGET_LABEL_GITLAB = 'gitlab'; + const WIDGET_LABEL_BITBUCKET = 'bitbucket'; + ``` + 1. When a type relates directly to other symbols, use the name of those as prefix for the type. ```ts diff --git a/contrib/catalog/ImmediateEntityProvider.ts b/contrib/catalog/ImmediateEntityProvider.ts new file mode 100644 index 0000000000..b66c62e330 --- /dev/null +++ b/contrib/catalog/ImmediateEntityProvider.ts @@ -0,0 +1,169 @@ +import { + ANNOTATION_LOCATION, + ANNOTATION_ORIGIN_LOCATION, + Entity, + entitySchemaValidator, +} from '@backstage/catalog-model'; +import { InputError } from '@backstage/errors'; +import { + DeferredEntity, + EntityProvider, + EntityProviderConnection, + parseEntityYaml, +} from '@backstage/plugin-catalog-backend'; +import bodyParser from 'body-parser'; +import express from 'express'; +import Router from 'express-promise-router'; +import lodash from 'lodash'; +import { Logger } from 'winston'; + +/** + * An entity provider attached to a router, that lets users perform direct + * manipulation of a set of entities using REST requests. + * + * @remarks + * + * Installation: + * + * Add it to the catalog builder in your + * `packages/backend/src/plugins/catalog.ts`. Note that it BOTH adds a provider + * and amends the catalog router: + * + * ``` + * const immediate = new ImmediateEntityProvider({ + * logger: env.logger, + * handleEntity: (deferred) => { + * // Optionally modify the incoming entity + * }, + * }); + * builder.addEntityProvider(immediate); + * + * // ... + * + * return router.use('/immediate', immediate.getRouter()); + * ``` + * + * API (assume a catalog prefix, e.g. `/api/catalog`): + * + * - `POST /immediate/entities`: Accepts a YAML document of entities, and + * inserts or updates the entities that match that document. Returns 201 OK on + * success. + * + * - `PUT /immediate/entities`: Accepts a YAML document of entities, and + * replaces the entire set of entities managed by the provider with those + * entities. Returns 201 OK on success. + */ +export class ImmediateEntityProvider implements EntityProvider { + private connection?: EntityProviderConnection; + private readonly entityValidator: (data: unknown) => Entity; + + constructor(private readonly options: ImmediateEntityProviderOptions) { + this.entityValidator = entitySchemaValidator(); + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ + getProviderName() { + return `ImmediateEntityProvider`; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ + async connect(connection: EntityProviderConnection) { + this.connection = connection; + } + + getRouter(): express.Router { + const router = Router(); + + router.use(bodyParser.raw({ type: '*/*' })); + + router.post('/entities', async (req, res) => { + if (!this.connection) { + throw new Error(`Service is not yet initialized`); + } + const deferred = await this.getRequestBodyEntities(req); + await this.connection.applyMutation({ + type: 'delta', + added: deferred, + removed: [], + }); + res.status(201).end(); + }); + + router.put('/entities', async (req, res) => { + if (!this.connection) { + throw new Error(`Service is not yet initialized`); + } + const deferred = await this.getRequestBodyEntities(req); + await this.connection.applyMutation({ + type: 'full', + entities: deferred, + }); + res.status(201).end(); + }); + + return router; + } + + private async getRequestBodyEntities( + req: express.Request, + ): Promise { + if (!Buffer.isBuffer(req.body) || !req.body.length) { + throw new InputError(`Missing request body`); + } + + const result: DeferredEntity[] = []; + + for await (const item of parseEntityYaml(req.body, { + type: 'immediate', + target: 'immediate', + })) { + if (item.type === 'entity') { + const deferred: DeferredEntity = { + entity: lodash.merge( + { + metadata: { + annotations: { + [ANNOTATION_ORIGIN_LOCATION]: 'immediate:immediate', + [ANNOTATION_LOCATION]: 'immediate:immediate', + }, + }, + }, + item.entity, + ), + locationKey: `immediate:`, + }; + + await this.options.handleEntity?.(req, deferred); + deferred.entity = this.entityValidator(deferred.entity); + + result.push(deferred); + } else if (item.type === 'error') { + throw new InputError(`Malformed entity YAML, ${item.error}`); + } else { + throw new InputError(`Internal error, failed to parse entity`); + } + } + + return result; + } +} + +/** + * Options for {@link ImmediateEntityProvider}. + */ +export interface ImmediateEntityProviderOptions { + /** + * The logger to use. + */ + logger: Logger; + + /** + * An optional function to perform adjustments to, or validate, an incoming + * entity before being stored. It is permitted to modify the deferred entity, + * but the request is static and has had its body consumed. + */ + handleEntity?: ( + request: express.Request, + deferred: DeferredEntity, + ) => void | Promise; +} diff --git a/contrib/catalog/LoadTestingEntityProvider.ts b/contrib/catalog/LoadTestingEntityProvider.ts new file mode 100644 index 0000000000..598099fce9 --- /dev/null +++ b/contrib/catalog/LoadTestingEntityProvider.ts @@ -0,0 +1,151 @@ +import { + ANNOTATION_LOCATION, + ANNOTATION_ORIGIN_LOCATION, + Entity, +} from '@backstage/catalog-model'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-backend'; +import { Logger } from 'winston'; + +/** + * An entity provider that can be used for load testing. Not for production use. + * + * @remarks + * + * Add it to the catalog builder in your + * `packages/backend/src/plugins/catalog.ts` doing some type of work, for + * example: + * + * ``` + * builder.addEntityProvider( + * new LoadTestingEntityProvider({ + * logger: env.logger, + * onStartup: async ({ connection, generateRandomEntities }) => { + * await connection.applyMutation({ + * type: 'full', + * entities: generateRandomEntities(100000).map(e => ({ + * entity: e, + * locationKey: 'l', + * })), + * }); + * }, + * }), + * ); + * ``` + * + * The provider will run the test, outputting some timing info onto the console. + * It will also clean up everything you added through the given connection. + */ +export class LoadTestingEntityProvider implements EntityProvider { + constructor(private readonly options: LoadTestingEntityProviderOptions) {} + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ + getProviderName() { + return `LoadTestingEntityProvider`; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ + async connect(connection: EntityProviderConnection) { + const delayStartup = this.options.delayStartup ?? 10_000; + const logger = this.options.logger.child({ + class: LoadTestingEntityProvider.prototype.constructor.name, + }); + + if (delayStartup) { + logger.info( + `[LOAD-TEST] Starting in ${(delayStartup / 1000).toFixed(1)}s`, + ); + } + + setTimeout(async () => { + const timer = () => { + const startedOn = Date.now(); + return () => `${((Date.now() - startedOn) / 1000).toFixed(1)}s`; + }; + + const overallTimer = timer(); + logger.info(`[LOAD-TEST] Started`); + + const runTimer = timer(); + try { + await this.options.onStartup({ + connection, + logger, + generateRandomEntities, + }); + logger.info(`[LOAD-TEST] Finished in ${runTimer()}`); + } catch (error) { + logger.error(`[LOAD-TEST] Failed after ${runTimer()}`, error); + } + + const cleanupTimer = timer(); + logger.info(`[LOAD-TEST] Running cleanup`); + await connection.applyMutation({ + type: 'full', + entities: [], + }); + + logger.info(`[LOAD-TEST] ***************************************`); + logger.info(`[LOAD-TEST] Test run time: ${runTimer()}`); + logger.info(`[LOAD-TEST] Cleanup run time: ${cleanupTimer()}`); + logger.info(`[LOAD-TEST] Total time: ${overallTimer()}`); + logger.info(`[LOAD-TEST] ***************************************`); + }, delayStartup); + } +} + +function generateRandomEntities(count: number): Entity[] { + const result: Entity[] = []; + + for (let i = 1; i <= count; ++i) { + result.push({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [ANNOTATION_ORIGIN_LOCATION]: 'url:http://example.com/load-testing', + [ANNOTATION_LOCATION]: 'url:http://example.com/load-testing', + }, + namespace: 'load-test', + name: `load-test-${i}`, + }, + spec: { + type: 'load-test-data', + owner: 'me', + lifecycle: 'experimental', + }, + }); + } + + return result; +} + +/** + * Options for LoadTestingEntityProvider. + */ +export interface LoadTestingEntityProviderOptions { + /** + * The logger to use. + */ + logger: Logger; + + /** + * The number of milliseconds of delay to wait before starting the test. This + * gives the backend a chance to settle into a stable state before the test + * starts. + * + * @defaultValue 5000 + */ + delayStartup?: number; + + /** + * What work to do on startup. + */ + onStartup: (context: { + connection: EntityProviderConnection; + logger: Logger; + generateRandomEntities(count: number): Entity[]; + }) => Promise; +} diff --git a/contrib/catalog/README.md b/contrib/catalog/README.md new file mode 100644 index 0000000000..e02760cdb0 --- /dev/null +++ b/contrib/catalog/README.md @@ -0,0 +1,27 @@ +# Catalog Contrib + +This directory contains various community contributions related to [the Backstage catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview). + +There is no guarantee of correctness or fitness of purpose of these +contributions, but we hope that they are helpful to someone! + +Installation instructions are generally in the doc comment on top of each class. + +## ImmediateEntityProvider + +Sometimes we get requests for the ability to POST/PUT entities directly to the +catalog, instead of its regular mode of operation where it pulls data from +authoritative sources itself. + +The core product does not intend to support this use case, since it comes with a +number of caveats. However, this entity provider demonstrates how to build a +very basic version of such functionality yourself. It does not offer any +protection from misuse, but can serve as a good starting point to build out such +a provider yourself, fit for your particular needs. + +## LoadTestingEntityProvider + +This is a trivial little test bed entity provider that lets you make huge batch +operations and get some timings back. It also tries to clean up after itself +when it's done. This can be useful if you are working on optimizing the catalog +itself, or on processors or similar that you add to it. diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml index cac9b61553..7e008c5d9d 100644 --- a/contrib/chart/backstage/templates/ingress.yaml +++ b/contrib/chart/backstage/templates/ingress.yaml @@ -74,7 +74,7 @@ spec: backend: {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} service: - name: {{ include "frontend.serviceName" . }} + name: {{ include "backend.serviceName" . }} port: number: 80 {{- else -}} diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index a95b276365..52f7888fb8 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -109,6 +109,10 @@ import { createRouter, createAwsAlbProvider, } from '@backstage/plugin-auth-backend'; +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; @@ -154,16 +158,20 @@ export default async function createPlugin({ const [id] = email?.split('@') ?? ''; // Fetch from an external system that returns entity claims like: // ['user:default/breanna.davison', ...] - const ent = [`user:default/${id}`]; + const userEntityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: id, + }); // Resolve group membership from the Backstage catalog const fullEnt = await ctx.catalogIdentityClient.resolveCatalogMembership({ - entityRefs: [id].concat(ent), + entityRefs: [id].concat([userEntityRef]), logger: ctx.logger, }); const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent: fullEnt }, + claims: { sub: userEntityRef, ent: fullEnt }, }); return { id, token }; }, diff --git a/cypress/yarn.lock b/cypress/yarn.lock index 96bc765d0d..f301e87d3a 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -907,9 +907,9 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.4: version "0.5.5" diff --git a/docs/architecture-decisions/adr006-avoid-react-fc.md b/docs/architecture-decisions/adr006-avoid-react-fc.md index ea43cb215d..6a7c431655 100644 --- a/docs/architecture-decisions/adr006-avoid-react-fc.md +++ b/docs/architecture-decisions/adr006-avoid-react-fc.md @@ -45,7 +45,7 @@ const GoodComponent = ({ text, children }: GoodProps) => ( ); -/* Or as a shorthand, if no specifc child type is required */ +/* Or as a shorthand, if no specific child type is required */ type GoodProps = PropsWithChildren<{ text: string }>; const GoodComponent = ({ text, children }: GoodProps) => (
diff --git a/docs/assets/search/architecture.drawio.svg b/docs/assets/search/architecture.drawio.svg index 1acad6d74b..d7a8177d1c 100644 --- a/docs/assets/search/architecture.drawio.svg +++ b/docs/assets/search/architecture.drawio.svg @@ -525,7 +525,7 @@
- Compile and Execut... + Compile and Execute... diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index c336da38dd..fea82894c8 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -51,11 +51,12 @@ export default async function createPlugin( // Let's use the username in the email ID as the user's default // unique identifier inside Backstage. const [id] = email.split('@'); - ent.push(stringifyEntityRef({ + const userEntityRef = stringifyEntityRef({ kind: 'User', namespace: DEFAULT_NAMESPACE, name: id, - })); + }); + ent.push(userEntityRef); // 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 @@ -68,7 +69,7 @@ export default async function createPlugin( // Issue the token containing the entity claims const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent }, + claims: { sub: userEntityRef, ent }, }); return { id, token }; }, @@ -130,6 +131,8 @@ can do this using the `CatalogIdentityClient` provided as context to Sign-In resolvers: ```ts +import { DEFAULT_NAMESPACE, stringifyEntityRef } from '@backstage/catalog-model'; + export default async function createPlugin( env: PluginEnvironment, ): Promise { @@ -140,6 +143,11 @@ export default async function createPlugin( signIn: { resolver: async ({ profile: { email } }, ctx) => { const [id] = email?.split('@') ?? ''; + const userEntityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: id, + }); // Fetch from an external system that returns entity claims like: // ['user:default/breanna.davison', ...] const ent = await externalSystemClient.getUsernames(email); @@ -150,7 +158,7 @@ export default async function createPlugin( logger: ctx.logger, }); const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id, ent: fullEnt }, + claims: { sub: userEntityRef, ent: fullEnt }, }); return { id, token }; }, diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 702e0c7736..612f4903d9 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -255,7 +255,7 @@ CPU/Memory for pods returned by the API server. Defaults to `false`. ##### `exposeDashboard` -This determines wether the `dashboardApp` and `dashboardParameters` should be +This determines whether the `dashboardApp` and `dashboardParameters` should be automatically configured in order to expose the GKE dashboard from the Kubernetes plugin. diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 80c9458aee..1b5535a464 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -41,16 +41,16 @@ export const CustomCatalogPage = ({ All your software catalog entities - - + + - - + + - - + + @@ -151,18 +151,18 @@ export const CustomCatalogPage = ({ return ( ... - - + + + + ... }; diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index 340daae7d3..92ce153da2 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -471,6 +471,7 @@ We also provide a high-level example of what a catalog process for a custom entity might look like: ```ts +import { CatalogProcessor, processingResult } from '@backstage/catalog-backend'; import { entityKindSchemaValidator } from '@backstage/catalog-model'; export class FoobarEntitiesProcessor implements CatalogProcessor { @@ -506,7 +507,7 @@ export class FoobarEntitiesProcessor implements CatalogProcessor { // Here we can modify the entity or emit results related to the entity // Typically you will want to emit any relations associated with the entity here - emit(results.relation({ ... })) + emit(processingResult.relation({ ... })) } return entity; diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index f5296f38e1..0326292e41 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -1,6 +1,6 @@ --- id: software-catalog-overview -title: Backstage Software Catalog (alpha) +title: Backstage Software Catalog sidebar_label: Overview # prettier-ignore description: The Backstage Software Catalog diff --git a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md index 1f771b4eaf..a2277e096c 100644 --- a/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md +++ b/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3.md @@ -180,6 +180,35 @@ These should be moved to `links` under the `output` object instead. ``` +## Watch out for `dash-case` + +The nunjucks compiler can run into issues if the `id` fields in your template steps use dash characters, since these IDs translate directly to JavaScript object properties when accessed as output. One possible migration path is to use `camelCase` for your action IDs. + +```diff + steps: +- id: my-custom-action +- ... +- +- id: publish-pull-request +- input: +- repoUrl: {{ steps.my-custom-action.output.repoUrl }} # Will not recognize 'my-custom-action' as a JS property since it contains dashes! + + steps: ++ id: myCustomAction ++ ... ++ ++ id: publishPullRequest ++ input: ++ repoUrl: ${{ steps.myCustomAction.output.repoUrl }} +``` + +Alternatively, it's possible to keep the `dash-case` syntax and use brackets for property access as you would in JavaScript: + +```yaml +input: + repoUrl: ${{ steps['my-custom-action'].output.repoUrl }} +``` + ### Summary Of course, we're always available on [discord](https://discord.gg/MUpMjP2) if diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index d5d4afbff2..3ff08e6e73 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -96,7 +96,7 @@ export const MyCustomFieldExtension = plugin.provide( ```tsx // packages/app/src/scaffolder/MyCustomExtension/index.ts -export { MyCustomFieldExtension } from './extension'; +export { MyCustomFieldExtension } from './extensions'; ``` Once all these files are in place, you then need to provide your custom @@ -119,6 +119,8 @@ Should look something like this instead: ```tsx import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension'; +import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder'; + const routes = ( ... diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index e7ed18cf8e..0163877a8b 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -251,7 +251,7 @@ use `ui:widget: password` or set some properties of `ui:backstage`: type: string ui:backstage: review: - show: false # wont print any info about 'hidden' property on Review Step + show: false # won't print any info about 'hidden' property on Review Step ``` ### Remove sections or fields based on feature flags @@ -364,7 +364,7 @@ spec: secretsKey: USER_OAUTH_TOKEN additionalScopes: github: - - workflow:write + - workflow allowedHosts: - github.com ... diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index d5aad1c541..4417e62c67 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -74,32 +74,26 @@ providers are used. storage solutions, source control systems). - [Instructions for upgrading from Alpha to Beta](how-to-guides.md#how-to-migrate-from-techdocs-alpha-to-beta) +**v1** ✅ + +TechDocs packages: + +- '@backstage/plugin-techdocs' +- '@backstage/plugin-techdocs-backend' +- '@backstage/plugin-techdocs-node' +- '@techdocs/cli' + +was promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + ### **Future work 🔮** -**General Availability (GA) release** - -[Milestone](https://github.com/backstage/backstage/milestone/30) - -- Bugs are rare, TechDocs APIs are stable and scales easily in large - organizations. - Better integration with [Scaffolder V2](https://github.com/backstage/backstage/issues/2771) (e.g. easy to choose and plug documentation template with Software Templates). +- Static site generator agnostic - Possible to configure several aspects about TechDocs (e.g. URL, homepage, theme). - -**Implement Feedback loop** - -[Milestone](https://github.com/backstage/backstage/milestone/31) - -- A feedback loop between documentation reader and writer using TechDocs -- The `+` in `docs-like-code+` experience - -**TechDocs widget framework** - -Platformize TechDocs with a widget framework so that it is easy for TechDocs -contributors to add pieces of functionality and for users to choose which -functionalities they want to adopt. As a pre-requisite, the re-architecture of -TechDocs frontend [RFC](https://github.com/backstage/backstage/issues/3998) -needs to be addressed. +- [TechDocs Addon Framework](https://github.com/backstage/backstage/issues/9636) ## Tech stack diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 23af83d09b..d781f71917 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -131,15 +131,3 @@ layer for users to determine whether they have the permission to view a particular docs site. There are a handful of features which are extremely hard to develop without a tightly integrated backend in place. Hence, support for `techdocs` without `techdocs-backend` is limited and challenging to develop. - -# Future work - -_Ideas here are far fetched and not in the project's milestone for near future -(~6 months)._ - -We currently depend on MkDocs to parse doc sites written in Markdown. And we -store the generated static assets and re-use it later to render in Backstage. A -better (futuristic) approach will be to directly parse whatever type of source -files you have in your docs repository and directly render in Backstage in -real-time. You can read more in this -[RFC - Simplifying TechDocs Frontend Architecture](https://github.com/backstage/backstage/issues/3998). diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index 3edc55574f..a3f4723173 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -160,15 +160,4 @@ techdocs: # object was not found (e.g. when the cache sercice is unavailable). The # default value is 1000 readTimeout: 500 - - # (Optional and Legacy) TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc. - # You don't have to specify this anymore. - - requestUrl: http://localhost:7007/api/techdocs - - # (Optional and Legacy) Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware - # to serve files from either a local directory or an External storage provider. - # You don't have to specify this anymore. - - storageUrl: http://localhost:7007/api/techdocs/static/docs ``` diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index f16c62e28a..64f78c0997 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -22,6 +22,12 @@ yarn backstage-cli versions:bump The reason for bumping all `@backstage` packages at once is to maintain the dependencies that they have between each other. +By default the bump command will upgrade `@backstage` packages to the latest `main` release line which is released monthly. For those in a hurry that want to track the `next` release line which releases weekly can do so using the `--release next` option. + +```bash +yarn backstage-cli versions:bump --release next +``` + If you are using other plugins you can pass in the `--pattern` option to update more than just the `@backstage/*` dependencies. diff --git a/docs/integrations/aws-s3/locations.md b/docs/integrations/aws-s3/locations.md index b0c6829e2c..9bac28f906 100644 --- a/docs/integrations/aws-s3/locations.md +++ b/docs/integrations/aws-s3/locations.md @@ -37,6 +37,7 @@ integrations: - accessKeyId: ${AWS_ACCESS_KEY_ID} secretAccessKey: ${AWS_SECRET_ACCESS_KEY} roleArn: 'arn:aws:iam::xxxxxxxxxxxx:role/example-role' + externalId: 'some-id' # optional ``` Configuration allows specifying custom S3 endpoint, along with diff --git a/docs/integrations/gitlab/locations.md b/docs/integrations/gitlab/locations.md index 965d183a9d..a71fc9bcba 100644 --- a/docs/integrations/gitlab/locations.md +++ b/docs/integrations/gitlab/locations.md @@ -28,7 +28,7 @@ can list the GitLab providers you want to fetch data from. Each entry is a structure with up to four elements: - `host`: The host of the GitLab instance, e.g. `gitlab.company.com`. -- `token` (optional): An authentication token as expected by GitLab. If this is +- `token` (optional): An authentication token as expected by GitLab. The token need at least `api`, `read_repository` and `write_repository` scopes. If this is not supplied, anonymous access will be used. - `apiBaseUrl` (optional): The URL of the GitLab API. For self-hosted installations, it is commonly at `https:///api/v4`. For gitlab.com, this diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index be07a67f22..9245cea7ff 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -66,7 +66,7 @@ These steps are generally kept isolated form each other, with each step focusing on its specific task. For example, we do not do linting or type checking together with the building or bundling. This is so that we can provide more flexibility and avoid duplicate work, improving performance. It is strongly -recommended that as a part of developing withing Backstage you use a code editor +recommended that as a part of developing within Backstage you use a code editor or IDE that has support for formatting, linting, and type checking. Let's dive into a detailed look at each of these steps and how they are @@ -521,7 +521,9 @@ The built-in configuration brings a couple of benefits and features. The most important one being a baseline transformer and module configuration that enables support for the listed [loaders](#loaders) within tests. It will also automatically detect and use `src/setupTests.ts` if it exists, and provides a -coverage configuration that works well with our selected transpilers. +coverage configuration that works well with our selected transpilers. The configuration +will also detect the appropriate Jest environment for each package role, running +`web-libraries` with the `"jsdom"` environment, `node-libraries` with `"node"`, and so on. The configuration also takes a project-wide approach, with the expectation most if not all packages within a monorepo will use the same base configuration. This diff --git a/docs/overview/background.md b/docs/overview/background.md index 00fb5107ea..b74a2e0272 100644 --- a/docs/overview/background.md +++ b/docs/overview/background.md @@ -32,7 +32,7 @@ maintain, and find the documentation for all that software in Backstage. One place for everything. Accessible to everyone. Backstage was originally built by Spotify and then donated to the CNCF. -Backstage is currently in the Sandbox phase. Read the announcement -[here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox). +Backstage is currently in the Incubation phase. Read the announcement +[here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). diff --git a/docs/overview/support.md b/docs/overview/support.md index 49075366af..5122a1c9ff 100644 --- a/docs/overview/support.md +++ b/docs/overview/support.md @@ -18,3 +18,45 @@ description: Support and Community Details and Links our email newsletter. - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star! ❤️ + +## Community Hub + +Check out the Backstage.io [Backstage Community Hub](https://backstage.io/community) for the Community Sessions, recordings, and community resources. + +### Adding a recording to the meetup page + +To add a new recording to the [meetup page](https://backstage.io/on-demand) +create a file in +[`microsite/data/on-demand`](https://github.com/backstage/backstage/tree/master/microsite/data/on-demand) +with your recording's information. Filenames should be in the format `yyyymmdd-xx.yaml`. The page will sort using the filename. Example file content: + +```yaml +--- +title: # name of the meetup +date: February 23, 2022 # date, format: Month day, year. +category: Meetup # Can be Event, Meetup, Webinar +description: # description, summary +youtubeUrl: # Url to youtube video +youtubeImgUrl: # Url to the preview image, for Youtube this is the format: https://i1.ytimg.com/vi//mqdefault.jpg +``` + +### Adding an upcoming meetup to the meetup page + +To add an upcoming meetup to the [meetup page](https://backstage.io/on-demand) +create a file in +[`microsite/data/on-demand`](https://github.com/backstage/backstage/tree/master/microsite/data/on-demand) +with your meetup's information. Filenames should be in the format `yyyymmdd-xx.yaml`, the page will sort using the filename. Example file content: + +```yaml +--- +title: # name of the meetup +date: February 23, 2022 # date, format: Month day, year. +category: Upcoming # Should be "Upcoming" +description: # description, summary +youtubeUrl: # Url to youtube video +youtubeImgUrl: # Url to the preview image, for Youtube this is the format: https://i1.ytimg.com/vi//mqdefault.jpg +rsvpUrl: # Link to registration, calendar item, etc +eventUrl: # Link to event landing page +``` + +After the meetup is done, and the recording is ready you can change it to a meetup recording. diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index 1197c37cc7..38b67e81de 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -37,8 +37,8 @@ Out of the box, Backstage includes: ## Backstage and the CNCF -Backstage is a CNCF Sandbox project. Read the announcement -[here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox). +Backstage is a CNCF Incubation project after graduating from Sandbox. Read the announcement +[here](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation). diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md new file mode 100644 index 0000000000..44a4f48213 --- /dev/null +++ b/docs/releases/v1.0.0.md @@ -0,0 +1,92 @@ +--- +id: v1.0.0 +title: v1.0.0 +description: Backstage Release v1.0.0 +--- + +These are the release notes for the v1.0.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### Packages Reaching 1.0 + +Several packages have reached version 1.0, you can read more about the effect of this in our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +#### Backstage Core + +- [`@backstage/app-defaults`](https://www.npmjs.com/package/@backstage/app-defaults) +- [`@backstage/config-loader`](https://www.npmjs.com/package/@backstage/config-loader) +- [`@backstage/config`](https://www.npmjs.com/package/@backstage/config) +- [`@backstage/core-app-api`](https://www.npmjs.com/package/@backstage/core-app-api) +- [`@backstage/core-plugin-api`](https://www.npmjs.com/package/@backstage/core-plugin-api) +- [`@backstage/dev-utils`](https://www.npmjs.com/package/@backstage/dev-utils) +- [`@backstage/errors`](https://www.npmjs.com/package/@backstage/errors) +- [`@backstage/integration-react`](https://www.npmjs.com/package/@backstage/integration-react) +- [`@backstage/integration`](https://www.npmjs.com/package/@backstage/integration) +- [`@backstage/test-utils`](https://www.npmjs.com/package/@backstage/test-utils) +- [`@backstage/types`](https://www.npmjs.com/package/@backstage/types) +- [`@backstage/version-bridge`](https://www.npmjs.com/package/@backstage/version-bridge) + +#### Software Catalog + +- [`@backstage/catalog-client`](https://www.npmjs.com/package/@backstage/catalog-client) +- [`@backstage/catalog-model`](https://www.npmjs.com/package/@backstage/catalog-model) +- [`@backstage/plugin-catalog-backend`](https://www.npmjs.com/package/@backstage/plugin-catalog-backend) +- [`@backstage/plugin-catalog-common`](https://www.npmjs.com/package/@backstage/plugin-catalog-common) +- [`@backstage/plugin-catalog-react`](https://www.npmjs.com/package/@backstage/plugin-catalog-react) +- [`@backstage/plugin-catalog`](https://www.npmjs.com/package/@backstage/plugin-catalog) + +#### Software Templates + +- [`@backstage/plugin-scaffolder-backend`](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend) +- [`@backstage/plugin-scaffolder-common`](https://www.npmjs.com/package/@backstage/plugin-scaffolder-common) +- [`@backstage/plugin-scaffolder`](https://www.npmjs.com/package/@backstage/plugin-scaffolder) + +#### TechDocs + +- [`@backstage/plugin-techdocs-backend`](https://www.npmjs.com/package/@backstage/plugin-techdocs-backend) +- [`@backstage/plugin-techdocs-node`](https://www.npmjs.com/package/@backstage/plugin-techdocs-node) +- [`@backstage/plugin-techdocs`](https://www.npmjs.com/package/@backstage/plugin-techdocs) +- [`@techdocs/cli`](https://www.npmjs.com/package/@techdocs/cli) + +### Switch from `@vscode/sqlite3` to `better-sqlite3` + +The default SQLite3 driver has been switched to [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3), as many users were running into issues with installation of `@vscode/sqlite3`. + +### New plugin: Google Calendar + +The new `@backstage/plugin-gcalendar` plugin provides a home page widget that shows your Google Calendar events. Contributed by [@alexrybch](https://github.com/alexrybch) ([#9719](https://github.com/backstage/backstage/pull/9719)) + +### Scaffolder Template Preview + +The scaffolder page now provides a editor and preview page for templates, where you can see a live preview of the template form. The preview page can be found under the `/preview` sub route, typically at `/create/preview`. Contributed by [@kuangp](https://github.com/kuangp) ([#9841](https://github.com/backstage/backstage/pull/9841)) + +### Package renames: + +The following packages have been renamed, be sure to update your usage: + +- `@backstage/search-common` -> `@backstage/plugin-search-common` +- `@backstage/techdocs-common` -> `@backstage/plugin-techdocs-node` + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/bFESRKVt) for discussions and support +- [Changelog](https://github.com/backstage/backstage/releases/tag/v1.0.0) for this release +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if you want to be informed about what is happening in the world of Backstage. diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.md b/microsite/blog/2020-05-22-phase-2-service-catalog.md index 2df8ce84f3..5ab994a343 100644 --- a/microsite/blog/2020-05-22-phase-2-service-catalog.md +++ b/microsite/blog/2020-05-22-phase-2-service-catalog.md @@ -1,5 +1,6 @@ --- -title: 'Starting Phase 2: The Service Catalog' +# prettier-ignore +title: "Starting Phase 2: The Service Catalog" author: Stefan Ålund, Spotify authorURL: http://twitter.com/stalund authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg diff --git a/microsite/blog/2020-09-08-announcing-tech-docs.md b/microsite/blog/2020-09-08-announcing-tech-docs.md index f9d7359526..578f03a54f 100644 --- a/microsite/blog/2020-09-08-announcing-tech-docs.md +++ b/microsite/blog/2020-09-08-announcing-tech-docs.md @@ -1,5 +1,6 @@ --- -title: 'Announcing TechDocs: Spotify’s docs-like-code plugin for Backstage' +# prettier-ignore +title: "Announcing TechDocs: Spotify’s docs-like-code plugin for Backstage" author: Gary Niemen, Spotify authorURL: https://github.com/garyniemen --- diff --git a/microsite/blog/2020-10-22-cost-insights-plugin.md b/microsite/blog/2020-10-22-cost-insights-plugin.md index 139e9a7ce9..b3b615c20b 100644 --- a/microsite/blog/2020-10-22-cost-insights-plugin.md +++ b/microsite/blog/2020-10-22-cost-insights-plugin.md @@ -1,5 +1,6 @@ --- -title: 'New Cost Insights plugin: The engineer’s solution to taming cloud costs' +# prettier-ignore +title: "New Cost Insights plugin: The engineer’s solution to taming cloud costs" author: Janisa Anandamohan, Spotify authorURL: https://twitter.com/janisa_a --- diff --git a/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.md b/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.md index 9b8c4637bd..8981f276be 100644 --- a/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.md +++ b/microsite/blog/2021-01-12-new-backstage-feature-kubernetes-for-service-owners.md @@ -1,5 +1,6 @@ --- -title: 'New Backstage feature: Kubernetes for Service owners' +# prettier-ignore +title: "New Backstage feature: Kubernetes for Service owners" author: Matthew Clarke, Spotify authorURL: https://github.com/mclarke47 --- diff --git a/microsite/blog/2021-06-24-announcing-backstage-search-platform.md b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md index 6e6c9b7819..6914077522 100644 --- a/microsite/blog/2021-06-24-announcing-backstage-search-platform.md +++ b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md @@ -1,5 +1,6 @@ --- -title: 'Announcing the Backstage Search platform: a customizable search tool built just for you' +# prettier-ignore +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 --- diff --git a/microsite/blog/2022-03-16-backstage-turns-two.md b/microsite/blog/2022-03-16-backstage-turns-two.md new file mode 100644 index 0000000000..172fb7b971 --- /dev/null +++ b/microsite/blog/2022-03-16-backstage-turns-two.md @@ -0,0 +1,68 @@ +--- +title: Backstage Turns Two! +author: Lee Mills, Spotify +authorURL: https://github.com/leemills83 +authorImageURL: https://avatars1.githubusercontent.com/u/1236238?s=460&v=4 +--- + +![A cake celebrating Backstage’s second birthday](assets/22-03-16/backstage-2nd-birthday.gif) + +**TLDR:** Backstage has turned two. Of course, this is software, so we expect things to move fast. But when you have a passionate, worldwide community building together, we’ve now seen firsthand just how fast that can be. We’re marking our second year as a community by becoming a CNCF incubating project, reaching 100 public adopters, releasing version 1.0 of the core app, and other very nice birthday gifts. Rather than a toddler finding their feet, Backstage today is beginning to look awfully grown-up for a two-year-old. + + + +## Out of the Sandbox and into Incubation + +Two years ago, a small team at Spotify launched Backstage into open source during our internal Hack Week. We knew we had something special on our hands and that Backstage could potentially be the most ambitious open source project in Spotify’s history (you can listen to me wax philosophical on this topic more in [Episode 2 of the new NerdOut@Spotify podcast](https://open.spotify.com/episode/332yTwGiILGKTS7dsHCj2P?si=pX9xTzB2SgmsROdlLLr24A).) + +When Spotify donated the project to the CNCF, we saw it as the first promising step toward making Backstage an industry standard. As the home to Kubernetes, Envoy, and so many other game-changing open technologies, the CNCF and its community has had a wide-ranging impact on the daily lives of developers everywhere. And that’s what we hoped for Backstage, even back then. + +But we couldn't have envisioned where we’d be two years later — part of an ever-growing community of people that feel as passionate as we do about unlocking better developer experience for every engineering organization. + +Yesterday, the CNCF announced that its [Technical Oversight Committee](https://github.com/cncf/toc) (TOC) voted to accept Backstage as a [CNCF incubating project](https://www.cncf.io/blog/2022/03/15/backstage-project-joins-the-cncf-incubator/). The move from the CNCF Sandbox to Incubation is an important step forward for the project’s technical governance. + +It’s also recognition for all the work the Backstage open source community has done to advance and grow this project since we launched the repo on GitHub just a few weeks after that Hack Week. We still have a long way to go to becoming the standard, but moving out of the CNCF Sandbox and beginning the Incubation phase is further validation of those aspirations. + +## What do retailers, gaming studios, banks, airlines, streamers, startups, and enterprise software makers all have in common? + +Developers, developers, developers — that’s what all companies have in common. Developers dealing with [more complexity](https://youtu.be/85TQEpNCaU0) than they’ve ever had to deal with before. Developers who want to be able to go from idea to bug fix or feature faster. + +Two years in, the Backstage ecosystem has grown by leaps and bounds. More than 5,000 contributors have joined the project and are submitting bugs, adding new features, and building valuable plugins that benefit the greater community. From [Expedia Group](https://backstage.spotify.com/blog/measuring-backstage-proof-of-value-at-expedia/) (adopter No.18) to Unity (adopter No.67), Backstage now has [100 publicly listed adopters](https://github.com/backstage/backstage/blob/master/ADOPTERS.md) across a variety of industries, including retail, gaming, financial services, manufacturing, and government. + +We have companies like [Roadie](http://roadie.io) providing hosting solutions and ThoughtWorks [providing technology and consulting services](https://www.thoughtworks.com/en-us/about-us/news/2021/thoughtworks-is-collaborating-with-spotify-to-deliver-better-dev) to teams looking to stand up Backstage in their own orgs. We’re starting to see more and more job listings for Backstage developers which tells us that companies of all shapes and sizes are going all-in on Backstage and developer effectiveness. + +Also, I’ll pause now to give a special shoutout to our early adopters. They stuck with us through all the bumps and breaking changes. But because of their early commitment, we now see instances of Backstage up and running in so many different kinds of engineering organizations. From [Zalando](https://youtu.be/6sg5uMCLxTA) to [American Airlines](https://backstage.spotify.com/blog/adopter-spotlight/american-airlines-runway/) and [Brex](https://backstage.spotify.com/blog/community-session/11-brex-backstage-upgrade-helper/), the growing pains of our earliest adopters helped pave the way for smoother adoptions for all who follow them. + +A few more numbers that make us proud: + +- 2,000+ project forks +- 20,000+ commits +- 10,000+ PRs & issues +- 15,000+ stars on GitHub +- 60+ open source plugins in the [Backstage Plugin Marketplace](https://backstage.io/plugins) + +## A global community of friendly, helpful contributors + +We open sourced Backstage in March 2020 and then almost immediately went into lockdown. When you think about this project in the context of the pandemic, it's really remarkable how much this community has been able to achieve. + +The Backstage community is truly global with adopters and contributors from almost every continent (sorry, Antarctica!) coming together to share what they’ve worked on and demo cool stuff in our monthly Community Sessions. The repo and the Discord server truly never sleep as a growing group of dedicated Backstage experts outside Spotify support one another and offer help 24/7. + +We asked a few contributors and maintainers to share their thoughts around this two-year milestone. + + + +Warms the heart, doesn’t it? Thanks again to all our contributors for making this possible. Seriously. We’re proud of what the community has accomplished and how we’ve accomplished it. All along, it’s your enthusiasm and warm/collaborative/positive vibes that have carried us and this project through. + +## Version 1.0 and beyond + +Tomorrow, we’ll be releasing version 1.0 of the Backstage core framework, including Software Catalog, Software Templates, TechDocs, and API Reference. With Backstage 1.0, the project’s core components are coming out of beta and into production with regular versioning and release cycle commitments (check back on this blog for more details!). + +v1.0 paves the way for more adoption, a bigger community, more contributions — and with this continued growth in the ecosystem, we expect even greater innovation on the platform just on the horizon with exchange of features and plugins. + +What comes next for Backstage is — in many ways — entirely up to you. We are looking to this community to [help us shape the next year](https://backstage.io/docs/overview/roadmap). + +If you’re already a part of this community, thank you for your hard work and dedication. Backstage wouldn’t be where it is today without you. We’ve come a long way together, but there’s still so much to do. + +And if you haven’t signed up yet, there’s plenty of room on this party boat! And there’s never been a better time to hop on board. As Irma ([@Irma12](https://github.com/Irma12)) said so beautifully: where else is there an opportunity to have so many people look at your code, hear you out, and help you out? + +Take a look at how to [start contributing](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) and [join the community](https://github.com/backstage/community)! diff --git a/microsite/blog/2022-03-17-backstage-1.0.md b/microsite/blog/2022-03-17-backstage-1.0.md new file mode 100644 index 0000000000..02ead0390c --- /dev/null +++ b/microsite/blog/2022-03-17-backstage-1.0.md @@ -0,0 +1,59 @@ +--- +# prettier-ignore +title: "New release: Backstage 1.0" +author: Francesco Corti, Spotify +authorURL: https://github.com/fcorti/ +authorImageURL: https://avatars.githubusercontent.com/u/6010860?v=4 +--- + +![backstage header](assets/22-03-16/release.1.0.gif) + +_TL;DR:_ If you’ve been waiting for Backstage to come out of beta, we’re excited to announce that Backstage 1.0 is here! + + + +It’s been two years since Spotify [open sourced Backstage](https://backstage.io/blog/2020/03/16/announcing-backstage) so companies of all shapes and sizes — and in every industry all over the world — could build highly extensible and customized developer portals to best suit their organization’s needs and culture. Now hundreds of adopters are using Backstage as the single pane of glass for their core development, managing millions of components and technical documentation, and helping hundreds of thousands of developers be more productive and happy. + +This milestone demonstrates Backstage’s maturity and the incredible effort from the core maintainers, contributors, and adopters. In a short time, we’ve seen a global community grow rapidly with people bringing their passion to this project and our goal of empowering developers. Because, yes, this is also your goal and your time to celebrate. + +# So what is Backstage 1.0? + +If you’re thinking “what is Backstage 1.0, and what does it include?” You’re not alone, so let’s get into it. + +To start, let’s define Backstage: it’s an open platform for building developer portals, mainly defined by a set of libraries together with plugins that can be added, removed, or customized so that the final behavior of the developer portal product is what an adopter wants. For the past two years, the open source platform has been in beta. With Backstage 1.0, we're coming out of beta and into production with regular versioning and release cycle commitments. So, if your company requires production-ready software, you can now use Backstage and join [over 100 other public adopters](https://github.com/backstage/backstage/blob/master/ADOPTERS.md)! + +Our definition of Backstage 1.0 includes: + +- Backstage Core 1.0 libraries as the set of libraries to make the platform work +- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) 1.0 +- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) 1.0 +- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) 1.0 + +Coming soon: [Backstage Search](https://backstage.io/docs/features/search/search-overview) 1.0 will be included in the near future as part of the regular releases to the Backstage platform. + +In terms of features, the maintainers will not be shipping new stuff as part of the major release but instead: + +- Refactoring the code packaging to better reflect the release numbering +- Removal of deprecated services to reduce the risk of introducing breaking changes +- Establishing clarity on the release lifecycle, numbering, and cadence as well as the support model +- Improvements to Backstage.io documentation + +# The road ahead + +In terms of maturity and stability, the road ahead for Backstage is bright and busy. The goal is to bring Backstage to the next level of maturity and continue improving it, according to adopter growth, feature set, and identified use cases. + +You can read more about Backstage’s roadmap [here](https://backstage.io/docs/overview/roadmap), but in terms of maturity and stability we would like to highlight the following: + +- CNCF incubation: The Backstage project [recently moved from Sandbox to Incubation](https://www.cncf.io/blog/2022/03/15/backstage-project-joins-the-cncf-incubator/) phase within the [CNCF maturity levels](https://www.cncf.io/projects/#:~:text=Maturity%20levels,should%20be%20adopting%20different%20projects.). +- Security auditing and strategy: With the CNCF’s support, the project maintainers are working on a security audit initiative to make the platform more secure and will share more details soon. The audit is part of an overall security strategy being developed this quarter. +- Paid plugins for advanced and enterprise use cases: Last year, [Spotify announced its work on paid plugins](https://backstage.spotify.com/blog/paid-plugins-announcement/) built on top of the Backstage open source platform. This is another example of the platform’s maturity and Spotify’s continued commitment to the project, with a focus on customers and use cases requiring enterprise-focused support and plugins developed and proven by Spotify. + +# Some links to peruse + +If you want to know more about this first major release, check out the following links to learn more about this release effort and receive support. + +- [Release notes](https://backstage.io/docs/releases/v1.0.0) and [Changelog](https://github.com/backstage/backstage/releases/tag/v1.0.0). +- [Backstage official documentation](https://backstage.io/docs/) and [backstage.spotify.com](https://backstage.spotify.com/). +- [Discord channel](https://discord.gg/EUaBAS58). +- [Backstage repository on GitHub](https://github.com/backstage/backstage). +- [List of public adopters](https://github.com/backstage/backstage/blob/master/ADOPTERS.md). diff --git a/microsite/blog/assets/22-03-16/backstage-2nd-birthday.gif b/microsite/blog/assets/22-03-16/backstage-2nd-birthday.gif new file mode 100644 index 0000000000..bb830efa2a Binary files /dev/null and b/microsite/blog/assets/22-03-16/backstage-2nd-birthday.gif differ diff --git a/microsite/blog/assets/22-03-16/release.1.0.gif b/microsite/blog/assets/22-03-16/release.1.0.gif new file mode 100644 index 0000000000..71e4a46358 Binary files /dev/null and b/microsite/blog/assets/22-03-16/release.1.0.gif differ diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index 1ea591b1d5..a7e5a1076c 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -51,7 +51,7 @@ class Footer extends React.Component { Subscribe to our newsletter - CNCF Sandbox + CNCF Incubation
More
diff --git a/microsite/data/on-demand/20211117-01.yaml b/microsite/data/on-demand/20211117-01.yaml new file mode 100644 index 0000000000..21410fc85f --- /dev/null +++ b/microsite/data/on-demand/20211117-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: November 17, 2021 +category: Meetup +description: At this month’s adopters session, get the scoop on Spotify’s plan for paid plugins, a first look at Box’s DevPortal, and answers to big questions, like “How will I know if Backstage will work at a large company like mine?” +youtubeUrl: https://youtu.be/apCDT3_DmFk +youtubeImgUrl: https://i1.ytimg.com/vi/apCDT3_DmFk/mqdefault.jpg diff --git a/microsite/data/on-demand/20211118-01.yaml b/microsite/data/on-demand/20211118-01.yaml new file mode 100644 index 0000000000..754344a62e --- /dev/null +++ b/microsite/data/on-demand/20211118-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: November 18, 2021 +category: Meetup +description: In the contributors track for this month’s community session, hear about upcoming deprecations, the fruits of Hacktoberfest, the future of both the Kubernetes plugin and the Tech Insights plugin, tips on contributing, and more. +youtubeUrl: https://youtu.be/n1OWGwYAOiI +youtubeImgUrl: https://i1.ytimg.com/vi/n1OWGwYAOiI/mqdefault.jpg diff --git a/microsite/data/on-demand/20211215-01.yaml b/microsite/data/on-demand/20211215-01.yaml new file mode 100644 index 0000000000..ce51fe3a9c --- /dev/null +++ b/microsite/data/on-demand/20211215-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: December 15, 2021 +category: Meetup +description: At our final adopters session of 2021, the community received a grab bag of end-of-year treats, including eye-catching infographics, a presentation from finance startup Brex about their path to adopting Backstage, and tips on branding your Backstage portal. Plus, get your official Backstage Zoom background. But first up, a quick look back at the year that was. +youtubeUrl: https://youtu.be/0QMQYSTKAx0 +youtubeImgUrl: https://i1.ytimg.com/vi/0QMQYSTKAx0/mqdefault.jpg diff --git a/microsite/data/on-demand/20211216-01.yaml b/microsite/data/on-demand/20211216-01.yaml new file mode 100644 index 0000000000..9600a5f13b --- /dev/null +++ b/microsite/data/on-demand/20211216-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: December 16, 2021 +category: Meetup +description: At our final contributors session of 2021, see how the new Backstage Upgrade Helper works and enjoy smoother upgrades, hear updates from the maintainers, and meet our contributor of the month. As in the adopters session, first we celebrate the year’s milestones and share a few resources you can use to spread the word about Backstage. +youtubeUrl: https://youtu.be/nYjI2j-lWEM +youtubeImgUrl: https://i1.ytimg.com/vi/nYjI2j-lWEM/mqdefault.jpg diff --git a/microsite/data/on-demand/20220216-01.yaml b/microsite/data/on-demand/20220216-01.yaml new file mode 100644 index 0000000000..1fc7371345 --- /dev/null +++ b/microsite/data/on-demand/20220216-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: February 16, 2022 +category: Meetup +description: This community session marks one year of Backstage community sessions! In this session, we celebrate one year of the Backstage community, hear from Patrik on stabilizing core APIs, learn more about Homepage Templates, find hidden info about your catalog entities, and Q&A. +youtubeUrl: https://youtu.be/evf_LV0KzIk +youtubeImgUrl: https://i1.ytimg.com/vi/evf_LV0KzIk/mqdefault.jpg diff --git a/microsite/data/on-demand/20220223-01.yaml b/microsite/data/on-demand/20220223-01.yaml new file mode 100644 index 0000000000..6efc9fe22c --- /dev/null +++ b/microsite/data/on-demand/20220223-01.yaml @@ -0,0 +1,7 @@ +--- +title: Community Sessions +date: February 23, 2022 +category: Meetup +description: Community Sessions Anniversary , SWAG opportunity❗, TechDocs add-on framework, URL Reader demo 👨‍💻, Q&A +youtubeUrl: https://youtu.be/Buu_KWdIFwU +youtubeImgUrl: https://i1.ytimg.com/vi/Buu_KWdIFwU/mqdefault.jpg diff --git a/microsite/data/on-demand/20220323.yaml b/microsite/data/on-demand/20220323.yaml new file mode 100644 index 0000000000..4306eaefdf --- /dev/null +++ b/microsite/data/on-demand/20220323.yaml @@ -0,0 +1,9 @@ +--- +title: Contributor Community Sessions +date: March 23, 2022 +category: Upcoming +description: Join the maintainers and contributors for the Contributor Community Sessions +youtubeUrl: https://youtu.be/evf_LV0KzIk +youtubeImgUrl: https://backstage.io/img/b-sessions.png +rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com +eventUrl: https://github.com/backstage/community/issues diff --git a/microsite/data/on-demand/20220420-1.yaml b/microsite/data/on-demand/20220420-1.yaml new file mode 100644 index 0000000000..a32402aa68 --- /dev/null +++ b/microsite/data/on-demand/20220420-1.yaml @@ -0,0 +1,9 @@ +--- +title: Adopters Community Sessions +date: April 20, 2022 +category: Upcoming +description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc. +youtubeUrl: https://youtu.be/mFi_X58igzk +youtubeImgUrl: https://backstage.io/img/b-sessions.png +rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com +eventUrl: https://github.com/backstage/community/issues diff --git a/microsite/data/on-demand/20220427-1.yaml b/microsite/data/on-demand/20220427-1.yaml new file mode 100644 index 0000000000..f6b8873bb8 --- /dev/null +++ b/microsite/data/on-demand/20220427-1.yaml @@ -0,0 +1,9 @@ +--- +title: Contributor Community Sessions +date: April 27, 2022 +category: Upcoming +description: Join the maintainers and contributors for the Contributor Community Sessions +youtubeUrl: https://youtu.be/evf_LV0KzIk +youtubeImgUrl: https://backstage.io/img/b-sessions.png +rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com +eventUrl: https://github.com/backstage/community/issues diff --git a/microsite/data/plugins/opsgenie.yaml b/microsite/data/plugins/opsgenie.yaml index 43ba040f75..3db06c04c8 100644 --- a/microsite/data/plugins/opsgenie.yaml +++ b/microsite/data/plugins/opsgenie.yaml @@ -3,7 +3,7 @@ title: Opsgenie author: K-Phoen authorUrl: https://github.com/K-Phoen category: Monitoring -description: Opsgenie offers a simple way to associate alerts to components and vizualize incidents. +description: Opsgenie offers a simple way to associate alerts to components and visualize incidents. documentation: https://github.com/K-Phoen/backstage-plugin-opsgenie/ iconUrl: https://avatars.githubusercontent.com/u/1818843?s=200&v=4 npmPackageName: '@k-phoen/backstage-plugin-opsgenie' diff --git a/microsite/package.json b/microsite/package.json index 0a90cb2ec5..427731ea47 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -16,10 +16,10 @@ "lock:check": "yarn-lock-check" }, "devDependencies": { - "@spotify/prettier-config": "^12.0.0", + "@spotify/prettier-config": "^13.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.5.1", + "prettier": "^2.6.1", "yarn-lock-check": "^1.0.5" }, "prettier": "@spotify/prettier-config" diff --git a/microsite/pages/en/community.js b/microsite/pages/en/community.js new file mode 100644 index 0000000000..a6795e2c24 --- /dev/null +++ b/microsite/pages/en/community.js @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; + +const Background = props => { + const { config: siteConfig } = props; + const { baseUrl } = siteConfig; + return ( +
+ + + Backstage Community + + + What's the use of having fun if you can't share it? Exactly. Join + the vibrant community around the Backstage project. Be it on + GitHub, social media, Discord... You'll find a welcoming + environment. To ensure this, we follow the{' '} + + {' '} + CNCF Code of Conduct + {' '} + in everything we do. + + + + + Main community channels +
- Chat and get support on our{' '} + Discord +
- Get into contributing with the{' '} + + Good First Issues + +
- Subscribe to the{' '} + + Community newsletter + +
- Join the{' '} + + Twitter community + +
+
+
+
+
+ + + + Community Sessions + + + Adopters Community Sessions +
+ Backstage Community Sessions is the monthly meetup where we all + come together to listen to the latest maintainer updates, learn + from each other about adopting, share exciting new demos or + discuss any relevant topic like developer effectiveness, developer + experience, developer portals, etc. +
+ + + Contributor Community Sessions +
+ Discuss all things contributing, diving deep under the hood of + Backstage (Backstage of Backstage? Backerstage?). An open + discussion with maintainers and contributors of Backstage. +
+ Meetups +
+ + + +
+
+ + + + + Backstage official Newsletter + + The official monthly Backstage newsletter. Containing the latest + news from your favorite project. + + + Subscribe + + + + + + + + + + Spotlight + + A recognition for valuable community work, the{' '} + Contributor Spotlight. Nominate contributing members for + their efforts! We'll put them in the spotlight ❤️. +
+
+ Nominate now +
+ + Open Mic Meetup + + A monthly casual get together of Backstage users sharing their + experiences and helping each other. Hosted by{' '} + Roadie.io and + Frontside Software. +
+
+ + Learn more + +
+ + Backstage Weekly + + A weekly newsletter with news, updates and things community from + your friends at Roadie.io. + + + Learn more + + +
+
+ + + + + + +
+ ); +}; + +module.exports = Background; diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 3804c45478..ba84d7d774 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -19,18 +19,16 @@ class Index extends React.Component { const { baseUrl } = siteConfig; return ( -
+
- - 🚀 Feature updates!{' '} - - Software Templates - {' '} - and{' '} - - TechDocs - {' '} - are now in beta. + +
+ 🗞️ Want to stay up to date with Backstage? Sign up for our{' '} + + Newsletter + + ! +
@@ -109,15 +107,7 @@ class Index extends React.Component { className="Block__GIF" src={`${baseUrl}animations/backstage-software-catalog-icon-1.gif`} /> - - Backstage Software Catalog{' '} - - (alpha) - - + Backstage Software Catalog Build an ecosystem, not a wilderness @@ -200,7 +190,7 @@ class Index extends React.Component { Learn more about the software catalog Read @@ -213,15 +203,7 @@ class Index extends React.Component { className="Block__GIF" src={`${baseUrl}animations/backstage-software-templates-icon-5.gif`} /> - - Backstage Software Templates{' '} - - (beta) - - + Backstage Software Templates Standards can set you free - - Backstage TechDocs{' '} - - (beta) - - + Backstage TechDocs Docs like code Cloud Native Computing Foundation {' '} - sandbox project + incubation project
diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js new file mode 100644 index 0000000000..0cde700697 --- /dev/null +++ b/microsite/pages/en/live.js @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; + +const Background = props => { + const { config: siteConfig } = props; + const { baseUrl } = siteConfig; + return ( +
+ + + + Community Sessions + + Please be aware we follow the{' '} + + {' '} + CNCF Code of Conduct + + . + + + + + + + + + + + + + + + + + + + + Don't be a stranger + + Main community channels +
- Chat and get support on our{' '} + Discord +
- Get into contributing with the{' '} + + Good First Issues + +
- Subscribe to the{' '} + + Community newsletter + +
- Join the{' '} + + Twitter community + +
+
+
+
+
+
+ ); +}; + +module.exports = Background; diff --git a/microsite/pages/en/nominate.js b/microsite/pages/en/nominate.js new file mode 100644 index 0000000000..6656257d52 --- /dev/null +++ b/microsite/pages/en/nominate.js @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; + +const Background = props => { + const { config: siteConfig } = props; + const { baseUrl } = siteConfig; + return ( +
+ + + Contributor Spotlight nomination + + + + + + + + + + + +
+ ); +}; + +module.exports = Background; diff --git a/microsite/pages/en/on-demand.js b/microsite/pages/en/on-demand.js new file mode 100644 index 0000000000..41a730205b --- /dev/null +++ b/microsite/pages/en/on-demand.js @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const fs = require('fs'); +const yaml = require('js-yaml'); +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const { + Block: { Container }, + BulletLine, +} = Components; + +const ondemandDirectory = require('path').join(process.cwd(), 'data/on-demand'); +const ondemandMetadata = fs + .readdirSync(ondemandDirectory) + .sort() + .reverse() + .map(file => yaml.load(fs.readFileSync(`./data/on-demand/${file}`, 'utf8'))); +const truncate = text => + text.length > 170 ? text.substr(0, 170) + '...' : text; + +const addVideoDocsLink = '/docs/overview/support'; +const defaultIconUrl = 'img/logo-gradient-on-dark.svg'; + +const Ondemand = () => ( +
+
+
+

Upcoming live events

+

Upcoming Backstage events

+ + + Add an event or recording + + +
+ + {ondemandMetadata + .filter(video => video.category === 'Upcoming') + .map( + ({ + title, + description, + category, + date, + youtubeUrl, + youtubeImgUrl, + rsvpUrl, + eventUrl, + }) => ( +
+
+
+

{title}

+

on {date}

+ + {category} +

+
+ {title} +

+
+
+
+

{truncate(description)}

+
+ +
+ ), + )} +
+ +
+

Community on demand

+
+ + {ondemandMetadata + .filter(video => video.category !== 'Upcoming') + .map( + ({ + title, + description, + category, + date, + youtubeUrl, + youtubeImgUrl, + }) => ( +
+
+
+

{title}

+

on {date}

+ + {category} +

+
+ {title} +

+
+
+
+

{truncate(description)}

+
+ +
+ ), + )} +
+
+

Do you have a recording of a meetup to include on this page?

+

+ + Add to the On-demand page + +

+
+ +

+ Help us create a go-to place for hours of Backstage related + content. +

+
+
+
+
+
+); + +module.exports = Ondemand; diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 2a19c43ca3..f0f8811666 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,4 +1,7 @@ { + "releases": { + "Release Notes": ["releases/v1.0.0"] + }, "docs": { "Overview": [ "overview/what-is-backstage", diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index 30fec73832..d757ed448b 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -8,6 +8,23 @@ // See https://docusaurus.io/docs/site-config for all the possible // site configuration options. +// This figures out what the most recent release version is, so that we can link to it in the header +const [{ name: latestRelease }] = require('fs') + .readdirSync(require('path').resolve(__dirname, '../docs/releases')) + .map(file => { + const match = file.match(/^v(\d+)\.(\d+)\.(\d+)\.md$/); + if (!match) { + return undefined; + } + const parts = match.slice(1).map(v => parseInt(v, 10)); + return { + name: file.slice(0, file.length - '.md'.length), + weight: parts[0] * 1000000 + parts[1] * 1000 + parts[2], + }; + }) + .filter(Boolean) + .sort((a, b) => b.weight - a.weight); + const siteConfig = { title: 'Backstage Software Catalog and Developer Platform', // Title for your website. tagline: 'An open platform for building developer portals', @@ -44,13 +61,18 @@ const siteConfig = { blog: true, label: 'Blog', }, + { + doc: `releases/${latestRelease}`, + href: '/releases', + label: 'Releases', + }, { page: 'demos', label: 'Demos', }, { - href: 'https://mailchi.mp/spotify/backstage-community', - label: 'Newsletter', + page: 'community', + label: 'Community', }, ], diff --git a/microsite/static/css/custom.css b/microsite/static/css/custom.css index 139fa7b1d2..30267a9f42 100644 --- a/microsite/static/css/custom.css +++ b/microsite/static/css/custom.css @@ -87,6 +87,10 @@ td { background-color: #121212; } +.MainContent--gradient { + background: linear-gradient(90.49deg, #121212 15.36%, #282828 70.44%); +} + /* marketing page */ .blockContent h2 { @@ -1073,11 +1077,13 @@ code { .Banner { position: relative; padding: 14px; - margin: 14px 20px; + padding-right: 50px; /* space for the dismiss button */ + margin: 36px 20px; border-radius: 4px; background-color: $primaryColor; font-family: Helvetica Neue, sans-serif; color: #000; + display: flex; } .Banner--hidden { diff --git a/microsite/static/css/on-demand.css b/microsite/static/css/on-demand.css new file mode 100644 index 0000000000..909459796c --- /dev/null +++ b/microsite/static/css/on-demand.css @@ -0,0 +1,143 @@ +.VideoCard { + background-color: #282828; + height: 100%; + padding: 16px; + display: flex; + flex-direction: column; +} + +.VideoGrid { + display: grid; + grid-gap: 1rem; + grid-template-columns: repeat(4, 1fr); + grid-auto-rows: 1fr; +} + +@media (max-width: 1200px) { + .VideoGrid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media only screen and (max-width: 815px) { + .VideoGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media only screen and (max-width: 485px) { + .VideoGrid { + grid-template-columns: 1fr; + } +} + +.VideoCard img { + float: left; + margin: 0px 16px 8px 0px; + height: 160px; + width: 300px; +} + +.VideoCardHeader { + display: flex; + flex-direction: row; + align-items: center; + max-height: fit-content; + min-height: fit-content; +} + +.VideoCardImage { + width: 200px; + height: 80px; + margin-right: 16px; +} + +.VideoCardImage img { + width: 100%; + max-width: 100%; +} + +.VideoCardTitle { + color: white; + vertical-align: top; + margin: 8px 0 0; +} + +.VideoCardInfo { + flex: 1; +} + +.VideoAddNewButton { + position: absolute; + bottom: 16px; + right: 0px; +} + +@media only screen and (max-width: 485px) { + .VideoAddNewButton { + bottom: -4px; + } +} + +.VideoButtonFilled { + padding: 4px 8px; + border-radius: 4px; + color: #69ddc7; +} + +.VideoButtonFilled:hover { + border: 1px solid #69ddc7; + background-color: transparent; +} + +.VideoCardChipOutlined { + font-size: small; + border-radius: 16px; + padding: 2px 8px; + border: 1px solid #69ddc7; + color: #69ddc7; +} + +.VideoCardFooter { + display: flex; + justify-content: flex-end; + align-items: flex-end; + margin-top: auto; + min-height: 2em; +} + +.VideoCardFooter a { + padding: 2px 8px; +} + +.VideoPageLayout { + margin: auto; + max-width: 1430px; + padding: 20px; +} + +.VideoPageHeader { + position: relative; +} + +.VideoPageHeader h2 { + display: inline-block; +} + +.VideoCardBody { + padding-top: 8px; +} + +.VideoCardDate, +.VideoCardDate a { + margin-bottom: 0.25em; + color: rgba(255, 255, 255, 0.6); +} + +.VideoCardDate a:hover { + color: white; +} + +#add-video-card { + border: 1px solid #69ddc7; +} diff --git a/microsite/static/img/b-sessions.png b/microsite/static/img/b-sessions.png new file mode 100644 index 0000000000..35fb401341 Binary files /dev/null and b/microsite/static/img/b-sessions.png differ diff --git a/microsite/static/img/news-fpo.png b/microsite/static/img/news-fpo.png new file mode 100644 index 0000000000..cff7df917f Binary files /dev/null and b/microsite/static/img/news-fpo.png differ diff --git a/microsite/static/img/openmic.jpeg b/microsite/static/img/openmic.jpeg new file mode 100644 index 0000000000..09d3089639 Binary files /dev/null and b/microsite/static/img/openmic.jpeg differ diff --git a/microsite/static/img/roadie-newsletter.png b/microsite/static/img/roadie-newsletter.png new file mode 100644 index 0000000000..e0b8ceb55b Binary files /dev/null and b/microsite/static/img/roadie-newsletter.png differ diff --git a/microsite/static/img/spotlight.png b/microsite/static/img/spotlight.png new file mode 100644 index 0000000000..5e1bb57c10 Binary files /dev/null and b/microsite/static/img/spotlight.png differ diff --git a/microsite/yarn.lock b/microsite/yarn.lock index a9161f57fd..efd56a9f28 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -909,10 +909,10 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== -"@spotify/prettier-config@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-12.0.0.tgz#936ca5e977cfccbccd1731ab98b1f2bf65852b5d" - integrity sha512-64WWqE40U/WwWV8iIQBseTU+b2t+SdJSyQoCLdVPCKM9uf7KOjRivVwXe4KlWoV3y7duNSGuB2UgWhkXzscVmQ== +"@spotify/prettier-config@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-13.0.0.tgz#ff6372cedf7e97e2e8a2d96c5ec12aa32cca3dbf" + integrity sha512-Hl3zgj67zaYejjLQLq0h0DlDMaI62EppO30AcStpLZNihjfwnFeS2+/gamiRHAnLki3P8bXaZVYwOV5NYNGQfw== "@types/cheerio@^0.22.8": version "0.22.23" @@ -4375,9 +4375,9 @@ minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: brace-expansion "^1.1.7" minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mixin-deep@^1.1.3, mixin-deep@^1.2.0: version "1.3.2" @@ -5209,10 +5209,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.5.1: - version "2.5.1" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^2.6.1: + version "2.6.1" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17" + integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A== prismjs@^1.22.0: version "1.27.0" diff --git a/package.json b/package.json index 822acf3030..540fbe0bad 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "backstage-create": "backstage-cli create --scope backstage --no-private", "create-plugin": "yarn backstage-create --select plugin", "remove-plugin": "backstage-cli remove-plugin", - "release": "node scripts/prepare-release.js && changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' && yarn install", + "release": "node scripts/prepare-release.js && changeset version && yarn diff --yes && yarn prettier --write '{packages,plugins}/*/{package.json,CHANGELOG.md}' '.changeset/*.json' && yarn install", "prettier:check": "prettier --check .", "lerna": "lerna", "storybook": "yarn --cwd storybook start", @@ -47,7 +47,7 @@ "resolutions": { "**/@graphql-codegen/cli/**/ws": "^7.4.6" }, - "version": "0.71.0", + "version": "1.1.0-next.0", "dependencies": { "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.15.0", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 9a682b9a1b..9c430cbac2 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/app-defaults +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-app-api@1.0.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-permission-react@0.3.4 + ## 0.2.1 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 5e322c6a21..5a9135a5df 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "0.2.1", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,10 +33,10 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-app-api": "^0.6.0", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-permission-react": "^0.3.3", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-permission-react": "^0.3.4", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,8 +46,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@types/jest": "^26.0.7", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 81696113e2..b3f5227511 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,111 @@ # example-app +## 0.2.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog@1.0.1-next.0 + - @backstage/plugin-user-settings@0.4.3-next.0 + - @backstage/cli@0.16.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/core-app-api@1.0.1-next.0 + - @backstage/plugin-search@0.7.5-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/plugin-scaffolder@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/plugin-techdocs@1.0.1-next.0 + - @backstage/plugin-tech-insights@0.1.14-next.0 + - @backstage/plugin-kubernetes@0.6.4-next.0 + - @backstage/plugin-airbrake@0.3.4-next.0 + - @backstage/plugin-api-docs@0.8.4-next.0 + - @backstage/plugin-azure-devops@0.1.20-next.0 + - @backstage/plugin-badges@0.2.28-next.0 + - @backstage/plugin-catalog-graph@0.2.16-next.0 + - @backstage/plugin-catalog-import@0.8.7-next.0 + - @backstage/plugin-circleci@0.3.4-next.0 + - @backstage/plugin-cloudbuild@0.3.4-next.0 + - @backstage/plugin-code-coverage@0.1.31-next.0 + - @backstage/plugin-cost-insights@0.11.26-next.0 + - @backstage/plugin-explore@0.3.35-next.0 + - @backstage/plugin-github-actions@0.5.4-next.0 + - @backstage/plugin-gocd@0.1.10-next.0 + - @backstage/plugin-home@0.4.20-next.0 + - @backstage/plugin-jenkins@0.7.3-next.0 + - @backstage/plugin-kafka@0.3.4-next.0 + - @backstage/plugin-lighthouse@0.3.4-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.12-next.0 + - @backstage/plugin-org@0.5.4-next.0 + - @backstage/plugin-pagerduty@0.3.31-next.0 + - @backstage/plugin-rollbar@0.4.4-next.0 + - @backstage/plugin-sentry@0.3.42-next.0 + - @backstage/plugin-todo@0.2.6-next.0 + - @backstage/app-defaults@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/plugin-apache-airflow@0.1.12-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + - @backstage/plugin-gcalendar@0.2.1-next.0 + - @backstage/plugin-gcp-projects@0.3.23-next.0 + - @backstage/plugin-graphiql@0.2.36-next.0 + - @backstage/plugin-newrelic@0.3.22-next.0 + - @backstage/plugin-shortcuts@0.2.5-next.0 + - @backstage/plugin-tech-radar@0.5.11-next.0 + +## 0.2.69 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/plugin-gcalendar@0.2.0 + - @backstage/plugin-scaffolder@1.0.0 + - @backstage/plugin-airbrake@0.3.3 + - @backstage/plugin-kafka@0.3.3 + - @backstage/plugin-todo@0.2.5 + - @backstage/app-defaults@1.0.0 + - @backstage/core-app-api@1.0.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-apache-airflow@0.1.11 + - @backstage/plugin-api-docs@0.8.3 + - @backstage/plugin-azure-devops@0.1.19 + - @backstage/plugin-badges@0.2.27 + - @backstage/plugin-catalog-graph@0.2.15 + - @backstage/plugin-catalog-import@0.8.6 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-catalog@1.0.0 + - @backstage/plugin-circleci@0.3.3 + - @backstage/plugin-cloudbuild@0.3.3 + - @backstage/plugin-code-coverage@0.1.30 + - @backstage/plugin-cost-insights@0.11.25 + - @backstage/plugin-explore@0.3.34 + - @backstage/plugin-gcp-projects@0.3.22 + - @backstage/plugin-github-actions@0.5.3 + - @backstage/plugin-gocd@0.1.9 + - @backstage/plugin-graphiql@0.2.35 + - @backstage/plugin-home@0.4.19 + - @backstage/plugin-jenkins@0.7.2 + - @backstage/plugin-kubernetes@0.6.3 + - @backstage/plugin-lighthouse@0.3.3 + - @backstage/plugin-newrelic@0.3.21 + - @backstage/plugin-org@0.5.3 + - @backstage/plugin-pagerduty@0.3.30 + - @backstage/plugin-permission-react@0.3.4 + - @backstage/plugin-rollbar@0.4.3 + - @backstage/plugin-search@0.7.4 + - @backstage/plugin-sentry@0.3.41 + - @backstage/plugin-shortcuts@0.2.4 + - @backstage/plugin-tech-insights@0.1.13 + - @backstage/plugin-tech-radar@0.5.10 + - @backstage/plugin-techdocs@1.0.0 + - @backstage/plugin-user-settings@0.4.2 + - @backstage/catalog-model@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-newrelic-dashboard@0.1.11 + - @backstage/plugin-search-common@0.3.2 + ## 0.2.68 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index c9bc3eef8c..ddc5c99178 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,69 +1,69 @@ { "name": "example-app", - "version": "0.2.68", + "version": "0.2.70-next.0", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.2.1", - "@backstage/catalog-model": "^0.13.0", - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-airbrake": "^0.3.2", - "@backstage/plugin-api-docs": "^0.8.2", - "@backstage/plugin-azure-devops": "^0.1.18", - "@backstage/plugin-apache-airflow": "^0.1.10", - "@backstage/plugin-badges": "^0.2.26", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-catalog-graph": "^0.2.14", - "@backstage/plugin-catalog-import": "^0.8.5", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-circleci": "^0.3.2", - "@backstage/plugin-cloudbuild": "^0.3.2", - "@backstage/plugin-code-coverage": "^0.1.29", - "@backstage/plugin-cost-insights": "^0.11.24", - "@backstage/plugin-explore": "^0.3.33", - "@backstage/plugin-gcalendar": "^0.1.0", - "@backstage/plugin-gcp-projects": "^0.3.21", - "@backstage/plugin-github-actions": "^0.5.2", - "@backstage/plugin-gocd": "^0.1.8", - "@backstage/plugin-graphiql": "^0.2.34", - "@backstage/plugin-home": "^0.4.18", - "@backstage/plugin-jenkins": "^0.7.1", - "@backstage/plugin-kafka": "^0.3.2", - "@backstage/plugin-kubernetes": "^0.6.2", - "@backstage/plugin-lighthouse": "^0.3.2", - "@backstage/plugin-newrelic": "^0.3.20", - "@backstage/plugin-newrelic-dashboard": "^0.1.10", - "@backstage/plugin-org": "^0.5.2", - "@backstage/plugin-pagerduty": "0.3.29", - "@backstage/plugin-permission-react": "^0.3.3", - "@backstage/plugin-rollbar": "^0.4.2", - "@backstage/plugin-scaffolder": "^0.15.0", - "@backstage/plugin-search": "^0.7.3", - "@backstage/plugin-search-common": "^0.3.1", - "@backstage/plugin-sentry": "^0.3.40", - "@backstage/plugin-shortcuts": "^0.2.3", - "@backstage/plugin-tech-radar": "^0.5.9", - "@backstage/plugin-techdocs": "^0.15.1", - "@backstage/plugin-todo": "^0.2.4", - "@backstage/plugin-user-settings": "^0.4.1", - "@backstage/plugin-tech-insights": "^0.1.12", + "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-airbrake": "^0.3.4-next.0", + "@backstage/plugin-api-docs": "^0.8.4-next.0", + "@backstage/plugin-azure-devops": "^0.1.20-next.0", + "@backstage/plugin-apache-airflow": "^0.1.12-next.0", + "@backstage/plugin-badges": "^0.2.28-next.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-catalog-graph": "^0.2.16-next.0", + "@backstage/plugin-catalog-import": "^0.8.7-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-circleci": "^0.3.4-next.0", + "@backstage/plugin-cloudbuild": "^0.3.4-next.0", + "@backstage/plugin-code-coverage": "^0.1.31-next.0", + "@backstage/plugin-cost-insights": "^0.11.26-next.0", + "@backstage/plugin-explore": "^0.3.35-next.0", + "@backstage/plugin-gcalendar": "^0.2.1-next.0", + "@backstage/plugin-gcp-projects": "^0.3.23-next.0", + "@backstage/plugin-github-actions": "^0.5.4-next.0", + "@backstage/plugin-gocd": "^0.1.10-next.0", + "@backstage/plugin-graphiql": "^0.2.36-next.0", + "@backstage/plugin-home": "^0.4.20-next.0", + "@backstage/plugin-jenkins": "^0.7.3-next.0", + "@backstage/plugin-kafka": "^0.3.4-next.0", + "@backstage/plugin-kubernetes": "^0.6.4-next.0", + "@backstage/plugin-lighthouse": "^0.3.4-next.0", + "@backstage/plugin-newrelic": "^0.3.22-next.0", + "@backstage/plugin-newrelic-dashboard": "^0.1.12-next.0", + "@backstage/plugin-org": "^0.5.4-next.0", + "@backstage/plugin-pagerduty": "0.3.31-next.0", + "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/plugin-rollbar": "^0.4.4-next.0", + "@backstage/plugin-scaffolder": "^1.0.1-next.0", + "@backstage/plugin-search": "^0.7.5-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-sentry": "^0.3.42-next.0", + "@backstage/plugin-shortcuts": "^0.2.5-next.0", + "@backstage/plugin-tech-radar": "^0.5.11-next.0", + "@backstage/plugin-techdocs": "^1.0.1-next.0", + "@backstage/plugin-todo": "^0.2.6-next.0", + "@backstage/plugin-user-settings": "^0.4.3-next.0", + "@backstage/plugin-tech-insights": "^0.1.14-next.0", "@backstage/theme": "^0.2.15", "@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.4.0", - "@roadiehq/backstage-plugin-github-insights": "^1.6.0", - "@roadiehq/backstage-plugin-github-pull-requests": "^1.5.0", - "@roadiehq/backstage-plugin-travis-ci": "^1.4.0", + "@roadiehq/backstage-plugin-buildkite": "^2.0.0", + "@roadiehq/backstage-plugin-github-insights": "^2.0.0", + "@roadiehq/backstage-plugin-github-pull-requests": "^2.0.0", + "@roadiehq/backstage-plugin-travis-ci": "^2.0.0", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", @@ -75,8 +75,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/plugin-permission-react": "^0.3.3", - "@backstage/test-utils": "^0.3.0", + "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/test-utils": "^1.0.1-next.0", "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index befd3274f3..ce765bd312 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -79,6 +79,7 @@ import { entityPage } from './components/catalog/EntityPage'; import { homePage } from './components/home/HomePage'; import { Root } from './components/Root'; import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions'; +import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate'; import { searchPage } from './components/search/SearchPage'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -179,6 +180,7 @@ const routes = ( path="/create" element={ - + boolean}> @@ -326,7 +327,7 @@ const overviewContent = ( {cicdCard} - Boolean(isGithubInsightsAvailable(e))}> + @@ -346,7 +347,7 @@ const overviewContent = ( - Boolean(isGithubPullRequestsAvailable(e))}> + diff --git a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts new file mode 100644 index 0000000000..2e23b08e53 --- /dev/null +++ b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts @@ -0,0 +1,54 @@ +/* + * 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 const defaultPreviewTemplate = `# Edit the template parameters below to see how they will render in the scaffolder form UI +parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + allowedKinds: + - Group + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + - title: Custom Fields + required: + - lowerCaseValue + properties: + lowerCaseValue: + title: Lower Cased Value + type: string + ui:field: LowerCaseValuePicker +`; diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index a8d461eabb..d80fdd136c 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/backend-common +## 0.13.2-next.0 + +### Patch Changes + +- 3ef123bbf0: Support external ID when assuming roles in S3 integration + + In order to assume a role created by a 3rd party as external + ID is needed. This change adds an optional field to the s3 + integration configuration and consumes that in the AwsS3UrlReader. + +- f9f512559b: Updated the visibility of database connection fields in config to be secret +- Updated dependencies + - @backstage/config-loader@1.0.1-next.0 + - @backstage/integration@1.0.1-next.0 + +## 0.13.1 + +### Patch Changes + +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- f24ef7864e: Minor typo fixes +- b66f70180f: Fix handling of bucket names with dots, in `AwsS3UrlReader` +- Updated dependencies + - @backstage/config-loader@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.13.0 ### Minor Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 1b901ea5ac..cd4d08df87 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -17,6 +17,7 @@ import Docker from 'dockerode'; import { Duration } from 'luxon'; import { ErrorRequestHandler } from 'express'; import express from 'express'; +import { GerritIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; @@ -252,6 +253,23 @@ export type FromReadableArrayOptions = Array<{ path: string; }>; +// @public +export class GerritUrlReader implements UrlReader { + constructor(integration: GerritIntegration); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(): Promise; + // (undocumented) + toString(): string; +} + // @public export function getRootLogger(): winston.Logger; diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 058805aa62..6c473af3c0 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -73,7 +73,7 @@ export interface Config { client: 'better-sqlite3' | 'sqlite3' | 'pg'; /** * Base database connection string or Knex object - * @secret + * @visibility secret */ connection: string | object; /** Database name prefix override */ @@ -109,7 +109,7 @@ export interface Config { client?: 'better-sqlite3' | 'sqlite3' | 'pg'; /** * Database connection string or Knex object override - * @secret + * @visibility secret */ connection?: string | object; /** @@ -138,7 +138,7 @@ export interface Config { store: 'redis'; /** * A redis connection string in the form `redis://user:pass@host:port`. - * @secret + * @visibility secret */ connection: string; } @@ -146,7 +146,7 @@ export interface Config { store: 'memcache'; /** * A memcache connection string in the form `user:pass@host:port`. - * @secret + * @visibility secret */ connection: string; }; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 5d6cf2e8df..a215ce6851 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.13.0", + "version": "0.13.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -35,11 +35,11 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.8", - "@backstage/config": "^0.1.15", - "@backstage/config-loader": "^0.9.7", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/types": "^0.1.3", + "@backstage/config": "^1.0.0", + "@backstage/config-loader": "^1.0.1-next.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "@google-cloud/storage": "^5.8.0", "@manypkg/get-packages": "^1.1.3", "@octokit/rest": "^18.5.3", @@ -89,8 +89,8 @@ } }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^2.0.0", @@ -108,7 +108,6 @@ "aws-sdk-mock": "^5.2.1", "better-sqlite3": "^7.5.0", "http-errors": "^2.0.0", - "jest": "^26.0.1", "mock-fs": "^5.1.0", "msw": "^0.35.0", "mysql2": "^2.2.5", diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index 2b2cc17f91..ea8aca38ea 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -93,7 +93,7 @@ describe('DatabaseManager', () => { plugin: { testdbname: { connection: { - database: 'database_name_overriden', + database: 'database_name_overridden', }, }, differentclient: { @@ -304,10 +304,10 @@ describe('DatabaseManager', () => { const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); const [_baseConfig, overrides] = mockCalls[0]; - // simple case where only database name is overriden + // simple case where only database name is overridden expect(overrides).toMatchObject({ connection: { - database: 'database_name_overriden', + database: 'database_name_overridden', }, }); }); @@ -581,7 +581,7 @@ describe('DatabaseManager', () => { plugin: { testdbname: { connection: { - database: 'database_name_overriden', + database: 'database_name_overridden', host: 'newhost', }, }, @@ -599,7 +599,7 @@ describe('DatabaseManager', () => { expect(baseConfig.get()).toMatchObject({ client: 'pg', connection: { - database: 'database_name_overriden', + database: 'database_name_overridden', host: 'newhost', user: 'foo', password: 'bar', @@ -608,7 +608,7 @@ describe('DatabaseManager', () => { expect(overrides).toHaveProperty('searchPath', ['testdbname']); expect(overrides).toHaveProperty( 'connection.database', - 'database_name_overriden', + 'database_name_overridden', ); }); diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts index cb65113a9c..044f807576 100644 --- a/packages/backend-common/src/database/connection.test.ts +++ b/packages/backend-common/src/database/connection.test.ts @@ -193,7 +193,7 @@ describe('database connection', () => { }); describe('ensureSchemaExists', () => { - it('returns sucessfully with pg client', async () => { + it('returns successfully with pg client', async () => { await ensureSchemaExists( new ConfigReader({ client: 'pg', diff --git a/packages/backend-common/src/logging/voidLogger.ts b/packages/backend-common/src/logging/voidLogger.ts index eb9993fe7f..79077f5c96 100644 --- a/packages/backend-common/src/logging/voidLogger.ts +++ b/packages/backend-common/src/logging/voidLogger.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PassThrough } from 'stream'; import * as winston from 'winston'; /** @@ -24,6 +23,6 @@ import * as winston from 'winston'; */ export function getVoidLogger(): winston.Logger { return winston.createLogger({ - transports: [new winston.transports.Stream({ stream: new PassThrough() })], + transports: [new winston.transports.Console({ silent: true })], }); } diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts index a1b070fc07..01fd27f45a 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts @@ -323,7 +323,14 @@ describe('AwsS3UrlReader', () => { AWSMock.setSDKInstance(aws); AWSMock.mock('S3', 'getObject', (_, callback) => { - callback({ statusCode: 304 }, null); + const error: aws.AWSError = { + code: 'NotModified', + message: 'Not Modified', + statusCode: 304, + name: 'oops', + time: new Date('2019-01-01T00:00:00.000Z'), + }; + callback(error, undefined); }); const s3 = new aws.S3(); diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.ts b/packages/backend-common/src/reading/AwsS3UrlReader.ts index d5de2a845c..4dd68bf32c 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.ts @@ -177,6 +177,7 @@ export class AwsS3UrlReader implements UrlReader { params: { RoleSessionName: 'backstage-aws-s3-url-reader', RoleArn: roleArn, + ExternalId: integration.config.externalId, }, }); } diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 2eb7a60095..bc088d8bc9 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -130,7 +130,7 @@ describe('AzureUrlReader', () => { { url: 'com/a/b/blob/master/path/to/c.yaml', config: createConfig(), - error: 'Invalid URL: com/a/b/blob/master/path/to/c.yaml', + error: 'Invalid URL', }, { url: '', diff --git a/packages/backend-common/src/reading/GerritUrlReader.test.ts b/packages/backend-common/src/reading/GerritUrlReader.test.ts new file mode 100644 index 0000000000..bc88fdcb20 --- /dev/null +++ b/packages/backend-common/src/reading/GerritUrlReader.test.ts @@ -0,0 +1,173 @@ +/* + * 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 { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { getVoidLogger } from '../logging'; +import { DefaultReadTreeResponseFactory } from './tree'; +import { UrlReaderPredicateTuple } from './types'; +import { + GerritIntegration, + readGerritIntegrationConfig, +} from '@backstage/integration'; +import { GerritUrlReader } from './GerritUrlReader'; + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +const gerritProcessor = new GerritUrlReader( + new GerritIntegration( + readGerritIntegrationConfig( + new ConfigReader({ + host: 'gerrit.com', + }), + ), + ), +); + +const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return GerritUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory, + }); +}; + +describe('GerritUrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('reader factory', () => { + it('creates a reader.', () => { + const readers = createReader({ + integrations: { + gerrit: [{ host: 'gerrit.com' }], + }, + }); + expect(readers).toHaveLength(1); + }); + + it('should not create a default entry.', () => { + const readers = createReader({ + integrations: {}, + }); + expect(readers).toHaveLength(0); + }); + }); + + describe('predicates without Gitiles', () => { + const readers = createReader({ + integrations: { + gerrit: [{ host: 'gerrit.com' }], + }, + }); + const predicate = readers[0].predicate; + + it('returns true for the configured host', () => { + expect(predicate(new URL('https://gerrit.com/path'))).toBe(true); + }); + + it('returns false for a different host.', () => { + expect(predicate(new URL('https://github.com/path'))).toBe(false); + }); + }); + + describe('predicates with gitilesBaseUrl set.', () => { + const readers = createReader({ + integrations: { + gerrit: [ + { host: 'gerrit-review.com', gitilesBaseUrl: 'https://gerrit.com' }, + ], + }, + }); + const predicate = readers[0].predicate; + + it('returns false since gitilesBaseUrl is set to the api host.', () => { + expect(predicate(new URL('https://gerrit-review.com/path'))).toBe(false); + }); + + it('returns false for host.', () => { + expect(predicate(new URL('https://gerrit.com/path'))).toBe(true); + }); + }); + + describe('readUrl', () => { + const responseBuffer = Buffer.from('Apache License'); + it('should be able to read file contents', async () => { + worker.use( + rest.get( + 'https://gerrit.com/projects/web%2Fproject/branches/master/files/LICENSE/content', + (_, res, ctx) => { + return res( + ctx.status(200), + ctx.body(responseBuffer.toString('base64')), + ); + }, + ), + ); + + const result = await gerritProcessor.readUrl( + 'https://gerrit.com/web/project/+/refs/heads/master/LICENSE', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe(responseBuffer.toString()); + }); + + it('should raise NotFoundError on 404.', async () => { + worker.use( + rest.get( + 'https://gerrit.com/projects/web%2Fproject/branches/master/files/LICENSE/content', + (_, res, ctx) => { + return res(ctx.status(404, 'File not found.')); + }, + ), + ); + + await expect( + gerritProcessor.readUrl( + 'https://gerrit.com/web/project/+/refs/heads/master/LICENSE', + ), + ).rejects.toThrow( + 'File https://gerrit.com/web/project/+/refs/heads/master/LICENSE not found.', + ); + }); + + it('should throw an error on non 404 errors.', async () => { + worker.use( + rest.get( + 'https://gerrit.com/projects/web%2Fproject/branches/master/files/LICENSE/content', + (_, res, ctx) => { + return res(ctx.status(500, 'Error!!!')); + }, + ), + ); + + await expect( + gerritProcessor.readUrl( + 'https://gerrit.com/web/project/+/refs/heads/master/LICENSE', + ), + ).rejects.toThrow( + 'https://gerrit.com/web/project/+/refs/heads/master/LICENSE' + + ' could not be read as https://gerrit.com/projects/web%2Fproject' + + '/branches/master/files/LICENSE/content, 500 Error!!!', + ); + }); + }); +}); diff --git a/packages/backend-common/src/reading/GerritUrlReader.ts b/packages/backend-common/src/reading/GerritUrlReader.ts new file mode 100644 index 0000000000..7cc0b4731b --- /dev/null +++ b/packages/backend-common/src/reading/GerritUrlReader.ts @@ -0,0 +1,124 @@ +/* + * 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 { NotFoundError } from '@backstage/errors'; +import { + GerritIntegration, + getGerritFileContentsApiUrl, + getGerritRequestOptions, +} from '@backstage/integration'; +import fetch, { Response } from 'node-fetch'; +import { + ReaderFactory, + ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, + SearchResponse, + UrlReader, +} from './types'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * Implements a {@link UrlReader} for files in Gerrit. + * + * @remarks + * To be able to link to Git contents for Gerrit providers in a user friendly + * way we are depending on that there is a Gitiles installation somewhere + * that we can link to. It is perfectly possible to integrate Gerrit with + * Backstage without Gitiles since all API calls goes directly to Gerrit. + * + * The "host" variable in the config is the Gerrit host. The address where + * Gitiles is installed may be on the same host but it could be on a + * separate host. For example a Gerrit instance could be hosted on + * "gerrit-review.company.com" but the repos could be browsable on a separate + * host, e.g. "gerrit.company.com" and the human readable URL would then + * not point to the API host. + * + * @public + */ +export class GerritUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + const integrations = ScmIntegrations.fromConfig(config); + if (!integrations.gerrit) { + return []; + } + return integrations.gerrit.list().map(integration => { + const reader = new GerritUrlReader(integration); + const predicate = (url: URL) => { + const gitilesUrl = new URL(integration.config.gitilesBaseUrl!); + // If gitilesUrl is not specfified it will default to + // "integration.config.host". + return url.host === gitilesUrl.host; + }; + return { reader, predicate }; + }); + }; + + constructor(private readonly integration: GerritIntegration) {} + + async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { + const apiUrl = getGerritFileContentsApiUrl(this.integration.config, url); + let response: Response; + try { + response = await fetch(apiUrl, { + method: 'GET', + ...getGerritRequestOptions(this.integration.config), + // TODO(freben): The signal cast is there because pre-3.x versions of + // node-fetch have a very slightly deviating AbortSignal type signature. + // The difference does not affect us in practice however. The cast can + // be removed after we support ESM for CLI dependencies and migrate to + // version 3 of node-fetch. + // https://github.com/backstage/backstage/issues/8242 + signal: options?.signal as any, + }); + } catch (e) { + throw new Error(`Unable to read gerrit file ${url}, ${e}`); + } + if (response.ok) { + const responseBody = await response.text(); + return { + buffer: async () => Buffer.from(responseBody, 'base64'), + }; + } + if (response.status === 404) { + throw new NotFoundError(`File ${url} not found.`); + } + throw new Error( + `${url} could not be read as ${apiUrl}, ${response.status} ${response.statusText}`, + ); + } + + async readTree(): Promise { + throw new Error('GerritReader does not implement readTree'); + } + + async search(): Promise { + throw new Error('GerritReader does not implement search'); + } + + toString() { + const { host, password } = this.integration.config; + return `gerrit{host=${host},authed=${Boolean(password)}}`; + } +} diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index c0aae25808..e676d0e14c 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -20,6 +20,7 @@ import { ReaderFactory, UrlReader } from './types'; import { UrlReaderPredicateMux } from './UrlReaderPredicateMux'; import { AzureUrlReader } from './AzureUrlReader'; import { BitbucketUrlReader } from './BitbucketUrlReader'; +import { GerritUrlReader } from './GerritUrlReader'; import { GithubUrlReader } from './GithubUrlReader'; import { GitlabUrlReader } from './GitlabUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; @@ -82,6 +83,7 @@ export class UrlReaders { factories: factories.concat([ AzureUrlReader.factory, BitbucketUrlReader.factory, + GerritUrlReader.factory, GithubUrlReader.factory, GitlabUrlReader.factory, GoogleGcsUrlReader.factory, diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 9c29bc93fe..c2c82e2bd5 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -16,6 +16,7 @@ export { AzureUrlReader } from './AzureUrlReader'; export { BitbucketUrlReader } from './BitbucketUrlReader'; +export { GerritUrlReader } from './GerritUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; export { GitlabUrlReader } from './GitlabUrlReader'; export { AwsS3UrlReader } from './AwsS3UrlReader'; diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 206e5633a8..8ed769b1ea 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/backend-tasks +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.2.1 + +### Patch Changes + +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.2.0 ### Minor Changes diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index 0093616d8f..9f02088bdf 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -15,6 +15,7 @@ export interface PluginTaskScheduler { scheduleTask( task: TaskScheduleDefinition & TaskInvocationDefinition, ): Promise; + triggerTask(id: string): Promise; } // @public diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index e4cecc5d85..9f8d9febce 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.2.0", + "version": "0.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -33,10 +33,10 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/types": "^1.0.0", "@types/luxon": "^2.0.4", "cron": "^1.8.2", "knex": "^1.0.2", @@ -48,10 +48,9 @@ "zod": "^3.9.5" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/cron": "^1.7.3", - "jest": "^26.0.1", "wait-for-expect": "^3.0.2" }, "files": [ diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index 646a2ce737..004426211c 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -17,9 +17,20 @@ import { getVoidLogger } from '@backstage/backend-common'; import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; import { Duration } from 'luxon'; -import waitForExpect from 'wait-for-expect'; import { migrateBackendTasks } from '../database/migrateBackendTasks'; import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; +import { ConflictError, NotFoundError } from '@backstage/errors'; +import { AbortSignal } from 'node-abort-controller'; + +jest.useFakeTimers(); + +function defer() { + let resolve = () => {}; + const promise = new Promise(_resolve => { + resolve = _resolve; + }); + return { promise, resolve }; +} describe('PluginTaskManagerImpl', () => { const databases = TestDatabases.create({ @@ -45,6 +56,7 @@ describe('PluginTaskManagerImpl', () => { const { manager } = await init(databaseId); const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); await manager.scheduleTask({ id: 'task1', timeout: Duration.fromMillis(5000), @@ -52,9 +64,8 @@ describe('PluginTaskManagerImpl', () => { fn, }); - await waitForExpect(() => { - expect(fn).toBeCalled(); - }); + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); }, 60_000, ); @@ -65,6 +76,7 @@ describe('PluginTaskManagerImpl', () => { const { manager } = await init(databaseId); const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); await manager.scheduleTask({ id: 'task2', timeout: Duration.fromMillis(5000), @@ -72,9 +84,79 @@ describe('PluginTaskManagerImpl', () => { fn, }); - await waitForExpect(() => { - expect(fn).toBeCalled(); + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + 60_000, + ); + }); + + describe('triggerTask', () => { + it.each(databases.eachSupportedId())( + 'can manually trigger a task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + initialDelay: Duration.fromObject({ years: 1 }), + fn, }); + + await manager.triggerTask('task1'); + jest.advanceTimersByTime(5000); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'cant trigger a non-existent task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn, + }); + + await expect(() => manager.triggerTask('task2')).rejects.toThrow( + NotFoundError, + ); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'cant trigger a running task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const { promise, resolve } = defer(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn: async () => { + resolve(); + await new Promise(r => setTimeout(r, 20000)); + }, + }); + + await promise; + await expect(() => manager.triggerTask('task1')).rejects.toThrow( + ConflictError, + ); }, 60_000, ); @@ -89,6 +171,7 @@ describe('PluginTaskManagerImpl', () => { const { manager } = await init(databaseId); const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); await manager .createScheduledTaskRunner({ timeout: Duration.fromMillis(5000), @@ -99,9 +182,8 @@ describe('PluginTaskManagerImpl', () => { fn, }); - await waitForExpect(() => { - expect(fn).toBeCalled(); - }); + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); }, 60_000, ); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts index 8c3166b441..44f46e7237 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -24,6 +24,8 @@ import { TaskScheduleDefinition, } from './types'; import { validateId } from './util'; +import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; +import { ConflictError, NotFoundError } from '@backstage/errors'; /** * Implements the actual task management. @@ -34,6 +36,28 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler { private readonly logger: Logger, ) {} + async triggerTask(id: string): Promise { + const knex = await this.databaseFactory(); + + // check if task exists + const rows = await knex(DB_TASKS_TABLE) + .select(knex.raw(1)) + .where('id', '=', id); + if (rows.length !== 1) { + throw new NotFoundError(`Task ${id} does not exist`); + } + + const updatedRows = await knex(DB_TASKS_TABLE) + .where('id', '=', id) + .whereNull('current_run_ticket') + .update({ + next_run_start_at: knex.fn.now(), + }); + if (updatedRows < 1) { + throw new ConflictError(`Task ${id} is currently running`); + } + } + async scheduleTask( task: TaskScheduleDefinition & TaskInvocationDefinition, ): Promise { diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 793e679c39..f235c58973 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -131,11 +131,11 @@ describe('TaskWorker', () => { cadence: '* * * * * *', timeoutAfterDuration: Duration.fromMillis(60000).toISO(), }; - - const worker = new TaskWorker('task1', fn, knex, logger); + const checkFrequency = Duration.fromObject({ milliseconds: 100 }); + const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); worker.start(settings); - waitForExpect(() => { + await waitForExpect(() => { expect(fn).toBeCalledTimes(3); }); }, diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index 84f45a7fe6..edec453a69 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -24,7 +24,7 @@ import { TaskFunction, TaskSettingsV2, taskSettingsV2Schema } from './types'; import { delegateAbortController, nowPlus, sleep } from './util'; import { CronTime } from 'cron'; -const WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); +const DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); /** * Performs the actual work of a task. @@ -32,17 +32,13 @@ const WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); * @private */ export class TaskWorker { - private readonly taskId: string; - private readonly fn: TaskFunction; - private readonly knex: Knex; - private readonly logger: Logger; - - constructor(taskId: string, fn: TaskFunction, knex: Knex, logger: Logger) { - this.taskId = taskId; - this.fn = fn; - this.knex = knex; - this.logger = logger; - } + constructor( + private readonly taskId: string, + private readonly fn: TaskFunction, + private readonly knex: Knex, + private readonly logger: Logger, + private readonly workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY, + ) {} async start(settings: TaskSettingsV2, options?: { signal?: AbortSignal }) { try { @@ -63,7 +59,7 @@ export class TaskWorker { break; } - await sleep(WORK_CHECK_FREQUENCY, options?.signal); + await sleep(this.workCheckFrequency, options?.signal); } this.logger.info(`Task worker finished: ${this.taskId}`); } catch (e) { diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index acd651adcf..513e0dbae4 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -134,6 +134,17 @@ export interface TaskRunner { * @public */ export interface PluginTaskScheduler { + /** + * Manually triggers a task by ID. + * + * If the task doesn't exist, a NotFoundError is thrown. + * If the task is currently running, a ConflictError is thrown. + * + * @param id - The task ID + * + */ + triggerTask(id: string): Promise; + /** * Schedules a task function for coordinated exclusive invocation across * workers. This convenience method performs both the scheduling and diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index bcf6d4a3f4..df9e2d5d2e 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/backend-test-utils +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.22 + +### Patch Changes + +- 89c7e47967: Minor README update +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.1.21 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 85965233d4..5ae5153890 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.21", + "version": "0.1.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -34,9 +34,9 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/cli": "^0.15.2", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/config": "^1.0.0", "better-sqlite3": "^7.5.0", "knex": "^1.0.2", "msw": "^0.35.0", @@ -46,8 +46,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "jest": "^26.0.1" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/packages/backend-test-utils/src/database/TestDatabases.ts b/packages/backend-test-utils/src/database/TestDatabases.ts index a7f5ceb344..76d12ce8f1 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.ts @@ -91,9 +91,11 @@ export class TestDatabases { const databases = new TestDatabases(supportedIds); - afterAll(async () => { - await databases.shutdown(); - }); + if (supportedIds.length > 0) { + afterAll(async () => { + await databases.shutdown(); + }); + } return databases; } diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index f62b14b00d..5603fbc72a 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,82 @@ # example-backend +## 0.2.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.0 + - @backstage/plugin-search-backend@0.5.0-next.0 + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/plugin-auth-backend@0.13.0-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.3-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.2-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-tech-insights-backend@0.2.11-next.0 + - @backstage/plugin-techdocs-backend@1.0.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.20-next.0 + - example-app@0.2.70-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-badges-backend@0.1.25-next.0 + - @backstage/plugin-code-coverage-backend@0.1.29-next.0 + - @backstage/plugin-kafka-backend@0.2.24-next.0 + - @backstage/plugin-kubernetes-backend@0.4.14-next.0 + - @backstage/plugin-scaffolder-backend@1.0.1-next.0 + - @backstage/plugin-todo-backend@0.1.28-next.0 + - @backstage/plugin-app-backend@0.3.31-next.0 + - @backstage/plugin-permission-backend@0.5.6-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + - @backstage/backend-tasks@0.2.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.10-next.0 + - @backstage/plugin-graphql-backend@0.1.21-next.0 + - @backstage/plugin-proxy-backend@0.2.25-next.0 + - @backstage/plugin-rollbar-backend@0.1.28-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.0 + - @backstage/plugin-tech-insights-node@0.2.9-next.0 + +## 0.2.69 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app-backend@0.3.30 + - @backstage/plugin-azure-devops-backend@0.3.9 + - @backstage/plugin-badges-backend@0.1.24 + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/plugin-jenkins-backend@0.1.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.5 + - @backstage/plugin-tech-insights-backend@0.2.10 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.14 + - @backstage/plugin-todo-backend@0.1.27 + - @backstage/plugin-kubernetes-backend@0.4.13 + - @backstage/plugin-scaffolder-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/backend-tasks@0.2.1 + - @backstage/plugin-auth-backend@0.12.2 + - @backstage/plugin-code-coverage-backend@0.1.28 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-techdocs-backend@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-search-backend-node@0.5.2 + - example-app@0.2.69 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-graphql-backend@0.1.20 + - @backstage/plugin-kafka-backend@0.2.23 + - @backstage/plugin-permission-backend@0.5.5 + - @backstage/plugin-permission-node@0.5.5 + - @backstage/plugin-proxy-backend@0.2.24 + - @backstage/plugin-rollbar-backend@0.1.27 + - @backstage/plugin-search-backend@0.4.8 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.2 + - @backstage/plugin-tech-insights-node@0.2.8 + ## 0.2.68 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index a6f91e036f..bbaa6af5c8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.68", + "version": "0.2.70-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,43 +23,42 @@ "lint": "backstage-cli package lint", "test": "backstage-cli package test", "clean": "backstage-cli package clean", - "build-image": "docker build ../.. -f Dockerfile --tag example-backend", - "migrate:create": "knex migrate:make -x ts" + "build-image": "docker build ../.. -f Dockerfile --tag example-backend" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/backend-tasks": "^0.2.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-app-backend": "^0.3.29", - "@backstage/plugin-auth-backend": "^0.12.1", - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/plugin-azure-devops-backend": "^0.3.8", - "@backstage/plugin-badges-backend": "^0.1.23", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/plugin-code-coverage-backend": "^0.1.27", - "@backstage/plugin-graphql-backend": "^0.1.19", - "@backstage/plugin-jenkins-backend": "^0.1.18", - "@backstage/plugin-kubernetes-backend": "^0.4.12", - "@backstage/plugin-kafka-backend": "^0.2.22", - "@backstage/plugin-permission-backend": "^0.5.4", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-node": "^0.5.4", - "@backstage/plugin-proxy-backend": "^0.2.23", - "@backstage/plugin-rollbar-backend": "^0.1.26", - "@backstage/plugin-scaffolder-backend": "^0.18.0", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.3.4", - "@backstage/plugin-search-backend": "^0.4.7", - "@backstage/plugin-search-backend-node": "^0.5.1", - "@backstage/plugin-search-backend-module-elasticsearch": "^0.1.1", - "@backstage/plugin-search-backend-module-pg": "^0.3.1", - "@backstage/plugin-techdocs-backend": "^0.14.2", - "@backstage/plugin-tech-insights-backend": "^0.2.9", - "@backstage/plugin-tech-insights-node": "^0.2.7", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.13", - "@backstage/plugin-todo-backend": "^0.1.26", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-app-backend": "^0.3.31-next.0", + "@backstage/plugin-auth-backend": "^0.13.0-next.0", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/plugin-azure-devops-backend": "^0.3.10-next.0", + "@backstage/plugin-badges-backend": "^0.1.25-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/plugin-code-coverage-backend": "^0.1.29-next.0", + "@backstage/plugin-graphql-backend": "^0.1.21-next.0", + "@backstage/plugin-jenkins-backend": "^0.1.20-next.0", + "@backstage/plugin-kubernetes-backend": "^0.4.14-next.0", + "@backstage/plugin-kafka-backend": "^0.2.24-next.0", + "@backstage/plugin-permission-backend": "^0.5.6-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-node": "^0.5.6-next.0", + "@backstage/plugin-proxy-backend": "^0.2.25-next.0", + "@backstage/plugin-rollbar-backend": "^0.1.28-next.0", + "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.3.6-next.0", + "@backstage/plugin-search-backend": "^0.5.0-next.0", + "@backstage/plugin-search-backend-node": "^0.5.3-next.0", + "@backstage/plugin-search-backend-module-elasticsearch": "^0.1.3-next.0", + "@backstage/plugin-search-backend-module-pg": "^0.3.2-next.0", + "@backstage/plugin-techdocs-backend": "^1.0.1-next.0", + "@backstage/plugin-tech-insights-backend": "^0.2.11-next.0", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.0", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.15-next.0", + "@backstage/plugin-todo-backend": "^0.1.28-next.0", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "better-sqlite3": "^7.5.0", @@ -69,14 +68,13 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "express-prom-bundle": "^6.3.6", - "knex": "^1.0.2", "pg": "^8.3.0", "pg-connection-string": "^2.3.0", "prom-client": "^14.0.1", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/backend/src/plugins/permission.ts b/packages/backend/src/plugins/permission.ts index 7337687c54..e4c2e1d435 100644 --- a/packages/backend/src/plugins/permission.ts +++ b/packages/backend/src/plugins/permission.ts @@ -16,11 +16,11 @@ import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-permission-backend'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { - PermissionPolicy, + AuthorizeResult, PolicyDecision, -} from '@backstage/plugin-permission-node'; +} from '@backstage/plugin-permission-common'; +import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; diff --git a/packages/backend/src/plugins/techInsights.ts b/packages/backend/src/plugins/techInsights.ts index e249b1db9b..94ce27656a 100644 --- a/packages/backend/src/plugins/techInsights.ts +++ b/packages/backend/src/plugins/techInsights.ts @@ -36,6 +36,7 @@ export default async function createPlugin( logger: env.logger, config: env.config, database: env.database, + scheduler: env.scheduler, discovery: env.discovery, factRetrievers: [ createFactRetrieverRegistration({ diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index d9683f0fe2..4830fd8fb0 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/catalog-client +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 0163c41be2: **BREAKING**: Removed the deprecated `presence` field in the `Location` and `AddLocationRequest` types. This field was already being ignored by the catalog backend and can be safely removed. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.9.0 ### Minor Changes diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index ac8f712e11..5fcae87dd5 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -11,7 +11,6 @@ export type AddLocationRequest = { type?: string; target: string; dryRun?: boolean; - presence?: 'optional' | 'required'; }; // @public @@ -79,7 +78,7 @@ export class CatalogClient implements CatalogApi { }; }); addLocation( - { type, target, dryRun, presence }: AddLocationRequest, + { type, target, dryRun }: AddLocationRequest, options?: CatalogRequestOptions, ): Promise; getEntities( @@ -195,7 +194,6 @@ type Location_2 = { id: string; type: string; target: string; - presence?: 'optional' | 'required'; }; export { Location_2 as Location }; ``` diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index b05b0d45c1..8e4eed4cea 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "0.9.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/errors": "^0.2.2", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/errors": "^1.0.0", "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 85aada616c..371ac55db4 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -272,7 +272,7 @@ export class CatalogClient implements CatalogApi { * {@inheritdoc CatalogApi.addLocation} */ async addLocation( - { type = 'url', target, dryRun, presence }: AddLocationRequest, + { type = 'url', target, dryRun }: AddLocationRequest, options?: CatalogRequestOptions, ): Promise { const response = await this.fetchApi.fetch( @@ -285,7 +285,7 @@ export class CatalogClient implements CatalogApi { ...(options?.token && { Authorization: `Bearer ${options?.token}` }), }, method: 'POST', - body: JSON.stringify({ type, target, presence }), + body: JSON.stringify({ type, target }), }, ); diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index ba0c5e9359..389b82c081 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -244,8 +244,6 @@ export type Location = { id: string; type: string; target: string; - /** @deprecated This field is is ignored */ - presence?: 'optional' | 'required'; }; /** @@ -257,8 +255,6 @@ export type AddLocationRequest = { type?: string; target: string; dryRun?: boolean; - /** @deprecated This field is is ignored */ - presence?: 'optional' | 'required'; }; /** diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 9bb530efac..589495bd7a 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/catalog-model +## 1.0.1-next.0 + +### Patch Changes + +- e0a51384ac: build(deps): bump `ajv` from 7.0.3 to 8.10.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 02ad19d189: **BREAKING**: Removed the deprecated `metadata.generation` field entirely. It is no longer present in TS types, nor in the REST API output. Entities that have not yet been re-stitched may still have the field present for some time, but it will get phased out gradually by your catalog instance. +- a04dbc22d7: **BREAKING**: Removed `EntityName`, use `CompoundEntityRef` type instead. + + **BREAKING**: Removed `getEntityName`, use `getCompoundEntityRef` instead. + +- 132189e466: **BREAKING**: The User kind has an updated TypeScript type where `spec.memberOf` + is optional. + + **NOTE HOWEVER**, that this only applies to the TypeScript types `UserEntity` + and `UserEntityV1alpha1`. The catalog validation still requires the field to be + set, even if it's in the form of an empty array. If you try to ingest data that + stops producing this field, those entities _will be rejected_ by the catalog. + The reason for these choices is that consumers will get a long grace period + where old code still can rely on the underlying data being present, giving users + ample time to update before actual breakages could happen. + +- d3e9ec43b7: **BREAKING**: Removed the `target` property from `EntityRelation`. This field has been replaced by `targetRef`. + This means that `target: { name: 'team-a', kind: 'group', namespace: 'default' }` is now replaced with `targetRef: 'group:default/team-a'` in entity relations. + + The entities API endpoint still return the old `target` field for to ease transitions, however the future removal of this field will be considered non breaking. + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.13.0 ### Minor Changes diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 618c1f7b69..fca23d6ab9 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -447,7 +447,7 @@ interface UserEntityV1alpha1 extends Entity { email?: string; picture?: string; }; - memberOf: string[]; + memberOf?: string[]; }; } export { UserEntityV1alpha1 as UserEntity }; diff --git a/packages/catalog-model/examples/apis/swapi-graphql.yaml b/packages/catalog-model/examples/apis/swapi-graphql.yaml index f3d924fc53..ccddc537e7 100644 --- a/packages/catalog-model/examples/apis/swapi-graphql.yaml +++ b/packages/catalog-model/examples/apis/swapi-graphql.yaml @@ -529,7 +529,7 @@ spec: terrains: [String] """ - The percentage of the planet surface that is naturally occuring water or bodies + The percentage of the planet surface that is naturally occurring water or bodies of water. """ surfaceWater: Float diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index d07a5a6861..4fec087f11 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "0.13.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,16 +34,16 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/types": "^0.1.3", - "ajv": "^7.0.3", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/types": "^1.0.0", + "ajv": "^8.10.0", "json-schema": "^0.4.0", "lodash": "^4.17.21", "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "@types/json-schema": "^7.0.5", "@types/lodash": "^4.14.151", diff --git a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts index 7f33e71f1c..1b5b7fe79b 100644 --- a/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/SchemaValidEntityPolicy.ts @@ -54,7 +54,7 @@ export class SchemaValidEntityPolicy implements EntityPolicy { } throw new Error( - `Malformed envelope, ${error.dataPath || ''} ${error.message}`, + `Malformed envelope, ${error.instancePath || ''} ${error.message}`, ); } } diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 82aaebd9d6..55c9b176ea 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -32,7 +32,7 @@ export interface UserEntityV1alpha1 extends Entity { email?: string; picture?: string; }; - memberOf: string[]; + memberOf?: string[]; }; } diff --git a/packages/catalog-model/src/validation/ajv.ts b/packages/catalog-model/src/validation/ajv.ts index c65c3df18a..15d0b674bf 100644 --- a/packages/catalog-model/src/validation/ajv.ts +++ b/packages/catalog-model/src/validation/ajv.ts @@ -41,7 +41,7 @@ export function throwAjvError( const error = errors[0]; throw new TypeError( - `${error.dataPath || ''} ${error.message}${ + `${error.instancePath || ''} ${error.message}${ error.params ? ` - ${Object.entries(error.params) .map(([key, val]) => `${key}: ${val}`) diff --git a/packages/catalog-model/src/validation/entityKindSchemaValidator.ts b/packages/catalog-model/src/validation/entityKindSchemaValidator.ts index 66259cc63d..dc5aa586aa 100644 --- a/packages/catalog-model/src/validation/entityKindSchemaValidator.ts +++ b/packages/catalog-model/src/validation/entityKindSchemaValidator.ts @@ -74,7 +74,7 @@ export function entityKindSchemaValidator( // Only in the case where kind and/or apiVersion have enum mismatches AND // have NO other errors, we call it a soft error. const softCandidates = validate.errors?.filter(e => - ['/kind', '/apiVersion'].includes(e.dataPath), + ['/kind', '/apiVersion'].includes(e.instancePath), ); if ( softCandidates?.length && diff --git a/packages/cli/.snyk b/packages/cli/.snyk index 0a32a6ac12..d012272068 100644 --- a/packages/cli/.snyk +++ b/packages/cli/.snyk @@ -57,6 +57,11 @@ ignore: reason: This is a development dependency only and is therefore acceptable expires: 2031-09-06T17:18:55.027Z created: 2021-09-06T17:18:55.027Z + 'snyk:lic:npm:eslint-plugin-deprecation:LGPL-3.0': + - '*': + reason: This is a development dependency only and is therefore acceptable + expires: 2032-03-22T13:37:00.000Z + created: 2022-03-22T13:37:00.000Z SNYK-JS-UNSETVALUE-2400660: - '*': reason: >- diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 08704d48a8..0887303913 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/cli +## 0.16.1-next.0 + +### Patch Changes + +- 6ad0c45648: Added an experimental `package fix` command which applies automated fixes to the target package. The initial fix that is available is to add missing monorepo dependencies to the target package. +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. +- 948a56f401: Added a new experimental `repo list-deprecations` command, which scans the entire project for usage of deprecated APIs. +- Updated dependencies + - @backstage/config-loader@1.0.1-next.0 + +## 0.16.0 + +### Minor Changes + +- 217547ae51: **BREAKING**: The provided Jest configuration now only matches files with a `.test.` infix, rather than any files that is suffixed with `test.`. In particular this means that files named just `test.ts` will no longer be considered a test file. + +### Patch Changes + +- 947ae3b40e: Applied the fix from version `0.15.3` of this package, which is part of the `v0.71.1` release of Backstage. +- 19eed0edd9: Fix for `overrides` not being properly forwarded from the extra configuration passed to `@backstage/cli/config/eslint-factory`. +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/config-loader@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.15.3 ### Patch Changes diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 1472260e33..6b9201559b 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -36,6 +36,25 @@ const transformIgnorePattern = [ 'typescript', ].join('|'); +// Provides additional config that's based on the role of the target package +function getRoleConfig(role) { + switch (role) { + case 'frontend': + case 'web-library': + case 'common-library': + case 'frontend-plugin': + case 'frontend-plugin-module': + return { testEnvironment: 'jsdom' }; + case 'cli': + case 'backend': + case 'node-library': + case 'backend-plugin': + case 'backend-plugin-module': + default: + return { testEnvironment: 'node' }; + } +} + async function getProjectConfig(targetPath, displayName) { const configJsPath = path.resolve(targetPath, 'jest.config.js'); const configTsPath = path.resolve(targetPath, 'jest.config.ts'); @@ -50,6 +69,7 @@ async function getProjectConfig(targetPath, displayName) { // All configs are merged together to create the final config, with longer paths taking precedence. // The merging of the configs is shallow, meaning e.g. all transforms are replaced if new ones are defined. const pkgJsonConfigs = []; + let closestPkgJson = undefined; let currentPath = targetPath; // Some sanity check to avoid infinite loop @@ -59,6 +79,9 @@ async function getProjectConfig(targetPath, displayName) { if (exists) { try { const data = fs.readJsonSync(packagePath); + if (!closestPkgJson) { + closestPkgJson = data; + } if (data.jest) { pkgJsonConfigs.unshift(data.jest); } @@ -115,6 +138,8 @@ async function getProjectConfig(targetPath, displayName) { testMatch: ['**/*.test.{js,jsx,ts,tsx,mjs,cjs}'], transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`], + + ...getRoleConfig(closestPkgJson?.backstage?.role), }; // Use src/setupTests.ts as the default location for configuring test env diff --git a/packages/cli/package.json b/packages/cli/package.json index 43e8ea04eb..a2ef979218 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.15.2", + "version": "0.16.1-next.0", "private": false, "publishConfig": { "access": "public" @@ -32,11 +32,11 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.8", - "@backstage/config": "^0.1.15", - "@backstage/config-loader": "^0.9.7", - "@backstage/errors": "^0.2.2", + "@backstage/config": "^1.0.0", + "@backstage/config-loader": "^1.0.1-next.0", + "@backstage/errors": "^1.0.0", "@backstage/release-manifests": "^0.0.2", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@hot-loader/react-dom-v16": "npm:@hot-loader/react-dom@^16.0.2", "@hot-loader/react-dom-v17": "npm:@hot-loader/react-dom@^17.0.2", "@manypkg/get-packages": "^1.1.3", @@ -45,9 +45,9 @@ "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-yaml": "^3.1.0", - "@spotify/eslint-config-base": "^12.0.0", + "@spotify/eslint-config-base": "^13.0.0", "@spotify/eslint-config-react": "^12.0.0", - "@spotify/eslint-config-typescript": "^12.0.0", + "@spotify/eslint-config-typescript": "^13.0.0", "@sucrase/jest-plugin": "^2.1.1", "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "6.2.x", @@ -68,15 +68,16 @@ "esbuild": "^0.14.10", "esbuild-loader": "^2.18.0", "eslint": "^8.6.0", - "eslint-webpack-plugin": "^2.6.0", "eslint-config-prettier": "^8.3.0", "eslint-formatter-friendly": "^7.0.0", + "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^25.3.4", + "eslint-plugin-jest": "^26.1.2", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-monorepo": "^0.3.2", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", + "eslint-webpack-plugin": "^3.1.1", "express": "^4.17.1", "fork-ts-checker-webpack-plugin": "^7.0.0-alpha.8", "fs-extra": "10.0.1", @@ -84,15 +85,15 @@ "handlebars": "^4.7.3", "html-webpack-plugin": "^5.3.1", "inquirer": "^8.2.0", - "jest": "^26.0.1", + "jest": "^27.5.1", "jest-css-modules": "^2.1.0", - "json-schema": "^0.4.0", "jest-transform-yaml": "^1.0.0", + "json-schema": "^0.4.0", "lodash": "^4.17.21", - "minimatch": "5.0.1", "mini-css-extract-plugin": "^2.4.2", - "npm-packlist": "^3.0.0", + "minimatch": "5.0.1", "node-libs-browser": "^2.2.1", + "npm-packlist": "^3.0.0", "ora": "^5.3.0", "postcss": "^8.1.0", "process": "^0.11.10", @@ -121,13 +122,13 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", @@ -147,9 +148,9 @@ "@types/yarnpkg__lockfile": "^1.1.4", "del": "^6.0.0", "mock-fs": "^5.1.0", + "msw": "^0.35.0", "nodemon": "^2.0.2", - "ts-node": "^10.0.0", - "msw": "^0.35.0" + "ts-node": "^10.0.0" }, "peerDependencies": { "@microsoft/api-extractor": "^7.19.2" diff --git a/packages/cli/src/commands/fix.ts b/packages/cli/src/commands/fix.ts new file mode 100644 index 0000000000..e4cdc4dc84 --- /dev/null +++ b/packages/cli/src/commands/fix.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2020 The 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 { paths } from '../lib/paths'; +import { ESLint } from 'eslint'; +import { join as joinPath, basename } from 'path'; +import fs from 'fs-extra'; +import { isChildPath } from '@backstage/cli-common'; +import { PackageGraph } from '../lib/monorepo'; + +function isTestPath(filePath: string) { + if (!isChildPath(joinPath(paths.targetDir, 'src'), filePath)) { + return true; + } + const name = basename(filePath); + return ( + name.startsWith('setupTests.') || + name.includes('.test.') || + name.includes('.stories.') + ); +} + +export async function command() { + const pkgJsonPath = paths.resolveTarget('package.json'); + const pkg = await fs.readJson(pkgJsonPath); + if (pkg.workspaces) { + throw new Error( + 'Adding dependencies to the workspace root is not supported', + ); + } + + const packages = await PackageGraph.listTargetPackages(); + const localPackageVersions = new Map( + packages.map(p => [p.packageJson.name, p.packageJson.version]), + ); + + const eslint = new ESLint({ + cwd: paths.targetDir, + overrideConfig: { + plugins: ['monorepo'], + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: [ + `!${joinPath(paths.targetDir, 'src/**')}`, + joinPath(paths.targetDir, 'src/**/*.test.*'), + joinPath(paths.targetDir, 'src/**/*.stories.*'), + joinPath(paths.targetDir, 'src/setupTests.*'), + ], + optionalDependencies: true, + peerDependencies: true, + bundledDependencies: true, + }, + ], + }, + }, + extensions: ['jsx', 'ts', 'tsx', 'mjs', 'cjs'], + }); + + const results = await eslint.lintFiles(['.']); + + const addedDeps = new Set(); + const addedDevDeps = new Set(); + const removedDevDeps = new Set(); + + for (const result of results) { + for (const message of result.messages) { + // Just in case + if (message.ruleId !== 'import/no-extraneous-dependencies') { + continue; + } + + const match = message.message.match(/^'([^']*)' should be listed/); + if (!match) { + continue; + } + const packageName = match[1]; + if (!localPackageVersions.has(packageName)) { + continue; + } + + if (message.message.endsWith('not devDependencies.')) { + addedDeps.add(packageName); + removedDevDeps.add(packageName); + } else if (isTestPath(result.filePath)) { + addedDevDeps.add(packageName); + } else { + addedDeps.add(packageName); + } + } + } + + if (addedDeps.size || addedDevDeps.size || removedDevDeps.size) { + for (const name of addedDeps) { + if (!pkg.dependencies) { + pkg.dependencies = {}; + } + pkg.dependencies[name] = `^${localPackageVersions.get(name)}`; + } + for (const name of addedDevDeps) { + if (!pkg.devDependencies) { + pkg.devDependencies = {}; + } + pkg.devDependencies[name] = `^${localPackageVersions.get(name)}`; + } + for (const name of removedDevDeps) { + delete pkg.devDependencies[name]; + } + if (Object.keys(pkg.devDependencies).length === 0) { + delete pkg.devDependencies; + } + + if (pkg.dependencies) { + pkg.dependencies = Object.fromEntries( + Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)), + ); + } + if (pkg.devDependencies) { + pkg.devDependencies = Object.fromEntries( + Object.entries(pkg.devDependencies).sort(([a], [b]) => + a.localeCompare(b), + ), + ); + } + + await fs.writeJson(pkgJsonPath, pkg, { spaces: 2 }); + } +} diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index bc427bb19b..2f345e8663 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -59,6 +59,14 @@ export function registerRepoCommand(program: CommanderStatic) { ) .option('--fix', 'Attempt to automatically fix violations') .action(lazy(() => import('./repo/lint').then(m => m.command))); + + command + .command('list-deprecations', { hidden: true }) + .description('List deprecations. [EXPERIMENTAL]') + .option('--json', 'Output as JSON') + .action( + lazy(() => import('./repo/list-deprecations').then(m => m.command)), + ); } export function registerScriptCommand(program: CommanderStatic) { @@ -125,6 +133,12 @@ export function registerScriptCommand(program: CommanderStatic) { .description('Run tests, forwarding args to Jest, defaulting to watch mode') .action(lazy(() => import('./testCommand').then(m => m.default))); + command + .command('fix', { hidden: true }) + .description('Applies automated fixes to the package. [EXPERIMENTAL]') + .option('--deps', 'Only fix monorepo dependencies in package.json') + .action(lazy(() => import('./fix').then(m => m.command))); + command .command('clean') .description('Delete cache directories') diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index 153bd16597..9a2a3b2e3e 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -63,7 +63,7 @@ export default async (cmd: Command) => { const data = await readPluginData(); const templateFiles = await diffTemplateFiles('default-plugin', data); await handleAllFiles(fileHandlers, templateFiles, promptFunc); - await finalize(); + finalize(); }; // Reads templating data from the existing plugin diff --git a/packages/cli/src/commands/repo/list-deprecations.ts b/packages/cli/src/commands/repo/list-deprecations.ts new file mode 100644 index 0000000000..b53118cc40 --- /dev/null +++ b/packages/cli/src/commands/repo/list-deprecations.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2020 The 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 chalk from 'chalk'; +import { ESLint } from 'eslint'; +import { Command } from 'commander'; +import { join as joinPath, relative as relativePath } from 'path'; +import { paths } from '../../lib/paths'; +import { PackageGraph } from '../../lib/monorepo'; + +export async function command(cmd: Command) { + const packages = await PackageGraph.listTargetPackages(); + + const eslint = new ESLint({ + cwd: paths.targetDir, + overrideConfig: { + plugins: ['deprecation'], + rules: { + 'deprecation/deprecation': 'error', + }, + parserOptions: { + project: [paths.resolveTargetRoot('tsconfig.json')], + }, + }, + extensions: ['jsx', 'ts', 'tsx', 'mjs', 'cjs'], + }); + + const { stderr } = process; + if (stderr.isTTY) { + stderr.write('Initializing TypeScript...'); + } + + const deprecations = []; + for (const [index, pkg] of packages.entries()) { + const results = await eslint.lintFiles(joinPath(pkg.dir, 'src')); + for (const result of results) { + for (const message of result.messages) { + if (message.ruleId !== 'deprecation/deprecation') { + continue; + } + + const path = relativePath(paths.targetRoot, result.filePath); + deprecations.push({ + path, + message: message.message, + line: message.line, + column: message.column, + }); + } + } + + if (stderr.isTTY) { + stderr.clearLine(0); + stderr.cursorTo(0); + stderr.write(`Scanning packages ${index + 1}/${packages.length}`); + } + } + + if (stderr.isTTY) { + stderr.clearLine(0); + stderr.cursorTo(0); + } + + if (cmd.json) { + console.log(JSON.stringify(deprecations, null, 2)); + } else { + for (const d of deprecations) { + const location = `${d.path}:${d.line}:${d.column}`; + const wrappedMessage = d.message.replace(/\r?\n\s*/g, ' '); + console.log(`${location} - ${chalk.yellow(wrappedMessage)}`); + } + } + + if (deprecations.length > 0) { + process.exit(1); + } +} diff --git a/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts index 43e5cbc526..2533131a40 100644 --- a/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts +++ b/packages/cli/src/lib/builder/buildTypeDefinitionsWorker.ts @@ -16,7 +16,7 @@ /** * NOTE: This is a worker thread function that is stringified and executed - * withing a `worker_threads.Worker`. Everything in this function must + * within a `worker_threads.Worker`. Everything in this function must * be self-contained. * Using TypeScript is fine as it is transpiled before being stringified. */ diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts index bd4ab42098..1b161976e3 100644 --- a/packages/cli/src/lib/bundler/backend.ts +++ b/packages/cli/src/lib/bundler/backend.ts @@ -35,9 +35,8 @@ export async function serveBackend(options: BackendServeOptions) { const waitForExit = async () => { for (const signal of ['SIGINT', 'SIGTERM'] as const) { process.on(signal, () => { - compiler.close(() => console.log('Stopped watcher')); // exit instead of resolve. The process is shutting down and resolving a promise here logs an error - process.exit(); + compiler.close(() => process.exit()); }); } diff --git a/packages/cli/src/lib/diff/types.ts b/packages/cli/src/lib/diff/types.ts index d22d690196..ba649f66ff 100644 --- a/packages/cli/src/lib/diff/types.ts +++ b/packages/cli/src/lib/diff/types.ts @@ -19,7 +19,7 @@ export type WriteFileFunc = (contents: string) => Promise; export type FileDiff = { // Relative path within the target directory path: string; - // Wether the target file exists in the target directory. + // Whether the target file exists in the target directory. missing: boolean; // Contents of the file in the target directory, or an empty string if the file is missing. targetContents: string; diff --git a/packages/cli/src/lib/role/packageRoles.test.ts b/packages/cli/src/lib/role/packageRoles.test.ts index 759be462d2..2772d263f1 100644 --- a/packages/cli/src/lib/role/packageRoles.test.ts +++ b/packages/cli/src/lib/role/packageRoles.test.ts @@ -152,7 +152,6 @@ describe('detectRoleFromPackage', () => { lint: 'backstage-cli lint', test: 'backstage-cli test', clean: 'backstage-cli clean', - 'migrate:create': 'knex migrate:make -x ts', }, }), ).toEqual('backend'); diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 73b9e54575..4462da88e9 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/codemods +## 0.1.36 + +### Patch Changes + +- 224441d0f9: Inlined the table of symbols used by the `core-imports` codemod so that future updates to the core packages don't break the codemod. An entry for has also been added to direct imports of `createApp` to `@backstage/app-defaults`. + ## 0.1.35 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 7c1e9a0d12..a4cd09a0fd 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.35", + "version": "0.1.36", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 50ec2afac1..3340633857 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/config-loader +## 1.0.1-next.0 + +### Patch Changes + +- e0a51384ac: build(deps): bump `ajv` from 7.0.3 to 8.10.0 +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- 664821371e: The `typescript-json-schema` dependency that is used during schema collection is now lazy loaded, as it eagerly loads in the TypeScript compiler. +- f910c2a3f8: build(deps): bump `typescript-json-schema` from 0.52.0 to 0.53.0 +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.9.7 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 027c4a605d..051048ded6 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.9.7", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -35,11 +35,11 @@ }, "dependencies": { "@backstage/cli-common": "^0.1.8", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/types": "^0.1.3", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/types": "^1.0.0", "@types/json-schema": "^7.0.6", - "ajv": "^7.0.3", + "ajv": "^8.10.0", "chokidar": "^3.5.2", "fs-extra": "10.0.1", "json-schema": "^0.4.0", diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 877cbb54e3..2754f788dd 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -28,6 +28,9 @@ const mockSchema = { }, }; +// Gotta make sure this is in the compiler cache before we start mocking the filesystem +require('typescript-json-schema'); + // 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 diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts index 3ff161cec4..98feffebb2 100644 --- a/packages/config-loader/src/lib/schema/collect.ts +++ b/packages/config-loader/src/lib/schema/collect.ts @@ -22,7 +22,6 @@ import { sep, } from 'path'; import { ConfigSchemaPackageEntry } from './types'; -import { getProgramFromFiles, generateSchema } from 'typescript-json-schema'; import { JsonObject } from '@backstage/types'; import { assertError } from '@backstage/errors'; @@ -149,7 +148,7 @@ export async function collectConfigSchemas( ...packagePaths.map(path => processItem({ name: path, packagePath: path })), ]); - const tsSchemas = compileTsSchemas(tsSchemaPaths); + const tsSchemas = await compileTsSchemas(tsSchemaPaths); return schemas.concat(tsSchemas); } @@ -157,11 +156,17 @@ export async function collectConfigSchemas( // This handles the support of TypeScript .d.ts config schema declarations. // We collect all typescript schema definition and compile them all in one go. // This is much faster than compiling them separately. -function compileTsSchemas(paths: string[]) { +async function compileTsSchemas(paths: string[]) { if (paths.length === 0) { return []; } + // Lazy loaded, because this brings up all of TypeScript and we don't + // want that eagerly loaded in tests + const { getProgramFromFiles, generateSchema } = await import( + 'typescript-json-schema' + ); + const program = getProgramFromFiles(paths, { incremental: false, isolatedModules: true, diff --git a/packages/config-loader/src/lib/schema/compile.test.ts b/packages/config-loader/src/lib/schema/compile.test.ts index 7b7db87c8f..999dabd307 100644 --- a/packages/config-loader/src/lib/schema/compile.test.ts +++ b/packages/config-loader/src/lib/schema/compile.test.ts @@ -32,9 +32,9 @@ describe('compileConfigSchemas', () => { errors: [ { keyword: 'type', - dataPath: '/a', + instancePath: '/a', schemaPath: '#/properties/a/type', - message: 'should be string', + message: 'must be string', params: { type: 'string' }, }, ], @@ -46,9 +46,9 @@ describe('compileConfigSchemas', () => { errors: [ { keyword: 'type', - dataPath: '/b', + instancePath: '/b', schemaPath: '#/properties/b/type', - message: 'should be number', + message: 'must be number', params: { type: 'number' }, }, ], diff --git a/packages/config-loader/src/lib/schema/compile.ts b/packages/config-loader/src/lib/schema/compile.ts index b7f705751e..c1059a28ae 100644 --- a/packages/config-loader/src/lib/schema/compile.ts +++ b/packages/config-loader/src/lib/schema/compile.ts @@ -57,11 +57,11 @@ export function compileConfigSchemas( }, compile(visibility: ConfigVisibility) { return (_data, context) => { - if (context?.dataPath === undefined) { + if (context?.instancePath === undefined) { return false; } if (visibility && visibility !== 'backend') { - const normalizedPath = context.dataPath.replace( + const normalizedPath = context.instancePath.replace( /\['?(.*?)'?\]/g, (_, segment) => `/${segment}`, ); @@ -77,10 +77,10 @@ export function compileConfigSchemas( metaSchema: { type: 'string' }, compile(deprecationDescription: string) { return (_data, context) => { - if (context?.dataPath === undefined) { + if (context?.instancePath === undefined) { return false; } - const normalizedPath = context.dataPath.replace( + const normalizedPath = context.instancePath.replace( /\['?(.*?)'?\]/g, (_, segment) => `/${segment}`, ); diff --git a/packages/config-loader/src/lib/schema/filtering.test.ts b/packages/config-loader/src/lib/schema/filtering.test.ts index df06975c9f..3a5de7e2c0 100644 --- a/packages/config-loader/src/lib/schema/filtering.test.ts +++ b/packages/config-loader/src/lib/schema/filtering.test.ts @@ -244,21 +244,21 @@ describe('filterErrorsByVisibility', () => { const errors = [ { keyword: 'something', - dataPath: '/a', + instancePath: '/a', schemaPath: '#/properties/a/something', params: {}, message: 'a', }, { keyword: 'something', - dataPath: '/b', + instancePath: '/b', schemaPath: '#/properties/b/something', params: {}, message: 'b', }, { keyword: 'something', - dataPath: '/c', + instancePath: '/c', schemaPath: '#/properties/c/something', params: {}, message: 'c', @@ -314,35 +314,35 @@ describe('filterErrorsByVisibility', () => { const errors = [ { keyword: 'type', - dataPath: '/a', + instancePath: '/a', schemaPath: '#/properties/a/type', params: { type: 'number' }, message: 'a', }, { keyword: 'type', - dataPath: '/b', + instancePath: '/b', schemaPath: '#/properties/b/type', params: { type: 'string' }, message: 'b', }, { keyword: 'type', - dataPath: '/c', + instancePath: '/c', schemaPath: '#/properties/c/type', params: { type: 'array' }, message: 'c', }, { keyword: 'type', - dataPath: '/c', + instancePath: '/c', schemaPath: '#/properties/c/type', params: { type: 'object' }, message: 'd', }, { keyword: 'type', - dataPath: '/c', + instancePath: '/c', schemaPath: '#/properties/c/type', params: { type: 'null' }, message: 'e', @@ -373,21 +373,21 @@ describe('filterErrorsByVisibility', () => { const errors = [ { keyword: 'required', - dataPath: '/a', + instancePath: '/a', schemaPath: '#/properties/o/required', params: { missingProperty: 'a' }, message: 'a', }, { keyword: 'required', - dataPath: '/b', + instancePath: '/b', schemaPath: '#/properties/o/required', params: { missingProperty: 'b' }, message: 'b', }, { keyword: 'required', - dataPath: '/c', + instancePath: '/c', schemaPath: '#/properties/o/required', params: { missingProperty: 'c' }, message: 'c', diff --git a/packages/config-loader/src/lib/schema/filtering.ts b/packages/config-loader/src/lib/schema/filtering.ts index 2855979b1f..e9a0327e06 100644 --- a/packages/config-loader/src/lib/schema/filtering.ts +++ b/packages/config-loader/src/lib/schema/filtering.ts @@ -173,7 +173,7 @@ export function filterErrorsByVisibility( } const vis = - visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY; + visibilityByDataPath.get(error.instancePath) ?? DEFAULT_CONFIG_VISIBILITY; return vis && includeVisibilities.includes(vis); }); } diff --git a/packages/config-loader/src/lib/schema/load.test.ts b/packages/config-loader/src/lib/schema/load.test.ts index 8510ccb1d8..41971f7d01 100644 --- a/packages/config-loader/src/lib/schema/load.test.ts +++ b/packages/config-loader/src/lib/schema/load.test.ts @@ -105,7 +105,7 @@ describe('loadConfigSchema', () => { expect(() => schema2.process([...configs, { data: { key1: 3 }, context: 'test2' }]), ).toThrow( - 'Config validation failed, Config should be string { type=string } at /key1', + 'Config validation failed, Config must be string { type=string } at /key1', ); await expect( @@ -142,7 +142,7 @@ describe('loadConfigSchema', () => { ]; expect(() => schema.process(configs)).toThrow( - 'Config validation failed, Config should be number { type=number } at /key2', + 'Config validation failed, Config must be number { type=number } at /key2', ); expect(schema.process(configs, { visibility: ['frontend'] })).toEqual([ { @@ -151,7 +151,7 @@ describe('loadConfigSchema', () => { }, ]); expect(() => schema.process(configs, { visibility: ['secret'] })).toThrow( - 'Config validation failed, Config should be number { type=number } at /key2', + 'Config validation failed, Config must be number { type=number } at /key2', ); }); @@ -203,12 +203,12 @@ describe('loadConfigSchema', () => { }, ]); expect(() => schema.process(mkConfig({ y: 1 }))).toThrow( - 'Config validation failed, Config should be string { type=string } at /nested/0/y', + 'Config validation failed, Config must be string { type=string } at /nested/0/y', ); expect(() => schema.process(mkConfig({ y: 1 }), { visibility: ['frontend'] }), ).toThrow( - 'Config validation failed, Config should be string { type=string } at /nested/0/y', + 'Config validation failed, Config must be string { type=string } at /nested/0/y', ); expect( schema.process(mkConfig({ x: 'a' }), { visibility: ['frontend'] }), @@ -229,7 +229,7 @@ describe('loadConfigSchema', () => { expect(() => schema.process(mkConfig({ y: 'aaaa' }), { visibility: ['frontend'] }), ).toThrow( - 'Config validation failed, Config should match pattern "^...$" { pattern=^...$ } at /nested/0/y', + 'Config validation failed, Config must match pattern "^...$" { pattern=^...$ } at /nested/0/y', ); // This is a bit of an edge case where we have a structural error, these should always be reported @@ -238,7 +238,7 @@ describe('loadConfigSchema', () => { visibility: ['frontend'], }), ).toThrow( - 'Config validation failed, Config should be array { type=array } at /nested', + 'Config validation failed, Config must be array { type=array } at /nested', ); }); }); @@ -273,7 +273,7 @@ describe('loadConfigSchema', () => { visibility: ['frontend'], }), ).toThrow( - "Config should have required property 'x a' { missingProperty=x a } at /other", + "Config must have required property 'x a' { missingProperty=x a } at /other", ); }); }); diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index aa7ed06305..959b438371 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -41,11 +41,11 @@ export type LoadConfigSchemaOptions = }; function errorsToError(errors: ValidationError[]): Error { - const messages = errors.map(({ dataPath, message, params }) => { + const messages = errors.map(({ instancePath, message, params }) => { const paramStr = Object.entries(params) .map(([name, value]) => `${name}=${value}`) .join(' '); - return `Config ${message || ''} { ${paramStr} } at ${dataPath}`; + return `Config ${message || ''} { ${paramStr} } at ${instancePath}`; }); const error = new Error(`Config validation failed, ${messages.join('; ')}`); (error as any).messages = messages; diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts index 75993b3c78..cc62192e30 100644 --- a/packages/config-loader/src/lib/schema/types.ts +++ b/packages/config-loader/src/lib/schema/types.ts @@ -53,7 +53,7 @@ export const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend'; */ export type ValidationError = { keyword: string; - dataPath: string; + instancePath: string; schemaPath: string; params: Record; propertyName?: string; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 9a3287e285..7ceb2e8548 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -226,7 +226,7 @@ export async function loadConfig( } } - const envConfigs = await readEnvConfig(process.env); + const envConfigs = readEnvConfig(process.env); const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => { let watchedFiles = Array.from(loadedPaths); diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 2af4d3763b..ce8d1a05f4 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/config +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.1.15 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 6b6bac4d3c..4f711b76e7 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.15", + "version": "1.0.0", "private": false, "publishConfig": { "access": "public", @@ -33,11 +33,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/test-utils": "^0.3.0", + "@backstage/test-utils": "^1.0.1-next.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index f286c3d787..3d3e851c80 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/core-app-api +## 1.0.1-next.0 + +### Patch Changes + +- a7bb762dab: fixed empty body issue for POST requests using FetchAPI with 'plugin://' prefix +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + - @backstage/version-bridge@1.0.0 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + ## 0.6.0 ### Minor Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index b1250172a7..6caaaf28b0 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.6.0", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,10 +33,10 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/types": "^0.1.3", - "@backstage/version-bridge": "^0.1.2", + "@backstage/config": "^1.0.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/types": "^1.0.0", + "@backstage/version-bridge": "^1.0.0", "@types/prop-types": "^15.7.3", "prop-types": "^15.7.2", "react-router-dom": "6.0.0-beta.0", @@ -49,8 +49,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts index 2f4ec6b536..157abf9d7c 100644 --- a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts +++ b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.test.ts @@ -90,4 +90,32 @@ describe('PluginProtocolResolverFetchMiddleware', () => { expect(resolve).toHaveBeenLastCalledWith(host); }, ); + + it('properly supports transferring request bodies too', async () => { + const resolve = jest.fn(); + const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi; + const middleware = new PluginProtocolResolverFetchMiddleware(discoveryApi); + const inner = jest.fn(); + const outer = middleware.apply(inner); + + resolve.mockResolvedValue('https://elsewhere.com'); + + await outer('plugin://a', { + method: 'POST', + body: '123', + }); + + expect(inner.mock.calls[0][0]).toBe('https://elsewhere.com'); + expect(inner.mock.calls[0][1].body).toBe('123'); + + await outer( + new Request('plugin://a', { + method: 'POST', + body: '123', + }), + ); + + expect(inner.mock.calls[1][0]).toBe('https://elsewhere.com'); + expect(inner.mock.calls[1][1].body).toEqual(Buffer.from('123', 'utf8')); + }); }); diff --git a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.ts b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.ts index 72834731fe..6265e8d3d2 100644 --- a/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.ts +++ b/packages/core-app-api/src/apis/implementations/FetchApi/PluginProtocolResolverFetchMiddleware.ts @@ -55,7 +55,7 @@ export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware { } const target = `${join(base, pathname)}${search}${hash}`; - return next(target, request); + return next(target, typeof input === 'string' ? init : input); }; } } diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 902e64c09f..ff7a43d0eb 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -190,7 +190,7 @@ export type AppOptions = { /** * A collection of ApiFactories to register in the application as default APIs. - * Theses APIs can not be overridden by plugin factories, but can be overridden + * These APIs cannot be overridden by plugin factories, but can be overridden * by plugin APIs provided through the * A collection of ApiFactories to register in the application to either * add new ones, or override factories provided by default or by plugins. 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 83d6035171..3f8622890b 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts @@ -185,7 +185,7 @@ describe('GheAuth AuthSessionStore', () => { await expect( withLogCollector(async () => { - await secondStore.setSession('no' as any); + secondStore.setSession('no' as any); }), ).resolves.toMatchObject({ warn: [ diff --git a/packages/core-app-api/src/routing/collectors.tsx b/packages/core-app-api/src/routing/collectors.tsx index 320f1a66d8..46734707a6 100644 --- a/packages/core-app-api/src/routing/collectors.tsx +++ b/packages/core-app-api/src/routing/collectors.tsx @@ -93,7 +93,7 @@ export const routeParentCollector = createCollector( 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 + // mount points within the children and remove the sticky state if (node.props?.path) { nextParent = routeRef; } else { diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 6bb05b9b6a..036385bcd6 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/core-components +## 0.9.3-next.0 + +### Patch Changes + +- 19648d5cf5: fix support config ref to use backstage/backstage + +## 0.9.2 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- 7c8cde4aa1: Change header style `word-wrap` from `break-all` to `break-word` +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.9.1 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 4b5be0537b..3406f8b876 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.9.1", + "version": "0.9.3-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,9 +33,9 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", + "@backstage/config": "^1.0.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", @@ -78,9 +78,9 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/core-app-api": "^0.6.0", - "@backstage/cli": "^0.15.2", - "@backstage/test-utils": "^0.3.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-components/src/components/EmptyState/EmptyState.test.tsx b/packages/core-components/src/components/EmptyState/EmptyState.test.tsx index 620e8d4d31..6487fcf4e6 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.test.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.test.tsx @@ -20,7 +20,7 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import Button from '@material-ui/core/Button'; describe('', () => { - it('render EmptyState component with type annotaion is missing', async () => { + it('render EmptyState component with type annotation is missing', async () => { const rendered = await renderWithEffects( wrapInTestApp( ( }, title: { color: theme.palette.bursts.fontColor, - wordBreak: 'break-all', + wordBreak: 'break-word', fontSize: theme.typography.h3.fontSize, marginBottom: 0, }, diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx index 4e2dcd8770..9ce4a9f997 100644 --- a/packages/core-components/src/layout/Sidebar/Page.tsx +++ b/packages/core-components/src/layout/Sidebar/Page.tsx @@ -155,7 +155,7 @@ export function SidebarPage(props: SidebarPageProps) { * This hook provides a react ref to the main content. * Allows to set an element as the main content and focus on that component. * - * *Note: If `contentRef` is not set `focusContent` is noop. `Content` component sets this ref automaticaly* + * *Note: If `contentRef` is not set `focusContent` is noop. `Content` component sets this ref automatically* * * @public * @example diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index a74e7dd6cc..08d469d5a3 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -110,7 +110,7 @@ export type SidebarContextType = { }; /** - * Context wether the `Sidebar` is open + * Context whether the `Sidebar` is open */ export const SidebarContext = createContext({ isOpen: false, diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 3702de3474..907645a3ba 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/core-plugin-api +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/version-bridge@1.0.0 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + ## 0.8.0 ### Minor Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 06463d378a..c47978f42b 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.8.0", + "version": "1.0.0", "private": false, "publishConfig": { "access": "public", @@ -33,9 +33,9 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/types": "^0.1.3", - "@backstage/version-bridge": "^0.1.2", + "@backstage/config": "^1.0.0", + "@backstage/types": "^1.0.0", + "@backstage/version-bridge": "^1.0.0", "history": "^5.0.0", "prop-types": "^15.7.2", "react-router-dom": "6.0.0-beta.0", @@ -46,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", - "@backstage/core-app-api": "^0.6.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index ac43bbebe8..946bd1d37a 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -17,7 +17,7 @@ import { ApiRef, createApiRef } from '../system'; /** - * Fetaure flag descriptor. + * Feature flag descriptor. * * @public */ diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 7c13b4ff0b..71b76f0e0a 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/create-app +## 0.4.24 + +### Patch Changes + +- 89c7e47967: Minor README update +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- efc73db10c: The main repo has switched from `@vscode/sqlite3` to `better-sqlite3` as its preferred SQLite installation. This decision was triggered by a number of issues with the former that arose because it needs build infrastructure in place and functional in order to be installed. The main drawback of this is that the new package uses the database client ID `better-sqlite3` instead of the plain `sqlite3`. + + If you want to perform the same switch in your own repository, + + - Replace all of your `package.json` dependencies on `@vscode/sqlite3` with the latest version of `better-sqlite3` instead + + ```diff + "dependencies": { + - "@vscode/sqlite3": "^5.0.7", + + "better-sqlite3": "^7.5.0", + ``` + + - In your app-config and tests, wherever you supply `client: 'sqlite3'`, instead supply `client: 'better-sqlite3` + + ```diff + backend: + database: + - client: sqlite3 + + client: better-sqlite3 + ``` + ## 0.4.23 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index cadd64b5ce..16dbc00d10 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.23", + "version": "0.4.24", "private": false, "publishConfig": { "access": "public" diff --git a/packages/create-app/templates/default-app/packages/README.md b/packages/create-app/templates/default-app/packages/README.md new file mode 100644 index 0000000000..6327fa0a99 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/README.md @@ -0,0 +1,9 @@ +# The Packages Folder + +This is where your own applications and centrally managed libraries live, each +in a separate folder of its own. + +From the start there's an `app` folder (for the frontend) and a `backend` folder +(for the Node backend), but you can also add more modules in here that house +your core additions and adaptations, such as themes, common React component +libraries, utilities, and similar. 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 69a09daef2..cd4d3e1222 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 @@ -13,8 +13,7 @@ "lint": "backstage-cli package lint", "test": "backstage-cli package test", "clean": "backstage-cli package clean", - "build-image": "docker build ../.. -f Dockerfile --tag backstage", - "migrate:create": "knex migrate:make -x ts" + "build-image": "docker build ../.. -f Dockerfile --tag backstage" }, "dependencies": { "app": "link:../app", @@ -41,7 +40,6 @@ "dockerode": "^3.3.1", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "knex": "^0.21.6", {{#if dbTypePG}} "pg": "^8.3.0", {{/if}} diff --git a/packages/create-app/templates/default-app/plugins/README.md b/packages/create-app/templates/default-app/plugins/README.md new file mode 100644 index 0000000000..58dc32cb08 --- /dev/null +++ b/packages/create-app/templates/default-app/plugins/README.md @@ -0,0 +1,9 @@ +# The Plugins Folder + +This is where your own plugins and their associated modules live, each in a +separate folder of its own. + +If you want to create a new plugin here, go to your project root directory, run +the command `yarn backstage-cli create`, and follow the on-screen instructions. + +You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 3e4a028e5f..9bd441f001 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/dev-utils +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/core-app-api@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/app-defaults@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/test-utils@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/app-defaults@1.0.0 + - @backstage/core-app-api@1.0.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/test-utils@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.2.25 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 1d77d87600..fba771a77f 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.2.25", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,14 +33,14 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/app-defaults": "^0.2.1", - "@backstage/core-app-api": "^0.6.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -59,7 +59,7 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 36d6abcfd1..9f9fcd8ca6 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -148,7 +148,7 @@ export class DevAppBuilder { } /** - * Adds an array of themes to overide the default theme. + * Adds an array of themes to override the default theme. */ addThemes(themes: AppTheme[]) { this.themes = themes; diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 4092b79fa0..72469875ef 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@backstage/cli-common": "^0.1.8", - "@backstage/errors": "^0.2.2", + "@backstage/errors": "^1.0.0", "@types/fs-extra": "^9.0.1", "@types/node": "^14.14.32", "@types/puppeteer": "^5.4.4", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 0c9295532a..20f938370b 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/errors +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.2.2 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index de20e7b5f2..104131b7b9 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "0.2.2", + "version": "1.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "cross-fetch": "^3.1.5", "serialize-error": "^8.0.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 9ded26d06b..df677fe2fa 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/integration-react +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + ## 0.1.25 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index fbd9739d08..93182d468b 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "0.1.25", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,10 +24,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/integration": "^0.8.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -38,9 +38,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/packages/integration-react/src/api/ScmAuth.test.ts b/packages/integration-react/src/api/ScmAuth.test.ts index 1ad391d8d2..7e174c4cbf 100644 --- a/packages/integration-react/src/api/ScmAuth.test.ts +++ b/packages/integration-react/src/api/ScmAuth.test.ts @@ -153,11 +153,11 @@ describe('ScmAuth', () => { githubAuth.getCredentials({ url: 'http://example.com', additionalScope: { - customScopes: { github: ['org:read', 'workflow:write'] }, + customScopes: { github: ['org:read', 'workflow'] }, }, }), ).resolves.toMatchObject({ - token: 'repo read:org read:user org:read workflow:write', + token: 'repo read:org read:user org:read workflow', }); const gitlabAuth = ScmAuth.forGitlab(mockAuthApi); diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index faac399f63..7df271fdd4 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/integration +## 1.0.1-next.0 + +### Patch Changes + +- 3ef123bbf0: Support external ID when assuming roles in S3 integration + + In order to assume a role created by a 3rd party as external + ID is needed. This change adds an optional field to the s3 + integration configuration and consumes that in the AwsS3UrlReader. + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- 403837cbac: Added an integration for Gerrit +- Updated dependencies + - @backstage/config@1.0.0 + ## 0.8.0 ### Minor Changes diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 84bca54a90..cdb260f089 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -35,6 +35,7 @@ export type AwsS3IntegrationConfig = { accessKeyId?: string; secretAccessKey?: string; roleArn?: string; + externalId?: string; }; // @public @@ -137,6 +138,7 @@ export class GerritIntegration implements ScmIntegration { export type GerritIntegrationConfig = { host: string; baseUrl?: string; + gitilesBaseUrl?: string; username?: string; password?: string; }; @@ -183,6 +185,17 @@ export function getBitbucketRequestOptions( headers: Record; }; +// @public +export function getGerritFileContentsApiUrl( + config: GerritIntegrationConfig, + url: string, +): string; + +// @public +export function getGerritRequestOptions(config: GerritIntegrationConfig): { + headers?: Record; +}; + // @public export function getGitHubFileFetchUrl( url: string, @@ -329,6 +342,9 @@ export interface IntegrationsByType { gitlab: ScmIntegrationsGroup; } +// @public +export function parseGerritJsonResponse(response: Response): Promise; + // @public export function readAwsS3IntegrationConfig( config: Config, diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index dc62695385..e2d26be28e 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -225,6 +225,12 @@ export interface Config { * @visibility backend */ roleArn?: string; + + /** + * External ID to use when assuming role + * @visibility backend + */ + externalId?: string; }>; }; } diff --git a/packages/integration/package.json b/packages/integration/package.json index 880cb8c1cf..c7c034c61e 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "0.8.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", + "@backstage/config": "^1.0.0", "cross-fetch": "^3.1.5", "git-url-parse": "^11.6.0", "@octokit/rest": "^18.5.3", @@ -42,9 +42,9 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", - "@backstage/config-loader": "^0.9.7-next.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/config-loader": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", "msw": "^0.35.0" diff --git a/packages/integration/src/awsS3/config.ts b/packages/integration/src/awsS3/config.ts index 45cbdbe210..477741b839 100644 --- a/packages/integration/src/awsS3/config.ts +++ b/packages/integration/src/awsS3/config.ts @@ -59,6 +59,11 @@ export type AwsS3IntegrationConfig = { * (Optional) ARN of role to be assumed */ roleArn?: string; + + /** + * (Optional) External ID to use when assuming role + */ + externalId?: string; }; /** @@ -98,6 +103,7 @@ export function readAwsS3IntegrationConfig( const accessKeyId = config.getOptionalString('accessKeyId'); const secretAccessKey = config.getOptionalString('secretAccessKey'); const roleArn = config.getOptionalString('roleArn'); + const externalId = config.getOptionalString('externalId'); return { host, @@ -106,6 +112,7 @@ export function readAwsS3IntegrationConfig( accessKeyId, secretAccessKey, roleArn, + externalId, }; } diff --git a/packages/integration/src/gerrit/config.test.ts b/packages/integration/src/gerrit/config.test.ts index ce1ddccd75..9c0d022548 100644 --- a/packages/integration/src/gerrit/config.test.ts +++ b/packages/integration/src/gerrit/config.test.ts @@ -56,6 +56,7 @@ describe('readGerritIntegrationConfig', () => { buildConfig({ host: 'a.com', baseUrl: 'https://a.com/api', + gitilesBaseUrl: 'https://a.com/git', username: 'u', password: 'p', }), @@ -63,6 +64,7 @@ describe('readGerritIntegrationConfig', () => { expect(output).toEqual({ host: 'a.com', baseUrl: 'https://a.com/api', + gitilesBaseUrl: 'https://a.com/git', username: 'u', password: 'p', }); @@ -77,6 +79,7 @@ describe('readGerritIntegrationConfig', () => { expect(output).toEqual({ host: 'a.com', baseUrl: 'https://a.com', + gitilesBaseUrl: 'https://a.com', username: undefined, password: undefined, }); @@ -107,6 +110,7 @@ describe('readGerritIntegrationConfig', () => { ).toEqual({ host: 'a.com', baseUrl: 'https://a.com/gerrit', + gitilesBaseUrl: 'https://a.com', }); }); }); @@ -116,7 +120,7 @@ describe('readGerritIntegrationConfigs', () => { return data.map(item => new ConfigReader(item)); } - it('reads all values', () => { + it('reads multiple configs', () => { const output = readGerritIntegrationConfigs( buildConfig([ { @@ -135,12 +139,14 @@ describe('readGerritIntegrationConfigs', () => { { host: 'a.com', baseUrl: 'https://a.com/api', + gitilesBaseUrl: 'https://a.com', username: 'u', password: 'p', }, { host: 'b.com', baseUrl: 'https://b.com/api', + gitilesBaseUrl: 'https://b.com', username: undefined, password: undefined, }, diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index 339b590c11..7dbb05d15b 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -38,6 +38,14 @@ export type GerritIntegrationConfig = { */ baseUrl?: string; + /** + * Optional base url for Gitiles. This is needed for creating a valid + * user-friendly url that can be used for browsing the content of the + * provider. If not set a default value will be created in the same way + * as the "baseUrl" option. + */ + gitilesBaseUrl?: string; + /** * The username to use for requests to gerrit. */ @@ -61,6 +69,7 @@ export function readGerritIntegrationConfig( ): GerritIntegrationConfig { const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); + let gitilesBaseUrl = config.getOptionalString('gitilesBaseUrl'); const username = config.getOptionalString('username'); const password = config.getOptionalString('password'); @@ -72,16 +81,26 @@ export function readGerritIntegrationConfig( throw new Error( `Invalid Gerrit integration config, '${baseUrl}' is not a valid baseUrl`, ); + } else if (gitilesBaseUrl && !isValidUrl(gitilesBaseUrl)) { + throw new Error( + `Invalid Gerrit integration config, '${gitilesBaseUrl}' is not a valid gitilesBaseUrl`, + ); } if (baseUrl) { baseUrl = trimEnd(baseUrl, '/'); } else { baseUrl = `https://${host}`; } + if (gitilesBaseUrl) { + gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/'); + } else { + gitilesBaseUrl = `https://${host}`; + } return { host, baseUrl, + gitilesBaseUrl, username, password, }; diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts new file mode 100644 index 0000000000..b966a31b9e --- /dev/null +++ b/packages/integration/src/gerrit/core.test.ts @@ -0,0 +1,177 @@ +/* + * Copyright 2020 The 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 { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import fetch from 'cross-fetch'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { GerritIntegrationConfig } from './config'; +import { + getGerritRequestOptions, + parseGerritJsonResponse, + parseGitilesUrl, + getGerritFileContentsApiUrl, +} from './core'; + +describe('gerrit core', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('getGerritRequestOptions', () => { + it('adds headers when a password is specified', () => { + const authRequest: GerritIntegrationConfig = { + host: 'gerrit.com', + username: 'U', + password: 'P', + }; + const anonymousRequest: GerritIntegrationConfig = { + host: 'gerrit.com', + }; + expect( + (getGerritRequestOptions(authRequest).headers as any).Authorization, + ).toEqual('Basic VTpQ'); + expect( + getGerritRequestOptions(anonymousRequest).headers as any, + ).toBeUndefined(); + }); + }); + + describe('parseGitilesUrl', () => { + it('can parse a valid gitiles urls.', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.com/gitiles', + }; + const { branch, filePath, project } = parseGitilesUrl( + config, + 'https://gerrit.com/gitiles/web/project/+/refs/heads/master/README.md', + ); + expect(project).toEqual('web/project'); + expect(branch).toEqual('master'); + expect(filePath).toEqual('README.md'); + + const { filePath: rootPath } = parseGitilesUrl( + config, + 'https://gerrit.com/gitiles/web/project/+/refs/heads/master', + ); + expect(rootPath).toEqual('/'); + }); + it('throws on incorrect gitiles urls.', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + }; + expect(() => + parseGitilesUrl( + config, + 'https://gerrit.com/+/refs/heads/master/README.md', + ), + ).toThrow(/project/); + expect(() => + parseGitilesUrl( + config, + 'https://gerrit.com/web/project/+/refs/changes/1/11/master/README.md', + ), + ).toThrow(/branch/); + }); + }); + + describe('getGerritFileContentsApiUrl', () => { + it('can create an url for anonymous access to the file fetch api.', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + baseUrl: 'https://gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + }; + const fileContentUrl = getGerritFileContentsApiUrl( + config, + 'https://gerrit.com/web/project/+/refs/heads/master/README.md', + ); + expect(fileContentUrl).toEqual( + 'https://gerrit.com/projects/web%2Fproject/branches/master/files/README.md/content', + ); + }); + it('can create an url for authenticated access to the file fetch api.', () => { + const authConfig: GerritIntegrationConfig = { + host: 'gerrit.com', + baseUrl: 'https://gerrit.com', + gitilesBaseUrl: 'https://gerrit.com', + username: 'u', + password: 'u', + }; + const authFileContentUrl = getGerritFileContentsApiUrl( + authConfig, + 'https://gerrit.com/web/project/+/refs/heads/master/README.md', + ); + expect(authFileContentUrl).toEqual( + 'https://gerrit.com/a/projects/web%2Fproject/branches/master/files/README.md/content', + ); + }); + }); + + describe('parseGerritJsonResponse', () => { + it('can strip the magic prefix from the response.', async () => { + const responseBody = ")]}'[]"; + const apiUrl = 'https://gerrit.com/projects/'; + worker.use( + rest.get(apiUrl, (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.text(responseBody), + ), + ), + ); + const response = await fetch(apiUrl, { method: 'GET' }); + const jsonData = await parseGerritJsonResponse(response); + expect(jsonData).toEqual([]); + }); + it('will throw if the magic prefix is missing from the response.', async () => { + const responseBody = '[]'; + const apiUrl = 'https://gerrit.com/projects/'; + worker.use( + rest.get(apiUrl, (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.text(responseBody), + ), + ), + ); + const response = await fetch(apiUrl, { method: 'GET' }); + await expect(parseGerritJsonResponse(response)).rejects.toThrow( + /body prefix missing/, + ); + }); + it('will throw on invalid json with the magic prefix.', async () => { + const responseBody = ")]}']{}["; + const apiUrl = 'https://gerrit.com/projects/'; + worker.use( + rest.get(apiUrl, (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.text(responseBody), + ), + ), + ); + const response = await fetch(apiUrl, { method: 'GET' }); + await expect(parseGerritJsonResponse(response)).rejects.toThrow( + /response from/, + ); + }); + }); +}); diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts new file mode 100644 index 0000000000..517d27d3ec --- /dev/null +++ b/packages/integration/src/gerrit/core.ts @@ -0,0 +1,165 @@ +/* + * 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 { trimStart } from 'lodash'; +import { GerritIntegrationConfig } from '.'; + +const GERRIT_BODY_PREFIX = ")]}'"; + +type GitFile = { + branch: string; + filePath: string; + project: string; +}; + +/** + * Parse a Gitiles URL and return branch, file path and project. + * + * @remarks + * + * Gerrit only handles code reviews so it does not have a native way to browse + * or showing the content of gits. Image if Github only had the "pull requests" + * tab. + * + * Any source code browsing is instead handled by optional services outside + * Gerrit. The url format chosen for the Gerrit url reader is the one used by + * the Gitiles project. Gerrit will work perfectly with Backstage without + * having Gitiles installed but there are some places in the Backstage GUI + * with links to the url used by the url reader. These will not work unless + * the urls point to an actual Gitiles installation. + * + * Gitiles url: + * https://g.com/optional_path/{project}/+/refs/heads/{branch}/{filePath} + * + * + * @param url - An URL pointing to a file stored in git. + * @public + */ + +export function parseGitilesUrl( + config: GerritIntegrationConfig, + url: string, +): GitFile { + const urlPath = url.replace(config.gitilesBaseUrl!, ''); + const parts = urlPath.split('/').filter(p => !!p); + + const projectEndIndex = parts.indexOf('+'); + + if (projectEndIndex <= 0) { + throw new Error(`Unable to parse project from url: ${url}`); + } + const project = trimStart(parts.slice(0, projectEndIndex).join('/'), '/'); + + const branchIndex = parts.indexOf('heads'); + if (branchIndex <= 0) { + throw new Error(`Unable to parse branch from url: ${url}`); + } + const branch = parts[branchIndex + 1]; + const filePath = parts.slice(branchIndex + 2).join('/'); + + return { + branch, + filePath: filePath === '' ? '/' : filePath, + project, + }; +} + +/** + * Return the authentication prefix. + * + * @remarks + * + * To authenticate with a password the API url must be prefixed with "/a/". + * If no password is set anonymous access (without the prefix) will + * be used. + * + * @param config - A Gerrit provider config. + * @public + */ +export function getAuthenticationPrefix( + config: GerritIntegrationConfig, +): string { + return config.password ? '/a/' : '/'; +} + +/** + * Return the url to fetch the contents of a file using the Gerrit API. + * + * @param url - An url pointing to a file in git. + * @public + */ +export function getGerritFileContentsApiUrl( + config: GerritIntegrationConfig, + url: string, +) { + const { branch, filePath, project } = parseGitilesUrl(config, url); + + return `${config.baseUrl}${getAuthenticationPrefix( + config, + )}projects/${encodeURIComponent( + project, + )}/branches/${branch}/files/${encodeURIComponent(filePath)}/content`; +} + +/** + * Return request headers for a Gerrit provider. + * + * @param config - A Gerrit provider config + * @public + */ +export function getGerritRequestOptions(config: GerritIntegrationConfig): { + headers?: Record; +} { + const headers: Record = {}; + + if (!config.password) { + return headers; + } + const buffer = Buffer.from(`${config.username}:${config.password}`, 'utf8'); + headers.Authorization = `Basic ${buffer.toString('base64')}`; + return { + headers, + }; +} + +/** + * Parse the json response from Gerrit and strip the magic prefix. + * + * @remarks + * + * To prevent against XSSI attacks the JSON response body from Gerrit starts + * with a magic prefix that must be stripped before it can be fed to a JSON + * parser. + * + * @param response - An API response. + * @public + */ +export async function parseGerritJsonResponse( + response: Response, +): Promise { + const responseBody = await response.text(); + if (responseBody.startsWith(GERRIT_BODY_PREFIX)) { + try { + return JSON.parse(responseBody.slice(GERRIT_BODY_PREFIX.length)); + } catch (ex) { + throw new Error( + `Invalid response from Gerrit: ${responseBody.slice(0, 10)} - ${ex}`, + ); + } + } + throw new Error( + `Gerrit JSON body prefix missing. Found: ${responseBody.slice(0, 10)}`, + ); +} diff --git a/packages/integration/src/gerrit/index.ts b/packages/integration/src/gerrit/index.ts index baad597a22..75e9779dbc 100644 --- a/packages/integration/src/gerrit/index.ts +++ b/packages/integration/src/gerrit/index.ts @@ -18,4 +18,10 @@ export { readGerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; +export { + getGerritFileContentsApiUrl, + getGerritRequestOptions, + parseGerritJsonResponse, +} from './core'; + export type { GerritIntegrationConfig } from './config'; diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index 7959a17d0c..9a4b005b2c 100644 --- a/packages/release-manifests/package.json +++ b/packages/release-manifests/package.json @@ -36,7 +36,7 @@ "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/test-utils": "^0.3.0", + "@backstage/test-utils": "^1.0.1-next.0", "msw": "^0.35.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" diff --git a/packages/search-common/CHANGELOG.md b/packages/search-common/CHANGELOG.md index 66262033e2..b0d255b47c 100644 --- a/packages/search-common/CHANGELOG.md +++ b/packages/search-common/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/search-common +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.0 + +## 0.3.2 + +### Patch Changes + +- 82b0190155: This package is no longer maintained. Use `@backstage/plugin-search-common`, going forward. +- Updated dependencies + - @backstage/plugin-search-common@0.3.2 + ## 0.3.1 ### Patch Changes diff --git a/packages/search-common/package.json b/packages/search-common/package.json index 800260c44c..d56f3dba8e 100644 --- a/packages/search-common/package.json +++ b/packages/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/search-common", "description": "No longer maintained. Use @backstage/plugin-search-common instead.", - "version": "0.3.1", + "version": "0.3.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -35,7 +35,7 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/plugin-search-common": "0.3.1" + "@backstage/plugin-search-common": "0.3.3-next.0" }, "devDependencies": {}, "jest": { diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 7f9482c7b3..c395e6823a 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,37 @@ # techdocs-cli-embedded-app +## 0.2.69-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog@1.0.1-next.0 + - @backstage/cli@0.16.1-next.0 + - @backstage/core-app-api@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/plugin-techdocs@1.0.1-next.0 + - @backstage/app-defaults@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/test-utils@1.0.1-next.0 + +## 0.2.68 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/app-defaults@1.0.0 + - @backstage/core-app-api@1.0.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/test-utils@1.0.0 + - @backstage/plugin-catalog@1.0.0 + - @backstage/plugin-techdocs@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + ## 0.2.67 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/app-config.yaml b/packages/techdocs-cli-embedded-app/app-config.yaml index 56e63ff97c..6ede05b587 100644 --- a/packages/techdocs-cli-embedded-app/app-config.yaml +++ b/packages/techdocs-cli-embedded-app/app-config.yaml @@ -7,4 +7,3 @@ backend: techdocs: builder: 'external' - requestUrl: http://localhost:3000/api diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 1104958262..c172f72a48 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,23 +1,23 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.67", + "version": "0.2.69-next.0", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.2.1", - "@backstage/catalog-model": "^0.13.0", - "@backstage/cli": "^0.15.2", - "@backstage/config": "^0.1.15", - "@backstage/core-app-api": "^0.6.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/plugin-techdocs": "^0.15.1", - "@backstage/test-utils": "^0.3.0", + "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/plugin-techdocs": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -29,7 +29,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/packages/techdocs-cli-embedded-app/src/apis.ts b/packages/techdocs-cli-embedded-app/src/apis.ts index 1fe6ba8086..d230a9f83c 100644 --- a/packages/techdocs-cli-embedded-app/src/apis.ts +++ b/packages/techdocs-cli-embedded-app/src/apis.ts @@ -64,17 +64,11 @@ class TechDocsDevStorageApi implements TechDocsStorageApi { } async getApiOrigin() { - return ( - this.configApi.getOptionalString('techdocs.requestUrl') ?? - (await this.discoveryApi.getBaseUrl('techdocs')) - ); + return await this.discoveryApi.getBaseUrl('techdocs'); } async getStorageUrl() { - return ( - this.configApi.getOptionalString('techdocs.storageUrl') ?? - `${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs` - ); + return `${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs`; } async getBuilder() { @@ -134,10 +128,7 @@ class TechDocsDevApi implements TechDocsApi { } async getApiOrigin() { - return ( - this.configApi.getOptionalString('techdocs.requestUrl') ?? - (await this.discoveryApi.getBaseUrl('techdocs')) - ); + return await this.discoveryApi.getBaseUrl('techdocs'); } async getEntityMetadata(_entityId: any) { diff --git a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx index f2dcefc00e..56686dd081 100644 --- a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx @@ -35,8 +35,8 @@ import { Content } from '@backstage/core-components'; import { Reader, - TechDocsPage, - TechDocsPageHeader, + TechDocsReaderPage, + TechDocsReaderPageHeader, } from '@backstage/plugin-techdocs'; const useStyles = makeStyles((theme: Theme) => ({ @@ -146,19 +146,19 @@ const DefaultTechDocsPage = () => { }; return ( - + {({ entityRef, onReady }) => ( <> - - + )} - + ); }; diff --git a/packages/techdocs-cli-embedded-app/src/config.ts b/packages/techdocs-cli-embedded-app/src/config.ts index 482ceb41ec..ae801dac2a 100644 --- a/packages/techdocs-cli-embedded-app/src/config.ts +++ b/packages/techdocs-cli-embedded-app/src/config.ts @@ -22,7 +22,6 @@ const PRODUCTION_CONFIG = { }, techdocs: { builder: 'external', - requestUrl: 'http://localhost:3000/api', }, }; @@ -32,7 +31,6 @@ const DEVELOPMENT_CONFIG = { }, techdocs: { builder: 'external', - requestUrl: 'http://localhost:7007/api', }, }; diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 6716a03e01..259a298d68 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,28 @@ # @techdocs/cli +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-techdocs-node@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-techdocs-node@1.0.0 + ## 0.8.17 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 42069bb8e2..b0843fc40d 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "0.8.17", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public" @@ -37,7 +37,7 @@ "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/commander": "^2.12.2", "@types/fs-extra": "^9.0.6", "@types/http-proxy": "^1.17.4", @@ -62,11 +62,11 @@ "ext": "ts" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/cli-common": "^0.1.8", - "@backstage/config": "^0.1.15", - "@backstage/plugin-techdocs-node": "^0.11.12", + "@backstage/config": "^1.0.0", + "@backstage/plugin-techdocs-node": "^1.0.1-next.0", "@types/dockerode": "^3.3.0", "commander": "^6.1.0", "dockerode": "^3.3.1", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 55c2743088..d0a2449784 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/techdocs-common +## 0.11.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.0.1-next.0 + +## 0.11.13 + +### Patch Changes + +- 82b0190155: This package is no longer maintained. Use `@backstage/plugin-techdocs-node`, going forward. +- Updated dependencies + - @backstage/plugin-techdocs-node@1.0.0 + ## 0.11.12 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index c56f0b63be..17058e81e6 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "No longer maintained. Use @backstage/plugin-techdocs-node instead.", - "version": "0.11.12", + "version": "0.11.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -36,7 +36,7 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/plugin-techdocs-node": "0.11.12" + "@backstage/plugin-techdocs-node": "1.0.1-next.0" }, "devDependencies": {}, "jest": { diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 2f9deae04f..443b42c579 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/test-utils +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-app-api@1.0.0 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-permission-react@0.3.4 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + ## 0.3.0 ### Minor Changes diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index 2f2671885e..94ef7af51e 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -7,8 +7,6 @@ import { AnalyticsApi } from '@backstage/core-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; -import { AuthorizeDecision } from '@backstage/plugin-permission-common'; -import { AuthorizeQuery } from '@backstage/plugin-permission-common'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ComponentType } from 'react'; import { Config } from '@backstage/config'; @@ -18,6 +16,8 @@ import { DiscoveryApi } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; import { ErrorApiError } from '@backstage/core-plugin-api'; import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; +import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; +import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -150,11 +150,13 @@ export interface MockFetchApiOptions { export class MockPermissionApi implements PermissionApi { constructor( requestHandler?: ( - request: AuthorizeQuery, + request: EvaluatePermissionRequest, ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY, ); // (undocumented) - authorize(request: AuthorizeQuery): Promise; + authorize( + request: EvaluatePermissionRequest, + ): Promise; } // @public diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 0cf752a72d..36e2f32ca7 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.3.0", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,13 +33,13 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-app-api": "^0.6.0", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-react": "^0.3.3", + "@backstage/config": "^1.0.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-react": "^0.3.4", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.10.1", @@ -55,7 +55,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.35.0" diff --git a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts index 96918b4e47..c7c8556355 100644 --- a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts +++ b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts @@ -54,7 +54,7 @@ const nullObservable = { /** * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests. - * Incudes withForError and getErrors methods for error testing. + * Includes withForError and getErrors methods for error testing. * @public */ export class MockErrorApi implements ErrorApi { diff --git a/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.test.ts b/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.test.ts index 88e7c995f5..b617d89cc4 100644 --- a/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.test.ts +++ b/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.test.ts @@ -16,7 +16,7 @@ import { AuthorizeResult, - Permission, + createPermission, } from '@backstage/plugin-permission-common'; import { MockPermissionApi } from './MockPermissionApi'; @@ -25,7 +25,9 @@ describe('MockPermissionApi', () => { const api = new MockPermissionApi(); await expect( - api.authorize({ permission: { name: 'permission.1' } as Permission }), + api.authorize({ + permission: createPermission({ name: 'permission.1', attributes: {} }), + }), ).resolves.toEqual({ result: AuthorizeResult.ALLOW }); }); @@ -37,7 +39,9 @@ describe('MockPermissionApi', () => { ); await expect( - api.authorize({ permission: { name: 'permission.2' } as Permission }), + api.authorize({ + permission: createPermission({ name: 'permission.2', attributes: {} }), + }), ).resolves.toEqual({ result: AuthorizeResult.DENY }); }); }); diff --git a/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.ts b/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.ts index ac5c44248d..163af5dd53 100644 --- a/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.ts +++ b/packages/test-utils/src/testUtils/apis/PermissionApi/MockPermissionApi.ts @@ -16,8 +16,8 @@ import { PermissionApi } from '@backstage/plugin-permission-react'; import { - AuthorizeDecision, - AuthorizeQuery, + EvaluatePermissionResponse, + EvaluatePermissionRequest, AuthorizeResult, } from '@backstage/plugin-permission-common'; @@ -31,12 +31,14 @@ import { export class MockPermissionApi implements PermissionApi { constructor( private readonly requestHandler: ( - request: AuthorizeQuery, + request: EvaluatePermissionRequest, ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY = () => AuthorizeResult.ALLOW, ) {} - async authorize(request: AuthorizeQuery): Promise { + async authorize( + request: EvaluatePermissionRequest, + ): Promise { return { result: this.requestHandler(request) }; } } diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx index 39e5da3df2..32ca3dfc02 100644 --- a/packages/test-utils/src/testUtils/appWrappers.test.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx @@ -48,14 +48,13 @@ describe('wrapInTestApp', () => { await Promise.resolve(); }); - expect(error).toEqual([ - expect.stringMatching( - /^Warning: An update to %s inside a test was not wrapped in act\(...\)/, + expect( + error.some(e => + e.includes( + 'Warning: An update to %s inside a test was not wrapped in act(...)', + ), ), - expect.stringMatching( - /^Warning: An update to %s inside a test was not wrapped in act\(...\)/, - ), - ]); + ).toBeTruthy(); }); it('should render a component in a test app without warning about missing act()', async () => { diff --git a/packages/theme/package.json b/packages/theme/package.json index cab489444a..95cd36309e 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -36,7 +36,7 @@ "@material-ui/core": "^4.12.2" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 238c61f68f..4c0b2f6083 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/types +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + ## 0.1.3 ### Patch Changes diff --git a/packages/types/package.json b/packages/types/package.json index e71e32a6c2..44472cc530 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/types", "description": "Common TypeScript types used within Backstage", - "version": "0.1.3", + "version": "1.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ }, "dependencies": {}, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/zen-observable": "^0.8.0", "zen-observable": "^0.8.15" }, diff --git a/packages/version-bridge/CHANGELOG.md b/packages/version-bridge/CHANGELOG.md index fe131f7987..e814333637 100644 --- a/packages/version-bridge/CHANGELOG.md +++ b/packages/version-bridge/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/version-bridge +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 + ## 0.1.2 ### Patch Changes diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index a954998e76..1b57e71ba9 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/version-bridge", "description": "Utilities used by @backstage packages to support multiple concurrent versions", - "version": "0.1.2", + "version": "1.0.0", "private": false, "publishConfig": { "access": "public", @@ -37,7 +37,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2" diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index 1267c8bb7d..9514e64539 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-airbrake-backend +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 046d7dbd31..12831093a7 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.2.2", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,8 +22,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", "@types/express": "*", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index b4c8dc5228..516dfd9378 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-airbrake +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/dev-utils@1.0.1-next.0 + - @backstage/test-utils@1.0.1-next.0 + +## 0.3.3 + +### Patch Changes + +- 89c7e47967: Minor README update +- 7ff853b2d5: build(deps): bump `object-hash` from 2.2.0 to 3.0.0 +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/dev-utils@1.0.0 + - @backstage/test-utils@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 4f2eeec043..c021c197bb 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,12 +23,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/app-defaults": "^0.2.1", - "@backstage/cli": "^0.15.2", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 40de2ce66f..b28d256c22 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-allure +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.19 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.1.18 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 9b7ed7282b..d5093fdb04 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.18", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,10 +25,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index a25a25e940..4c6483b998 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-analytics-module-ga +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.1.14 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/config@1.0.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 376173b9b2..b493e95eaa 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.13", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -38,10 +38,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index a4f7195fec..8a5182534d 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -220,7 +220,7 @@ describe('GoogleAnalytics', () => { }); // Wait for any/all promises involved to settle. - await new Promise(resolve => setImmediate(resolve)); + await new Promise(resolve => setTimeout(resolve)); // A pageview should have been fired immediately. const [command, data] = ReactGA.testModeAPI.calls[1]; @@ -251,7 +251,7 @@ describe('GoogleAnalytics', () => { }); // Wait for any/all promises involved to settle. - await new Promise(resolve => setImmediate(resolve)); + await new Promise(resolve => setTimeout(resolve)); // A pageview should have been fired immediately. const [command, data] = ReactGA.testModeAPI.calls[1]; @@ -290,7 +290,7 @@ describe('GoogleAnalytics', () => { }); // Wait for any/all promises involved to settle. - await new Promise(resolve => setImmediate(resolve)); + await new Promise(resolve => setTimeout(resolve)); // User ID should have been set after the pageview. const [setCommand, setData] = ReactGA.testModeAPI.calls[2]; @@ -318,7 +318,7 @@ describe('GoogleAnalytics', () => { }); // Wait for any/all promises involved to settle. - await new Promise(resolve => setImmediate(resolve)); + await new Promise(resolve => setTimeout(resolve)); // A pageview should have been fired immediately. const [command, data] = ReactGA.testModeAPI.calls[1]; @@ -369,7 +369,7 @@ describe('GoogleAnalytics', () => { }); // Wait for any/all promises involved to settle. - await new Promise(resolve => setImmediate(resolve)); + await new Promise(resolve => setTimeout(resolve)); // User ID should have been set first. const [setCommand, setData] = ReactGA.testModeAPI.calls[1]; diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 40af918ebf..d5c6475f42 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-apache-airflow +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.1.11 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 86d12e6f11..41e7f12ff3 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.1.10", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,8 +23,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +36,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index b2fc5f0110..8ea14ac0e4 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-api-docs +## 0.8.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.8.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- c689d7a94c: Switched to using `CatalogFilterLayout` from `@backstage/plugin-catalog-react`. +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-catalog@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.8.2 ### Patch Changes diff --git a/plugins/api-docs/dev/graphql-example-api.yaml b/plugins/api-docs/dev/graphql-example-api.yaml index f3d924fc53..ccddc537e7 100644 --- a/plugins/api-docs/dev/graphql-example-api.yaml +++ b/plugins/api-docs/dev/graphql-example-api.yaml @@ -529,7 +529,7 @@ spec: terrains: [String] """ - The percentage of the planet surface that is naturally occuring water or bodies + The percentage of the planet surface that is naturally occurring water or bodies of water. """ surfaceWater: Float diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 1abd50a577..efd7d49fa8 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.8.2", + "version": "0.8.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ }, "dependencies": { "@asyncapi/react-component": "1.0.0-next.33", - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -56,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx index d38383f004..9fc6b2c3c2 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx @@ -24,13 +24,7 @@ import { TableProps, } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { - CatalogTable, - CatalogTableRow, - FilteredEntityLayout, - EntityListContainer, - FilterContainer, -} from '@backstage/plugin-catalog'; +import { CatalogTable, CatalogTableRow } from '@backstage/plugin-catalog'; import { EntityKindPicker, EntityLifecyclePicker, @@ -40,6 +34,7 @@ import { EntityTypePicker, UserListFilterKind, UserListPicker, + CatalogFilterLayout, } from '@backstage/plugin-catalog-react'; import React from 'react'; import { registerComponentRouteRef } from '../../routes'; @@ -95,22 +90,22 @@ export const DefaultApiExplorerPage = ({ All your APIs - - + + - + + - - + + diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 082605d859..ff734beb44 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-app-backend +## 0.3.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.3.30 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/config-loader@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + ## 0.3.29 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 7a57385afe..541afc873d 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.29", + "version": "0.3.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config-loader": "^0.9.7", - "@backstage/config": "^0.1.15", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config-loader": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", "globby": "^11.0.0", "express": "^4.17.1", @@ -50,9 +50,9 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", - "@backstage/types": "^0.1.3", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/types": "^1.0.0", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", "msw": "^0.35.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 13761f4e6c..1430e2b3f6 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-auth-backend +## 0.13.0-next.0 + +### Minor Changes + +- 15d3a3c39a: **BREAKING**: All sign-in resolvers must now return a `token` in their sign-in result. Returning an `id` is no longer supported. + +### Patch Changes + +- 2cc1d1b235: Applied the fix from version 0.12.3 of this package, which is part of the v1.0.1 release of Backstage. +- 6ee04078e1: **DEPRECATION**: The `tokenIssuer` option for `OAuthAdapter` is no longer needed and has been deprecated. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.12.3 + +### Patch Changes + +- Fix migrations to do the right thing on sqlite databases, and reapply the column type fix for those who are _not_ on sqlite databases. + + Reconstruction of #10317 in the form of a patch release instead. + +## 0.12.2 + +### Patch Changes + +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.1.6 + ## 0.12.1 ### Patch Changes diff --git a/plugins/auth-backend/migrations/20210326100300_timestamptz.js b/plugins/auth-backend/migrations/20210326100300_timestamptz.js index 79b839dc6a..4a7aa3e97d 100644 --- a/plugins/auth-backend/migrations/20210326100300_timestamptz.js +++ b/plugins/auth-backend/migrations/20210326100300_timestamptz.js @@ -21,7 +21,7 @@ */ exports.up = async function up(knex) { // Sqlite does not support alter column. - if (knex.client.config.client.includes('sqlite3')) { + if (!knex.client.config.client.includes('sqlite3')) { await knex.schema.alterTable('signing_keys', table => { table .timestamp('created_at', { useTz: true, precision: 0 }) @@ -38,7 +38,7 @@ exports.up = async function up(knex) { */ exports.down = async function down(knex) { // Sqlite does not support alter column. - if (knex.client.config.client.includes('sqlite3')) { + if (!knex.client.config.client.includes('sqlite3')) { await knex.schema.alterTable('signing_keys', table => { table .timestamp('created_at', { useTz: false, precision: 0 }) diff --git a/plugins/auth-backend/migrations/20220321100910_timestamptz_again.js b/plugins/auth-backend/migrations/20220321100910_timestamptz_again.js new file mode 100644 index 0000000000..ab7e713b96 --- /dev/null +++ b/plugins/auth-backend/migrations/20220321100910_timestamptz_again.js @@ -0,0 +1,58 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +// NOTE: This may look like a plain duplicate of the previous one, but that +// file had a bug added when improving sqlite driver support: +// https://github.com/backstage/backstage/pull/10053/files#diff-30bb343265e71ca2f1cdcccd5ac8fdbb2a597507c5531bf26945059783377b15R24 +// Since the old file was released to end users, those who created a new +// Backstage app specifically for PostgreSQL since the release will be missing +// this fix on their table. So we re-apply it. + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Sqlite does not support alter column. + if (!knex.client.config.client.includes('sqlite3')) { + await knex.schema.alterTable('signing_keys', table => { + table + .timestamp('created_at', { useTz: true, precision: 0 }) + .notNullable() + .defaultTo(knex.fn.now()) + .comment('The creation time of the key') + .alter({ alterType: true }); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // Sqlite does not support alter column. + if (!knex.client.config.client.includes('sqlite3')) { + await knex.schema.alterTable('signing_keys', table => { + table + .timestamp('created_at', { useTz: false, precision: 0 }) + .notNullable() + .defaultTo(knex.fn.now()) + .comment('The creation time of the key') + .alter({ alterType: true }); + }); + } +}; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index acf5a1eac6..43813a5773 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.12.1", + "version": "0.13.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/types": "^0.1.3", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/types": "^1.0.0", "@google-cloud/firestore": "^5.0.2", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", @@ -76,8 +76,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts b/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts index a7192ed4ab..6a3ce87f82 100644 --- a/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts +++ b/plugins/auth-backend/src/identity/FirestoreKeyStore.test.ts @@ -44,6 +44,8 @@ jest.mock('@google-cloud/firestore', () => ({ Firestore: jest.fn().mockImplementation(() => firestoreMock), })); +jest.useFakeTimers('legacy'); + describe('FirestoreKeyStore', () => { const key = { kid: '123', @@ -69,11 +71,6 @@ describe('FirestoreKeyStore', () => { beforeEach(() => { jest.clearAllMocks(); - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); }); it('can create an instance without settings', async () => { diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 07c8196dd2..87e6b96745 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -50,7 +50,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -110,7 +109,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -157,7 +155,6 @@ describe('oauth helpers', () => { displayName: "Adam l'Hôpital", }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index c1130b4270..0ce9dff66c 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -17,7 +17,7 @@ import express from 'express'; import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthResponse, OAuthState } from './types'; +import { OAuthHandlers, OAuthState } from './types'; const mockResponseData = { providerInfo: { @@ -30,18 +30,11 @@ const mockResponseData = { email: 'foo@bar.com', }, backstageIdentity: { - id: 'foo', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }, }; -function mkTokenBody(payload: unknown): string { - return Buffer.from(JSON.stringify(payload), 'utf8') - .toString('base64') - .replace(/=/g, ''); -} - describe('OAuthAdapter', () => { class MyAuthProvider implements OAuthHandlers { async start() { @@ -324,13 +317,11 @@ describe('OAuthAdapter', () => { expect(mockResponse.json).toHaveBeenCalledWith({ ...mockResponseData, backstageIdentity: { - id: mockResponseData.backstageIdentity.id, token: mockResponseData.backstageIdentity.token, - idToken: mockResponseData.backstageIdentity.token, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, }, }); @@ -356,95 +347,6 @@ describe('OAuthAdapter', () => { ); }); - it('correctly populates incomplete identities', async () => { - const mockRefresh = jest.fn< - Promise<{ response: OAuthResponse }>, - [express.Request] - >(); - - const oauthProvider = new OAuthAdapter( - { - refresh: mockRefresh, - start: jest.fn(), - handler: jest.fn(), - } as OAuthHandlers, - { - ...oAuthProviderOptions, - tokenIssuer: { - issueToken: async ({ claims }) => `a.${mkTokenBody(claims)}.a`, - listPublicKeys: async () => ({ keys: [] }), - }, - disableRefresh: false, - isOriginAllowed: () => false, - }, - ); - - const mockRequest = { - header: () => 'XMLHttpRequest', - cookies: { - 'test-provider-refresh-token': 'token', - }, - query: {}, - } as unknown as express.Request; - - const mockResponse = { - json: jest.fn().mockReturnThis(), - status: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - // Without a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: '', - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(1); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - idToken: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - identity: { - type: 'user', - userEntityRef: 'user:default/foo', - ownershipEntityRefs: [], - }, - }, - }); - - // With a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(2); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - idToken: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - identity: { - type: 'user', - userEntityRef: 'user:my-ns/foo', - ownershipEntityRefs: [], - }, - }, - }); - }); - it('sets the correct cookie configuration using a callbackUrl', async () => { const config = { baseUrl: 'http://domain.org/auth', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 07611ddb9c..637dc1cff2 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -17,11 +17,6 @@ import express, { CookieOptions } from 'express'; import crypto from 'crypto'; import { URL } from 'url'; -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -58,7 +53,8 @@ export type Options = { cookieDomain: string; cookiePath: string; appOrigin: string; - tokenIssuer: TokenIssuer; + /** @deprecated This option is no longer needed */ + tokenIssuer?: TokenIssuer; isOriginAllowed: (origin: string) => boolean; callbackUrl: string; }; @@ -263,22 +259,11 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { if (!identity) { return undefined; } - - if (identity.token) { - return prepareBackstageIdentityResponse(identity); + if (!identity.token) { + throw new InputError(`Identity response must return a token`); } - const userEntityRef = stringifyEntityRef( - parseEntityRef(identity.id, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); - const token = await this.options.tokenIssuer.issueToken({ - claims: { sub: userEntityRef }, - }); - - return prepareBackstageIdentityResponse({ ...identity, token }); + return prepareBackstageIdentityResponse(identity); } private setNonceCookie = (res: express.Response, nonce: string) => { diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts index 37b5bc1f68..eb801fdd46 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts @@ -123,9 +123,8 @@ describe('AwsAlbAuthProvider', () => { }), signInResolver: async () => { return { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }; }, }); @@ -136,11 +135,8 @@ describe('AwsAlbAuthProvider', () => { expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', - idToken: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', diff --git a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts index aab0103140..eb2f5c478d 100644 --- a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts +++ b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts @@ -45,7 +45,7 @@ describe('GcpIapProvider', () => { const iapToken = { sub: 's', email: 'e@mail.com' }; authHandler.mockResolvedValueOnce({ email: 'e@mail.com' }); - signInResolver.mockResolvedValueOnce({ id: 'i', token: backstageToken }); + signInResolver.mockResolvedValueOnce({ token: backstageToken }); tokenValidator.mockResolvedValueOnce(iapToken); const app = express(); @@ -61,8 +61,6 @@ describe('GcpIapProvider', () => { ); expect(response.body).toEqual({ backstageIdentity: { - id: 'i', - idToken: backstageToken, token: backstageToken, identity: { type: 'user', diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index 29e8679fe0..c875f856b6 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -318,9 +318,9 @@ export const createGithubProvider = ( OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); - const enterpriseInstanceUrl = envConfig.getOptionalString( - 'enterpriseInstanceUrl', - ); + const enterpriseInstanceUrl = envConfig + .getOptionalString('enterpriseInstanceUrl') + ?.replace(/\/$/, ''); const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); const authorizationUrl = enterpriseInstanceUrl ? `${enterpriseInstanceUrl}/login/oauth/authorize` diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts index 048a3b9344..61f08bc535 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts @@ -43,7 +43,7 @@ import { describe('Oauth2ProxyAuthProvider', () => { const mockToken = - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob'; + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob'; let provider: Oauth2ProxyAuthProvider; let logger: jest.Mocked; @@ -122,7 +122,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); @@ -142,7 +141,6 @@ describe('Oauth2ProxyAuthProvider', () => { const profile = { displayName: 'some value' }; mockRequest.header.mockReturnValue(`Bearer token`); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); authHandler.mockResolvedValue({ profile: profile }); @@ -162,12 +160,10 @@ describe('Oauth2ProxyAuthProvider', () => { ); expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'some-id', - idToken: mockToken, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, token: mockToken, }, @@ -186,7 +182,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts index 24442379c4..dac06f964e 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts @@ -27,13 +27,10 @@ describe('prepareBackstageIdentityResponse', () => { const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'] }); expect( prepareBackstageIdentityResponse({ - id: 'x', token, }), ).toEqual({ - id: 'x', token, - idToken: token, identity: { type: 'user', userEntityRef: 'k:ns/n', @@ -41,67 +38,4 @@ describe('prepareBackstageIdentityResponse', () => { }, }); }); - - it('populates incomplete identities', () => { - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'k:n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'k:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'ns/n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:ns/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n', ent: ['k:ns/o'] }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: ['k:ns/o'], - }, - }); - }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts index 5a4e0895d9..761618c225 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -41,21 +36,11 @@ export function prepareBackstageIdentityResponse( ): BackstageIdentityResponse { const { sub, ent } = parseJwtPayload(result.token); - const userEntityRef = stringifyEntityRef( - parseEntityRef(sub, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); return { - ...{ - // TODO: idToken is for backwards compatibility and can be removed in the future - idToken: result.token, - ...result, - }, + ...result, identity: { type: 'user', - userEntityRef, + userEntityRef: sub, ownershipEntityRefs: ent ?? [], }, }; diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 1e0dc95f19..d0860296f2 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-auth-node +## 0.2.0-next.0 + +### Minor Changes + +- 15d3a3c39a: **BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 7840fc7d74..f7fdd6369b 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/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 { Entity } from '@backstage/catalog-model'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; // @public @@ -13,10 +12,6 @@ export interface BackstageIdentityResponse extends BackstageSignInResult { // @public export interface BackstageSignInResult { - // @deprecated - entity?: Entity; - // @deprecated - id: string; token: string; } diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index ce4fda9cf0..c50bdd06ba 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.1.5", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,16 +23,15 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", "jose": "^1.27.1", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "msw": "^0.35.0", "uuid": "^8.0.0" }, diff --git a/plugins/auth-node/src/IdentityClient.test.ts b/plugins/auth-node/src/IdentityClient.test.ts index 72ef7f2a57..88d4021dc3 100644 --- a/plugins/auth-node/src/IdentityClient.test.ts +++ b/plugins/auth-node/src/IdentityClient.test.ts @@ -135,12 +135,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); @@ -202,12 +201,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); diff --git a/plugins/auth-node/src/IdentityClient.ts b/plugins/auth-node/src/IdentityClient.ts index d8e841bf75..54c2670ae8 100644 --- a/plugins/auth-node/src/IdentityClient.ts +++ b/plugins/auth-node/src/IdentityClient.ts @@ -87,7 +87,6 @@ export class IdentityClient { } const user: BackstageIdentityResponse = { - id: decoded.sub, token, identity: { type: 'user', diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index b574c2204d..0d3e015beb 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; - /** * A representation of a successful Backstage sign-in. * @@ -25,25 +23,6 @@ import { Entity } from '@backstage/catalog-model'; * @public */ export interface BackstageSignInResult { - /** - * An opaque ID that uniquely identifies the user within Backstage. - * - * This is typically the same as the user entity `metadata.name`. - * - * @deprecated Use the `identity` field instead - */ - id: string; - - /** - * The entity that the user is represented by within Backstage. - * - * This entity may or may not exist within the Catalog, and it can be used - * to read and store additional metadata about the user. - * - * @deprecated Use the `identity` field instead. - */ - entity?: Entity; - /** * The token used to authenticate the user within Backstage. */ diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 43ab9e2d02..a86b09f0ec 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-azure-devops-backend +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.3.9 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.3.8 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 6ee5e71688..0af0ba9658 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.8", + "version": "0.3.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,8 +23,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", "@backstage/plugin-azure-devops-common": "^0.2.2", "@types/express": "^4.17.6", "azure-devops-node-api": "^11.0.1", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", "msw": "^0.35.0" diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 24936f883a..73463fcc57 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 1154f40058..9b7c615875 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-azure-devops +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.19 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- 4e6327ef7a: Updated readme +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.18 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index d28efb84cc..15c09a2a3b 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.1.18", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,12 +30,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", "@backstage/plugin-azure-devops-common": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index e55338dd68..9e6b727a37 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-badges-backend +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.1.24 + +### Patch Changes + +- 89c7e47967: Minor README update +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.23 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 9eaa3f084f..4148066eb9 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.23", + "version": "0.1.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", "@types/express": "^4.17.6", "badge-maker": "^3.3.0", "cors": "^2.8.5", @@ -48,7 +48,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges-backend/src/types.ts b/plugins/badges-backend/src/types.ts index 4342a5af40..0467b18155 100644 --- a/plugins/badges-backend/src/types.ts +++ b/plugins/badges-backend/src/types.ts @@ -44,7 +44,7 @@ export interface Badge { link?: string; /** Badge message */ message: string; - /** Badge style (apperance). One of "plastic", "flat", "flat-square", "for-the-badge" and "social" */ + /** Badge style (appearance). One of "plastic", "flat", "flat-square", "for-the-badge" and "social" */ style?: BadgeStyle; } diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index dd2ffd1747..90fd5eb8f2 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-badges +## 0.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.2.27 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- 5f7d1e303d: Fixed an error caused by the `EntityBadgesDialog` trying to access the entity before it is loaded. +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.2.26 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 718d38c00a..3c01608ffc 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.26", + "version": "0.2.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges/src/components/EntityBadgesDialog.tsx b/plugins/badges/src/components/EntityBadgesDialog.tsx index 1ae5f69326..422dbc96a3 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useEntity } from '@backstage/plugin-catalog-react'; +import { useAsyncEntity } from '@backstage/plugin-catalog-react'; import { Box, Button, @@ -44,7 +44,7 @@ type Props = { export const EntityBadgesDialog = ({ open, onClose }: Props) => { const theme = useTheme(); - const { entity } = useEntity(); + const { entity } = useAsyncEntity(); const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); const badgesApi = useApi(badgesApiRef); diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index 4f4480ab62..25f49f5800 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-bazaar-backend +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + - @backstage/backend-test-utils@0.1.23-next.0 + +## 0.1.14 + +### Patch Changes + +- 89c7e47967: Minor README update +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/backend-test-utils@0.1.22 + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 2052d52d59..7132abd9b7 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.13", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,9 +23,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -34,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 4c139005bf..888df89ced 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-bazaar +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog@1.0.1-next.0 + - @backstage/cli@0.16.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.16.0 + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-catalog@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + ## 0.1.17 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 9bca7c1c6c..a2ead73157 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.17", + "version": "0.1.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,13 +24,13 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/cli": "^0.15.2", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/dev-utils": "^0.2.25", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "cross-fetch": "^3.1.5" }, diff --git a/plugins/bazaar/src/api.ts b/plugins/bazaar/src/api.ts index 2d661506bc..d2486f74dc 100644 --- a/plugins/bazaar/src/api.ts +++ b/plugins/bazaar/src/api.ts @@ -17,6 +17,7 @@ import { createApiRef, DiscoveryApi, + FetchApi, IdentityApi, } from '@backstage/core-plugin-api'; @@ -47,49 +48,53 @@ export interface BazaarApi { export class BazaarClient implements BazaarApi { private readonly identityApi: IdentityApi; private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; constructor(options: { identityApi: IdentityApi; discoveryApi: DiscoveryApi; + fetchApi: FetchApi; }) { this.identityApi = options.identityApi; this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi; } async updateProject(bazaarProject: any): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - return await fetch(`${baseUrl}/projects`, { - method: 'PUT', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(bazaarProject), - }).then(resp => resp.json()); + return await this.fetchApi + .fetch(`${baseUrl}/projects`, { + method: 'PUT', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(bazaarProject), + }) + .then(resp => resp.json()); } async addProject(bazaarProject: any): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - return await fetch(`${baseUrl}/projects`, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(bazaarProject), - }).then(resp => resp.json()); + return await this.fetchApi + .fetch(`${baseUrl}/projects`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(bazaarProject), + }) + .then(resp => resp.json()); } async getProjectById(id: number): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - const response = await fetch( + const response = await this.fetchApi.fetch( `${baseUrl}/projects/${encodeURIComponent(id)}`, - { - method: 'GET', - }, ); return response.ok ? response : null; @@ -98,11 +103,8 @@ export class BazaarClient implements BazaarApi { async getProjectByRef(entityRef: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - const response = await fetch( + const response = await this.fetchApi.fetch( `${baseUrl}/projects/${encodeURIComponent(entityRef)}`, - { - method: 'GET', - }, ); return response.ok ? response : null; @@ -111,19 +113,16 @@ export class BazaarClient implements BazaarApi { async getMembers(id: number): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - return await fetch( - `${baseUrl}/projects/${encodeURIComponent(id)}/members`, - { - method: 'GET', - }, - ).then(resp => resp.json()); + return await this.fetchApi + .fetch(`${baseUrl}/projects/${encodeURIComponent(id)}/members`) + .then(resp => resp.json()); } async addMember(id: number, userId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); const { picture } = await this.identityApi.getProfileInfo(); - await fetch( + await this.fetchApi.fetch( `${baseUrl}/projects/${encodeURIComponent( id, )}/member/${encodeURIComponent(userId)}`, @@ -141,28 +140,26 @@ export class BazaarClient implements BazaarApi { async deleteMember(id: number, userId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - await fetch( + await this.fetchApi.fetch( `${baseUrl}/projects/${encodeURIComponent( id, )}/member/${encodeURIComponent(userId)}`, - { - method: 'DELETE', - }, + { method: 'DELETE' }, ); } async getProjects(): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - return await fetch(`${baseUrl}/projects`, { - method: 'GET', - }).then(resp => resp.json()); + return await this.fetchApi + .fetch(`${baseUrl}/projects`) + .then(resp => resp.json()); } async deleteProject(id: number): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - await fetch(`${baseUrl}/projects/${encodeURIComponent(id)}`, { + await this.fetchApi.fetch(`${baseUrl}/projects/${encodeURIComponent(id)}`, { method: 'DELETE', }); } diff --git a/plugins/bazaar/src/plugin.ts b/plugins/bazaar/src/plugin.ts index 5eb5e2aacd..438208624e 100644 --- a/plugins/bazaar/src/plugin.ts +++ b/plugins/bazaar/src/plugin.ts @@ -21,6 +21,7 @@ import { createRoutableExtension, identityApiRef, discoveryApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { bazaarApiRef, BazaarClient } from './api'; @@ -35,9 +36,10 @@ export const bazaarPlugin = createPlugin({ deps: { identityApi: identityApiRef, discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, }, - factory: ({ identityApi, discoveryApi }) => - new BazaarClient({ identityApi, discoveryApi }), + factory: ({ identityApi, discoveryApi, fetchApi }) => + new BazaarClient({ identityApi, discoveryApi, fetchApi }), }), ], }); diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 2442a96f5f..db71faef2f 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-bitrise +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.30 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.1.29 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 1f599e3afe..c48eda52b1 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.29", + "version": "0.1.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,10 +24,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index f20bd9fce2..78f6610e92 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index d49e229b47..4c9062eb84 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.2", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,19 +33,19 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "aws-sdk": "^2.840.0", "lodash": "^4.17.21", "p-limit": "^3.0.2", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151", "aws-sdk-mock": "^5.2.1", "yaml": "^1.9.2" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index e5cce00527..71a1962750 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 1a6a70ad5e..6ca9bbe712 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,21 +33,21 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index 3852f83063..50771795bf 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.1.2-next.0 + +### Patch Changes + +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 4980eed0fd..c13557596d 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,21 +33,21 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.test.ts b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.test.ts index 58845fc3d1..41f467ebaa 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.test.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.test.ts @@ -30,7 +30,7 @@ describe('BitbucketRepositoryParser', () => { presence: 'optional', }), ]; - const actual = await defaultRepositoryParser({ + const actual = defaultRepositoryParser({ target: `${browseUrl}${path}`, }); diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index ff15890f73..13c38a58eb 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-github +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + +## 0.1.1 + +### Patch Changes + +- 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional. +- e949d68059: Made sure to move the catalog-related github and ldap config into their right places +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts new file mode 100644 index 0000000000..a1702799c2 --- /dev/null +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2020 The 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 { + catalog?: { + processors?: { + /** + * GithubMultiOrgReaderProcessor configuration + */ + githubMultiOrg?: { + /** + * The configuration parameters for each GitHub org to process. + */ + orgs: Array<{ + /** + * The name of the GitHub org to process. + */ + name: string; + /** + * The namespace of the group created for this org. + * + * Defaults to org name if omitted. + */ + groupNamespace?: string; + + /** + * The namespace of the users created from this org. + * + * Defaults to empty string if omitted. + */ + userNamespace?: string; + }>; + }; + }; + }; +} diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 0e63cf9da2..0dc8ebebf9 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "@octokit/graphql": "^4.5.8", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -47,11 +47,13 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-github/src/lib/org.ts b/plugins/catalog-backend-module-github/src/lib/org.ts index 3c71f350e6..79a96b5e71 100644 --- a/plugins/catalog-backend-module-github/src/lib/org.ts +++ b/plugins/catalog-backend-module-github/src/lib/org.ts @@ -58,7 +58,10 @@ export function assignGroupsToUsers( for (const [groupName, userNames] of groupMemberUsers.entries()) { for (const userName of userNames) { const user = usersByName.get(userName); - if (user && !user.spec.memberOf.includes(groupName)) { + if (user && !user.spec.memberOf?.includes(groupName)) { + if (!user.spec.memberOf) { + user.spec.memberOf = []; + } user.spec.memberOf.push(groupName); } } @@ -74,7 +77,7 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { const transitiveMemberOf = new Set(); const todo = [ - ...user.spec.memberOf, + ...(user.spec.memberOf ?? []), ...groups .filter(g => g.spec.members?.includes(user.metadata.name)) .map(g => g.metadata.name), diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 75ddcba68b..97e40ddba2 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 976cee1e69..412acacefb 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,21 +33,21 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index e0cf628845..1e54237e60 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-tasks@0.2.2-next.0 + +## 0.4.1 + +### Patch Changes + +- 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional. +- a6c367d937: Add config support for LDAP search options. +- 74375be2c6: Updated to no longer rely on the `RecursivePartial` export from `@backstage/plugin-catalog-backend`. +- e949d68059: Made sure to move the catalog-related github and ldap config into their right places +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-tasks@0.2.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index edeef4cb82..3c6cd4849b 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -134,7 +134,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { }); // (undocumented) static fromConfig( - config: Config, + configRoot: Config, options: { logger: Logger; groupTransformer?: GroupTransformer; diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 2605eb62f3..8ae3145811 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -17,8 +17,223 @@ import { JsonValue } from '@backstage/types'; export interface Config { + /** + * LdapOrgEntityProvider / LdapOrgReaderProcessor configuration + */ + ldap?: { + /** + * The configuration parameters for each single LDAP provider. + */ + providers: Array<{ + /** + * The prefix of the target that this matches on, e.g. + * "ldaps://ds.example.net", with no trailing slash. + */ + target: string; + + /** + * The settings to use for the bind command. If none are specified, + * the bind command is not issued. + */ + bind?: { + /** + * The DN of the user to auth as. + * + * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The secret of the user to auth as (its password). + * + * @visibility secret + */ + secret: string; + }; + + /** + * The settings that govern the reading and interpretation of users. + */ + users: { + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + }; + + /** + * The settings that govern the reading and interpretation of groups. + */ + groups: { + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + }; + }>; + }; + /** * Configuration options for the catalog plugin. + * + * TODO(freben): Deprecate this entire block */ catalog?: { /** diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 514f8f9e10..3b760a4df6 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.4.0", + "version": "0.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-tasks": "^0.2.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", "lodash": "^4.17.21", @@ -46,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-ldap/src/ldap/org.ts b/plugins/catalog-backend-module-ldap/src/ldap/org.ts index fe59451a9d..28ccbd1d3b 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/org.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/org.ts @@ -61,7 +61,7 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { const transitiveMemberOf = new Set(); const todo = [ - ...user.spec.memberOf, + ...(user.spec.memberOf ?? []), ...groups .filter(g => g.spec.members?.includes(user.metadata.name)) .map(g => g.metadata.name), diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index bad3941767..b31b7ae266 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -114,17 +114,11 @@ export class LdapOrgEntityProvider implements EntityProvider { const config = configRoot.getOptionalConfig('ldap') || configRoot.getOptionalConfig('catalog.processors.ldapOrg'); - if (!config) { - throw new TypeError( - `There is no LDAP configuration. Please add it as "ldap.providers".`, - ); - } - - const providers = readLdapConfig(config); + const providers = config ? readLdapConfig(config) : []; const provider = providers.find(p => options.target === p.target); if (!provider) { throw new TypeError( - `There is no LDAP configuration that matches ${options.target}. Please add a configuration entry for it under "ldap.providers".`, + `There is no LDAP configuration that matches "${options.target}". Please add a configuration entry for it under "ldap.providers".`, ); } diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index bec24a1744..74a7e153b9 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -43,17 +43,20 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { private readonly userTransformer?: UserTransformer; static fromConfig( - config: Config, + configRoot: Config, options: { logger: Logger; groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; }, ) { - const c = config.getOptionalConfig('catalog.processors.ldapOrg'); + // TODO(freben): Deprecate the old catalog.processors.ldapOrg config + const config = + configRoot.getOptionalConfig('ldap') || + configRoot.getOptionalConfig('catalog.processors.ldapOrg'); return new LdapOrgReaderProcessor({ ...options, - providers: c ? readLdapConfig(c) : [], + providers: config ? readLdapConfig(config) : [], }); } @@ -85,7 +88,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { const provider = this.providers.find(p => location.target === p.target); if (!provider) { throw new Error( - `There is no LDAP Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.ldapOrg.providers.`, + `There is no LDAP configuration that matches "${location.target}". Please add a configuration entry for it under "ldap.providers".`, ); } diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 859b43813d..0f907f969d 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-tasks@0.2.2-next.0 + +## 0.3.0 + +### Minor Changes + +- 331f258e06: **BREAKING**: `MicrosoftGraphOrgEntityProvider.fromConfig` now requires a `schedule` field in its options, which simplifies scheduling. If you want to retain the old behavior of calling its `run()` method manually, you can set the new field value to the string `'manual'`. But you may prefer to instead give it a scheduled task runner from the backend tasks package: + + ```diff + // packages/backend/src/plugins/catalog.ts + +import { Duration } from 'luxon'; + +import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + + + // The target parameter below needs to match one of the providers' target + + // value specified in your app-config. + + builder.addEntityProvider( + + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { + + id: 'production', + + target: 'https://graph.microsoft.com/v1.0', + + logger: env.logger, + + schedule: env.scheduler.createScheduledTaskRunner({ + + frequency: Duration.fromObject({ minutes: 5 }), + + timeout: Duration.fromObject({ minutes: 3 }), + + }), + + }), + + ); + ``` + +### Patch Changes + +- 759b32b0ce: support advanced querying capabilities using the config option `queryMode` +- 89c7e47967: Minor README update +- 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional. +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-tasks@0.2.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + ## 0.2.19 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index d9488486ad..741e378fd4 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -92,26 +92,31 @@ yarn add @backstage/plugin-catalog-backend-module-msgraph 4. The `MicrosoftGraphOrgEntityProvider` is not registered by default, so you have to register it in the catalog plugin. Pass the target to reference a - provider from the configuration. As entity providers are not part of the - entity refresh loop, you have to run them manually. + provider from the configuration. -```typescript -// packages/backend/src/plugins/catalog.ts -const msGraphOrgEntityProvider = MicrosoftGraphOrgEntityProvider.fromConfig( - env.config, - { - id: 'https://graph.microsoft.com/v1.0', - target: 'https://graph.microsoft.com/v1.0', - logger: env.logger, - }, -); -builder.addEntityProvider(msGraphOrgEntityProvider); +```diff + // packages/backend/src/plugins/catalog.ts ++import { Duration } from 'luxon'; ++import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; -// Trigger a read every 5 minutes -useHotCleanup( - module, - runPeriodically(() => msGraphOrgEntityProvider.read(), 5 * 60 * 1000), -); + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + ++ // The target parameter below needs to match one of the providers' target ++ // value specified in your app-config (see above). ++ builder.addEntityProvider( ++ MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { ++ id: 'production', ++ target: 'https://graph.microsoft.com/v1.0', ++ logger: env.logger, ++ schedule: env.scheduler.createScheduledTaskRunner({ ++ frequency: Duration.fromObject({ minutes: 5 }), ++ timeout: Duration.fromObject({ minutes: 3 }), ++ }), ++ }), ++ ); ``` ### Using the Processor diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index e3840dc6f7..2e741e65fe 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -14,6 +14,7 @@ import { Logger } from 'winston'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import * as msal from '@azure/msal-node'; import { Response as Response_2 } from 'node-fetch'; +import { TaskRunner } from '@backstage/backend-tasks'; import { UserEntity } from '@backstage/catalog-model'; // @public @@ -119,19 +120,23 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { connect(connection: EntityProviderConnection): Promise; // (undocumented) static fromConfig( - config: Config, - options: { - id: string; - target: string; - logger: Logger; - userTransformer?: UserTransformer; - groupTransformer?: GroupTransformer; - organizationTransformer?: OrganizationTransformer; - }, + configRoot: Config, + options: MicrosoftGraphOrgEntityProviderOptions, ): MicrosoftGraphOrgEntityProvider; // (undocumented) getProviderName(): string; - read(): Promise; + read(options?: { logger?: Logger }): Promise; +} + +// @public +export interface MicrosoftGraphOrgEntityProviderOptions { + groupTransformer?: GroupTransformer; + id: string; + logger: Logger; + organizationTransformer?: OrganizationTransformer; + schedule: 'manual' | TaskRunner; + target: string; + userTransformer?: UserTransformer; } // @public diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index d47abf60bb..db5ab81c4b 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -58,7 +58,7 @@ export interface Config { clientSecret: string; // TODO: Consider not making these config options and pass them in the - // constructor instead. They are probably not environment specifc, so + // constructor instead. They are probably not environment specific, so // they could also be configured "in code". /** diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 58ff3b2b3d..8a7e76dbb9 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.2.19", + "version": "0.3.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,21 +34,23 @@ }, "dependencies": { "@azure/msal-node": "^1.1.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/plugin-catalog-backend": "^0.24.0", + "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", "node-fetch": "^2.6.7", "p-limit": "^3.0.2", + "uuid": "^8.0.0", "winston": "^3.2.1", "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts index 467b03733e..5ff4fa4e0e 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts @@ -157,7 +157,7 @@ describe('MicrosoftGraphClient', () => { expect(userProfile).toEqual({ surname: 'Example' }); }); - it('should throw expection if load user profile fails', async () => { + it('should throw exception if load user profile fails', async () => { worker.use( rest.get('https://example.com/users/user-id', (_, res, ctx) => res(ctx.status(404)), diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/org.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/org.ts index fe59451a9d..28ccbd1d3b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/org.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/org.ts @@ -61,7 +61,7 @@ export function buildMemberOf(groups: GroupEntity[], users: UserEntity[]) { const transitiveMemberOf = new Set(); const todo = [ - ...user.spec.memberOf, + ...(user.spec.memberOf ?? []), ...groups .filter(g => g.spec.members?.includes(user.metadata.name)) .map(g => g.metadata.name), diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 7174a6012c..e9760f7698 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -507,6 +507,9 @@ export function resolveRelations( retrieveItems(groupMemberOf, id).forEach(p => { const parentGroup = groupMap.get(p); if (parentGroup) { + if (!user.spec.memberOf) { + user.spec.memberOf = []; + } user.spec.memberOf.push(stringifyEntityRef(parentGroup)); } }); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 71aabaeef2..dfd0a7782e 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -25,6 +26,7 @@ import { EntityProviderConnection, } from '@backstage/plugin-catalog-backend'; import { merge } from 'lodash'; +import * as uuid from 'uuid'; import { Logger } from 'winston'; import { GroupTransformer, @@ -39,6 +41,62 @@ import { UserTransformer, } from '../microsoftGraph'; +/** + * Options for {@link MicrosoftGraphOrgEntityProvider}. + * + * @public + */ +export interface MicrosoftGraphOrgEntityProviderOptions { + /** + * A unique, stable identifier for this provider. + * + * @example "production" + */ + id: string; + + /** + * The target that this provider should consume. + * + * Should exactly match the "target" field of one of the providers + * configuration entries. + */ + target: string; + + /** + * The logger to use. + */ + logger: Logger; + + /** + * The refresh schedule to use. + * + * @remarks + * + * If you pass in 'manual', you are responsible for calling the `read` method + * manually at some interval. + * + * But more commonly you will pass in the result of + * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * to enable automatic scheduling of tasks. + */ + schedule: 'manual' | TaskRunner; + + /** + * The function that transforms a user entry in msgraph to an entity. + */ + userTransformer?: UserTransformer; + + /** + * The function that transforms a group entry in msgraph to an entity. + */ + groupTransformer?: GroupTransformer; + + /** + * The function that transforms an organization entry in msgraph to an entity. + */ + organizationTransformer?: OrganizationTransformer; +} + /** * Reads user and group entries out of Microsoft Graph, and provides them as * User and Group entities for the catalog. @@ -47,25 +105,21 @@ import { */ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; + private scheduleFn?: () => Promise; static fromConfig( - config: Config, - options: { - id: string; - target: string; - logger: Logger; - userTransformer?: UserTransformer; - groupTransformer?: GroupTransformer; - organizationTransformer?: OrganizationTransformer; - }, + configRoot: Config, + options: MicrosoftGraphOrgEntityProviderOptions, ) { - const c = config.getOptionalConfig('catalog.processors.microsoftGraphOrg'); - const providers = c ? readMicrosoftGraphConfig(c) : []; + const config = configRoot.getOptionalConfig( + 'catalog.processors.microsoftGraphOrg', + ); + const providers = config ? readMicrosoftGraphConfig(config) : []; const provider = providers.find(p => options.target.startsWith(p.target)); if (!provider) { throw new Error( - `There is no Microsoft Graph Org provider that matches ${options.target}. Please add a configuration entry for it under catalog.processors.microsoftGraphOrg.providers.`, + `There is no Microsoft Graph Org provider that matches "${options.target}". Please add a configuration entry for it under "catalog.processors.microsoftGraphOrg.providers".`, ); } @@ -73,7 +127,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { target: options.target, }); - return new MicrosoftGraphOrgEntityProvider({ + const result = new MicrosoftGraphOrgEntityProvider({ id: options.id, userTransformer: options.userTransformer, groupTransformer: options.groupTransformer, @@ -81,6 +135,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { logger, provider, }); + + result.schedule(options.schedule); + + return result; } constructor( @@ -102,19 +160,21 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; + await this.scheduleFn?.(); } /** * Runs one complete ingestion loop. Call this method regularly at some * appropriate cadence. */ - async read() { + async read(options?: { logger?: Logger }) { if (!this.connection) { throw new Error('Not initialized'); } + const logger = options?.logger ?? this.options.logger; const provider = this.options.provider; - const { markReadComplete } = trackProgress(this.options.logger); + const { markReadComplete } = trackProgress(logger); const client = MicrosoftGraphClient.create(this.options.provider); const { users, groups } = await readMicrosoftGraphOrg( @@ -130,7 +190,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { groupTransformer: this.options.groupTransformer, userTransformer: this.options.userTransformer, organizationTransformer: this.options.organizationTransformer, - logger: this.options.logger, + logger: logger, }, ); @@ -146,6 +206,34 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { markCommitComplete(); } + + private schedule( + schedule: MicrosoftGraphOrgEntityProviderOptions['schedule'], + ) { + if (schedule === 'manual') { + return; + } + + this.scheduleFn = async () => { + const id = `${this.getProviderName()}:refresh`; + await schedule.run({ + id, + fn: async () => { + const logger = this.options.logger.child({ + class: MicrosoftGraphOrgEntityProvider.prototype.constructor.name, + taskId: id, + taskInstanceId: uuid.v4(), + }); + + try { + await this.read({ logger }); + } catch (error) { + logger.error(error); + } + }, + }); + }; + } } // Helps wrap the timing and logging behaviors @@ -173,12 +261,12 @@ function trackProgress(logger: Logger) { // Makes sure that emitted entities have a proper location based on their uuid export function withLocations(providerId: string, entity: Entity): Entity { - const uuid = + const uid = entity.metadata.annotations?.[MICROSOFT_GRAPH_USER_ID_ANNOTATION] || entity.metadata.annotations?.[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] || entity.metadata.annotations?.[MICROSOFT_GRAPH_TENANT_ID_ANNOTATION] || entity.metadata.name; - const location = `msgraph:${providerId}/${encodeURIComponent(uuid)}`; + const location = `msgraph:${providerId}/${encodeURIComponent(uid)}`; return merge( { metadata: { diff --git a/plugins/catalog-backend-module-msgraph/src/processors/index.ts b/plugins/catalog-backend-module-msgraph/src/processors/index.ts index 7f70bde994..7bc3559c8b 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/index.ts @@ -15,4 +15,5 @@ */ export { MicrosoftGraphOrgEntityProvider } from './MicrosoftGraphOrgEntityProvider'; +export type { MicrosoftGraphOrgEntityProviderOptions } from './MicrosoftGraphOrgEntityProvider'; export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index b0b7403311..89e0408cc8 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-catalog-backend +## 1.0.1-next.0 + +### Patch Changes + +- 9fe24b0fc8: Adjust the error messages when entities fail validation, to clearly state what entity that failed it +- 95408dbe99: Enable internal batching of very large deletions, to not run into SQL binding limits +- ffec894ed0: add gitlab to AnnotateScmSlugEntityProcessor +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-scaffolder-common@1.0.1-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 6145ca7189: **BREAKING**: A number of types and classes have been removed, without a prior deprecation period. These were all very internal, essentially unused by the vast majority of users, and their being exposed was leading to excessive breaking of public interfaces for little-to-zero benefit. So for the 1.0 release of the catalog, the following interface changes have been made (but should have no effect on most users): + + - The return type of `CatalogBuilder.build()` now only has the fields `processingEngine` and `router` which is what most users actually consume; the other three fields (`entitiesCatalog`, `locationAnalyzer`, `locationService`) that see very little use have been removed. If you were relying on the presence of either of these in any way, please [open an issue](https://github.com/backstage/backstage/issues/new/choose) that describes your use case, and we'll see how we could fill the gap. + + - The function `createRouter` is removed; use `CatalogBuilder` as follows instead: + + ```ts + const builder = await CatalogBuilder.create(env); + // add things as needed, e.g builder.addProcessor(new ScaffolderEntitiesProcessor()); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; + ``` + + - The following types were removed: + + - `CatalogProcessingOrchestrator` + - `CatalogRule` + - `CatalogRulesEnforcer` + - `EntityAncestryResponse` + - `EntityFacetsRequest` + - `EntityFacetsResponse` + - `EntityPagination` + - `EntityProcessingRequest` + - `EntityProcessingResult` + - `EntitiesCatalog` + - `EntitiesRequest` + - `EntitiesResponse` + - `LocationService` + - `LocationInput` + - `LocationStore` + - `PageInfo` + - `RefreshOptions` + - `RefreshService` + - `RouterOptions` + + - The following classes were removed: + + - `DefaultCatalogProcessingOrchestrator` + - `DefaultCatalogRulesEnforcer` + +- 02ad19d189: **BREAKING**: Removed the deprecated `metadata.generation` field entirely. It is no longer present in TS types, nor in the REST API output. Entities that have not yet been re-stitched may still have the field present for some time, but it will get phased out gradually by your catalog instance. +- 7250b6993d: **BREAKING**: Removed the previously deprecated `results` export. Please use `processingResult` instead. +- 077e7c132f: **BREAKING**: Removed the following deprecated symbols: + + - `catalogBuilder.setRefreshInterval`, use `catalogBuilder.setProcessingInterval` instead. + - `catalogBuilder.setRefreshIntervalSeconds`, use `catalogBuilder.setProcessingIntervalSeconds` instead. + - `createRandomRefreshInterval`, use `createRandomProcessingInterval` instead. + - `RefreshIntervalFunction`, use `ProcessingIntervalFunction` instead. + +- 74375be2c6: **BREAKING**: Removed the export of the `RecursivePartial` utility type. If you relied on this type it can be redefined like this: + + ```ts + type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; + }; + ``` + +- ced3016f2a: **BREAKING**: The deprecated `CatalogEntityDocument` export has been removed, it can be imported from `@backstage/plugin-catalog-common` instead. +- 0163c41be2: **BREAKING**: Removed the deprecated `presence` field from `LocationInput`. +- d3e9ec43b7: **BREAKING**: Removed the `target` property from `EntityRelation`. This field has been replaced by `targetRef`. + This means that `target: { name: 'team-a', kind: 'group', namespace: 'default' }` is now replaced with `targetRef: 'group:default/team-a'` in entity relations. + + The entities API endpoint still return the old `target` field for to ease transitions, however the future removal of this field will be considered non breaking. + +### Patch Changes + +- 89c7e47967: Minor README update +- 26fb159a30: Pass in auth token to ancestry endpoint +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- f24ef7864e: Minor typo fixes +- e949d68059: Made sure to move the catalog-related github and ldap config into their right places +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/plugin-scaffolder-common@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-permission-node@0.5.5 + - @backstage/plugin-search-common@0.3.2 + ## 0.24.0 ### Minor Changes diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d880159a36..bd99205189 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -8,17 +8,14 @@ import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; -import { ConditionalPolicyDecision } from '@backstage/plugin-permission-node'; +import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; -import express from 'express'; import { GetEntitiesRequest } from '@backstage/catalog-client'; -import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; -import { Location as Location_2 } from '@backstage/catalog-client'; import { Logger } from 'winston'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; @@ -28,6 +25,7 @@ import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Readable } from 'stream'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; import { Router } from 'express'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TokenManager } from '@backstage/backend-common'; @@ -112,19 +110,11 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { export class CatalogBuilder { addEntityPolicy(...policies: EntityPolicy[]): CatalogBuilder; addEntityProvider(...providers: EntityProvider[]): CatalogBuilder; - addPermissionRules( - ...permissionRules: PermissionRule< - Entity, - EntitiesSearchFilter, - unknown[] - >[] - ): void; + // @alpha + addPermissionRules(...permissionRules: CatalogPermissionRule[]): void; addProcessor(...processors: CatalogProcessor[]): CatalogBuilder; build(): Promise<{ - entitiesCatalog: EntitiesCatalog; - locationAnalyzer: LocationAnalyzer; processingEngine: CatalogProcessingEngine; - locationService: LocationService; router: Router; }>; static create(env: CatalogEnvironment): CatalogBuilder; @@ -149,23 +139,37 @@ export const catalogConditions: Conditions<{ hasAnnotation: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [annotation: string] >; - hasLabel: PermissionRule; + hasLabel: PermissionRule< + Entity, + EntitiesSearchFilter, + 'catalog-entity', + [label: string] + >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [key: string, value?: string | undefined] >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [key: string, value?: string | undefined] >; - isEntityKind: PermissionRule; + isEntityKind: PermissionRule< + Entity, + EntitiesSearchFilter, + 'catalog-entity', + [kinds: string[]] + >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [claims: string[]] >; }>; @@ -179,6 +183,10 @@ export type CatalogEnvironment = { permissions: PermissionAuthorizer; }; +// @alpha +export type CatalogPermissionRule = + PermissionRule; + // @public (undocumented) export interface CatalogProcessingEngine { // (undocumented) @@ -187,12 +195,6 @@ export interface CatalogProcessingEngine { stop(): Promise; } -// @public -export interface CatalogProcessingOrchestrator { - // (undocumented) - process(request: EntityProcessingRequest): Promise; -} - // @public (undocumented) export type CatalogProcessor = { getProcessorName(): string; @@ -267,22 +269,6 @@ export type CatalogProcessorResult = | CatalogProcessorRelationResult | CatalogProcessorErrorResult; -// @public -export type CatalogRule = { - allow: Array<{ - kind: string; - }>; - locations?: Array<{ - target?: string; - type: string; - }>; -}; - -// @public -export type CatalogRulesEnforcer = { - isAllowed(entity: Entity, location: LocationSpec): boolean; -}; - // @public (undocumented) export class CodeOwnersProcessor implements CatalogProcessor { constructor(options: { @@ -305,14 +291,17 @@ export class CodeOwnersProcessor implements CatalogProcessor { } // @alpha -export const createCatalogPermissionRule: ( - rule: PermissionRule, -) => PermissionRule; +export const createCatalogConditionalDecision: ( + permission: ResourcePermission<'catalog-entity'>, + conditions: PermissionCriteria< + PermissionCondition<'catalog-entity', unknown[]> + >, +) => ConditionalPolicyDecision; // @alpha -export const createCatalogPolicyDecision: ( - conditions: PermissionCriteria>, -) => ConditionalPolicyDecision; +export const createCatalogPermissionRule: ( + rule: PermissionRule, +) => PermissionRule; // @public export function createRandomProcessingInterval(options: { @@ -320,9 +309,6 @@ export function createRandomProcessingInterval(options: { maxSeconds: number; }): ProcessingIntervalFunction; -// @public -export function createRouter(options: RouterOptions): Promise; - // @public @deprecated (undocumented) export class DefaultCatalogCollator { constructor(options: { @@ -389,101 +375,18 @@ export type DefaultCatalogCollatorFactoryOptions = { catalogClient?: CatalogApi; }; -// @public (undocumented) -export class DefaultCatalogProcessingOrchestrator - implements CatalogProcessingOrchestrator -{ - constructor(options: { - processors: CatalogProcessor[]; - integrations: ScmIntegrationRegistry; - logger: Logger; - parser: CatalogProcessorParser; - policy: EntityPolicy; - rulesEnforcer: CatalogRulesEnforcer; - }); - // (undocumented) - process(request: EntityProcessingRequest): Promise; -} - -// @public -export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { - constructor(rules: CatalogRule[]); - static readonly defaultRules: CatalogRule[]; - static fromConfig(config: Config): DefaultCatalogRulesEnforcer; - isAllowed(entity: Entity, location: LocationSpec): boolean; -} - // @public export type DeferredEntity = { entity: Entity; locationKey?: string; }; -// @public (undocumented) -export type EntitiesCatalog = { - entities(request?: EntitiesRequest): Promise; - removeEntityByUid( - uid: string, - options?: { - authorizationToken?: string; - }, - ): Promise; - entityAncestry( - entityRef: string, - options?: { - authorizationToken?: string; - }, - ): Promise; - facets(request: EntityFacetsRequest): Promise; -}; - -// @public (undocumented) -export type EntitiesRequest = { - filter?: EntityFilter; - fields?: (entity: Entity) => Entity; - pagination?: EntityPagination; - authorizationToken?: string; -}; - -// @public (undocumented) -export type EntitiesResponse = { - entities: Entity[]; - pageInfo: PageInfo; -}; - // @public export type EntitiesSearchFilter = { key: string; values?: string[]; }; -// @public (undocumented) -export type EntityAncestryResponse = { - rootEntityRef: string; - items: Array<{ - entity: Entity; - parentEntityRefs: string[]; - }>; -}; - -// @public -export interface EntityFacetsRequest { - authorizationToken?: string; - facets: string[]; - filter?: EntityFilter; -} - -// @public -export interface EntityFacetsResponse { - facets: Record< - string, - Array<{ - value: string; - count: number; - }> - >; -} - // @public export type EntityFilter = | { @@ -497,34 +400,6 @@ export type EntityFilter = } | EntitiesSearchFilter; -// @public -export type EntityPagination = { - limit?: number; - offset?: number; - after?: string; -}; - -// @public -export type EntityProcessingRequest = { - entity: Entity; - state?: JsonObject; -}; - -// @public -export type EntityProcessingResult = - | { - ok: true; - state: JsonObject; - completedEntity: Entity; - deferredEntities: DeferredEntity[]; - relations: EntityRelationSpec[]; - errors: Error[]; - } - | { - ok: false; - errors: Error[]; - }; - // @public export interface EntityProvider { connect(connection: EntityProviderConnection): Promise; @@ -593,50 +468,6 @@ export type LocationEntityProcessorOptions = { integrations: ScmIntegrationRegistry; }; -// @public -export interface LocationInput { - // @deprecated (undocumented) - presence?: 'optional' | 'required'; - // (undocumented) - target: string; - // (undocumented) - type: string; -} - -// @public -export interface LocationService { - // (undocumented) - createLocation( - location: LocationInput, - dryRun: boolean, - options?: { - authorizationToken?: string; - }, - ): Promise<{ - location: Location_2; - entities: Entity[]; - exists?: boolean; - }>; - // (undocumented) - deleteLocation( - id: string, - options?: { - authorizationToken?: string; - }, - ): Promise; - // (undocumented) - getLocation( - id: string, - options?: { - authorizationToken?: string; - }, - ): Promise; - // (undocumented) - listLocations(options?: { - authorizationToken?: string; - }): Promise; -} - // @public export type LocationSpec = { type: string; @@ -644,28 +475,6 @@ export type LocationSpec = { presence?: 'optional' | 'required'; }; -// @public -export interface LocationStore { - // (undocumented) - createLocation(location: LocationInput): Promise; - // (undocumented) - deleteLocation(id: string): Promise; - // (undocumented) - getLocation(id: string): Promise; - // (undocumented) - listLocations(): Promise; -} - -// @public (undocumented) -export type PageInfo = - | { - hasNextPage: false; - } - | { - hasNextPage: true; - endCursor: string; - }; - // @public (undocumented) export function parseEntityYaml( data: Buffer, @@ -677,23 +486,37 @@ export const permissionRules: { hasAnnotation: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [annotation: string] >; - hasLabel: PermissionRule; + hasLabel: PermissionRule< + Entity, + EntitiesSearchFilter, + 'catalog-entity', + [label: string] + >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [key: string, value?: string | undefined] >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [key: string, value?: string | undefined] >; - isEntityKind: PermissionRule; + isEntityKind: PermissionRule< + Entity, + EntitiesSearchFilter, + 'catalog-entity', + [kinds: string[]] + >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, + 'catalog-entity', [claims: string[]] >; }; @@ -762,35 +585,6 @@ export const processingResult: Readonly<{ readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult; }>; -// @public -export type RefreshOptions = { - entityRef: string; - authorizationToken?: string; -}; - -// @public -export interface RefreshService { - refresh(options: RefreshOptions): Promise; -} - -// @public -export interface RouterOptions { - // (undocumented) - config: Config; - // (undocumented) - entitiesCatalog?: EntitiesCatalog; - // (undocumented) - locationAnalyzer?: LocationAnalyzer; - // (undocumented) - locationService: LocationService; - // (undocumented) - logger: Logger; - // (undocumented) - permissionIntegrationRouter?: express.Router; - // (undocumented) - refreshService?: RefreshService; -} - // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { constructor(options: { reader: UrlReader; logger: Logger }); diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 957f18c15f..950ed2f63c 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -105,38 +105,5 @@ export interface Config { allow: Array; }>; }>; - - /** - * List of processor-specific options and attributes - */ - processors?: { - /** - * GithubMultiOrgReaderProcessor configuration - */ - githubMultiOrg?: { - /** - * The configuration parameters for each GitHub org to process. - */ - orgs: Array<{ - /** - * The name of the GitHub org to process. - */ - name: string; - /** - * The namespace of the group created for this org. - * - * Defaults to org name if omitted. - */ - groupNamespace?: string; - - /** - * The namespace of the users created from this org. - * - * Defaults to empty string if omitted. - */ - userNamespace?: string; - }>; - }; - }; }; } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 54b19f5269..0470b599b3 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "0.24.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,18 +34,18 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-node": "^0.5.4", - "@backstage/plugin-scaffolder-common": "^0.3.0", - "@backstage/plugin-search-common": "^0.3.1", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-node": "^0.5.6-next.0", + "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", @@ -68,10 +68,10 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-search-backend-node": "0.5.1", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-search-backend-node": "0.5.3-next.0", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-backend/src/catalog/index.ts b/plugins/catalog-backend/src/catalog/index.ts index 59f67b2f52..ba34673bba 100644 --- a/plugins/catalog-backend/src/catalog/index.ts +++ b/plugins/catalog-backend/src/catalog/index.ts @@ -14,15 +14,4 @@ * limitations under the License. */ -export type { - EntitiesCatalog, - EntitiesRequest, - EntitiesResponse, - EntitiesSearchFilter, - EntityAncestryResponse, - EntityFacetsRequest, - EntityFacetsResponse, - EntityFilter, - EntityPagination, - PageInfo, -} from './types'; +export type { EntitiesSearchFilter, EntityFilter } from './types'; diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index b575b96c6c..34060814aa 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -31,7 +31,6 @@ export type EntityFilter = /** * A pagination rule for entities. - * @public */ export type EntityPagination = { limit?: number; @@ -60,7 +59,6 @@ export type EntitiesSearchFilter = { values?: string[]; }; -/** @public */ export type PageInfo = | { hasNextPage: false; @@ -70,7 +68,6 @@ export type PageInfo = endCursor: string; }; -/** @public */ export type EntitiesRequest = { filter?: EntityFilter; fields?: (entity: Entity) => Entity; @@ -78,13 +75,11 @@ export type EntitiesRequest = { authorizationToken?: string; }; -/** @public */ export type EntitiesResponse = { entities: Entity[]; pageInfo: PageInfo; }; -/** @public */ export type EntityAncestryResponse = { rootEntityRef: string; items: Array<{ @@ -95,8 +90,6 @@ export type EntityAncestryResponse = { /** * The request shape for {@link EntitiesCatalog.facets}. - * - * @public */ export interface EntityFacetsRequest { /** @@ -121,8 +114,6 @@ export interface EntityFacetsRequest { /** * The response shape for {@link EntitiesCatalog.facets}. - * - * @public */ export interface EntityFacetsResponse { /** @@ -131,8 +122,7 @@ export interface EntityFacetsResponse { facets: Record>; } -/** @public */ -export type EntitiesCatalog = { +export interface EntitiesCatalog { /** * Fetch entities. * @@ -167,4 +157,4 @@ export type EntitiesCatalog = { * @param request - Request options */ facets(request: EntityFacetsRequest): Promise; -}; +} diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 061be0a767..c9fe0da0a3 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -164,8 +164,9 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options); if (toRemove.length) { - // TODO(freben): Batch split, to not hit variable limits? - /* + let removedCount = 0; + for (const refs of lodash.chunk(toRemove, 1000)) { + /* WITH RECURSIVE -- All the nodes that can be reached downwards from our root descendants(root_id, entity_ref) AS ( @@ -200,78 +201,79 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { -- Exclude all lines that had such a foreign ancestor WHERE ancestors.root_id IS NULL; */ - const removedCount = await tx('refresh_state') - .whereIn('entity_ref', function orphanedEntityRefs(orphans) { - return ( - orphans - // All the nodes that can be reached downwards from our root - .withRecursive('descendants', function descendants(outer) { - return outer - .select({ root_id: 'id', entity_ref: 'target_entity_ref' }) - .from('refresh_state_references') - .where('source_key', options.sourceKey) - .whereIn('target_entity_ref', toRemove) - .union(function recursive(inner) { - return inner - .select({ - root_id: 'descendants.root_id', - entity_ref: - 'refresh_state_references.target_entity_ref', - }) - .from('descendants') - .join('refresh_state_references', { - 'descendants.entity_ref': - 'refresh_state_references.source_entity_ref', - }); - }); - }) - // All the nodes that can be reached upwards from the descendants - .withRecursive('ancestors', function ancestors(outer) { - return outer - .select({ - root_id: tx.raw('CAST(NULL as INT)', []), - via_entity_ref: 'entity_ref', - to_entity_ref: 'entity_ref', - }) - .from('descendants') - .union(function recursive(inner) { - return inner - .select({ - root_id: tx.raw( - 'CASE WHEN source_key IS NOT NULL THEN id ELSE NULL END', - [], - ), - via_entity_ref: 'source_entity_ref', - to_entity_ref: 'ancestors.to_entity_ref', - }) - .from('ancestors') - .join('refresh_state_references', { - target_entity_ref: 'ancestors.via_entity_ref', - }); - }); - }) - // Start out with all of the descendants - .select('descendants.entity_ref') - .from('descendants') - // Expand with all ancestors that point to those, but aren't the current root - .leftOuterJoin('ancestors', function keepaliveRoots() { - this.on( - 'ancestors.to_entity_ref', - '=', - 'descendants.entity_ref', - ); - this.andOnNotNull('ancestors.root_id'); - this.andOn('ancestors.root_id', '!=', 'descendants.root_id'); - }) - .whereNull('ancestors.root_id') - ); - }) - .delete(); + removedCount += await tx('refresh_state') + .whereIn('entity_ref', function orphanedEntityRefs(orphans) { + return ( + orphans + // All the nodes that can be reached downwards from our root + .withRecursive('descendants', function descendants(outer) { + return outer + .select({ root_id: 'id', entity_ref: 'target_entity_ref' }) + .from('refresh_state_references') + .where('source_key', options.sourceKey) + .whereIn('target_entity_ref', refs) + .union(function recursive(inner) { + return inner + .select({ + root_id: 'descendants.root_id', + entity_ref: + 'refresh_state_references.target_entity_ref', + }) + .from('descendants') + .join('refresh_state_references', { + 'descendants.entity_ref': + 'refresh_state_references.source_entity_ref', + }); + }); + }) + // All the nodes that can be reached upwards from the descendants + .withRecursive('ancestors', function ancestors(outer) { + return outer + .select({ + root_id: tx.raw('CAST(NULL as INT)', []), + via_entity_ref: 'entity_ref', + to_entity_ref: 'entity_ref', + }) + .from('descendants') + .union(function recursive(inner) { + return inner + .select({ + root_id: tx.raw( + 'CASE WHEN source_key IS NOT NULL THEN id ELSE NULL END', + [], + ), + via_entity_ref: 'source_entity_ref', + to_entity_ref: 'ancestors.to_entity_ref', + }) + .from('ancestors') + .join('refresh_state_references', { + target_entity_ref: 'ancestors.via_entity_ref', + }); + }); + }) + // Start out with all of the descendants + .select('descendants.entity_ref') + .from('descendants') + // Expand with all ancestors that point to those, but aren't the current root + .leftOuterJoin('ancestors', function keepaliveRoots() { + this.on( + 'ancestors.to_entity_ref', + '=', + 'descendants.entity_ref', + ); + this.andOnNotNull('ancestors.root_id'); + this.andOn('ancestors.root_id', '!=', 'descendants.root_id'); + }) + .whereNull('ancestors.root_id') + ); + }) + .delete(); - await tx('refresh_state_references') - .where('source_key', '=', options.sourceKey) - .whereIn('target_entity_ref', toRemove) - .delete(); + await tx('refresh_state_references') + .where('source_key', '=', options.sourceKey) + .whereIn('target_entity_ref', refs) + .delete(); + } this.options.logger.debug( `removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`, diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index e4bfdb8d0f..587a0bd7d5 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -23,8 +23,6 @@ import { LocationSpec } from '../api'; * Rules to apply to catalog entities. * * An undefined list of matchers means match all, an empty list of matchers means match none. - * - * @public */ export type CatalogRule = { allow: Array<{ @@ -39,8 +37,6 @@ export type CatalogRule = { /** * Decides whether an entity from a given location is allowed to enter the * catalog, according to some rule set. - * - * @public */ export type CatalogRulesEnforcer = { isAllowed(entity: Entity, location: LocationSpec): boolean; @@ -49,8 +45,6 @@ export type CatalogRulesEnforcer = { /** * Implements the default catalog rule set, consuming the config keys * `catalog.rules` and `catalog.locations.[].rules`. - * - * @public */ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { /** @@ -131,7 +125,7 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { constructor(private readonly rules: CatalogRule[]) {} /** - * Checks wether a specific entity/location combination is allowed + * Checks whether a specific entity/location combination is allowed * according to the configured rules. */ isAllowed(entity: Entity, location: LocationSpec) { diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 1f4729b041..c97d029b5c 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -export { DefaultCatalogRulesEnforcer } from './CatalogRules'; -export type { CatalogRule, CatalogRulesEnforcer } from './CatalogRules'; export type { AnalyzeLocationEntityField, AnalyzeLocationExistingEntity, diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts index 8cf250de2a..ac4415549e 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts @@ -76,7 +76,7 @@ describe('CodeOwnersProcessor', () => { expect(result).toEqual(entity); }); - it('should ingore invalid locations type', async () => { + it('should ignore invalid locations type', async () => { const { entity, processor } = setupTest(); const result = await processor.preProcessEntity( diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts index 1e553a44f8..7fd6514bb8 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts @@ -94,7 +94,98 @@ describe('AnnotateScmSlugEntityProcessor', () => { const location: LocationSpec = { type: 'url', target: - 'https://gitlab.com/backstage/backstage/-/blob/master/catalog-info.yaml', + 'https://not-in-mock-config.example.com/backstage/backstage/-/blob/master/catalog-info.yaml', + }; + + const processor = AnnotateScmSlugEntityProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }); + }); + }); + describe('gitlab', () => { + it('adds annotation', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://gitlab.com/group/subgroup/project/-/blob/master/catalog-info.yaml', + }; + + const processor = AnnotateScmSlugEntityProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'gitlab.com/project-slug': 'group/subgroup/project', + }, + }, + }); + }); + + it('does not override existing annotation', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'gitlab.com/project-slug': 'backstage/community', + }, + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://gitlab.com/group/subgroup/project/-/blob/master/catalog-info.yaml', + }; + + const processor = AnnotateScmSlugEntityProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'gitlab.com/project-slug': 'backstage/community', + }, + }, + }); + }); + + it('should not add annotation for other providers', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://not-in-mock-config.example.com/group/subgroup/project/-/blob/master/catalog-info.yaml', }; const processor = AnnotateScmSlugEntityProcessor.fromConfig( diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts index e44db4d44d..c4f7218896 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts @@ -24,6 +24,7 @@ import { identity, merge, pickBy } from 'lodash'; import { CatalogProcessor, LocationSpec } from '../../api'; const GITHUB_ACTIONS_ANNOTATION = 'github.com/project-slug'; +const GITLAB_ACTIONS_ANNOTATION = 'gitlab.com/project-slug'; /** @public */ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor { @@ -53,16 +54,26 @@ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor { location.target, ); - if (!scmIntegration || scmIntegration.type !== 'github') { + if (!scmIntegration) { return entity; } - const gitUrl = parseGitUrl(location.target); - let githubProjectSlug = - entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]; + let annotation; + switch (scmIntegration.type) { + case 'github': + annotation = GITHUB_ACTIONS_ANNOTATION; + break; + case 'gitlab': + annotation = GITLAB_ACTIONS_ANNOTATION; + break; + default: + return entity; + } - if (!githubProjectSlug) { - githubProjectSlug = `${gitUrl.owner}/${gitUrl.name}`; + let projectSlug = entity.metadata.annotations?.[annotation]; + if (!projectSlug) { + const gitUrl = parseGitUrl(location.target); + projectSlug = `${gitUrl.owner}/${gitUrl.name}`; } return merge( @@ -70,7 +81,7 @@ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor { metadata: { annotations: pickBy( { - [GITHUB_ACTIONS_ANNOTATION]: githubProjectSlug, + [annotation]: projectSlug, }, identity, ), diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts index d637f4eda0..0d4bedabbb 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts @@ -22,7 +22,7 @@ import { DbLocationsRow } from '../../database/tables'; import { getEntityLocationRef } from '../../processing/util'; import { EntityProvider, EntityProviderConnection } from '../../api'; import { locationSpecToLocationEntity } from '../../util/conversion'; -import { LocationInput, LocationStore } from '../../service'; +import { LocationInput, LocationStore } from '../../service/types'; export class DefaultLocationStore implements LocationStore, EntityProvider { private _connection: EntityProviderConnection | undefined; diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 7bfc66276e..adff97336e 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -345,7 +345,7 @@ describe('PlaceholderProcessor', () => { }, ), ).rejects.toThrow( - 'Placeholder $text could not form a URL out of ./a/b/catalog-info.yaml and ../c/catalog-info.yaml, TypeError: Invalid base URL: ./a/b/catalog-info.yaml', + /^Placeholder \$text could not form a URL out of \.\/a\/b\/catalog-info\.yaml and \.\.\/c\/catalog-info\.yaml, TypeError \[ERR_INVALID_URL\]/, ); expect(read).not.toBeCalled(); diff --git a/plugins/catalog-backend/src/permissions/conditionExports.ts b/plugins/catalog-backend/src/permissions/conditionExports.ts index 2846c8c3ec..1c1ba70631 100644 --- a/plugins/catalog-backend/src/permissions/conditionExports.ts +++ b/plugins/catalog-backend/src/permissions/conditionExports.ts @@ -18,41 +18,49 @@ import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createConditionExports } from '@backstage/plugin-permission-node'; import { permissionRules } from './rules'; -const conditionExports = createConditionExports({ +const { conditions, createConditionalDecision } = createConditionExports({ pluginId: 'catalog', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, rules: permissionRules, }); /** - * These conditions are used when creating conditional decisions that are returned - * by authorization policies. + * These conditions are used when creating conditional decisions for catalog + * entities that are returned by authorization policies. * * @alpha */ -export const catalogConditions = conditionExports.conditions; +export const catalogConditions = conditions; /** - * `createCatalogPolicyDecision` can be used when authoring policies to create - * conditional decisions. + * `createCatalogConditionalDecision` can be used when authoring policies to + * create conditional decisions. It requires a permission of type + * `ResourcePermission<'catalog-entity'>` to be passed as the first parameter. + * It's recommended that you use the provided `isResourcePermission` and + * `isPermission` helper methods to narrow the type of the permission passed to + * the handle method as shown below. * * ``` * // MyAuthorizationPolicy.ts * ... * import { createCatalogPolicyDecision } from '@backstage/plugin-catalog-backend'; + * import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; * * class MyAuthorizationPolicy implements PermissionPolicy { * async handle(request, user) { * ... * - * return createCatalogPolicyDecision({ - * anyOf: [...insert conditions here...], - * }); - * } + * if (isResourcePermission(request.permission, RESOURCE_TYPE_CATALOG_ENTITY)) { + * return createCatalogConditionalDecision( + * request.permission, + * { anyOf: [...insert conditions here...] } + * ); + * } + * + * ... * } * ``` * * @alpha */ -export const createCatalogPolicyDecision = - conditionExports.createPolicyDecision; +export const createCatalogConditionalDecision = createConditionalDecision; diff --git a/plugins/catalog-backend/src/permissions/index.ts b/plugins/catalog-backend/src/permissions/index.ts index cefc17ed99..19d57de908 100644 --- a/plugins/catalog-backend/src/permissions/index.ts +++ b/plugins/catalog-backend/src/permissions/index.ts @@ -16,6 +16,6 @@ export { catalogConditions, - createCatalogPolicyDecision, + createCatalogConditionalDecision, } from './conditionExports'; export * from './rules'; diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 27a6033362..3808eced7b 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -16,12 +16,14 @@ import { get } from 'lodash'; import { Entity } from '@backstage/catalog-model'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply: (resource: Entity, key: string, value?: string) => { const foundValue = get(resource[propertyType], key); if (value !== undefined) { diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index c6c5951153..68ca65418d 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; /** @@ -27,6 +28,7 @@ export const hasAnnotation = createCatalogPermissionRule({ name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply: (resource: Entity, annotation: string) => !!resource.metadata.annotations?.hasOwnProperty(annotation), toQuery: (annotation: string) => ({ diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 7296eecd89..9f37cbfb59 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -15,6 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; /** @@ -25,6 +26,7 @@ import { createCatalogPermissionRule } from './util'; export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply: (resource: Entity, label: string) => !!resource.metadata.labels?.hasOwnProperty(label), toQuery: (label: string) => ({ diff --git a/plugins/catalog-backend/src/permissions/rules/index.ts b/plugins/catalog-backend/src/permissions/rules/index.ts index f9d192781e..d3f405621a 100644 --- a/plugins/catalog-backend/src/permissions/rules/index.ts +++ b/plugins/catalog-backend/src/permissions/rules/index.ts @@ -36,4 +36,5 @@ export const permissionRules = { isEntityOwner, }; +export type { CatalogPermissionRule } from './util'; export { createCatalogPermissionRule } from './util'; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index c330c903d9..3bd600e8fe 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; @@ -25,6 +26,7 @@ import { createCatalogPermissionRule } from './util'; export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply(resource: Entity, kinds: string[]) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 23f118abc4..5403da2310 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -15,6 +15,7 @@ */ import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; /** @@ -26,6 +27,7 @@ import { createCatalogPermissionRule } from './util'; export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply: (resource: Entity, claims: string[]) => { if (!resource.relations) { return false; diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index a1316c7fb2..36b351595d 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -15,9 +15,23 @@ */ import { Entity } from '@backstage/catalog-model'; -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { + makeCreatePermissionRule, + PermissionRule, +} from '@backstage/plugin-permission-node'; import { EntitiesSearchFilter } from '../../catalog/types'; +/** + * Convenience type for {@link @backstage/plugin-permission-node#PermissionRule} + * instances with the correct resource type, resource, and filter to work with + * the catalog. + * + * @alpha + */ +export type CatalogPermissionRule = + PermissionRule; + /** * Helper function for creating correctly-typed * {@link @backstage/plugin-permission-node#PermissionRule}s for the @@ -27,5 +41,6 @@ import { EntitiesSearchFilter } from '../../catalog/types'; */ export const createCatalogPermissionRule = makeCreatePermissionRule< Entity, - EntitiesSearchFilter + EntitiesSearchFilter, + typeof RESOURCE_TYPE_CATALOG_ENTITY >(); diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index c050779a8b..156503da8e 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -241,7 +241,7 @@ export class DefaultCatalogProcessingOrchestrator validateEntity(entity); } catch (e) { throw new ConflictError( - `Entity envelope failed validation after preprocessing`, + `Entity envelope for ${context.entityRef} failed validation after preprocessing`, e, ); } @@ -262,7 +262,7 @@ export class DefaultCatalogProcessingOrchestrator } } catch (e) { throw new InputError( - `Processor ${processor.constructor.name} threw an error while validating the entity`, + `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`, e, ); } @@ -271,7 +271,7 @@ export class DefaultCatalogProcessingOrchestrator if (!foundKind) { throw new InputError( - 'No processor recognized the entity as valid, possibly caused by a foreign kind or apiVersion', + `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`, ); } } diff --git a/plugins/catalog-backend/src/processing/index.ts b/plugins/catalog-backend/src/processing/index.ts index 315cea37d7..b392c3aa76 100644 --- a/plugins/catalog-backend/src/processing/index.ts +++ b/plugins/catalog-backend/src/processing/index.ts @@ -14,14 +14,7 @@ * limitations under the License. */ -export type { - CatalogProcessingOrchestrator, - CatalogProcessingEngine, - EntityProcessingRequest, - EntityProcessingResult, - DeferredEntity, -} from './types'; -export { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; +export type { CatalogProcessingEngine, DeferredEntity } from './types'; export { createRandomProcessingInterval } from './refresh'; export type { ProcessingIntervalFunction } from './refresh'; diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index ac9a33bf62..bb44670443 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -33,6 +33,7 @@ import { catalogEntityReadPermission, CatalogEntityDocument, } from '@backstage/plugin-catalog-common'; +import { Permission } from '@backstage/plugin-permission-common'; /** * @public @@ -45,7 +46,8 @@ export class DefaultCatalogCollator { protected filter?: GetEntitiesRequest['filter']; protected readonly catalogClient: CatalogApi; public readonly type: string = 'software-catalog'; - public readonly visibilityPermission = catalogEntityReadPermission; + public readonly visibilityPermission: Permission = + catalogEntityReadPermission; protected tokenManager: TokenManager; static fromConfig( diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts index 4d28559c82..98bcfcd8d4 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollatorFactory.ts @@ -34,6 +34,7 @@ import { catalogEntityReadPermission, CatalogEntityDocument, } from '@backstage/plugin-catalog-common'; +import { Permission } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; /** @public */ @@ -49,7 +50,8 @@ export type DefaultCatalogCollatorFactoryOptions = { /** @public */ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { public readonly type: string = 'software-catalog'; - public readonly visibilityPermission = catalogEntityReadPermission; + public readonly visibilityPermission: Permission = + catalogEntityReadPermission; private locationTemplate: string; private filter?: GetEntitiesRequest['filter']; diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index 1f0e5ea644..b29e1121b3 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -14,15 +14,11 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { NotAllowedError } from '@backstage/errors'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { - createConditionTransformer, - PermissionRule, -} from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../catalog/types'; +import { createConditionTransformer } from '@backstage/plugin-permission-node'; import { isEntityKind } from '../permissions/rules/isEntityKind'; +import { CatalogPermissionRule } from '../permissions/rules'; import { AuthorizedEntitiesCatalog } from './AuthorizedEntitiesCatalog'; describe('AuthorizedEntitiesCatalog', () => { @@ -36,9 +32,7 @@ describe('AuthorizedEntitiesCatalog', () => { authorize: jest.fn(), }; - const createCatalog = ( - ...rules: PermissionRule[] - ) => + const createCatalog = (...rules: CatalogPermissionRule[]) => new AuthorizedEntitiesCatalog( fakeCatalog, fakePermissionApi, diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 8dd507c74e..99d7f0e41a 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -17,7 +17,6 @@ import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, - Entity, EntityPolicies, EntityPolicy, FieldFormatEntityPolicy, @@ -32,7 +31,6 @@ import { ScmIntegrations } from '@backstage/integration'; import { createHash } from 'crypto'; import { Router } from 'express'; import lodash, { keyBy } from 'lodash'; -import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog'; import { CatalogProcessor, @@ -76,12 +74,13 @@ import { AuthorizedRefreshService } from './AuthorizedRefreshService'; import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; -import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; -import { permissionRules as catalogPermissionRules } from '../permissions/rules'; +import { + CatalogPermissionRule, + permissionRules as catalogPermissionRules, +} from '../permissions/rules'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { - PermissionRule, createConditionTransformer, createPermissionIntegrationRouter, } from '@backstage/plugin-permission-node'; @@ -136,11 +135,7 @@ export class CatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; - private permissionRules: PermissionRule< - Entity, - EntitiesSearchFilter, - unknown[] - >[]; + private permissionRules: CatalogPermissionRule[]; /** * Creates a catalog builder. @@ -340,14 +335,9 @@ export class CatalogBuilder { * {@link @backstage/plugin-permission-node#PermissionRule}. * * @param permissionRules - Additional permission rules + * @alpha */ - addPermissionRules( - ...permissionRules: PermissionRule< - Entity, - EntitiesSearchFilter, - unknown[] - >[] - ) { + addPermissionRules(...permissionRules: CatalogPermissionRule[]) { this.permissionRules.push(...permissionRules); } @@ -355,10 +345,7 @@ export class CatalogBuilder { * Wires up and returns all of the component parts of the catalog */ async build(): Promise<{ - entitiesCatalog: EntitiesCatalog; - locationAnalyzer: LocationAnalyzer; processingEngine: CatalogProcessingEngine; - locationService: LocationService; router: Router; }> { const { config, database, logger, permissions } = this.env; @@ -460,10 +447,7 @@ export class CatalogBuilder { await connectEntityProviders(processingDatabase, entityProviders); return { - entitiesCatalog, - locationAnalyzer, processingEngine, - locationService, router, }; } diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 8db15cb4a1..6def04a11c 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -21,12 +21,15 @@ import type { Location } from '@backstage/catalog-client'; import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import request from 'supertest'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog } from '../catalog/types'; import { LocationInput, LocationService, RefreshService } from './types'; import { basicEntityFilter } from './request'; import { createRouter } from './createRouter'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { + createPermissionIntegrationRouter, + createPermissionRule, +} from '@backstage/plugin-permission-node'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; describe('createRouter readonly disabled', () => { @@ -568,12 +571,13 @@ describe('NextRouter permissioning', () => { let app: express.Express; let refreshService: RefreshService; - const fakeRule = { + const fakeRule = createPermissionRule({ name: 'FAKE_RULE', description: 'fake rule', + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, apply: () => true, toQuery: () => ({ key: '', values: [] }), - }; + }); beforeAll(async () => { entitiesCatalog = { @@ -631,7 +635,11 @@ describe('NextRouter permissioning', () => { id: '123', resourceType: 'catalog-entity', resourceRef: 'component:default/spidey-sense', - conditions: { rule: 'FAKE_RULE', params: ['user:default/spiderman'] }, + conditions: { + rule: 'FAKE_RULE', + resourceType: 'catalog-entity', + params: ['user:default/spiderman'], + }, }, ], }; diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index df289d579b..a35309035f 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -22,7 +22,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import yn from 'yn'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog } from '../catalog/types'; import { LocationAnalyzer } from '../ingestion/types'; import { basicEntityFilter, diff --git a/plugins/catalog-backend/src/service/index.ts b/plugins/catalog-backend/src/service/index.ts index e1c7f54475..b1e91e4382 100644 --- a/plugins/catalog-backend/src/service/index.ts +++ b/plugins/catalog-backend/src/service/index.ts @@ -14,14 +14,5 @@ * limitations under the License. */ -export type { - LocationService, - RefreshService, - RefreshOptions, - LocationStore, - LocationInput, -} from './types'; -export { createRouter } from './createRouter'; -export type { RouterOptions } from './createRouter'; export type { CatalogEnvironment } from './CatalogBuilder'; export { CatalogBuilder } from './CatalogBuilder'; diff --git a/plugins/catalog-backend/src/service/request/parseEntityPaginationParams.ts b/plugins/catalog-backend/src/service/request/parseEntityPaginationParams.ts index c91c0b0214..87b19e13de 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityPaginationParams.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityPaginationParams.ts @@ -15,7 +15,7 @@ */ import { InputError } from '@backstage/errors'; -import { EntityPagination } from '../../catalog'; +import { EntityPagination } from '../../catalog/types'; import { parseIntegerParam, parseStringParam } from './common'; /** diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index fc315b9d89..f6cfa2e783 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -25,8 +25,6 @@ import { Location } from '@backstage/catalog-client'; export interface LocationInput { type: string; target: string; - /** @deprecated This field is ignored and will be removed */ - presence?: 'optional' | 'required'; } /** diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 4e5bb6571a..5227daf20b 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-common +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/search-common@0.3.3-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- Updated dependencies + - @backstage/search-common@0.3.2 + - @backstage/plugin-permission-common@0.5.3 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index a73cae9994..c15cbae103 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -3,14 +3,15 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; import { IndexableDocument } from '@backstage/search-common'; -import { Permission } from '@backstage/plugin-permission-common'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha -export const catalogEntityCreatePermission: Permission; +export const catalogEntityCreatePermission: BasicPermission; // @alpha -export const catalogEntityDeletePermission: Permission; +export const catalogEntityDeletePermission: ResourcePermission<'catalog-entity'>; // @public export interface CatalogEntityDocument extends IndexableDocument { @@ -29,19 +30,24 @@ export interface CatalogEntityDocument extends IndexableDocument { } // @alpha -export const catalogEntityReadPermission: Permission; +export type CatalogEntityPermission = ResourcePermission< + typeof RESOURCE_TYPE_CATALOG_ENTITY +>; // @alpha -export const catalogEntityRefreshPermission: Permission; +export const catalogEntityReadPermission: ResourcePermission<'catalog-entity'>; // @alpha -export const catalogLocationCreatePermission: Permission; +export const catalogEntityRefreshPermission: ResourcePermission<'catalog-entity'>; // @alpha -export const catalogLocationDeletePermission: Permission; +export const catalogLocationCreatePermission: BasicPermission; // @alpha -export const catalogLocationReadPermission: Permission; +export const catalogLocationDeletePermission: BasicPermission; + +// @alpha +export const catalogLocationReadPermission: BasicPermission; // @alpha export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index fe131bd22f..7f30d912c7 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "0.2.2", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/search-common": "^0.3.1" + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/search-common": "^0.3.3-next.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist", diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index 08184eb871..dfa90c448b 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -31,5 +31,6 @@ export { catalogLocationCreatePermission, catalogLocationDeletePermission, } from './permissions'; +export type { CatalogEntityPermission } from './permissions'; export * from './search'; diff --git a/plugins/catalog-common/src/permissions.ts b/plugins/catalog-common/src/permissions.ts index 31a8f32118..aa95e8c00f 100644 --- a/plugins/catalog-common/src/permissions.ts +++ b/plugins/catalog-common/src/permissions.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { Permission } from '@backstage/plugin-permission-common'; +import { + createPermission, + ResourcePermission, +} from '@backstage/plugin-permission-common'; /** * Permission resource type which corresponds to catalog entities. @@ -24,6 +27,15 @@ import { Permission } from '@backstage/plugin-permission-common'; */ export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; +/** + * Convenience type for catalog entity + * {@link @backstage/plugin-permission-common#ResourcePermission}s. + * @alpha + */ +export type CatalogEntityPermission = ResourcePermission< + typeof RESOURCE_TYPE_CATALOG_ENTITY +>; + /** * This permission is used to authorize actions that involve reading one or more * entities from the catalog. @@ -32,13 +44,13 @@ export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; * exist in the catalog — both in the frontend and in API responses. * @alpha */ -export const catalogEntityReadPermission: Permission = { +export const catalogEntityReadPermission = createPermission({ name: 'catalog.entity.read', attributes: { action: 'read', }, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, -}; +}); /** * This permission is used to authorize actions that involve creating a new @@ -46,38 +58,38 @@ export const catalogEntityReadPermission: Permission = { * catalog. * @alpha */ -export const catalogEntityCreatePermission: Permission = { +export const catalogEntityCreatePermission = createPermission({ name: 'catalog.entity.create', attributes: { action: 'create', }, -}; +}); /** * This permission is used to designate actions that involve removing one or * more entities from the catalog. * @alpha */ -export const catalogEntityDeletePermission: Permission = { +export const catalogEntityDeletePermission = createPermission({ name: 'catalog.entity.delete', attributes: { action: 'delete', }, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, -}; +}); /** * This permission is used to designate refreshing one or more entities from the * catalog. * @alpha */ -export const catalogEntityRefreshPermission: Permission = { +export const catalogEntityRefreshPermission = createPermission({ name: 'catalog.entity.refresh', attributes: { action: 'update', }, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, -}; +}); /** * This permission is used to designate actions that involve reading one or more @@ -87,33 +99,33 @@ export const catalogEntityRefreshPermission: Permission = { * not exist in the catalog — both in the frontend and in API responses. * @alpha */ -export const catalogLocationReadPermission: Permission = { +export const catalogLocationReadPermission = createPermission({ name: 'catalog.location.read', attributes: { action: 'read', }, -}; +}); /** * This permission is used to designate actions that involve creating catalog * locations. * @alpha */ -export const catalogLocationCreatePermission: Permission = { +export const catalogLocationCreatePermission = createPermission({ name: 'catalog.location.create', attributes: { action: 'create', }, -}; +}); /** * This permission is used to designate actions that involve deleting locations * from the catalog. * @alpha */ -export const catalogLocationDeletePermission: Permission = { +export const catalogLocationDeletePermission = createPermission({ name: 'catalog.location.delete', attributes: { action: 'delete', }, -}; +}); diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 5a22ee83b8..fb7a4eadb2 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-graph +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.2.15 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + ## 0.2.14 ### Patch Changes diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index 82db1dac3b..d9d8019fd4 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -128,6 +128,8 @@ export const EntityRelationsGraph: ({ relationPairs, className, zoom, + renderNode, + renderLabel, }: { rootEntityNames: CompoundEntityRef | CompoundEntityRef[]; maxDepth?: number | undefined; @@ -142,6 +144,10 @@ export const EntityRelationsGraph: ({ relationPairs?: RelationPairs | undefined; className?: string | undefined; zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + renderNode?: DependencyGraphTypes.RenderNodeFunction | undefined; + renderLabel?: + | DependencyGraphTypes.RenderLabelFunction + | undefined; }) => JSX.Element; // @public diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index d86df7d56c..1eb8fafd6d 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.14", + "version": "0.2.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,11 +24,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -45,11 +45,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx index 2bf303bb92..5d9e9b18a6 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx @@ -21,6 +21,7 @@ import { RELATION_OWNER_OF, RELATION_PART_OF, } from '@backstage/catalog-model'; +import { DependencyGraphTypes } from '@backstage/core-components'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; @@ -30,6 +31,7 @@ import { EntityRelationsGraph } from './EntityRelationsGraph'; describe('', () => { let Wrapper: FunctionComponent; let catalog: jest.Mocked; + const CUSTOM_TEST_ID = 'custom-test-id'; beforeAll(() => { Object.defineProperty(window.SVGElement.prototype, 'getBBox', { @@ -378,4 +380,49 @@ describe('', () => { userEvent.click(await findByText('k:d/a1')); expect(onNodeClick).toBeCalledTimes(1); }); + + test('render custom node', async () => { + const renderNode = (props: DependencyGraphTypes.RenderNodeProps) => ( + + {props.node.id} + + + ); + + const { findAllByTestId, container } = await renderInTestApp( + + + , + ); + + const node = await findAllByTestId(CUSTOM_TEST_ID); + expect(node[0]).toBeInTheDocument(); + expect(container.querySelector('circle')).toBeInTheDocument(); + }); + + test('render custom label', async () => { + const renderLabel = (props: DependencyGraphTypes.RenderLabelProps) => ( + + {`Test-Label${props.edge.label}`} + + + ); + + const { findAllByTestId, findAllByText, container } = await renderInTestApp( + + + , + ); + const node = await findAllByTestId(CUSTOM_TEST_ID); + expect(node[0]).toBeInTheDocument(); + expect(container.querySelector('circle')).toBeInTheDocument(); + const labels = await findAllByText('Test-Labelvisible'); + expect(labels[0]).toBeInTheDocument(); + }); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx index c5c83a54cd..bf3fbf8f3a 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx @@ -28,7 +28,7 @@ import React, { MouseEvent, useEffect, useMemo } from 'react'; import { CustomLabel } from './CustomLabel'; import { CustomNode } from './CustomNode'; import { ALL_RELATION_PAIRS, RelationPairs } from './relations'; -import { Direction, EntityNode } from './types'; +import { Direction, EntityEdge, EntityNode } from './types'; import { useEntityRelationNodesAndEdges } from './useEntityRelationNodesAndEdges'; const useStyles = makeStyles(theme => ({ @@ -79,6 +79,8 @@ export const EntityRelationsGraph = ({ relationPairs = ALL_RELATION_PAIRS, className, zoom = 'enabled', + renderNode, + renderLabel, }: { rootEntityNames: CompoundEntityRef | CompoundEntityRef[]; maxDepth?: number; @@ -91,6 +93,8 @@ export const EntityRelationsGraph = ({ relationPairs?: RelationPairs; className?: string; zoom?: 'enabled' | 'disabled' | 'enable-on-click'; + renderNode?: DependencyGraphTypes.RenderNodeFunction; + renderLabel?: DependencyGraphTypes.RenderLabelFunction; }) => { const theme = useTheme(); const classes = useStyles(); @@ -127,8 +131,8 @@ export const EntityRelationsGraph = ({ + + {/* filter drawer, for example and friends */} + + + {/* content view, for example a */} + + + ``` + +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/version-bridge@1.0.0 + - @backstage/plugin-permission-react@0.3.4 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + ## 0.9.0 ### Minor Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 7859e1d2d8..71ba6452da 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -16,10 +16,10 @@ import { IconButton } from '@material-ui/core'; import { LinkProps } from '@backstage/core-components'; import { Observable } from '@backstage/types'; import { Overrides } from '@material-ui/core/styles/overrides'; -import { Permission } from '@backstage/plugin-permission-common'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { StyleRules } from '@material-ui/core/styles/withStyles'; @@ -63,6 +63,13 @@ export { CatalogApi }; // @public export const catalogApiRef: ApiRef; +// @public (undocumented) +export const CatalogFilterLayout: { + (props: { children: React_2.ReactNode }): JSX.Element; + Filters: (props: { children: React_2.ReactNode }) => JSX.Element; + Content: (props: { children: React_2.ReactNode }) => JSX.Element; +}; + // @public (undocumented) export type CatalogReactComponentsNameToClassKey = { CatalogReactUserListPicker: CatalogReactUserListPickerClassKey; @@ -254,15 +261,11 @@ export type EntityRefLinkProps = { } & Omit; // @public -export const EntityRefLinks: ({ - entityRefs, - defaultKind, - ...linkProps -}: EntityRefLinksProps) => JSX.Element; +export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; // @public export type EntityRefLinksProps = { - entityRefs: (Entity | CompoundEntityRef)[]; + entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; } & Omit; @@ -472,9 +475,6 @@ export function useAsyncEntity< // @public export function useEntity(): { entity: TEntity; - loading: boolean; - error?: Error; - refresh?: VoidFunction; }; // @public @@ -489,7 +489,9 @@ export function useEntityOwnership(): { }; // @alpha -export function useEntityPermission(permission: Permission): { +export function useEntityPermission( + permission: ResourcePermission<'catalog-entity'>, +): { loading: boolean; allowed: boolean; error?: Error; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 5ba52e8ac7..62b101d909 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "0.9.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,16 +34,18 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-react": "^0.3.3", - "@backstage/types": "^0.1.3", - "@backstage/version-bridge": "^0.1.2", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^1.0.0", + "@backstage/version-bridge": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -61,11 +63,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-scaffolder-common": "^0.3.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx similarity index 78% rename from plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx rename to plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx index 4c0d17599d..751eb35f1d 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; +import React, { useState } from 'react'; import { Box, Button, @@ -25,10 +25,10 @@ import { useTheme, } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; -import React, { useState } from 'react'; +import { BackstageTheme } from '@backstage/theme'; /** @public */ -export function FilterContainer(props: { children: React.ReactNode }) { +export const Filters = (props: { children: React.ReactNode }) => { const isMidSizeScreen = useMediaQuery(theme => theme.breakpoints.down('md'), ); @@ -69,4 +69,25 @@ export function FilterContainer(props: { children: React.ReactNode }) { {props.children} ); -} +}; + +/** @public */ +export const Content = (props: { children: React.ReactNode }) => { + return ( + + {props.children} + + ); +}; + +/** @public */ +export const CatalogFilterLayout = (props: { children: React.ReactNode }) => { + return ( + + {props.children} + + ); +}; + +CatalogFilterLayout.Filters = Filters; +CatalogFilterLayout.Content = Content; diff --git a/plugins/techdocs-node/__mocks__/@azure/identity.ts b/plugins/catalog-react/src/components/CatalogFilterLayout/index.ts similarity index 78% rename from plugins/techdocs-node/__mocks__/@azure/identity.ts rename to plugins/catalog-react/src/components/CatalogFilterLayout/index.ts index 6aeb738963..b69aab56ed 100644 --- a/plugins/techdocs-node/__mocks__/@azure/identity.ts +++ b/plugins/catalog-react/src/components/CatalogFilterLayout/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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,8 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export class DefaultAzureCredential { - /** - * Creates an instance of the DefaultAzureCredential class. - */ -} + +export { CatalogFilterLayout } from './CatalogFilterLayout'; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index d92cd2ec87..30dda011d4 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -25,7 +25,7 @@ import { LinkProps } from '@backstage/core-components'; * @public */ export type EntityRefLinksProps = { - entityRefs: (Entity | CompoundEntityRef)[]; + entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; } & Omit; @@ -34,17 +34,20 @@ export type EntityRefLinksProps = { * * @public */ -export const EntityRefLinks = ({ - entityRefs, - defaultKind, - ...linkProps -}: EntityRefLinksProps) => ( - <> - {entityRefs.map((r, i) => ( - - {i > 0 && ', '} - - - ))} - -); +export function EntityRefLinks(props: EntityRefLinksProps) { + const { entityRefs, defaultKind, ...linkProps } = props; + return ( + <> + {entityRefs.map((r, i) => ( + + {i > 0 && ', '} + + + ))} + + ); +} diff --git a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx index fdb07658d0..f1278c6f60 100644 --- a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx +++ b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx @@ -69,6 +69,7 @@ export const EntitySearchBar = () => { ', () => { - it('renders all tags', () => { + const mockCatalogApiRef = { + getEntityFacets: async () => ({ + facets: { 'metadata.tags': tags.map(value => ({ value })) }, + }), + } as unknown as CatalogApi; + + it('renders all tags', async () => { const rendered = render( - - - , + + + + + , ); - expect(rendered.getByText('Tags')).toBeInTheDocument(); + await waitFor(() => expect(rendered.getByText('Tags')).toBeInTheDocument()); fireEvent.click(rendered.getByTestId('tag-picker-expand')); - taggedEntities - .flatMap(e => e.metadata.tags!) - .forEach(tag => { - expect(rendered.getByText(tag)).toBeInTheDocument(); - }); + tags.forEach(tag => { + expect(rendered.getByText(tag)).toBeInTheDocument(); + }); }); - it('renders unique tags in alphabetical order', () => { + it('renders unique tags in alphabetical order', async () => { const rendered = render( - - - , + + + + + , ); - expect(rendered.getByText('Tags')).toBeInTheDocument(); + await waitFor(() => expect(rendered.getByText('Tags')).toBeInTheDocument()); fireEvent.click(rendered.getByTestId('tag-picker-expand')); @@ -79,43 +68,47 @@ describe('', () => { ]); }); - it('respects the query parameter filter value', () => { + it('respects the query parameter filter value', async () => { const updateFilters = jest.fn(); const queryParameters = { tags: ['tag3'] }; render( - - - , + + + + + , ); - expect(updateFilters).toHaveBeenLastCalledWith({ - tags: new EntityTagFilter(['tag3']), - }); + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: new EntityTagFilter(['tag3']), + }), + ); }); - it('adds tags to filters', () => { + it('adds tags to filters', async () => { const updateFilters = jest.fn(); const rendered = render( - - - , + + + + + , + ); + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: undefined, + }), ); - expect(updateFilters).toHaveBeenLastCalledWith({ - tags: undefined, - }); fireEvent.click(rendered.getByTestId('tag-picker-expand')); fireEvent.click(rendered.getByText('tag1')); @@ -124,23 +117,25 @@ describe('', () => { }); }); - it('removes tags from filters', () => { + it('removes tags from filters', async () => { const updateFilters = jest.fn(); const rendered = render( - - - , + + + + + , + ); + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: new EntityTagFilter(['tag1']), + }), ); - expect(updateFilters).toHaveBeenLastCalledWith({ - tags: new EntityTagFilter(['tag1']), - }); fireEvent.click(rendered.getByTestId('tag-picker-expand')); expect(rendered.getByLabelText('tag1')).toBeChecked(); @@ -150,30 +145,36 @@ describe('', () => { }); }); - it('responds to external queryParameters changes', () => { + it('responds to external queryParameters changes', async () => { const updateFilters = jest.fn(); const rendered = render( - - - , + + + + + , + ); + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: new EntityTagFilter(['tag1']), + }), ); - expect(updateFilters).toHaveBeenLastCalledWith({ - tags: new EntityTagFilter(['tag1']), - }); rendered.rerender( - - - , + + + + + , ); expect(updateFilters).toHaveBeenLastCalledWith({ tags: new EntityTagFilter(['tag2']), diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx index 5b5e0e7ab9..ddb70c6d31 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { Box, Checkbox, @@ -30,6 +29,9 @@ import { Autocomplete } from '@material-ui/lab'; import React, { useEffect, useMemo, useState } from 'react'; import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityTagFilter } from '../../filters'; +import { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/lib/useAsync'; +import { catalogApiRef } from '../../api'; /** @public */ export type CatalogReactEntityTagPickerClassKey = 'input'; @@ -49,8 +51,18 @@ const checkedIcon = ; /** @public */ export const EntityTagPicker = () => { const classes = useStyles(); - const { updateFilters, backendEntities, filters, queryParameters } = - useEntityList(); + const { updateFilters, filters, queryParameters } = useEntityList(); + + const catalogApi = useApi(catalogApiRef); + const { value: availableTags } = useAsync(async () => { + const facet = 'metadata.tags'; + const { facets } = await catalogApi.getEntityFacets({ + facets: [facet], + filter: filters.kind?.getCatalogFilters(), + }); + + return facets[facet].map(({ value }) => value); + }, [filters.kind]); const queryParamTags = useMemo( () => [queryParameters.tags].flat().filter(Boolean) as string[], @@ -75,19 +87,7 @@ export const EntityTagPicker = () => { }); }, [selectedTags, updateFilters]); - const availableTags = useMemo( - () => - [ - ...new Set( - backendEntities - .flatMap((e: Entity) => e.metadata.tags) - .filter(Boolean) as string[], - ), - ].sort(), - [backendEntities], - ); - - if (!availableTags.length) return null; + if (!availableTags?.length) return null; return ( diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index e055f4056f..163d819a35 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -43,6 +43,7 @@ export const FavoriteEntity = (props: FavoriteEntityProps) => { ); return ( toggleStarredEntity()} diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index a4e8523beb..f0adb878d6 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -107,15 +107,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) { )} {!!Object.keys(metadata.labels || {}).length && ( - - Labels - - - } - > + Labels}> {Object.entries(metadata.labels!).map(entry => ( ))} diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 807d6bb702..7a26a72bf9 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './CatalogFilterLayout'; export * from './EntityKindPicker'; export * from './EntityLifecyclePicker'; export * from './EntityOwnerPicker'; diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx index f61283f2cf..5e86a94180 100644 --- a/plugins/catalog-react/src/hooks/useEntity.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -16,19 +16,21 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { useEntity, EntityProvider, AsyncEntityProvider } from './useEntity'; +import { + useEntity, + useAsyncEntity, + EntityProvider, + AsyncEntityProvider, +} from './useEntity'; import { Entity } from '@backstage/catalog-model'; -describe('EntityProvider', () => { - it('should provide no entity', async () => { +describe('useEntity', () => { + it('should throw if no entity is provided', async () => { const { result } = renderHook(() => useEntity(), { wrapper: ({ children }) => , }); - expect(result.current.entity).toBe(undefined); - expect(result.current.loading).toBe(true); - expect(result.current.error).toBe(undefined); - expect(result.current.refresh).toBe(undefined); + expect(result.error?.message).toMatch(/entity has not been loaded/); }); it('should provide an entity', async () => { @@ -40,15 +42,12 @@ describe('EntityProvider', () => { }); expect(result.current.entity).toBe(entity); - expect(result.current.loading).toBe(false); - expect(result.current.error).toBe(undefined); - expect(result.current.refresh).toBe(undefined); }); }); -describe('AsyncEntityProvider', () => { +describe('useAsyncEntity', () => { it('should provide no entity', async () => { - const { result } = renderHook(() => useEntity(), { + const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( ), @@ -63,7 +62,7 @@ describe('AsyncEntityProvider', () => { it('should provide an entity', async () => { const entity = { kind: 'MyEntity' } as Entity; const refresh = () => {}; - const { result } = renderHook(() => useEntity(), { + const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( { it('should provide an error', async () => { const error = new Error('oh no'); - const { result } = renderHook(() => useEntity(), { + const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( ( */ export function useEntity(): { entity: TEntity; - /** @deprecated use {@link useAsyncEntity} instead */ - loading: boolean; - /** @deprecated use {@link useAsyncEntity} instead */ - error?: Error; - /** @deprecated use {@link useAsyncEntity} instead */ - refresh?: VoidFunction; } { const versionedHolder = useVersionedContext<{ 1: EntityLoadingStatus }>('entity-context'); @@ -123,18 +117,12 @@ export function useEntity(): { } if (!value.entity) { - // Once we have removed the additional fields from being returned we can drop this deprecation - // and move to the error instead. - // throw new Error('useEntity hook is being called outside of an EntityLayout where the entity has not been loaded. If this is intentional, please use useAsyncEntity instead.'); - - // eslint-disable-next-line no-console - console.warn( - 'DEPRECATION: useEntity hook is being called outside of an EntityLayout where the entity has not been loaded. If this is intentional, please use useAsyncEntity instead. This warning will be replaced with an error in future releases.', + throw new Error( + 'useEntity hook is being called outside of an EntityLayout where the entity has not been loaded. If this is intentional, please use useAsyncEntity instead.', ); } - const { entity, loading, error, refresh } = value; - return { entity: entity as TEntity, loading, error, refresh }; + return { entity: value.entity as TEntity }; } /** diff --git a/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx b/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx index 553d999b61..766b587da3 100644 --- a/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityPermission.test.tsx @@ -17,18 +17,18 @@ import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common'; import { renderHook } from '@testing-library/react-hooks'; import { useEntityPermission } from './useEntityPermission'; -import { useEntity } from './useEntity'; +import { useAsyncEntity } from './useEntity'; import { usePermission } from '@backstage/plugin-permission-react'; jest.mock('./useEntity', () => ({ ...jest.requireActual('./useEntity'), - useEntity: jest.fn(), + useAsyncEntity: jest.fn(), })); jest.mock('@backstage/plugin-permission-react', () => ({ ...jest.requireActual('@backstage/plugin-permission-react'), usePermission: jest.fn(), })); -const useEntityMock = useEntity as jest.Mock; +const useEntityMock = useAsyncEntity as jest.Mock; const usePermissionMock = usePermission as jest.Mock; describe('useEntityPermission', () => { diff --git a/plugins/catalog-react/src/hooks/useEntityPermission.ts b/plugins/catalog-react/src/hooks/useEntityPermission.ts index 092114936a..3a2c12ff36 100644 --- a/plugins/catalog-react/src/hooks/useEntityPermission.ts +++ b/plugins/catalog-react/src/hooks/useEntityPermission.ts @@ -15,35 +15,43 @@ */ import { stringifyEntityRef } from '@backstage/catalog-model'; -import { Permission } from '@backstage/plugin-permission-common'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; import { usePermission } from '@backstage/plugin-permission-react'; -import { useEntity } from './useEntity'; +import { useAsyncEntity } from './useEntity'; /** * A thin wrapper around the * {@link @backstage/plugin-permission-react#usePermission} hook which uses the * current entity in context to make an authorization request for the given - * permission. + * {@link @backstage/plugin-catalog-common#CatalogEntityPermission}. * * Note: this hook blocks the permission request until the entity has loaded in * context. If you have the entityRef and need concurrent requests, use the * `usePermission` hook directly. * @alpha */ -export function useEntityPermission(permission: Permission): { +export function useEntityPermission( + // TODO(joeporpeglia) Replace with `CatalogEntityPermission` when the issue described in + // https://github.com/backstage/backstage/pull/10128 is fixed. + permission: ResourcePermission<'catalog-entity'>, +): { loading: boolean; allowed: boolean; error?: Error; } { - const { entity, loading: loadingEntity, error: entityError } = useEntity(); + const { + entity, + loading: loadingEntity, + error: entityError, + } = useAsyncEntity(); const { allowed, loading: loadingPermission, error: permissionError, - } = usePermission( + } = usePermission({ permission, - entity ? stringifyEntityRef(entity) : undefined, - ); + resourceRef: entity ? stringifyEntityRef(entity) : undefined, + }); if (loadingEntity || loadingPermission) { return { loading: true, allowed: false }; diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 4c9c292518..1f484e58d0 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog +## 1.0.1-next.0 + +### Patch Changes + +- 2a3cac4c60: Corrected replacements for depreciated FilteredEntityLayout items +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- c689d7a94c: **DEPRECATION**: The `FilteredEntityLayout` and related components have been moved to `@backstage/plugin-catalog-react` and renamed. The original components are now deprecated and should be replaced as follows: + + - `FilteredEntityLayout` -> `CatalogFilterLayout` + - `FilterContainer` -> `CatalogFilterLayout.Filters` + - `EntityListContainer` -> `CatalogFilterLayout.Content` + +- d4afa7e30d: Fix for `EntitySwitch` not properly falling back to render the default entity page when the entity is missing. +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-search-common@0.3.2 + ## 0.10.0 ### Minor Changes diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 43cde23c57..a316ab0d03 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -161,6 +161,8 @@ export interface DefaultCatalogPageProps { // (undocumented) columns?: TableColumn[]; // (undocumented) + initialKind?: string; + // (undocumented) initiallySelectedFilter?: UserListFilterKind; } @@ -283,10 +285,10 @@ export interface EntityLinksCardProps { // @public (undocumented) export type EntityLinksEmptyStateClassKey = 'code'; -// @public (undocumented) -export function EntityListContainer(props: { - children: React_2.ReactNode; -}): JSX.Element; +// @public @deprecated (undocumented) +export const EntityListContainer: (props: { + children: ReactNode; +}) => JSX.Element; // @public export function EntityOrphanWarning(): JSX.Element; @@ -319,15 +321,13 @@ export interface EntitySwitchProps { children: ReactNode; } -// @public (undocumented) -export function FilterContainer(props: { - children: React_2.ReactNode; -}): JSX.Element; +// @public @deprecated (undocumented) +export const FilterContainer: (props: { children: ReactNode }) => JSX.Element; -// @public (undocumented) -export function FilteredEntityLayout(props: { - children: React_2.ReactNode; -}): JSX.Element; +// @public @deprecated (undocumented) +export const FilteredEntityLayout: (props: { + children: React.ReactNode; +}) => JSX.Element; // @public export function hasCatalogProcessingErrors( diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index bdc3e64fcc..053b35a519 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "0.10.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,17 +34,17 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.2", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -60,11 +60,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/plugin-permission-react": "^0.3.3", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 9a878e3b60..563ccd0cd7 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -448,7 +448,7 @@ describe('', () => { expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href'); }); - it('renders disbaled techdocs link when route is not bound', async () => { + it('renders disabled techdocs link when route is not bound', async () => { const entity = { apiVersion: 'v1', kind: 'Component', diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 8bb9bae764..8d65e54980 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -25,6 +25,7 @@ import { } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { + CatalogFilterLayout, EntityLifecyclePicker, EntityListProvider, EntityOwnerPicker, @@ -36,11 +37,6 @@ import { import React from 'react'; import { createComponentRouteRef } from '../../routes'; import { CatalogTable, CatalogTableRow } from '../CatalogTable'; -import { - FilteredEntityLayout, - EntityListContainer, - FilterContainer, -} from '../FilteredEntityLayout'; import { CatalogKindHeader } from '../CatalogKindHeader'; /** @@ -52,10 +48,16 @@ export interface DefaultCatalogPageProps { initiallySelectedFilter?: UserListFilterKind; columns?: TableColumn[]; actions?: TableProps['actions']; + initialKind?: string; } export function DefaultCatalogPage(props: DefaultCatalogPageProps) { - const { columns, actions, initiallySelectedFilter = 'owned' } = props; + const { + columns, + actions, + initiallySelectedFilter = 'owned', + initialKind = 'component', + } = props; const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); @@ -64,25 +66,27 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { - }> + } + > All your software catalog entities - - + + - - + + - - + + diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 8768e9728e..9588327c3b 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -15,7 +15,10 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { + AsyncEntityProvider, + EntityProvider, +} from '@backstage/plugin-catalog-react'; import { render } from '@testing-library/react'; import React from 'react'; import { isKind } from './conditions'; @@ -76,6 +79,18 @@ describe('EntitySwitch', () => { expect(rendered.queryByText('A')).not.toBeInTheDocument(); expect(rendered.queryByText('B')).not.toBeInTheDocument(); expect(rendered.queryByText('C')).toBeInTheDocument(); + + rendered.rerender( + + + {content} + + , + ); + + expect(rendered.queryByText('A')).not.toBeInTheDocument(); + expect(rendered.queryByText('B')).not.toBeInTheDocument(); + expect(rendered.queryByText('C')).toBeInTheDocument(); }); it('should switch child when filters switch', () => { diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx index 1b8667ce2a..993e7daa80 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx @@ -63,7 +63,7 @@ export interface EntitySwitchProps { /** @public */ export const EntitySwitch = (props: EntitySwitchProps) => { - const { entity } = useAsyncEntity(); + const { entity, loading } = useAsyncEntity(); const apis = useApiHolder(); const results = useElementFilter( props.children, @@ -75,11 +75,23 @@ export const EntitySwitch = (props: EntitySwitchProps) => { }) .getElements() .flatMap((element: ReactElement) => { - if (!entity) { + // Nothing is rendered while loading + if (loading) { return []; } + const { if: condition, children: elementsChildren } = element.props as EntitySwitchCase; + + // If the entity is missing or there is an error, render the default page + if (!entity) { + return [ + { + if: condition === undefined, + children: elementsChildren, + }, + ]; + } return [ { if: condition?.(entity, { apis }) ?? true, @@ -87,7 +99,7 @@ export const EntitySwitch = (props: EntitySwitchProps) => { }, ]; }), - [apis, entity], + [apis, entity, loading], ); const hasAsyncCases = results.some( r => typeof r.if === 'object' && 'then' in r.if, diff --git a/plugins/catalog/src/components/FilteredEntityLayout/index.ts b/plugins/catalog/src/components/FilteredEntityLayout/index.ts index ded61a4edc..a9277a3298 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/index.ts +++ b/plugins/catalog/src/components/FilteredEntityLayout/index.ts @@ -14,6 +14,24 @@ * limitations under the License. */ -export { FilteredEntityLayout } from './FilteredEntityLayout'; -export { FilterContainer } from './FilterContainer'; -export { EntityListContainer } from './EntityListContainer'; +import { CatalogFilterLayout } from '@backstage/plugin-catalog-react'; + +/** + * @public + * @deprecated Use `CatalogFilterLayout` from `@backstage/plugin-catalog-react` instead. + */ +export const FilteredEntityLayout = CatalogFilterLayout as (props: { + children: React.ReactNode; +}) => JSX.Element; + +/** + * @public + * @deprecated Use `CatalogFilterLayout.Filters` from `@backstage/plugin-catalog-react` instead. + */ +export const FilterContainer = CatalogFilterLayout.Filters; + +/** + * @public + * @deprecated Use `CatalogFilterLayout.Content` from `@backstage/plugin-catalog-react` instead. + */ +export const EntityListContainer = CatalogFilterLayout.Content; diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 253e3d60bf..9b6f244eb8 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-cicd-statistics +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + +## 0.1.5 + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index 08cffaeba6..bb80e7674a 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.4", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,9 +37,9 @@ "@types/luxon": "^2.0.5" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@date-io/luxon": "^1.3.13", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.2", diff --git a/plugins/cicd-statistics/src/apis/types.ts b/plugins/cicd-statistics/src/apis/types.ts index 0a87b40fc2..32d9d3f2df 100644 --- a/plugins/cicd-statistics/src/apis/types.ts +++ b/plugins/cicd-statistics/src/apis/types.ts @@ -47,7 +47,7 @@ export const statusTypes: Array = [ /** * The branch enum of either 'master' or 'branch' (or possibly the meta 'all'). * - * The concept of what constitues a master branch is generic. It might be called + * The concept of what constitutes a master branch is generic. It might be called * something like 'release' or 'main' or 'trunk' in the underlying CI/CD system, * which is then up to the Api to map accordingly. */ diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index e44eb0c8cb..3fd6adf138 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-circleci +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index d315c9a161..2ce855e625 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -55,10 +55,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 6c2378d472..e197b795ef 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-cloudbuild +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index bf4a65bd07..a04b5537a4 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 33a4c4e4d5..df7dc94d86 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-code-climate +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 4913939be4..5d77bb39b7 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.2", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,10 +23,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,9 +40,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 7bb82bf483..4eb5c949e4 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-code-coverage-backend +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.1.28 + +### Patch Changes + +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.27 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index e4b9032f78..4dd916b75a 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.1.27", + "version": "0.1.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,12 +23,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/code-coverage-backend/src/service/converter/cobertura.ts b/plugins/code-coverage-backend/src/service/converter/cobertura.ts index e7f7490304..025d40a4f6 100644 --- a/plugins/code-coverage-backend/src/service/converter/cobertura.ts +++ b/plugins/code-coverage-backend/src/service/converter/cobertura.ts @@ -27,7 +27,7 @@ export class Cobertura implements Converter { * convert cobertura into shared json coverage format * * @param xml - cobertura xml object - * @param scmFiles - list of files that are commited to SCM + * @param scmFiles - list of files that are committed to SCM */ convert(xml: CoberturaXML, scmFiles: string[]): FileEntry[] { const ppc = xml.coverage.packages diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 8dd1a067a8..9ba5be81c7 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-code-coverage +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.30 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.29 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index f0b115fd18..09f2b77a65 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.1.29", + "version": "0.1.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,12 +24,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 98d3099c0e..cfb9b0553d 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-config-schema +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.1.26 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.1.25 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index a521e0b35c..376d1c2ecc 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.25", + "version": "0.1.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,12 +24,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 4f8139df31..f632dd29f2 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-cost-insights +## 0.11.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.11.25 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + ## 0.11.24 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 9034fe85b5..921c9a5aab 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.24", + "version": "0.11.26-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -60,10 +60,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/src/components/BarChart/BarChart.tsx b/plugins/cost-insights/src/components/BarChart/BarChart.tsx index 98a2410b7b..27ef69db7a 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChart.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChart.tsx @@ -104,7 +104,7 @@ export const BarChart = ({ .slice(stepStart, stepEnd); // Pin the domain to the largest value in the series. - // Intentially redundant - This could simply be derived from the first element in the already sorted list, + // Intentionally redundant - This could simply be derived from the first element in the already sorted list, // but that may not be the case in the future when custom sorting is implemented. const globalResourcesMax = resources.reduce( (max, r: ResourceData) => Math.max(max, r.current, r.previous), diff --git a/plugins/cost-insights/src/utils/scroll.tsx b/plugins/cost-insights/src/utils/scroll.tsx index 1cf824aeda..65fce35174 100644 --- a/plugins/cost-insights/src/utils/scroll.tsx +++ b/plugins/cost-insights/src/utils/scroll.tsx @@ -17,7 +17,7 @@ import React, { useEffect, useRef } from 'react'; import { ScrollTo, useScroll } from '../hooks/useScroll'; /* - Utility component use in conjuction with useScroll that allows scrollable components to control behavior and offset. + Utility component use in conjunction with useScroll that allows scrollable components to control behavior and offset. 1. ScrollAnchor must be a direct child of a scrollable component. 2. ScrollAnchor's parent position must be relative. 3. ScrollAnchor's id must be unique. diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index d8e6a50f9d..696882ffb1 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-explore-react +## 0.0.15 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + ## 0.0.14 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 650ec24660..82d247dd9a 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.14", + "version": "0.0.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-plugin-api": "^0.8.0" + "@backstage/core-plugin-api": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", - "@backstage/dev-utils": "^0.2.25-next.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 2f7da4efa6..b990926fe7 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-explore +## 0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.34 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-explore-react@0.0.15 + - @backstage/catalog-model@1.0.0 + ## 0.3.33 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 43de4f12b0..6eb013cb9c 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.33", + "version": "0.3.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-explore-react": "^0.0.14", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-explore-react": "^0.0.15", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 17f5fcca21..61c74682f2 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-firehydrant +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.20 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + ## 0.1.19 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 5956acdec9..a05734b0a4 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.19", + "version": "0.1.21-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,9 +25,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,10 +39,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index f8e0bbf1cf..4bfb9a152b 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-fossa +## 0.2.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.2.35 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.2.34 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index a63bf02b2f..4d1cc15ac2 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.34", + "version": "0.2.36-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,11 +35,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md new file mode 100644 index 0000000000..788ef3c0f4 --- /dev/null +++ b/plugins/gcalendar/CHANGELOG.md @@ -0,0 +1,21 @@ +# @backstage/plugin-gcalendar + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.2.0 + +### Minor Changes + +- 6b226ff8a9: Added Google calendar widget + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/errors@1.0.0 diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index d5fb6765e3..99fa2c6eeb 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.1.0", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 40932b3138..e510ca45e0 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-gcp-projects +## 0.3.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.3.22 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.3.21 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 288976c279..4e9351d9f0 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.21", + "version": "0.3.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,8 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index dccca428cf..03909158c0 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-git-release-manager +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.16 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration@1.0.0 + ## 0.3.15 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 77c6091e19..8be28dddb2 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.15", + "version": "0.3.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/integration": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/git-release-manager/src/components/Differ.test.tsx b/plugins/git-release-manager/src/components/Differ.test.tsx index 20483767b4..a5af1c893e 100644 --- a/plugins/git-release-manager/src/components/Differ.test.tsx +++ b/plugins/git-release-manager/src/components/Differ.test.tsx @@ -52,7 +52,7 @@ describe('Differ', () => { expect(next).not.toBeInTheDocument(); }); - it('should render icon & current & next (with seperator)', () => { + it('should render icon & current & next (with separator)', () => { const { getByTestId, queryByTestId } = render( - Error occured while fetching information for "{project.owner}/ + Error occurred while fetching information for "{project.owner}/ {project.repo}" ({gitBatchInfo.error.message}) ); diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 30fc5ffa56..fab6de3cd1 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-github-actions +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.5.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index a3e9790de5..756e0b559b 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.5.2", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,11 +36,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -55,10 +55,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 28c5ce330e..3ef48342f8 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-github-deployments +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/integration-react@1.0.1-next.0 + +## 0.1.34 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.33 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 08691a55cf..af1c6cbcc0 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.33", + "version": "0.1.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,13 +24,13 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index 7b23841a80..458650c75f 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-gitops-profiles +## 0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.3.21 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.3.20 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 465621ccc1..2c3a6b9d25 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.20", + "version": "0.3.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,8 +35,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index c2cf3065a5..f557fb98a4 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-gocd +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.9 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index d190a21027..5b0334f2fd 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.8", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index faa5207325..d69db1eb91 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-graphiql +## 0.2.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.2.35 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.2.34 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 2c931179c2..6b896c76e4 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.34", + "version": "0.2.36-next.0", "private": false, "publishConfig": { "access": "public", @@ -34,8 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index 95c254431e..af1434a1a9 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-graphql-backend +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-catalog-graphql@0.3.8-next.0 + +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/plugin-catalog-graphql@0.3.7 + - @backstage/config@1.0.0 + ## 0.1.19 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index d30272b9dc..40a2c4f651 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.19", + "version": "0.1.21-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,9 +34,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/plugin-catalog-graphql": "^0.3.6", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/plugin-catalog-graphql": "^0.3.8-next.0", "@graphql-tools/schema": "^8.3.1", "graphql-modules": "^2.0.0", "@types/express": "^4.17.6", @@ -51,7 +51,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.35.0", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 36fc7239d1..30fd09d5a4 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-home +## 0.4.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search@0.7.5-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.4.19 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-search@0.7.4 + - @backstage/catalog-model@1.0.0 + ## 0.4.18 ### Patch Changes diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 6fae50151c..d5f5c52ea1 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -39,6 +39,11 @@ export const ComponentAccordion: ({ ContextProvider?: ((props: any) => JSX.Element) | undefined; }) => JSX.Element; +// @public (undocumented) +export type ComponentRenderer = { + Renderer?: (props: RendererProps) => JSX.Element; +}; + // Warning: (ae-missing-release-tag) "ComponentTab" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -103,7 +108,6 @@ export const HomepageCompositionRoot: (props: { children?: ReactNode; }) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "HomePageRandomJoke" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -161,5 +165,6 @@ export const WelcomeTitle: () => JSX.Element; // Warnings were encountered during analysis: // -// src/extensions.d.ts:24:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts +// src/extensions.d.ts:6:5 - (ae-forgotten-export) The symbol "RendererProps" needs to be exported by the entry point index.d.ts +// src/extensions.d.ts:27:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/home/package.json b/plugins/home/package.json index 761cfb369f..fd83c6ff28 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.18", + "version": "0.4.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,12 +34,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-search": "^0.7.3", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-search": "^0.7.5-next.0", + "@backstage/plugin-stack-overflow": "^0.0.0", "@backstage/theme": "^0.2.15", + "@backstage/config": "^0.1.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +54,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/home/src/extensions.tsx b/plugins/home/src/extensions.tsx index 4a234f9813..4139e301a3 100644 --- a/plugins/home/src/extensions.tsx +++ b/plugins/home/src/extensions.tsx @@ -21,6 +21,9 @@ import { InfoCard } from '@backstage/core-components'; import { SettingsModal } from './components'; import { createReactExtension, useApp } from '@backstage/core-plugin-api'; +/** + * @public + */ export type ComponentRenderer = { Renderer?: (props: RendererProps) => JSX.Element; }; diff --git a/plugins/home/src/index.ts b/plugins/home/src/index.ts index 4871323677..d30b76a7fc 100644 --- a/plugins/home/src/index.ts +++ b/plugins/home/src/index.ts @@ -35,3 +35,4 @@ export { export { SettingsModal, HeaderWorldClock } from './components'; export type { ClockConfig } from './components'; export { createCardExtension } from './extensions'; +export type { ComponentRenderer } from './extensions'; diff --git a/plugins/home/src/templates/DefaultTemplate.stories.tsx b/plugins/home/src/templates/DefaultTemplate.stories.tsx index c726294ba6..e2e570dcf6 100644 --- a/plugins/home/src/templates/DefaultTemplate.stories.tsx +++ b/plugins/home/src/templates/DefaultTemplate.stories.tsx @@ -28,12 +28,15 @@ import { MockStarredEntitiesApi, entityRouteRef, } from '@backstage/plugin-catalog-react'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigReader } from '@backstage/config'; import { HomePageSearchBar, SearchContextProvider, searchApiRef, searchPlugin, } from '@backstage/plugin-search'; +import { HomePageStackOverflowQuestions } from '@backstage/plugin-stack-overflow'; import { Grid, makeStyles } from '@material-ui/core'; import React, { ComponentType } from 'react'; @@ -56,6 +59,14 @@ export default { starredEntitiesApi, ], [searchApiRef, { query: () => Promise.resolve({ results: [] }) }], + [ + configApiRef, + new ConfigReader({ + stackoverflow: { + baseUrl: 'https://api.stackexchange.com/2.2', + }, + }), + ], ]} > @@ -131,14 +142,17 @@ export const DefaultTemplate = () => { {/* placeholder for content */} -
+
- - {/* placeholder for content */} -
- + diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 49908779da..520feb9863 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-ilert +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.29 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.28 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 9a8450a7c5..35bc07cd6b 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.28", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,11 +24,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", @@ -43,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index 6a7e524a27..602af2a0d1 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-jenkins-backend +## 0.1.20-next.0 + +### Patch Changes + +- 71f8708f00: Make `resourceRef` required in `JenkinsApi` to match usage. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-jenkins-common@0.1.3-next.0 + +## 0.1.19 + +### Patch Changes + +- 89c7e47967: Minor README update +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-jenkins-common@0.1.2 + ## 0.1.18 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index b5cc4f3749..86585ff5d3 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.18", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,14 +25,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/plugin-jenkins-common": "^0.1.1", - "@backstage/plugin-permission-common": "^0.5.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/plugin-jenkins-common": "^0.1.3-next.0", + "@backstage/plugin-permission-common": "^0.5.3", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -41,7 +41,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts index 45f7424e40..a5453b5ebe 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts @@ -34,6 +34,7 @@ const mockedJenkinsClient = { const mockedJenkins = jenkins as jest.Mocked; mockedJenkins.mockReturnValue(mockedJenkinsClient); +const resourceRef = 'component:default/example-component'; const jobFullName = 'example-jobName/foo'; const buildNumber = 19; const jenkinsInfo: JenkinsInfo = { @@ -332,7 +333,7 @@ describe('JenkinsApi', () => { const result = await jenkinsApi.getProjects(jenkinsInfo); expect(result).toHaveLength(1); - // TODO: I am really just asserting the previous behaviour wth no understanding here. + // TODO: I am really just asserting the previous behaviour with no understanding here. // In my 2 Jenkins instances, 1 returns a lot of different and confusing BuildData sections and 1 returns none ☹️ expect(result[0].lastBuild!.source).toEqual({ branchName: 'master', @@ -351,6 +352,216 @@ describe('JenkinsApi', () => { const result = await jenkinsApi.getProjects(jenkinsInfo); + expect(result).toHaveLength(1); + expect(result[0].lastBuild!.tests).toEqual({ + total: 635, + passed: 632, + skipped: 1, + failed: 2, + testUrl: + 'https://jenkins.example.com/job/example-jobName/job/exampleBuild/7/testReport/', + }); + }); + }); + describe('augmented with null values', () => { + const projectWithScmActionsAndNulls: JenkinsProject = { + actions: [ + {}, + {}, + {}, + {}, + { + _class: 'jenkins.scm.api.metadata.ContributorMetadataAction', + contributor: 'testuser', + contributorDisplayName: 'Mr. T User', + contributorEmail: null, + }, + {}, + { + _class: 'jenkins.scm.api.metadata.ObjectMetadataAction', + objectDescription: '', + objectDisplayName: 'Add LICENSE, CoC etc', + objectUrl: 'https://github.com/backstage/backstage/pull/1', + }, + {}, + {}, + { + _class: 'com.cloudbees.plugins.credentials.ViewCredentialsAction', + stores: {}, + }, + ], + displayName: 'Example Build', + fullDisplayName: 'Example jobName » Example Build', + fullName: 'example-jobName/exampleBuild', + inQueue: false, + lastBuild: { + actions: [ + { + _class: 'hudson.model.CauseAction', + causes: [ + { + _class: 'jenkins.branch.BranchIndexingCause', + shortDescription: 'Branch indexing', + }, + ], + }, + null, + {}, + {}, + { + _class: 'org.jenkinsci.plugins.workflow.cps.EnvActionImpl', + environment: {}, + }, + {}, + {}, + {}, + {}, + {}, + { + _class: 'hudson.plugins.git.util.BuildData', + buildsByBranchName: { + 'PR-1': { + _class: 'hudson.plugins.git.util.Build', + buildNumber: 5, + buildResult: null, + marked: { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + revision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + }, + }, + lastBuiltRevision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'PR-1', + }, + ], + }, + remoteUrls: ['https://github.com/backstage/backstage.git'], + scmName: '', + }, + { + _class: 'hudson.plugins.git.util.BuildData', + buildsByBranchName: { + master: { + _class: 'hudson.plugins.git.util.Build', + buildNumber: 5, + buildResult: null, + marked: { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + revision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + }, + }, + lastBuiltRevision: { + SHA1: '6c6b34c0fb91cf077a01fe62d3e8e996b4ea5861', + branch: [ + { + SHA1: '14d31bde346fcad64ab939f82d195db36701cfcb', + name: 'master', + }, + ], + }, + remoteUrls: ['https://github.com/backstage/backstage.git'], + scmName: '', + }, + {}, + {}, + { + _class: 'hudson.tasks.junit.TestResultAction', + failCount: 2, + skipCount: 1, + totalCount: 635, + urlName: 'testReport', + }, + {}, + {}, + { + _class: + 'org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction', + restartEnabled: false, + restartableStages: [], + }, + {}, + ], + timestamp: 1, + building: false, + duration: 10, + result: 'success', + displayName: '#7', + fullDisplayName: 'Example jobName » Example Build #7', + url: 'https://jenkins.example.com/job/example-jobName/job/exampleBuild/7/', + number: 7, + }, + }; + + it('augments project', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + + expect(result).toHaveLength(1); + expect(result[0].status).toEqual('success'); + }); + it('augments build', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + + expect(result).toHaveLength(1); + // TODO: I am really just asserting the previous behaviour with no understanding here. + // In my 2 Jenkins instances, 1 returns a lot of different and confusing BuildData sections and 1 returns none ☹️ + expect(result[0].lastBuild!.source).toEqual({ + branchName: 'master', + commit: { + hash: '14d31bde', + }, + url: 'https://github.com/backstage/backstage/pull/1', + displayName: 'Add LICENSE, CoC etc', + author: 'Mr. T User', + }); + }); + it('finds test report', async () => { + mockedJenkinsClient.job.get.mockResolvedValueOnce({ + jobs: [projectWithScmActionsAndNulls], + }); + + const result = await jenkinsApi.getProjects(jenkinsInfo); + expect(result).toHaveLength(1); expect(result[0].lastBuild!.tests).toEqual({ total: 635, @@ -413,7 +624,7 @@ describe('JenkinsApi', () => { ); }); it('buildProject', async () => { - await jenkinsApi.buildProject(jenkinsInfo, jobFullName); + await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef); expect(mockedJenkins).toHaveBeenCalledWith({ baseUrl: jenkinsInfo.baseUrl, @@ -431,7 +642,7 @@ describe('JenkinsApi', () => { ]); await expect(() => - jenkinsApi.buildProject(jenkinsInfo, jobFullName), + jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef), ).rejects.toThrow(NotAllowedError); }); @@ -442,7 +653,7 @@ describe('JenkinsApi', () => { }, ]); - await jenkinsApi.buildProject(jenkinsInfo, jobFullName); + await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef); expect(mockedJenkins).toHaveBeenCalledWith({ baseUrl: jenkinsInfo.baseUrl, headers: jenkinsInfo.headers, @@ -453,7 +664,7 @@ describe('JenkinsApi', () => { it('buildProject with crumbIssuer option', async () => { const info: JenkinsInfo = { ...jenkinsInfo, crumbIssuer: true }; - await jenkinsApi.buildProject(info, jobFullName); + await jenkinsApi.buildProject(info, jobFullName, resourceRef); expect(mockedJenkins).toHaveBeenCalledWith({ baseUrl: jenkinsInfo.baseUrl, diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.ts b/plugins/jenkins-backend/src/service/jenkinsApi.ts index da16bbc553..38ab20b17b 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.ts @@ -139,7 +139,7 @@ export class JenkinsApiImpl { async buildProject( jenkinsInfo: JenkinsInfo, jobFullName: string, - resourceRef?: string, + resourceRef: string, options?: { token?: string }, ) { const client = await JenkinsApiImpl.getClient(jenkinsInfo); @@ -211,7 +211,7 @@ export class JenkinsApiImpl { build.actions .filter( (action: any) => - action._class === 'hudson.plugins.git.util.BuildData', + action?._class === 'hudson.plugins.git.util.BuildData', ) .map((action: any) => { const [first]: any = Object.values(action.buildsByBranchName); @@ -253,7 +253,7 @@ export class JenkinsApiImpl { const scmInfo: ScmDetails | undefined = project.actions .filter( (action: any) => - action._class === 'jenkins.scm.api.metadata.ObjectMetadataAction', + action?._class === 'jenkins.scm.api.metadata.ObjectMetadataAction', ) .map((action: any) => { return { @@ -272,7 +272,7 @@ export class JenkinsApiImpl { const author = project.actions .filter( (action: any) => - action._class === + action?._class === 'jenkins.scm.api.metadata.ContributorMetadataAction', ) .map((action: any) => { @@ -297,7 +297,7 @@ export class JenkinsApiImpl { return build.actions .filter( (action: any) => - action._class === 'hudson.tasks.junit.TestResultAction', + action?._class === 'hudson.tasks.junit.TestResultAction', ) .map((action: any) => { return { diff --git a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts index a88dbf965e..d9a97e2f2d 100644 --- a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts +++ b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts @@ -97,7 +97,7 @@ export class JenkinsConfig { const unnamedAllPresent = baseUrl && username && apiKey; if (!(unnamedAllPresent || unnamedNonePresent)) { throw new Error( - `Found partial default jenkins config. All (or none) of baseUrl, username ans apiKey must be provided.`, + `Found partial default jenkins config. All (or none) of baseUrl, username and apiKey must be provided.`, ); } diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index a5e6f41749..712d0a1519 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-jenkins-common +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.1-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + ## 0.1.1 ### Patch Changes diff --git a/plugins/jenkins-common/api-report.md b/plugins/jenkins-common/api-report.md index 7b0d3759ba..771161e1cb 100644 --- a/plugins/jenkins-common/api-report.md +++ b/plugins/jenkins-common/api-report.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Permission } from '@backstage/plugin-permission-common'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; // @public -export const jenkinsExecutePermission: Permission; +export const jenkinsExecutePermission: ResourcePermission<'catalog-entity'>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 43a77b3541..7362038f53 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.1", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-permission-common": "^0.5.2" + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-permission-common": "^0.5.3" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/jenkins-common/src/permissions.ts b/plugins/jenkins-common/src/permissions.ts index 2fa8c14232..5ca5ec6adf 100644 --- a/plugins/jenkins-common/src/permissions.ts +++ b/plugins/jenkins-common/src/permissions.ts @@ -14,17 +14,17 @@ * limitations under the License. */ import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; -import { Permission } from '@backstage/plugin-permission-common'; +import { createPermission } from '@backstage/plugin-permission-common'; /** * This permission is used to determine if a user is allowed to execute an action in jenkins plugin * * @public */ -export const jenkinsExecutePermission: Permission = { +export const jenkinsExecutePermission = createPermission({ name: 'jenkins.execute', attributes: { action: 'update', }, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, -}; +}); diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index ec0da83839..3ce8278e7c 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-jenkins +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/plugin-jenkins-common@0.1.3-next.0 + +## 0.7.2 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-jenkins-common@0.1.2 + ## 0.7.1 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index a42208f80a..8f70bfa672 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.7.1", + "version": "0.7.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,12 +35,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-jenkins-common": "^0.1.1", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-jenkins-common": "^0.1.3-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/src/constants.ts b/plugins/jenkins/src/constants.ts index e1be37ee46..53978bc8f6 100644 --- a/plugins/jenkins/src/constants.ts +++ b/plugins/jenkins/src/constants.ts @@ -14,6 +14,6 @@ * limitations under the License. */ export const JENKINS_ANNOTATION = 'jenkins.io/job-full-name'; -// @deprecated The legacy annotation used for identifing Jenkins jobs, use +// @deprecated The legacy annotation used for identifying Jenkins jobs, use // JENKINS_ANNOTATION instead. export const LEGACY_JENKINS_ANNOTATION = 'jenkins.io/github-folder'; diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index caf43d94a7..6a413e6925 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-kafka-backend +## 0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.2.22 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 3ae224adb2..0796f8c952 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.22", + "version": "0.2.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -47,7 +47,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 8d48855d5d..14c7883ce2 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-kafka +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.3 + +### Patch Changes + +- 89c7e47967: Minor README update +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index fd28be15c4..88d44a67a3 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,10 +24,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,10 +39,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 24827f736d..177bcca549 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-kubernetes-backend +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-kubernetes-common@0.2.9-next.0 + +## 0.4.13 + +### Patch Changes + +- dab7f8dbd3: build(deps): bump `@google-cloud/container` from 2.3.0 to 3.0.0 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-kubernetes-common@0.2.8 + ## 0.4.12 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 06eec1e859..9682d879b4 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.4.12", + "version": "0.4.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,11 +35,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-kubernetes-common": "^0.2.7", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-kubernetes-common": "^0.2.9-next.0", "@google-cloud/container": "^3.0.0", "@kubernetes/client-node": "^0.16.0", "@types/express": "^4.17.6", @@ -58,11 +58,10 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/aws4": "^1.5.1", "supertest": "^6.1.3", - "aws-sdk-mock": "^5.2.1", - "bdd-lazy-var": "^2.6.0" + "aws-sdk-mock": "^5.2.1" }, "files": [ "dist", diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts index 7806160e58..2696587e69 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts @@ -16,7 +16,6 @@ import AWS from 'aws-sdk'; import AWSMock from 'aws-sdk-mock'; import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; -import { get, def } from 'bdd-lazy-var'; describe('AwsIamKubernetesAuthTranslator tests', () => { let role: any = undefined; @@ -48,7 +47,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { jest.resetAllMocks(); }); - def('subject', () => { + function executeTranslation() { AWSMock.mock('STS', 'assumeRole', (_params: any, callback: Function) => { callback(null, assumeResponse); }); @@ -71,7 +70,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { mockedCredentials = undefined; return response; - }); + } it('returns a signed url for AWS credentials', async () => { // These credentials are not real. @@ -81,8 +80,8 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', ); - const subject = await get('subject'); - expect(subject.serviceAccountToken).toBeDefined(); + const response = await executeTranslation(); + expect(response.serviceAccountToken).toBeDefined(); }); describe('When the role is assumed', () => { @@ -96,15 +95,17 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { describe('When the role is valid', () => { it('returns a signed url for AWS credentials', async () => { - const subject = await get('subject'); - expect(subject.serviceAccountToken).toBeDefined(); + const response = await executeTranslation(); + expect(response.serviceAccountToken).toBeDefined(); }); }); describe('When the role is invalid', () => { it('returns the original AWS credentials', async () => { assumeResponse = undefined; - await expect(get('subject')).rejects.toThrow(/Unable to assume role:/); + await expect(executeTranslation()).rejects.toThrow( + /Unable to assume role:/, + ); }); }); }); @@ -112,7 +113,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { describe('When no AWS creds are available', () => { it('throws unable to get AWS credentials', async () => { mockedCredentials = new Error(); - await expect(get('subject')).rejects.toThrow('No AWS credentials found.'); + await expect(executeTranslation()).rejects.toThrow( + 'No AWS credentials found.', + ); }); }); @@ -123,7 +126,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { 'AKIAIOSFODNN7EXAMPLE', undefinedSecret, ); - await expect(get('subject')).rejects.toThrow( + await expect(executeTranslation()).rejects.toThrow( 'Invalid AWS credentials found.', ); }); diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 4f25805063..37c26956fe 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -110,7 +110,7 @@ export interface ClusterDetails { * @remarks * Note that you should specify the app used for the dashboard * using the dashboardApp property, in order to properly format - * links to kubernetes resources, otherwise it will assume that you're running the standard one. + * links to kubernetes resources, otherwise it will assume that you're running the standard one. * @see dashboardApp * @see dashboardParameters */ diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 3f045f242b..05558a3e77 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-kubernetes-common +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index ea5d4c3e85..3a519b0579 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.2.7", + "version": "0.2.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,11 +38,11 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", + "@backstage/catalog-model": "^1.0.1-next.0", "@kubernetes/client-node": "^0.16.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "jest": { "roots": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 61446156ac..94585a2149 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-kubernetes +## 0.6.4-next.0 + +### Patch Changes + +- f616d99f6f: Fix division by zero in currentToDeclaredResourceToPerc when pod resources weren't set +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/plugin-kubernetes-common@0.2.9-next.0 + +## 0.6.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-kubernetes-common@0.2.8 + ## 0.6.2 ### Patch Changes diff --git a/plugins/kubernetes/api-report.md b/plugins/kubernetes/api-report.md index f635a61cef..e8a3da83a9 100644 --- a/plugins/kubernetes/api-report.md +++ b/plugins/kubernetes/api-report.md @@ -7,6 +7,9 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { ClientPodStatus } from '@backstage/plugin-kubernetes-common'; +import { ClusterAttributes } from '@backstage/plugin-kubernetes-common'; +import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -14,19 +17,119 @@ import type { JsonObject } from '@backstage/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { OAuthApi } from '@backstage/core-plugin-api'; import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +import { V1ConfigMap } from '@kubernetes/client-node'; +import { V1CronJob } from '@kubernetes/client-node'; +import { V1Deployment } from '@kubernetes/client-node'; +import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; +import { V1Ingress } from '@kubernetes/client-node'; +import { V1Job } from '@kubernetes/client-node'; +import { V1ObjectMeta } from '@kubernetes/client-node'; +import { V1Pod } from '@kubernetes/client-node'; +import { V1ReplicaSet } from '@kubernetes/client-node'; +import { V1Service } from '@kubernetes/client-node'; + +// Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvider" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "AwsKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class AwsKubernetesAuthProvider implements KubernetesAuthProvider { + // (undocumented) + decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise; +} + +// Warning: (ae-forgotten-export) The symbol "ClusterProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "Cluster" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const Cluster: ({ + clusterObjects, + podsWithErrors, +}: ClusterProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "ClusterContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ClusterContext: React_2.Context; + +// Warning: (ae-missing-release-tag) "ClusterLinksFormatter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ClusterLinksFormatter = ( + options: ClusterLinksFormatterOptions, +) => URL; + +// Warning: (ae-missing-release-tag) "ClusterLinksFormatterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ClusterLinksFormatterOptions { + // (undocumented) + dashboardParameters?: JsonObject; + // (undocumented) + dashboardUrl?: URL; + // (undocumented) + kind: string; + // (undocumented) + object: any; +} -// Warning: (ae-forgotten-export) The symbol "ClusterLinksFormatter" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "clusterLinksFormatters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const clusterLinksFormatters: Record; +// Warning: (ae-forgotten-export) The symbol "CronJobsAccordionsProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "CronJobsAccordions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const CronJobsAccordions: ({}: CronJobsAccordionsProps) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "CustomResourcesProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "CustomResources" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const CustomResources: ({}: CustomResourcesProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "DeploymentResources" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DeploymentResources { + // (undocumented) + deployments: V1Deployment[]; + // (undocumented) + horizontalPodAutoscalers: V1HorizontalPodAutoscaler[]; + // (undocumented) + pods: V1Pod[]; + // (undocumented) + replicaSets: V1ReplicaSet[]; +} + // Warning: (ae-missing-release-tag) "EntityKubernetesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const EntityKubernetesContent: (_props: {}) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "ErrorPanelProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ErrorPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ErrorPanel: ({ + entityName, + errorMessage, + clustersWithErrors, +}: ErrorPanelProps) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "ErrorReportingProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ErrorReporting" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ErrorReporting: ({ + detectedErrors, +}: ErrorReportingProps) => JSX.Element; + // Warning: (ae-forgotten-export) The symbol "FormatClusterLinkOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "formatClusterLink" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -35,11 +138,84 @@ export function formatClusterLink( options: FormatClusterLinkOptions, ): string | undefined; +// Warning: (ae-missing-release-tag) "GoogleKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { + constructor(authProvider: OAuthApi); + // (undocumented) + authProvider: OAuthApi; + // (undocumented) + decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise; +} + +// Warning: (ae-missing-release-tag) "GoogleServiceAccountAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class GoogleServiceAccountAuthProvider + implements KubernetesAuthProvider +{ + // (undocumented) + decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise; +} + +// Warning: (ae-missing-release-tag) "GroupedResponses" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface GroupedResponses extends DeploymentResources { + // (undocumented) + configMaps: V1ConfigMap[]; + // (undocumented) + cronJobs: V1CronJob[]; + // (undocumented) + customResources: any[]; + // (undocumented) + ingresses: V1Ingress[]; + // (undocumented) + jobs: V1Job[]; + // (undocumented) + services: V1Service[]; +} + +// Warning: (ae-missing-release-tag) "GroupedResponsesContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const GroupedResponsesContext: React_2.Context; + +// Warning: (ae-missing-release-tag) "HorizontalPodAutoscalerDrawer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const HorizontalPodAutoscalerDrawer: ({ + hpa, + expanded, + children, +}: { + hpa: V1HorizontalPodAutoscaler; + expanded?: boolean | undefined; + children?: React_2.ReactNode; +}) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "IngressesAccordionsProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "IngressesAccordions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const IngressesAccordions: ({}: IngressesAccordionsProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "isKubernetesAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const isKubernetesAvailable: (entity: Entity) => boolean; +// Warning: (ae-forgotten-export) The symbol "JobsAccordionsProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "JobsAccordions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const JobsAccordions: ({ jobs }: JobsAccordionsProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "KubernetesApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -111,6 +287,38 @@ export class KubernetesBackendClient implements KubernetesApi { ): Promise; } +// Warning: (ae-forgotten-export) The symbol "KubernetesContentProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "KubernetesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const KubernetesContent: ({ + entity, +}: KubernetesContentProps) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "KubernetesDrawerable" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "KubernetesDrawerProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "KubernetesDrawer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const KubernetesDrawer: ({ + object, + renderObject, + kind, + buttonVariant, + expanded, + children, +}: KubernetesDrawerProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "KubernetesObjects" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface KubernetesObjects { + // (undocumented) + error: string | undefined; + // (undocumented) + kubernetesObjects: ObjectsByEntityResponse | undefined; +} + // Warning: (ae-missing-release-tag) "kubernetesPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -123,9 +331,67 @@ const kubernetesPlugin: BackstagePlugin< export { kubernetesPlugin }; export { kubernetesPlugin as plugin }; +// Warning: (ae-missing-release-tag) "PodDrawer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PodDrawer: ({ + pod, + expanded, +}: { + pod: V1Pod; + expanded?: boolean | undefined; +}) => JSX.Element; + +// Warning: (ae-missing-release-tag) "PodNamesWithErrorsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PodNamesWithErrorsContext: React_2.Context>; + +// Warning: (ae-missing-release-tag) "PodNamesWithMetricsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PodNamesWithMetricsContext: React_2.Context< + Map +>; + +// Warning: (ae-forgotten-export) The symbol "PodsTablesProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "PodsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PodsTable: ({ + pods, + extraColumns, +}: PodsTablesProps) => 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) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const Router: (_props: Props) => JSX.Element; + +// Warning: (ae-missing-release-tag) "ServiceAccountKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class ServiceAccountKubernetesAuthProvider + implements KubernetesAuthProvider +{ + // (undocumented) + decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise; +} + +// Warning: (ae-forgotten-export) The symbol "ServicesAccordionsProps" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ServicesAccordions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ServicesAccordions: ({}: ServicesAccordionsProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "useKubernetesObjects" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const useKubernetesObjects: ( + entity: Entity, + intervalMs?: number, +) => KubernetesObjects; ``` diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 3192cdfc16..32cfbc87f6 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.6.2", + "version": "0.6.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,17 +34,18 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-kubernetes-common": "^0.2.7", - "@kubernetes/client-node": "^0.16.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-kubernetes-common": "^0.2.9-next.0", "@backstage/theme": "^0.2.15", + "@kubernetes/client-node": "^0.16.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "@types/react": "^16.13.1 || ^17.0.0", "cronstrue": "^1.122.0", "js-yaml": "^4.0.0", "lodash": "^4.17.21", @@ -56,10 +57,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs-node/src/setupTests.ts b/plugins/kubernetes/src/components/KubernetesDrawer/index.ts similarity index 72% rename from plugins/techdocs-node/src/setupTests.ts rename to plugins/kubernetes/src/components/KubernetesDrawer/index.ts index d342cfb26a..ecd4b1b653 100644 --- a/plugins/techdocs-node/src/setupTests.ts +++ b/plugins/kubernetes/src/components/KubernetesDrawer/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,7 +14,4 @@ * limitations under the License. */ -import { StorageFilesMock } from './testUtils/StorageFilesMock'; - -(global as any).rootDir = StorageFilesMock.rootDir; -(global as any).storageFilesMock = new StorageFilesMock(); +export * from './KubernetesDrawer'; diff --git a/plugins/kubernetes/src/components/index.ts b/plugins/kubernetes/src/components/index.ts new file mode 100644 index 0000000000..72a3cd3ee1 --- /dev/null +++ b/plugins/kubernetes/src/components/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './Cluster'; +export * from './CronJobsAccordions'; +export * from './CustomResources'; +export * from './ErrorPanel'; +export * from './ErrorReporting'; +export * from './HorizontalPodAutoscalers'; +export * from './IngressesAccordions'; +export * from './JobsAccordions'; +export { KubernetesDrawer } from './KubernetesDrawer'; +export * from './Pods'; +export * from './ServicesAccordions'; +export * from './KubernetesContent'; diff --git a/plugins/kubernetes/src/error-detection/error-detection.test.ts b/plugins/kubernetes/src/error-detection/error-detection.test.ts index dcac7db907..ef64ff6a20 100644 --- a/plugins/kubernetes/src/error-detection/error-detection.test.ts +++ b/plugins/kubernetes/src/error-detection/error-detection.test.ts @@ -231,7 +231,7 @@ describe('detectErrors', () => { expect(errors).toBeDefined(); expect(errors).toHaveLength(0); }); - it('should detect in deployment which cant progress', () => { + it('should detect in deployment which cannot progress', () => { const result = detectErrors(oneDeployment(failingDeploy as any)); expect(result.size).toBe(1); diff --git a/plugins/kubernetes/src/hooks/index.ts b/plugins/kubernetes/src/hooks/index.ts index 88db2d8197..0a5835eee4 100644 --- a/plugins/kubernetes/src/hooks/index.ts +++ b/plugins/kubernetes/src/hooks/index.ts @@ -16,5 +16,6 @@ export * from './useKubernetesObjects'; export * from './PodNamesWithErrors'; +export * from './PodNamesWithMetrics'; export * from './GroupedResponses'; export * from './Cluster'; diff --git a/plugins/kubernetes/src/index.ts b/plugins/kubernetes/src/index.ts index 71e1779c84..688d52d7e1 100644 --- a/plugins/kubernetes/src/index.ts +++ b/plugins/kubernetes/src/index.ts @@ -29,3 +29,6 @@ export { Router, isKubernetesAvailable } from './Router'; export * from './api'; export * from './kubernetes-auth-provider'; export * from './utils/clusterLinks'; +export * from './components'; +export * from './hooks'; +export * from './types'; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/index.ts b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts index f6a07d44a9..6f8e3f415f 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/index.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts @@ -17,3 +17,7 @@ export { kubernetesAuthProvidersApiRef } from './types'; export type { KubernetesAuthProvidersApi } from './types'; export { KubernetesAuthProviders } from './KubernetesAuthProviders'; +export { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; +export { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; +export { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider'; +export { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@gitbeaker/node/index.ts b/plugins/kubernetes/src/types/index.ts similarity index 67% rename from plugins/scaffolder-backend/src/scaffolder/__mocks__/@gitbeaker/node/index.ts rename to plugins/kubernetes/src/types/index.ts index 7166dc0f63..db229eae34 100644 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/@gitbeaker/node/index.ts +++ b/plugins/kubernetes/src/types/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,20 +14,4 @@ * limitations under the License. */ -export const mockGitlabClient = { - Namespaces: { - show: jest.fn(), - }, - Projects: { - create: jest.fn(), - }, - Users: { - current: jest.fn(), - }, -}; - -export class Gitlab { - constructor() { - return mockGitlabClient; - } -} +export * from './types'; diff --git a/plugins/kubernetes/src/utils/pod.tsx b/plugins/kubernetes/src/utils/pod.tsx index 50cc983b9d..7bed160a5f 100644 --- a/plugins/kubernetes/src/utils/pod.tsx +++ b/plugins/kubernetes/src/utils/pod.tsx @@ -118,6 +118,8 @@ export const currentToDeclaredResourceToPerc = ( current: number | string, resource: number | string, ): string => { + if (Number(resource) === 0) return `0%`; + if (typeof current === 'number' && typeof resource === 'number') { return `${Math.round((current / resource) * 100)}%`; } diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 9e81f0b73f..3459a14592 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-lighthouse +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index e1e317f795..3c573e8d0b 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,11 +35,11 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index 62e6db2635..76faeafd1b 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-newrelic-dashboard +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.11 + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index 0b17fd6c51..d086218151 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.1.10", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,19 +23,19 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/dev-utils": "^0.2.25", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5" diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx index 0b6a676f52..69608833f7 100644 --- a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx @@ -71,7 +71,7 @@ export const DashboardSnapshot = ({ src={url} /> ) : ( - 'Dashboard loading... , click here to open if it didnt render correctly' + 'Dashboard loading... , click here to open if it did not render correctly' )} diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index dc37652092..72d9b05d0a 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-newrelic +## 0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.3.21 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.3.20 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index f8512a7b21..edf3ffbf64 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.20", + "version": "0.3.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,8 +35,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 39e507bb25..7525fe0490 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-org +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.5.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- 132189e466: Updated the code to handle User kind `spec.memberOf` now being optional. +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index dfae209493..8b8c6709dc 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.5.2", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,16 +24,17 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "pluralize": "^8.0.0", "qs": "^6.10.1", + "p-limit": "^3.1.0", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" @@ -42,11 +43,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/catalog-client": "^0.9.0", - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx new file mode 100644 index 0000000000..6f7680849c --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2020 The 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 { Entity } from '@backstage/catalog-model'; +import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { BackstageTheme } from '@backstage/theme'; +import { + Box, + createStyles, + Grid, + makeStyles, + Typography, +} from '@material-ui/core'; +import React from 'react'; +import pluralize from 'pluralize'; +import { catalogIndexRouteRef } from '../../../routes'; +import { useGetEntities } from './useGetEntities'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + card: { + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[2], + borderRadius: '4px', + padding: theme.spacing(2), + color: '#fff', + transition: `${theme.transitions.duration.standard}ms`, + '&:hover': { + boxShadow: theme.shadows[4], + }, + }, + bold: { + fontWeight: theme.typography.fontWeightBold, + }, + entityTypeBox: { + background: (props: { type: string }) => + theme.getPageTheme({ themeId: props.type }).backgroundImage, + }, + }), +); + +const EntityCountTile = ({ + counter, + type, + name, + url, +}: { + counter: number; + type: string; + name: string; + url: string; +}) => { + const classes = useStyles({ type }); + + return ( + + + + {counter} + + + {pluralize(name, counter)} + + + + ); +}; + +export const ComponentsGrid = ({ + entity, + relationsType, + isGroup, + entityFilterKind, +}: { + entity: Entity; + relationsType: string; + isGroup: boolean; + entityFilterKind?: string[]; +}) => { + const catalogLink = useRouteRef(catalogIndexRouteRef); + const { componentsWithCounters, loading, error } = useGetEntities( + entity, + relationsType, + isGroup, + entityFilterKind, + ); + + if (loading) { + return ; + } else if (error) { + return ; + } + + return ( + + {componentsWithCounters?.map(c => ( + + + + ))} + + ); +}; diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 483cf9744f..dcaae2d4bf 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -112,9 +112,9 @@ const getEntitiesMock = ( request?: GetEntitiesRequest, ): Promise => { const filterKinds = - !Array.isArray(request?.filter) && Array.isArray(request?.filter?.kind) - ? request?.filter?.kind ?? [] - : []; // we expect the request to be like { filter: { kind: ['API','System'], .... }. If changed in OwnerShipCard, let's change in also here + Array.isArray(request?.filter) && Array.isArray(request?.filter[0].kind) + ? request?.filter[0].kind ?? [] + : []; // we expect the request to be like { filter: [{ kind: ['API','System'], 'relations.ownedBy': [group:default/my-team], .... }]. If changed in OwnerShipCard, let's change in also here return Promise.resolve({ items: items.filter(item => filterKinds.find(k => k === item.kind)), } as GetEntitiesResponse); @@ -160,7 +160,12 @@ describe('OwnershipCard', () => { ); expect(catalogApi.getEntities).toHaveBeenCalledWith({ - filter: { kind: ['Component', 'API'] }, + filter: [ + { + kind: ['Component', 'API', 'System'], + 'relations.ownedBy': ['group:default/my-team'], + }, + ], fields: [ 'kind', 'metadata.name', @@ -182,7 +187,10 @@ describe('OwnershipCard', () => { expect( queryByText(getByText('LIBRARY').parentElement!, '1'), ).toBeInTheDocument(); - expect(() => getByText('SYSTEM')).toThrowError(); + expect(getByText('SYSTEM')).toBeInTheDocument(); + expect( + queryByText(getByText('SYSTEM').parentElement!, '1'), + ).toBeInTheDocument(); }); it('applies CustomFilterDefinition', async () => { @@ -238,7 +246,7 @@ describe('OwnershipCard', () => { expect(getByText('OPENAPI').closest('a')).toHaveAttribute( 'href', - '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D=my-team&filters%5Buser%5D=all', + '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=my-team&filters%5Buser%5D=all', ); }); @@ -280,7 +288,7 @@ describe('OwnershipCard', () => { expect(getByText('OPENAPI').closest('a')).toHaveAttribute( 'href', - '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D=user%3Athe-user&filters%5Bowners%5D=my-team&filters%5Buser%5D=all', + '/create/?filters%5Bkind%5D=API&filters%5Btype%5D=openapi&filters%5Bowners%5D%5B0%5D=the-user&filters%5Bowners%5D%5B1%5D=my-team&filters%5Buser%5D=all', ); }); }); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 8635d0d7d4..13e8502b7a 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,123 +14,42 @@ * limitations under the License. */ -import { Entity, UserEntity } from '@backstage/catalog-model'; +import { InfoCard, InfoCardVariants } from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; import { - InfoCard, - InfoCardVariants, - Link, - Progress, - ResponseErrorPanel, -} from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, - isOwnerOf, - useEntity, -} from '@backstage/plugin-catalog-react'; -import { BackstageTheme } from '@backstage/theme'; -import { - Box, - createStyles, - Grid, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, makeStyles, - Typography, + Switch, + Tooltip, } from '@material-ui/core'; -import qs from 'qs'; -import React from 'react'; -import pluralize from 'pluralize'; -import useAsync from 'react-use/lib/useAsync'; -import { catalogIndexRouteRef } from '../../../routes'; +import React, { useState } from 'react'; +import { ComponentsGrid } from './ComponentsGrid'; -type EntityTypeProps = { - kind: string; - type: string; - count: number; -}; - -const useStyles = makeStyles((theme: BackstageTheme) => - createStyles({ - card: { - border: `1px solid ${theme.palette.divider}`, - boxShadow: theme.shadows[2], - borderRadius: '4px', - padding: theme.spacing(2), - color: '#fff', - transition: `${theme.transitions.duration.standard}ms`, - '&:hover': { - boxShadow: theme.shadows[4], - }, +const useStyles = makeStyles(theme => ({ + list: { + [theme.breakpoints.down('xs')]: { + padding: `0 0 12px`, }, - bold: { - fontWeight: theme.typography.fontWeightBold, + }, + listItemText: { + [theme.breakpoints.down('xs')]: { + paddingRight: 0, + paddingLeft: 0, }, - entityTypeBox: { - background: (props: { type: string }) => - theme.getPageTheme({ themeId: props.type }).backgroundImage, + }, + listItemSecondaryAction: { + [theme.breakpoints.down('xs')]: { + width: '100%', + top: 'auto', + right: 'auto', + position: 'relative', + transform: 'unset', }, - }), -); - -const EntityCountTile = ({ - counter, - type, - name, - url, -}: { - counter: number; - type: string; - name: string; - url: string; -}) => { - const classes = useStyles({ type }); - - return ( - - - - {counter} - - - {pluralize(name, counter)} - - - - ); -}; - -const getQueryParams = ( - owner: Entity, - selectedEntity: EntityTypeProps, -): string => { - const ownerName = humanizeEntityRef(owner, { defaultKind: 'group' }); - const { kind, type } = selectedEntity; - const filters = { - kind, - type, - owners: [ownerName], - user: 'all', - }; - if (owner.kind === 'User') { - const user = owner as UserEntity; - filters.owners = [...filters.owners, ...user.spec.memberOf]; - } - const queryParams = qs.stringify( - { - filters, - }, - { - arrayFormat: 'repeat', - }, - ); - - return queryParams; -}; + }, +})); export const OwnershipCard = ({ variant, @@ -139,90 +58,48 @@ export const OwnershipCard = ({ variant?: InfoCardVariants; entityFilterKind?: string[]; }) => { + const classes = useStyles(); const { entity } = useEntity(); - const catalogApi = useApi(catalogApiRef); - const catalogLink = useRouteRef(catalogIndexRouteRef); - - const { - loading, - error, - value: componentsWithCounters, - } = useAsync(async () => { - const kinds = entityFilterKind ?? ['Component', 'API']; - const entitiesList = await catalogApi.getEntities({ - filter: { - kind: kinds, - }, - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); - - const ownedEntitiesList = entitiesList.items.filter(component => - isOwnerOf(entity, component), - ); - - const counts = ownedEntitiesList.reduce( - (acc: EntityTypeProps[], ownedEntity) => { - const match = acc.find( - x => - x.kind === ownedEntity.kind && - x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), - ); - if (match) { - match.count += 1; - } else { - acc.push({ - kind: ownedEntity.kind, - type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, - count: 1, - }); - } - return acc; - }, - [], - ); - - // Return top N (six) entities to be displayed in ownership boxes - const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); - - return topN.map(topOwnedEntity => ({ - counter: topOwnedEntity.count, - type: topOwnedEntity.type, - name: topOwnedEntity.type.toLocaleUpperCase('en-US'), - queryParams: getQueryParams(entity, topOwnedEntity), - })) as Array<{ - counter: number; - type: string; - name: string; - queryParams: string; - }>; - }, [catalogApi, entity]); - - if (loading) { - return ; - } else if (error) { - return ; - } + const isGroup = entity.kind === 'Group'; + const [relationsType, setRelationsType] = useState('direct'); return ( - - {componentsWithCounters?.map(c => ( - - - - ))} - + + + + + Direct Relations + + + relationsType === 'direct' + ? setRelationsType('aggregated') + : setRelationsType('direct') + } + name="pin" + inputProps={{ 'aria-label': 'Ownership Type Switch' }} + disabled={!isGroup} + /> + + Aggregated Relations + + + + ); }; diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts new file mode 100644 index 0000000000..48760002fa --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -0,0 +1,215 @@ +/* + * Copyright 2020 The 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 { + Entity, + RELATION_MEMBER_OF, + RELATION_PARENT_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { + CatalogApi, + catalogApiRef, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; +import limiterFactory from 'p-limit'; +import { useApi } from '@backstage/core-plugin-api'; +import useAsync from 'react-use/lib/useAsync'; +import qs from 'qs'; + +const limiter = limiterFactory(10); + +type EntityTypeProps = { + kind: string; + type: string; + count: number; +}; + +const getQueryParams = ( + ownersEntityRef: string[], + selectedEntity: EntityTypeProps, +): string => { + const { kind, type } = selectedEntity; + const owners = ownersEntityRef.map(owner => owner.split('/')[1]); + const filters = { + kind, + type, + owners, + user: 'all', + }; + const queryParams = qs.stringify({ + filters, + }); + + return queryParams; +}; + +const getOwnersEntityRef = (owner: Entity): string[] => { + let owners = [stringifyEntityRef(owner)]; + if (owner.kind === 'User') { + const ownerGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { + kind: 'Group', + }); + const ownerGroupsName = ownerGroups.map(ownerGroup => + stringifyEntityRef({ + kind: ownerGroup.kind, + namespace: ownerGroup.namespace, + name: ownerGroup.name, + }), + ); + owners = [...owners, ...ownerGroupsName]; + } + return owners; +}; + +const getAggregatedOwnersEntityRef = async ( + parentGroup: Entity, + catalogApi: CatalogApi, +): Promise => { + const requestedEntities: Entity[] = []; + const outstandingEntities = new Map>(); + const processedEntities = new Set(); + requestedEntities.push(parentGroup); + let currentEntity = parentGroup; + + while (requestedEntities.length > 0) { + const childRelations = getEntityRelations( + currentEntity, + RELATION_PARENT_OF, + { + kind: 'Group', + }, + ); + + await Promise.all( + childRelations.map(childGroup => + limiter(async () => { + const promise = catalogApi.getEntityByRef(childGroup); + outstandingEntities.set(childGroup.name, promise); + try { + const processedEntity = await promise; + if (processedEntity) { + requestedEntities.push(processedEntity); + } + } finally { + outstandingEntities.delete(childGroup.name); + } + }), + ), + ); + requestedEntities.shift(); + processedEntities.add( + stringifyEntityRef({ + kind: currentEntity.kind, + namespace: currentEntity.metadata.namespace, + name: currentEntity.metadata.name, + }), + ); + // always set currentEntity to the first element of array requestedEntities + currentEntity = requestedEntities[0]; + } + + return Array.from(processedEntities); +}; + +export function useGetEntities( + entity: Entity, + relationsType: string, + isGroup: boolean, + entityFilterKind?: string[], +): { + componentsWithCounters: + | { + counter: number; + type: string; + name: string; + queryParams: string; + }[] + | undefined; + loading: boolean; + error?: Error; +} { + const catalogApi = useApi(catalogApiRef); + const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + + const { + loading, + error, + value: componentsWithCounters, + } = useAsync(async () => { + const owners = + relationsType === 'aggregated' && isGroup + ? await getAggregatedOwnersEntityRef(entity, catalogApi) + : getOwnersEntityRef(entity); + const ownedEntitiesList = await catalogApi.getEntities({ + filter: [ + { + kind: kinds, + 'relations.ownedBy': owners, + }, + ], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); + + const counts = ownedEntitiesList.items.reduce( + (acc: EntityTypeProps[], ownedEntity) => { + const match = acc.find( + x => + x.kind === ownedEntity.kind && + x.type === (ownedEntity.spec?.type ?? ownedEntity.kind), + ); + if (match) { + match.count += 1; + } else { + acc.push({ + kind: ownedEntity.kind, + type: ownedEntity.spec?.type?.toString() ?? ownedEntity.kind, + count: 1, + }); + } + return acc; + }, + [], + ); + + // Return top N (six) entities to be displayed in ownership boxes + const topN = counts.sort((a, b) => b.count - a.count).slice(0, 6); + + return topN.map(topOwnedEntity => ({ + counter: topOwnedEntity.count, + type: topOwnedEntity.type, + name: topOwnedEntity.type.toLocaleUpperCase('en-US'), + queryParams: getQueryParams(owners, topOwnedEntity), + })) as Array<{ + counter: number; + type: string; + name: string; + queryParams: string; + }>; + }, [catalogApi, entity, relationsType]); + + return { + componentsWithCounters, + loading, + error, + }; +} diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 677023e20f..70ce7e5958 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-pagerduty +## 0.3.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.30 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.29 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index a4dd8865e7..a096f43e87 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.3.29", + "version": "0.3.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 10b0f2900d..cd6ea633b8 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-periskop-backend +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 744bf00d71..e95e7cd7cb 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,8 +24,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", "@types/express": "*", "cross-fetch": "^3.0.6", "express": "^4.17.1", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.6" diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index f9914713ce..06075ffdbe 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-periskop +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.1 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 28d7c6ef19..4178ddb1ee 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,11 +25,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 1193a0f16f..4b69bdbdc3 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-permission-backend +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-permission-node@0.5.5 + ## 0.5.4 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 70971ed136..05e056a5d3 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.4", + "version": "0.5.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,12 +22,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-node": "^0.5.4", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-node": "^0.5.6-next.0", "@types/express": "*", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -39,7 +39,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 9981496658..451a6895a9 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -25,7 +25,10 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { + createPermissionIntegrationRouter, + createPermissionRule, +} from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; describe('PermissionIntegrationClient', () => { @@ -35,8 +38,8 @@ describe('PermissionIntegrationClient', () => { const mockConditions: PermissionCriteria = { not: { allOf: [ - { rule: 'RULE_1', params: [] }, - { rule: 'RULE_2', params: ['abc'] }, + { rule: 'RULE_1', resourceType: 'test-resource', params: [] }, + { rule: 'RULE_2', resourceType: 'test-resource', params: ['abc'] }, ], }, }; @@ -272,22 +275,24 @@ describe('PermissionIntegrationClient', () => { id: resourceRef, })), rules: [ - { + createPermissionRule({ name: 'RULE_1', description: 'Test rule 1', + resourceType: 'test-resource', apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, - }, - { + }), + createPermissionRule({ name: 'RULE_2', description: 'Test rule 2', + resourceType: 'test-resource', apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, - }, + }), ], }), ); @@ -336,7 +341,11 @@ describe('PermissionIntegrationClient', () => { id: '123', resourceRef: 'testResource1', resourceType: 'test-resource', - conditions: { rule: 'RULE_1', params: ['no'] }, + conditions: { + rule: 'RULE_1', + resourceType: 'test-resource', + params: ['no'], + }, }, ]), ).resolves.toEqual([{ id: '123', result: AuthorizeResult.DENY }]); @@ -353,15 +362,33 @@ describe('PermissionIntegrationClient', () => { allOf: [ { allOf: [ - { rule: 'RULE_1', params: ['yes'] }, - { not: { rule: 'RULE_2', params: ['no'] } }, + { + rule: 'RULE_1', + resourceType: 'test-resource', + params: ['yes'], + }, + { + not: { + rule: 'RULE_2', + resourceType: 'test-resource', + params: ['no'], + }, + }, ], }, { not: { allOf: [ - { rule: 'RULE_1', params: ['no'] }, - { rule: 'RULE_2', params: ['yes'] }, + { + rule: 'RULE_1', + resourceType: 'test-resource', + params: ['no'], + }, + { + rule: 'RULE_2', + resourceType: 'test-resource', + params: ['yes'], + }, ], }, }, diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts index 2b2161ee71..2c31d371c3 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.ts @@ -17,11 +17,13 @@ import fetch from 'node-fetch'; import { z } from 'zod'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + ConditionalPolicyDecision, +} from '@backstage/plugin-permission-common'; import { ApplyConditionsRequestEntry, ApplyConditionsResponseEntry, - ConditionalPolicyDecision, } from '@backstage/plugin-permission-node'; const responseSchema = z.object({ diff --git a/plugins/permission-backend/src/service/router.test.ts b/plugins/permission-backend/src/service/router.test.ts index b98b32b01c..6c6ad14af4 100644 --- a/plugins/permission-backend/src/service/router.test.ts +++ b/plugins/permission-backend/src/service/router.test.ts @@ -110,6 +110,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'basic', name: 'test.permission1', attributes: {}, }, @@ -117,6 +118,7 @@ describe('createRouter', () => { { id: '234', permission: { + type: 'basic', name: 'test.permission2', attributes: {}, }, @@ -129,6 +131,7 @@ describe('createRouter', () => { expect(policy.handle).toHaveBeenCalledWith( { permission: { + type: 'basic', name: 'test.permission1', attributes: {}, }, @@ -138,6 +141,7 @@ describe('createRouter', () => { expect(policy.handle).toHaveBeenCalledWith( { permission: { + type: 'basic', name: 'test.permission2', attributes: {}, }, @@ -163,6 +167,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'basic', name: 'test.permission', attributes: {}, }, @@ -174,6 +179,7 @@ describe('createRouter', () => { expect(policy.handle).toHaveBeenCalledWith( { permission: { + type: 'basic', name: 'test.permission', attributes: {}, }, @@ -201,6 +207,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'resource', name: 'test.permission', resourceType: 'test-resource-1', attributes: {}, @@ -258,6 +265,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'resource', name: 'test.permission.1', resourceType: 'test-resource-1', attributes: {}, @@ -267,6 +275,7 @@ describe('createRouter', () => { { id: '234', permission: { + type: 'resource', name: 'test.permission.2', resourceType: 'test-resource-2', attributes: {}, @@ -276,6 +285,7 @@ describe('createRouter', () => { { id: '345', permission: { + type: 'resource', name: 'test.permission.3', resourceType: 'test-resource-1', attributes: {}, @@ -285,6 +295,7 @@ describe('createRouter', () => { { id: '456', permission: { + type: 'resource', name: 'test.permission.4', resourceType: 'test-resource-2', attributes: {}, @@ -384,6 +395,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'resource', name: 'test.permission.1', resourceType: 'test-resource-1', attributes: {}, @@ -393,6 +405,7 @@ describe('createRouter', () => { { id: '234', permission: { + type: 'resource', name: 'test.permission.2', resourceType: 'test-resource-2', attributes: {}, @@ -402,6 +415,7 @@ describe('createRouter', () => { { id: '345', permission: { + type: 'resource', name: 'test.permission.3', resourceType: 'test-resource-1', attributes: {}, @@ -411,6 +425,7 @@ describe('createRouter', () => { { id: '456', permission: { + type: 'resource', name: 'test.permission.4', resourceType: 'test-resource-1', attributes: {}, @@ -420,6 +435,7 @@ describe('createRouter', () => { { id: '567', permission: { + type: 'resource', name: 'test.permission.5', resourceType: 'test-resource-2', attributes: {}, @@ -429,6 +445,7 @@ describe('createRouter', () => { { id: '678', permission: { + type: 'basic', name: 'test.permission.6', attributes: {}, }, @@ -519,6 +536,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'resource', name: 'test.permission.1', resourceType: 'test-resource-1', attributes: {}, @@ -528,6 +546,7 @@ describe('createRouter', () => { { id: '234', permission: { + type: 'resource', name: 'test.permission.2', resourceType: 'test-resource-2', attributes: {}, @@ -537,6 +556,7 @@ describe('createRouter', () => { { id: '345', permission: { + type: 'resource', name: 'test.permission.3', resourceType: 'test-resource-1', attributes: {}, @@ -546,6 +566,7 @@ describe('createRouter', () => { { id: '456', permission: { + type: 'resource', name: 'test.permission.4', resourceType: 'test-resource-1', attributes: {}, @@ -630,6 +651,7 @@ describe('createRouter', () => { id: '123', resourceRef: 'test/resource', permission: { + type: 'resource', name: 'test.permission', resourceType: 'test-resource-1', attributes: {}, @@ -639,6 +661,7 @@ describe('createRouter', () => { id: '234', resourceRef: 'test/resource', permission: { + type: 'resource', name: 'test.permission', resourceType: 'test-resource-1', attributes: {}, @@ -687,10 +710,37 @@ describe('createRouter', () => { undefined, '', {}, - [{ permission: { name: 'test.permission', attributes: {} } }], - { items: [{ permission: { name: 'test.permission', attributes: {} } }] }, + [ + { + permission: { + type: 'basic', + name: 'test.permission', + attributes: {}, + }, + }, + ], + { + items: [ + { + permission: { + type: 'basic', + name: 'test.permission', + attributes: {}, + }, + }, + ], + }, { items: [{ id: '123' }] }, - { items: [{ id: '123', permission: { name: 'test.permission' } }] }, + { + items: [ + { + id: '123', + permission: { name: 'test.permission', attributes: {} }, + }, + ], + }, + { items: [{ id: '123', permission: { type: 'basic', attributes: {} } }] }, + { items: [{ id: '123', permission: { type: 'basic' } }] }, { items: [ { id: '123', permission: { attributes: { invalid: 'attribute' } } }, @@ -724,6 +774,7 @@ describe('createRouter', () => { { id: '123', permission: { + type: 'resource', name: 'test.permission', resourceType: 'test-resource-1', attributes: {}, diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index 48f7b1a2e5..ea7fbcca24 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -30,11 +30,13 @@ import { } from '@backstage/plugin-auth-node'; import { AuthorizeResult, - AuthorizeDecision, - AuthorizeQuery, - Identified, - AuthorizeRequest, - AuthorizeResponse, + EvaluatePermissionResponse, + EvaluatePermissionRequest, + IdentifiedPermissionMessage, + EvaluatePermissionRequestBatch, + EvaluatePermissionResponseBatch, + isResourcePermission, + PermissionAttributes, } from '@backstage/plugin-permission-common'; import { ApplyConditionsRequestEntry, @@ -46,29 +48,44 @@ import { memoize } from 'lodash'; import DataLoader from 'dataloader'; import { Config } from '@backstage/config'; -const querySchema: z.ZodSchema> = z.object({ - id: z.string(), - resourceRef: z.string().optional(), - permission: z.object({ - name: z.string(), - resourceType: z.string().optional(), - attributes: z.object({ - action: z - .union([ - z.literal('create'), - z.literal('read'), - z.literal('update'), - z.literal('delete'), - ]) - .optional(), - }), - }), +const attributesSchema: z.ZodSchema = z.object({ + action: z + .union([ + z.literal('create'), + z.literal('read'), + z.literal('update'), + z.literal('delete'), + ]) + .optional(), }); -const requestSchema: z.ZodSchema = z.object({ - items: z.array(querySchema), +const permissionSchema = z.union([ + z.object({ + type: z.literal('basic'), + name: z.string(), + attributes: attributesSchema, + }), + z.object({ + type: z.literal('resource'), + name: z.string(), + attributes: attributesSchema, + resourceType: z.string(), + }), +]); + +const evaluatePermissionRequestSchema: z.ZodSchema< + IdentifiedPermissionMessage +> = z.object({ + id: z.string(), + resourceRef: z.string().optional(), + permission: permissionSchema, }); +const evaluatePermissionRequestBatchSchema: z.ZodSchema = + z.object({ + items: z.array(evaluatePermissionRequestSchema), + }); + /** * Options required when constructing a new {@link express#Router} using * {@link createRouter}. @@ -84,12 +101,12 @@ export interface RouterOptions { } const handleRequest = async ( - requests: Identified[], + requests: IdentifiedPermissionMessage[], user: BackstageIdentityResponse | undefined, policy: PermissionPolicy, permissionIntegrationClient: PermissionIntegrationClient, authHeader?: string, -): Promise[]> => { +): Promise[]> => { const applyConditionsLoaderFor = memoize((pluginId: string) => { return new DataLoader< ApplyConditionsRequestEntry, @@ -109,6 +126,12 @@ const handleRequest = async ( }; } + if (!isResourcePermission(request.permission)) { + throw new Error( + `Conditional decision returned from permission policy for non-resource permission ${request.permission.name}`, + ); + } + if (decision.resourceType !== request.permission.resourceType) { throw new Error( `Invalid resource conditions returned from permission policy for permission ${request.permission.name}`, @@ -163,15 +186,17 @@ export async function createRouter( router.post( '/authorize', async ( - req: Request, - res: Response, + req: Request, + res: Response, ) => { const token = getBearerTokenFromAuthorizationHeader( req.header('authorization'), ); const user = token ? await identity.authenticate(token) : undefined; - const parseResult = requestSchema.safeParse(req.body); + const parseResult = evaluatePermissionRequestBatchSchema.safeParse( + req.body, + ); if (!parseResult.success) { throw new InputError(parseResult.error.toString()); diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index bdeb000bd4..61722c29dd 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-common +## 0.5.3 + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index db05766dc0..ab0c68470d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -15,37 +15,11 @@ export type AnyOfCriteria = { anyOf: NonEmptyArray>; }; -// @public -export type AuthorizeDecision = - | { - result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; - } - | { - result: AuthorizeResult.CONDITIONAL; - conditions: PermissionCriteria; - }; - -// @public -export type AuthorizeQuery = { - permission: Permission; - resourceRef?: string; -}; - -// @public -export type AuthorizeRequest = { - items: Identified[]; -}; - // @public export type AuthorizeRequestOptions = { token?: string; }; -// @public -export type AuthorizeResponse = { - items: Identified[]; -}; - // @public export enum AuthorizeResult { ALLOW = 'ALLOW', @@ -53,13 +27,59 @@ export enum AuthorizeResult { DENY = 'DENY', } +// @public +export type BasicPermission = PermissionBase<'basic', {}>; + +// @public +export type ConditionalPolicyDecision = { + result: AuthorizeResult.CONDITIONAL; + pluginId: string; + resourceType: string; + conditions: PermissionCriteria; +}; + +// @public +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; + resourceType: TResourceType; +}): ResourcePermission; + +// @public +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; +}): BasicPermission; + +// @public +export type DefinitivePolicyDecision = { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; +}; + // @public export type DiscoveryApi = { getBaseUrl(pluginId: string): Promise; }; // @public -export type Identified = T & { +export type EvaluatePermissionRequest = { + permission: Permission; + resourceRef?: string; +}; + +// @public +export type EvaluatePermissionRequestBatch = + PermissionMessageBatch; + +// @public +export type EvaluatePermissionResponse = PolicyDecision; + +// @public +export type EvaluatePermissionResponseBatch = + PermissionMessageBatch; + +// @public +export type IdentifiedPermissionMessage = T & { id: string; }; @@ -69,9 +89,21 @@ export function isCreatePermission(permission: Permission): boolean; // @public export function isDeletePermission(permission: Permission): boolean; +// @public +export function isPermission( + permission: Permission, + comparedPermission: T, +): permission is T; + // @public export function isReadPermission(permission: Permission): boolean; +// @public +export function isResourcePermission( + permission: Permission, + resourceType?: T, +): permission is ResourcePermission; + // @public export function isUpdatePermission(permission: Permission): boolean; @@ -81,11 +113,7 @@ export type NotCriteria = { }; // @public -export type Permission = { - name: string; - attributes: PermissionAttributes; - resourceType?: string; -}; +export type Permission = BasicPermission | ResourcePermission; // @public export type PermissionAttributes = { @@ -96,22 +124,34 @@ export type PermissionAttributes = { export interface PermissionAuthorizer { // (undocumented) authorize( - queries: AuthorizeQuery[], + requests: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise; + ): Promise; } +// @public +export type PermissionBase = { + name: string; + attributes: PermissionAttributes; +} & { + type: TType; +} & TFields; + // @public export class PermissionClient implements PermissionAuthorizer { constructor(options: { discovery: DiscoveryApi; config: Config }); authorize( - queries: AuthorizeQuery[], + queries: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise; + ): Promise; } // @public -export type PermissionCondition = { +export type PermissionCondition< + TResourceType extends string = string, + TParams extends unknown[] = unknown[], +> = { + resourceType: TResourceType; rule: string; params: TParams; }; @@ -122,4 +162,23 @@ export type PermissionCriteria = | AnyOfCriteria | NotCriteria | TQuery; + +// @public +export type PermissionMessageBatch = { + items: IdentifiedPermissionMessage[]; +}; + +// @public +export type PolicyDecision = + | DefinitivePolicyDecision + | ConditionalPolicyDecision; + +// @public +export type ResourcePermission = + PermissionBase< + 'resource', + { + resourceType: TResourceType; + } + >; ``` diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index b3147de3ee..1919ae5167 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.5.2", + "version": "0.5.3", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -41,14 +41,14 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", "cross-fetch": "^3.1.5", "uuid": "^8.0.0", "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" } diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 0e57f618b7..65a7c7fbbe 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -18,9 +18,13 @@ import { RestContext, rest } from 'msw'; import { setupServer } from 'msw/node'; import { ConfigReader } from '@backstage/config'; import { PermissionClient } from './PermissionClient'; -import { AuthorizeQuery, AuthorizeResult, Identified } from './types/api'; +import { + EvaluatePermissionRequest, + AuthorizeResult, + IdentifiedPermissionMessage, +} from './types/api'; import { DiscoveryApi } from './types/discovery'; -import { Permission } from './types/permission'; +import { createPermission } from './permissions'; const server = setupServer(); const token = 'fake-token'; @@ -36,16 +40,11 @@ const client: PermissionClient = new PermissionClient({ config: new ConfigReader({ permission: { enabled: true } }), }); -const mockPermission: Permission = { +const mockPermission = createPermission({ name: 'test.permission', attributes: {}, - resourceType: 'test-resource', -}; - -const mockAuthorizeQuery = { - permission: mockPermission, - resourceRef: 'foo', -}; + resourceType: 'foo', +}); describe('PermissionClient', () => { beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); @@ -53,11 +52,18 @@ describe('PermissionClient', () => { afterEach(() => server.resetHandlers()); describe('authorize', () => { + const mockAuthorizeQuery = { + permission: mockPermission, + resourceRef: 'foo:bar', + }; + const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map((a: Identified) => ({ - id: a.id, - result: AuthorizeResult.ALLOW, - })); + const responses = req.body.items.map( + (a: IdentifiedPermissionMessage) => ({ + id: a.id, + result: AuthorizeResult.ALLOW, + }), + ); return res(json({ items: responses })); }); @@ -84,7 +90,7 @@ describe('PermissionClient', () => { items: [ expect.objectContaining({ permission: mockPermission, - resourceRef: 'foo', + resourceRef: 'foo:bar', }), ], }); @@ -141,7 +147,7 @@ describe('PermissionClient', () => { mockAuthorizeHandler.mockImplementationOnce( (req, res, { json }: RestContext) => { const responses = req.body.items.map( - (a: Identified) => ({ + (a: IdentifiedPermissionMessage) => ({ id: a.id, outcome: AuthorizeResult.ALLOW, }), @@ -158,10 +164,12 @@ describe('PermissionClient', () => { it('should allow all when permission.enabled is false', async () => { mockAuthorizeHandler.mockImplementationOnce( (req, res, { json }: RestContext) => { - const responses = req.body.map((a: Identified) => ({ - id: a.id, - result: AuthorizeResult.DENY, - })); + const responses = req.body.map( + (a: IdentifiedPermissionMessage) => ({ + id: a.id, + result: AuthorizeResult.DENY, + }), + ); return res(json({ items: responses })); }, @@ -180,10 +188,12 @@ describe('PermissionClient', () => { it('should allow all when permission.enabled is not configured', async () => { mockAuthorizeHandler.mockImplementationOnce( (req, res, { json }: RestContext) => { - const responses = req.body.map((a: Identified) => ({ - id: a.id, - outcome: AuthorizeResult.DENY, - })); + const responses = req.body.map( + (a: IdentifiedPermissionMessage) => ({ + id: a.id, + outcome: AuthorizeResult.DENY, + }), + ); return res(json(responses)); }, diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 9431050690..72f6b04567 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -21,13 +21,13 @@ import * as uuid from 'uuid'; import { z } from 'zod'; import { AuthorizeResult, - AuthorizeQuery, - AuthorizeDecision, - Identified, + EvaluatePermissionRequest, + EvaluatePermissionResponse, + IdentifiedPermissionMessage, PermissionCriteria, PermissionCondition, - AuthorizeResponse, - AuthorizeRequest, + EvaluatePermissionResponseBatch, + EvaluatePermissionRequestBatch, } from './types/api'; import { DiscoveryApi } from './types/discovery'; import { @@ -41,6 +41,7 @@ const permissionCriteriaSchema: z.ZodSchema< z .object({ rule: z.string(), + resourceType: z.string(), params: z.array(z.unknown()), }) .strict() @@ -107,9 +108,9 @@ export class PermissionClient implements PermissionAuthorizer { * @public */ async authorize( - queries: AuthorizeQuery[], + queries: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise { + ): Promise { // TODO(permissions): it would be great to provide some kind of typing guarantee that // conditional responses will only ever be returned for requests containing a resourceType // but no resourceRef. That way clients who aren't prepared to handle filtering according @@ -119,7 +120,7 @@ export class PermissionClient implements PermissionAuthorizer { return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); } - const request: AuthorizeRequest = { + const request: EvaluatePermissionRequestBatch = { items: queries.map(query => ({ id: uuid.v4(), ...query, @@ -145,7 +146,7 @@ export class PermissionClient implements PermissionAuthorizer { const responsesById = responseBody.items.reduce((acc, r) => { acc[r.id] = r; return acc; - }, {} as Record>); + }, {} as Record>); return request.items.map(query => responsesById[query.id]); } @@ -155,9 +156,9 @@ export class PermissionClient implements PermissionAuthorizer { } private assertValidResponse( - request: AuthorizeRequest, + request: EvaluatePermissionRequestBatch, json: any, - ): asserts json is AuthorizeResponse { + ): asserts json is EvaluatePermissionResponseBatch { const authorizedResponses = responseSchema.parse(json); const responseIds = authorizedResponses.items.map(r => r.id); const hasAllRequestIds = request.items.every(r => diff --git a/plugins/permission-common/src/permissions/createPermission.ts b/plugins/permission-common/src/permissions/createPermission.ts new file mode 100644 index 0000000000..586403ceb4 --- /dev/null +++ b/plugins/permission-common/src/permissions/createPermission.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BasicPermission, + Permission, + PermissionAttributes, + ResourcePermission, +} from '../types'; + +/** + * Utility function for creating a valid {@link ResourcePermission}, inferring + * the appropriate type and resource type parameter. + * + * @public + */ +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; + resourceType: TResourceType; +}): ResourcePermission; +/** + * Utility function for creating a valid {@link BasicPermission}. + * + * @public + */ +export function createPermission(input: { + name: string; + attributes: PermissionAttributes; +}): BasicPermission; +export function createPermission({ + name, + attributes, + resourceType, +}: { + name: string; + attributes: PermissionAttributes; + resourceType?: string; +}): Permission { + if (resourceType) { + return { + type: 'resource', + name, + attributes, + resourceType, + }; + } + + return { + type: 'basic', + name, + attributes, + }; +} diff --git a/plugins/permission-common/src/permissions/index.ts b/plugins/permission-common/src/permissions/index.ts index 736d898539..acb15e45cf 100644 --- a/plugins/permission-common/src/permissions/index.ts +++ b/plugins/permission-common/src/permissions/index.ts @@ -15,3 +15,4 @@ */ export * from './util'; +export { createPermission } from './createPermission'; diff --git a/plugins/permission-common/src/permissions/util.ts b/plugins/permission-common/src/permissions/util.ts index 188609f4ae..1a5ed434a7 100644 --- a/plugins/permission-common/src/permissions/util.ts +++ b/plugins/permission-common/src/permissions/util.ts @@ -14,7 +14,35 @@ * limitations under the License. */ -import { Permission } from '../types'; +import { Permission, ResourcePermission } from '../types'; + +/** + * Check if the two parameters are equivalent permissions. + * @public + */ +export function isPermission( + permission: Permission, + comparedPermission: T, +): permission is T { + return permission.name === comparedPermission.name; +} + +/** + * Check if a given permission is a {@link ResourcePermission}. When + * `resourceType` is supplied as the second parameter, also checks if + * the permission has the specified resource type. + * @public + */ +export function isResourcePermission( + permission: Permission, + resourceType?: T, +): permission is ResourcePermission { + if (!('resourceType' in permission)) { + return false; + } + + return !resourceType || permission.resourceType === resourceType; +} /** * Check if a given permission is related to a create action. diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index e0ab1f1642..6aaabf978d 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -21,7 +21,15 @@ import { Permission } from './permission'; * requests. * @public */ -export type Identified = T & { id: string }; +export type IdentifiedPermissionMessage = T & { id: string }; + +/** + * A batch of request or response items. + * @public + */ +export type PermissionMessageBatch = { + items: IdentifiedPermissionMessage[]; +}; /** * The result of an authorization request. @@ -43,22 +51,45 @@ export enum AuthorizeResult { } /** - * An individual authorization request for {@link PermissionClient#authorize}. + * A definitive decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}. + * + * @remarks + * + * This indicates that the policy unconditionally allows (or denies) the request. + * * @public */ -export type AuthorizeQuery = { - permission: Permission; - resourceRef?: string; +export type DefinitivePolicyDecision = { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; }; /** - * A batch of authorization requests from {@link PermissionClient#authorize}. + * A conditional decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}. + * + * @remarks + * + * This indicates that the policy allows authorization for the request, given that the returned + * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin + * which knows about the referenced permission rules. + * * @public */ -export type AuthorizeRequest = { - items: Identified[]; +export type ConditionalPolicyDecision = { + result: AuthorizeResult.CONDITIONAL; + pluginId: string; + resourceType: string; + conditions: PermissionCriteria; }; +/** + * A decision returned by the {@link @backstage/plugin-permission-node#PermissionPolicy}. + * + * @public + */ +export type PolicyDecision = + | DefinitivePolicyDecision + | ConditionalPolicyDecision; + /** * A condition returned with a CONDITIONAL authorization response. * @@ -67,7 +98,11 @@ export type AuthorizeRequest = { * claims from a identity token. * @public */ -export type PermissionCondition = { +export type PermissionCondition< + TResourceType extends string = string, + TParams extends unknown[] = unknown[], +> = { + resourceType: TResourceType; rule: string; params: TParams; }; @@ -79,7 +114,7 @@ export type PermissionCondition = { type NonEmptyArray = [T, ...T[]]; /** - * Represnts a logical AND for the provided criteria. + * Represents a logical AND for the provided criteria. * @public */ export type AllOfCriteria = { @@ -87,7 +122,7 @@ export type AllOfCriteria = { }; /** - * Represnts a logical OR for the provided criteria. + * Represents a logical OR for the provided criteria. * @public */ export type AnyOfCriteria = { @@ -113,20 +148,37 @@ export type PermissionCriteria = | TQuery; /** - * An individual authorization response from {@link PermissionClient#authorize}. + * An individual request sent to the permission backend. * @public */ -export type AuthorizeDecision = - | { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY } - | { - result: AuthorizeResult.CONDITIONAL; - conditions: PermissionCriteria; - }; +export type EvaluatePermissionRequest = { + permission: Permission; + resourceRef?: string; +}; /** - * A batch of authorization responses from {@link PermissionClient#authorize}. + * A batch of requests sent to the permission backend. * @public */ -export type AuthorizeResponse = { - items: Identified[]; -}; +export type EvaluatePermissionRequestBatch = + PermissionMessageBatch; + +/** + * An individual response from the permission backend. + * + * @remarks + * + * This response type is an alias of {@link PolicyDecision} to maintain separation between the + * {@link @backstage/plugin-permission-node#PermissionPolicy} interface and the permission backend + * api. They may diverge at some point in the future. The response + * + * @public + */ +export type EvaluatePermissionResponse = PolicyDecision; + +/** + * A batch of responses from the permission backend. + * @public + */ +export type EvaluatePermissionResponseBatch = + PermissionMessageBatch; diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index fa98de73f1..1a993a981c 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -16,11 +16,15 @@ export { AuthorizeResult } from './api'; export type { - AuthorizeQuery, - AuthorizeRequest, - AuthorizeDecision, - AuthorizeResponse, - Identified, + EvaluatePermissionRequest, + EvaluatePermissionRequestBatch, + EvaluatePermissionResponse, + EvaluatePermissionResponseBatch, + IdentifiedPermissionMessage, + PermissionMessageBatch, + ConditionalPolicyDecision, + DefinitivePolicyDecision, + PolicyDecision, PermissionCondition, PermissionCriteria, AllOfCriteria, @@ -29,8 +33,11 @@ export type { } from './api'; export type { DiscoveryApi } from './discovery'; export type { + BasicPermission, PermissionAttributes, Permission, PermissionAuthorizer, + PermissionBase, + ResourcePermission, AuthorizeRequestOptions, } from './permission'; diff --git a/plugins/permission-common/src/types/permission.ts b/plugins/permission-common/src/types/permission.ts index 16934d8771..34e7884793 100644 --- a/plugins/permission-common/src/types/permission.ts +++ b/plugins/permission-common/src/types/permission.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuthorizeQuery, AuthorizeDecision } from './api'; +import { EvaluatePermissionRequest, EvaluatePermissionResponse } from './api'; /** * The attributes related to a given permission; these should be generic and widely applicable to @@ -26,17 +26,10 @@ export type PermissionAttributes = { }; /** - * A permission that can be checked through authorization. - * - * Permissions are the "what" part of authorization, the action to be performed. This may be reading - * an entity from the catalog, executing a software template, or any other action a plugin author - * may wish to protect. - * - * To evaluate authorization, a permission is paired with a Backstage identity (the "who") and - * evaluated using an authorization policy. + * Generic type for building {@link Permission} types. * @public */ -export type Permission = { +export type PermissionBase = { /** * The name of the permission. */ @@ -47,14 +40,53 @@ export type Permission = { * all by name. */ attributes: PermissionAttributes; +} & { /** - * Some permissions can be authorized based on characteristics of a resource - * such a catalog entity. For these permissions, the resourceType field - * denotes the type of the resource whose resourceRef should be passed when - * authorizing. + * String value indicating the type of the permission (e.g. 'basic', + * 'resource'). The allowed authorization flows in the permission system + * depend on the type. For example, a `resourceRef` should only be provided + * when authorizing permissions of type 'resource'. */ - resourceType?: string; -}; + type: TType; // Property appears on separate object to prevent expansion of Permission types in api reports. +} & TFields; + +/** + * A permission that can be checked through authorization. + * + * @remarks + * + * Permissions are the "what" part of authorization, the action to be performed. This may be reading + * an entity from the catalog, executing a software template, or any other action a plugin author + * may wish to protect. + * + * To evaluate authorization, a permission is paired with a Backstage identity (the "who") and + * evaluated using an authorization policy. + * @public + */ +export type Permission = BasicPermission | ResourcePermission; + +/** + * A standard {@link Permission} with no additional capabilities or restrictions. + * @public + */ +export type BasicPermission = PermissionBase<'basic', {}>; + +/** + * ResourcePermissions are {@link Permission}s that can be authorized based on + * characteristics of a resource such a catalog entity. + * @public + */ +export type ResourcePermission = + PermissionBase< + 'resource', + { + /** + * Denotes the type of the resource whose resourceRef should be passed when + * authorizing. + */ + resourceType: TResourceType; + } + >; /** * A client interacting with the permission backend can implement this authorizer interface. @@ -62,9 +94,9 @@ export type Permission = { */ export interface PermissionAuthorizer { authorize( - queries: AuthorizeQuery[], + requests: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise; + ): Promise; } /** diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 87f0e47c31..3ee804e333 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-permission-node +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/backend-common@0.13.2-next.0 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-auth-node@0.1.6 + ## 0.5.4 ### Patch Changes diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index f6de7f4909..57379dd68b 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -5,19 +5,23 @@ ```ts import { AllOfCriteria } from '@backstage/plugin-permission-common'; import { AnyOfCriteria } from '@backstage/plugin-permission-common'; -import { AuthorizeDecision } from '@backstage/plugin-permission-common'; -import { AuthorizeQuery } from '@backstage/plugin-permission-common'; import { AuthorizeRequestOptions } from '@backstage/plugin-permission-common'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; +import { DefinitivePolicyDecision } from '@backstage/plugin-permission-common'; +import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; +import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import express from 'express'; -import { Identified } from '@backstage/plugin-permission-common'; +import { IdentifiedPermissionMessage } from '@backstage/plugin-permission-common'; import { NotCriteria } from '@backstage/plugin-permission-common'; +import { Permission } from '@backstage/plugin-permission-common'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PolicyDecision } from '@backstage/plugin-permission-common'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; import { TokenManager } from '@backstage/backend-common'; // @public @@ -26,7 +30,7 @@ export type ApplyConditionsRequest = { }; // @public -export type ApplyConditionsRequestEntry = Identified<{ +export type ApplyConditionsRequestEntry = IdentifiedPermissionMessage<{ resourceRef: string; resourceType: string; conditions: PermissionCriteria; @@ -38,28 +42,22 @@ export type ApplyConditionsResponse = { }; // @public -export type ApplyConditionsResponseEntry = Identified; +export type ApplyConditionsResponseEntry = + IdentifiedPermissionMessage; // @public export type Condition = TRule extends PermissionRule< any, any, + infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? (...params: TParams) => PermissionCondition : never; -// @public -export type ConditionalPolicyDecision = { - result: AuthorizeResult.CONDITIONAL; - pluginId: string; - resourceType: string; - conditions: PermissionCriteria; -}; - // @public export type Conditions< - TRules extends Record>, + TRules extends Record>, > = { [Name in keyof TRules]: Condition; }; @@ -71,39 +69,49 @@ export type ConditionTransformer = ( // @public export const createConditionExports: < + TResourceType extends string, TResource, - TRules extends Record>, + TRules extends Record< + string, + PermissionRule + >, >(options: { pluginId: string; - resourceType: string; + resourceType: TResourceType; rules: TRules; }) => { conditions: Conditions; - createPolicyDecision: ( - conditions: PermissionCriteria, + createConditionalDecision: ( + permission: ResourcePermission, + conditions: PermissionCriteria< + PermissionCondition + >, ) => ConditionalPolicyDecision; }; // @public -export const createConditionFactory: ( - rule: PermissionRule, -) => (...params: TParams) => { - rule: string; - params: TParams; -}; +export const createConditionFactory: < + TResourceType extends string, + TParams extends any[], +>( + rule: PermissionRule, +) => (...params: TParams) => PermissionCondition; // @public export const createConditionTransformer: < TQuery, - TRules extends PermissionRule[], + TRules extends PermissionRule[], >( permissionRules: [...TRules], ) => ConditionTransformer; // @public -export const createPermissionIntegrationRouter: (options: { - resourceType: string; - rules: PermissionRule[]; +export const createPermissionIntegrationRouter: < + TResourceType extends string, + TResource, +>(options: { + resourceType: TResourceType; + rules: PermissionRule, unknown[]>[]; getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>; }) => express.Router; @@ -111,15 +119,11 @@ export const createPermissionIntegrationRouter: (options: { export const createPermissionRule: < TResource, TQuery, + TResourceType extends string, TParams extends unknown[], >( - rule: PermissionRule, -) => PermissionRule; - -// @public -export type DefinitivePolicyDecision = { - result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; -}; + rule: PermissionRule, +) => PermissionRule; // @alpha export const isAndCriteria: ( @@ -137,17 +141,19 @@ export const isOrCriteria: ( ) => criteria is AnyOfCriteria; // @public -export const makeCreatePermissionRule: () => < - TParams extends unknown[], ->( - rule: PermissionRule, -) => PermissionRule; +export const makeCreatePermissionRule: < + TResource, + TQuery, + TResourceType extends string, +>() => ( + rule: PermissionRule, +) => PermissionRule; // @public export interface PermissionPolicy { // (undocumented) handle( - request: PolicyAuthorizeQuery, + request: PolicyQuery, user?: BackstageIdentityResponse, ): Promise; } @@ -156,29 +162,28 @@ export interface PermissionPolicy { export type PermissionRule< TResource, TQuery, + TResourceType extends string, TParams extends unknown[] = unknown[], > = { name: string; description: string; + resourceType: TResourceType; apply(resource: TResource, ...params: TParams): boolean; toQuery(...params: TParams): PermissionCriteria; }; // @public -export type PolicyAuthorizeQuery = Omit; - -// @public -export type PolicyDecision = - | DefinitivePolicyDecision - | ConditionalPolicyDecision; +export type PolicyQuery = { + permission: Permission; +}; // @public export class ServerPermissionClient implements PermissionAuthorizer { // (undocumented) authorize( - queries: AuthorizeQuery[], + requests: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise; + ): Promise; // (undocumented) static fromConfig( config: Config, diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 3fdcbd9820..06667615bd 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.5.4", + "version": "0.5.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,18 +33,18 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/plugin-permission-common": "^0.5.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/plugin-permission-common": "^0.5.3", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 53300061a0..f5711ed4a0 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -16,10 +16,10 @@ import { ServerPermissionClient } from './ServerPermissionClient'; import { - Permission, - Identified, - AuthorizeQuery, + IdentifiedPermissionMessage, + EvaluatePermissionRequest, AuthorizeResult, + createPermission, } from '@backstage/plugin-permission-common'; import { ConfigReader } from '@backstage/config'; import { @@ -32,10 +32,12 @@ import { RestContext, rest } from 'msw'; const server = setupServer(); const mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map((r: Identified) => ({ - id: r.id, - result: AuthorizeResult.ALLOW, - })); + const responses = req.body.items.map( + (r: IdentifiedPermissionMessage) => ({ + id: r.id, + result: AuthorizeResult.ALLOW, + }), + ); return res(json({ items: responses })); }); @@ -48,11 +50,10 @@ const discovery: PluginEndpointDiscovery = { return mockBaseUrl; }, }; -const testPermission: Permission = { +const testPermission = createPermission({ name: 'test.permission', attributes: {}, - resourceType: 'test-resource', -}; +}); const config = new ConfigReader({ permission: { enabled: true }, backend: { auth: { keys: [{ secret: 'a-secret-key' }] } }, diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index 8c03016268..56381dff17 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -20,9 +20,9 @@ import { } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { - AuthorizeQuery, + EvaluatePermissionRequest, AuthorizeRequestOptions, - AuthorizeDecision, + EvaluatePermissionResponse, AuthorizeResult, PermissionClient, PermissionAuthorizer, @@ -78,9 +78,9 @@ export class ServerPermissionClient implements PermissionAuthorizer { } async authorize( - queries: AuthorizeQuery[], + requests: EvaluatePermissionRequest[], options?: AuthorizeRequestOptions, - ): Promise { + ): Promise { // Check if permissions are enabled before validating the server token. That // way when permissions are disabled, the noop token manager can be used // without fouling up the logic inside the ServerPermissionClient, because @@ -89,9 +89,9 @@ export class ServerPermissionClient implements PermissionAuthorizer { !this.permissionEnabled || (await this.isValidServerToken(options?.token)) ) { - return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); + return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); } - return this.permissionClient.authorize(queries, options); + return this.permissionClient.authorize(requests, options); } private async isValidServerToken( diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index f3585d11ad..54d5652996 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -14,17 +14,22 @@ * limitations under the License. */ -import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + createPermission, +} from '@backstage/plugin-permission-common'; import { createConditionExports } from './createConditionExports'; +import { createPermissionRule } from './createPermissionRule'; const testIntegration = () => createConditionExports({ pluginId: 'test-plugin', resourceType: 'test-resource', rules: { - testRule1: { + testRule1: createPermissionRule({ name: 'testRule1', description: 'Test rule 1', + resourceType: 'test-resource', apply: jest.fn( (_resource: any, _firstParam: string, _secondParam: number) => true, ), @@ -32,16 +37,17 @@ const testIntegration = () => query: 'testRule1', params: [firstParam, secondParam], })), - }, - testRule2: { + }), + testRule2: createPermissionRule({ name: 'testRule2', description: 'Test rule 2', - apply: jest.fn((_firstParam: object) => false), + resourceType: 'test-resource', + apply: jest.fn((_resource: any, _firstParam: object) => false), toQuery: jest.fn((firstParam: object) => ({ query: 'testRule2', params: [firstParam], })), - }, + }), }, }); @@ -52,11 +58,13 @@ describe('createConditionExports', () => { expect(conditions.testRule1('a', 1)).toEqual({ rule: 'testRule1', + resourceType: 'test-resource', params: ['a', 1], }); expect(conditions.testRule2({ baz: 'quux' })).toEqual({ rule: 'testRule2', + resourceType: 'test-resource', params: [{ baz: 'quux' }], }); }); @@ -64,18 +72,35 @@ describe('createConditionExports', () => { describe('createPolicyDecisions', () => { it('wraps conditions in an object with resourceType and pluginId', () => { - const { createPolicyDecision } = testIntegration(); + const { createConditionalDecision } = testIntegration(); + const testPermission = createPermission({ + name: 'test.permission', + attributes: {}, + resourceType: 'test-resource', + }); expect( - createPolicyDecision({ - allOf: [{ rule: 'testRule1', params: ['a', 1] }], + createConditionalDecision(testPermission, { + allOf: [ + { + rule: 'testRule1', + resourceType: 'test-resource', + params: ['a', 1], + }, + ], }), ).toEqual({ result: AuthorizeResult.CONDITIONAL, pluginId: 'test-plugin', resourceType: 'test-resource', conditions: { - allOf: [{ rule: 'testRule1', params: ['a', 1] }], + allOf: [ + { + rule: 'testRule1', + resourceType: 'test-resource', + params: ['a', 1], + }, + ], }, }); }); diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts index fd351128ed..ba34c07fed 100644 --- a/plugins/permission-node/src/integration/createConditionExports.ts +++ b/plugins/permission-node/src/integration/createConditionExports.ts @@ -16,10 +16,11 @@ import { AuthorizeResult, + ConditionalPolicyDecision, PermissionCondition, PermissionCriteria, + ResourcePermission, } from '@backstage/plugin-permission-common'; -import { ConditionalPolicyDecision } from '../policy'; import { PermissionRule } from '../types'; import { createConditionFactory } from './createConditionFactory'; @@ -32,9 +33,10 @@ import { createConditionFactory } from './createConditionFactory'; export type Condition = TRule extends PermissionRule< any, any, + infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? (...params: TParams) => PermissionCondition : never; /** @@ -44,39 +46,44 @@ export type Condition = TRule extends PermissionRule< * @public */ export type Conditions< - TRules extends Record>, + TRules extends Record>, > = { [Name in keyof TRules]: Condition; }; /** - * Creates the recommended condition-related exports for a given plugin based on the built-in - * {@link PermissionRule}s it supports. + * Creates the recommended condition-related exports for a given plugin based on + * the built-in {@link PermissionRule}s it supports. * * @remarks * * The function returns a `conditions` object containing a - * {@link @backstage/plugin-permission-common#PermissionCondition} factory for each of the - * supplied {@link PermissionRule}s, along with a `createConditions` function which builds the - * wrapper object needed to enclose conditions when authoring {@link PermissionPolicy} implementations. + * {@link @backstage/plugin-permission-common#PermissionCondition} factory for + * each of the supplied {@link PermissionRule}s, along with a + * `createConditionalDecision` function which builds the wrapper object needed + * to enclose conditions when authoring {@link PermissionPolicy} + * implementations. * - * Plugin authors should generally call this method with all the built-in {@link PermissionRule}s - * the plugin supports, and export the resulting `conditions` object and `createConditions` - * function so that they can be used by {@link PermissionPolicy} authors. + * Plugin authors should generally call this method with all the built-in + * {@link PermissionRule}s the plugin supports, and export the resulting + * `conditions` object and `createConditionalDecision` function so that they can + * be used by {@link PermissionPolicy} authors. * * @public */ export const createConditionExports = < + TResourceType extends string, TResource, - TRules extends Record>, + TRules extends Record>, >(options: { pluginId: string; - resourceType: string; + resourceType: TResourceType; rules: TRules; }): { conditions: Conditions; - createPolicyDecision: ( - conditions: PermissionCriteria, + createConditionalDecision: ( + permission: ResourcePermission, + conditions: PermissionCriteria>, ) => ConditionalPolicyDecision; } => { const { pluginId, resourceType, rules } = options; @@ -89,7 +96,8 @@ export const createConditionExports = < }), {} as Conditions, ), - createPolicyDecision: ( + createConditionalDecision: ( + _permission: ResourcePermission, conditions: PermissionCriteria, ) => ({ result: AuthorizeResult.CONDITIONAL, diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 8dd43f5da6..1941b5ac0b 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -15,14 +15,16 @@ */ import { createConditionFactory } from './createConditionFactory'; +import { createPermissionRule } from './createPermissionRule'; describe('createConditionFactory', () => { - const testRule = { + const testRule = createPermissionRule({ name: 'test-rule', description: 'test-description', + resourceType: 'test-resource', apply: jest.fn(), toQuery: jest.fn(), - }; + }); it('returns a function', () => { expect(createConditionFactory(testRule)).toEqual(expect.any(Function)); @@ -33,6 +35,7 @@ describe('createConditionFactory', () => { const conditionFactory = createConditionFactory(testRule); expect(conditionFactory('a', 'b', 1, 2)).toEqual({ rule: 'test-rule', + resourceType: 'test-resource', params: ['a', 'b', 1, 2], }); }); diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 84a8dc86a6..15a5b4fd08 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -33,8 +34,11 @@ import { PermissionRule } from '../types'; * @public */ export const createConditionFactory = - (rule: PermissionRule) => - (...params: TParams) => ({ + ( + rule: PermissionRule, + ) => + (...params: TParams): PermissionCondition => ({ rule: rule.name, + resourceType: rule.resourceType, params, }); diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 8a93a4ea27..917449561d 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -19,25 +19,28 @@ import { PermissionCriteria, } from '@backstage/plugin-permission-common'; import { createConditionTransformer } from './createConditionTransformer'; +import { createPermissionRule } from './createPermissionRule'; const transformConditions = createConditionTransformer([ - { + createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', + resourceType: 'test-resource', apply: jest.fn(), toQuery: jest.fn( (firstParam: string, secondParam: number) => `test-rule-1:${firstParam}/${secondParam}`, ), - }, - { + }), + createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', + resourceType: 'test-resource', apply: jest.fn(), toQuery: jest.fn( (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, ), - }, + }), ]); describe('createConditionTransformer', () => { @@ -46,18 +49,30 @@ describe('createConditionTransformer', () => { expectedResult: PermissionCriteria; }[] = [ { - conditions: { rule: 'test-rule-1', params: ['abc', 123] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['abc', 123], + }, expectedResult: 'test-rule-1:abc/123', }, { - conditions: { rule: 'test-rule-2', params: [{ foo: 0 }] }, + conditions: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ foo: 0 }], + }, expectedResult: 'test-rule-2:{"foo":0}', }, { conditions: { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { @@ -67,8 +82,12 @@ describe('createConditionTransformer', () => { { conditions: { allOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { @@ -77,7 +96,11 @@ describe('createConditionTransformer', () => { }, { conditions: { - not: { rule: 'test-rule-2', params: [{}] }, + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{}], + }, }, expectedResult: { not: 'test-rule-2:{}', @@ -88,15 +111,31 @@ describe('createConditionTransformer', () => { allOf: [ { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{}], + }, ], }, { not: { allOf: [ - { rule: 'test-rule-1', params: ['b', 2] }, - { rule: 'test-rule-2', params: [{ c: 3 }] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['b', 2], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ c: 3 }], + }, ], }, }, @@ -120,15 +159,33 @@ describe('createConditionTransformer', () => { allOf: [ { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{ b: 2 }] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ b: 2 }], + }, ], }, { not: { allOf: [ - { rule: 'test-rule-1', params: ['c', 3] }, - { not: { rule: 'test-rule-2', params: [{ d: 4 }] } }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['c', 3], + }, + { + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ d: 4 }], + }, + }, ], }, }, diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index ee4224b8bd..112bb30d01 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -29,7 +29,7 @@ import { const mapConditions = ( criteria: PermissionCriteria, - getRule: (name: string) => PermissionRule, + getRule: (name: string) => PermissionRule, ): PermissionCriteria => { if (isAndCriteria(criteria)) { return { @@ -70,7 +70,7 @@ export type ConditionTransformer = ( */ export const createConditionTransformer = < TQuery, - TRules extends PermissionRule[], + TRules extends PermissionRule[], >( permissionRules: [...TRules], ): ConditionTransformer => { diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index 6408de46a1..65c43c7f30 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -18,6 +18,7 @@ import { AuthorizeResult } from '@backstage/plugin-permission-common'; import express, { Express, Router } from 'express'; import request, { Response } from 'supertest'; import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter'; +import { createPermissionRule } from './createPermissionRule'; const mockGetResources: jest.MockedFunction< Parameters[0]['getResources'] @@ -25,21 +26,23 @@ const mockGetResources: jest.MockedFunction< resourceRefs.map(resourceRef => ({ id: resourceRef })), ); -const testRule1 = { +const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', + resourceType: 'test-resource', apply: jest.fn( (_resource: any, _firstParam: string, _secondParam: number) => true, ), toQuery: jest.fn(), -}; +}); -const testRule2 = { +const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', - apply: jest.fn((_firstParam: object) => false), + resourceType: 'test-resource', + apply: jest.fn((_resource: any, _firstParam: object) => false), toQuery: jest.fn(), -}; +}); describe('createPermissionIntegrationRouter', () => { let app: Express; @@ -65,29 +68,57 @@ describe('createPermissionIntegrationRouter', () => { describe('POST /.well-known/backstage/permissions/apply-conditions', () => { it.each([ - { rule: 'test-rule-1', params: ['abc', 123] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['abc', 123], + }, { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { - not: { rule: 'test-rule-2', params: [{}] }, + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{}], + }, }, { allOf: [ { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{}], + }, ], }, { not: { allOf: [ - { rule: 'test-rule-1', params: ['b', 2] }, - { rule: 'test-rule-2', params: [{ c: 3 }] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['b', 2], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ c: 3 }], + }, ], }, }, @@ -119,26 +150,52 @@ describe('createPermissionIntegrationRouter', () => { }); it.each([ - { rule: 'test-rule-2', params: [{ foo: 0 }] }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ foo: 0 }], + }, { allOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{}] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { allOf: [ { anyOf: [ - { rule: 'test-rule-1', params: ['a', 1] }, - { rule: 'test-rule-2', params: [{ b: 2 }] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['a', 1], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ b: 2 }], + }, ], }, { not: { allOf: [ - { rule: 'test-rule-1', params: ['c', 3] }, - { not: { rule: 'test-rule-2', params: [{ d: 4 }] } }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: ['c', 3], + }, + { + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [{ d: 4 }], + }, + }, ], }, }, @@ -179,25 +236,45 @@ describe('createPermissionIntegrationRouter', () => { id: '123', resourceRef: 'default:test/resource-1', resourceType: 'test-resource', - conditions: { rule: 'test-rule-1', params: [] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, }, { id: '234', resourceRef: 'default:test/resource-1', resourceType: 'test-resource', - conditions: { rule: 'test-rule-2', params: [] }, + conditions: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [], + }, }, { id: '345', resourceRef: 'default:test/resource-2', resourceType: 'test-resource', - conditions: { not: { rule: 'test-rule-1', params: [] } }, + conditions: { + not: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, + }, }, { id: '456', resourceRef: 'default:test/resource-3', resourceType: 'test-resource', - conditions: { not: { rule: 'test-rule-2', params: [] } }, + conditions: { + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [], + }, + }, }, { id: '567', @@ -205,8 +282,16 @@ describe('createPermissionIntegrationRouter', () => { resourceType: 'test-resource', conditions: { anyOf: [ - { rule: 'test-rule-1', params: [] }, - { rule: 'test-rule-2', params: [] }, + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: [], + }, ], }, }, @@ -248,6 +333,7 @@ describe('createPermissionIntegrationRouter', () => { resourceType: 'test-incorrect-resource-1', conditions: { rule: 'test-rule-1', + resourceType: 'test-incorrect-resource-1', params: [{}], }, }, @@ -257,6 +343,7 @@ describe('createPermissionIntegrationRouter', () => { resourceType: 'test-resource', conditions: { rule: 'test-rule-1', + resourceType: 'test-resource', params: [{}], }, }, @@ -266,6 +353,7 @@ describe('createPermissionIntegrationRouter', () => { resourceType: 'test-incorrect-resource-2', conditions: { rule: 'test-rule-1', + resourceType: 'test-incorrect-resource-2', params: [{}], }, }, @@ -291,7 +379,11 @@ describe('createPermissionIntegrationRouter', () => { id: '123', resourceRef: 'default:test/resource', resourceType: 'test-resource', - conditions: { rule: 'test-rule-1', params: [] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, }, ], }); @@ -324,19 +416,31 @@ describe('createPermissionIntegrationRouter', () => { id: '123', resourceRef: 'default:test/resource-1', resourceType: 'test-resource', - conditions: { rule: 'test-rule-1', params: [] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, }, { id: '234', resourceRef: 'default:test/missing-resource', resourceType: 'test-resource', - conditions: { rule: 'test-rule-1', params: [] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, }, { id: '345', resourceRef: 'default:test/resource-2', resourceType: 'test-resource', - conditions: { rule: 'test-rule-1', params: [] }, + conditions: { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: [], + }, }, ], }); @@ -377,6 +481,17 @@ describe('createPermissionIntegrationRouter', () => { ], }, { items: [{ conditions: { anyOf: [] } }] }, + { + items: [ + { conditions: { rule: 'TEST_RULE', resourceType: 'test-resource' } }, + ], + }, + { items: [{ conditions: { rule: 'TEST_RULE', params: ['foo'] } }] }, + { + items: [ + { conditions: { resourceType: 'test-resource', params: ['foo'] } }, + ], + }, ])(`returns 400 for invalid input %#`, async input => { const response = await request(app) .post('/.well-known/backstage/permissions/apply-conditions') diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index efb5d7c3a6..b4cf4ac08c 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -21,7 +21,8 @@ import { InputError } from '@backstage/errors'; import { errorHandler } from '@backstage/backend-common'; import { AuthorizeResult, - Identified, + DefinitivePolicyDecision, + IdentifiedPermissionMessage, PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; @@ -32,7 +33,6 @@ import { isNotCriteria, isOrCriteria, } from './util'; -import { DefinitivePolicyDecision } from '../policy/types'; const permissionCriteriaSchema: z.ZodSchema< PermissionCriteria @@ -44,6 +44,7 @@ const permissionCriteriaSchema: z.ZodSchema< z .object({ rule: z.string(), + resourceType: z.string(), params: z.array(z.unknown()), }) .strict(), @@ -67,7 +68,7 @@ const applyConditionsRequestSchema = z.object({ * * @public */ -export type ApplyConditionsRequestEntry = Identified<{ +export type ApplyConditionsRequestEntry = IdentifiedPermissionMessage<{ resourceRef: string; resourceType: string; conditions: PermissionCriteria; @@ -88,7 +89,8 @@ export type ApplyConditionsRequest = { * * @public */ -export type ApplyConditionsResponseEntry = Identified; +export type ApplyConditionsResponseEntry = + IdentifiedPermissionMessage; /** * A batch of {@link ApplyConditionsResponseEntry} objects. @@ -99,10 +101,10 @@ export type ApplyConditionsResponse = { items: ApplyConditionsResponseEntry[]; }; -const applyConditions = ( - criteria: PermissionCriteria, +const applyConditions = ( + criteria: PermissionCriteria>, resource: TResource | undefined, - getRule: (name: string) => PermissionRule, + getRule: (name: string) => PermissionRule, ): boolean => { // If resource was not found, deny. This avoids leaking information from the // apply-conditions API which would allow a user to differentiate between @@ -126,6 +128,14 @@ const applyConditions = ( return getRule(criteria.rule).apply(resource, ...criteria.params); }; +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * @ignore + */ +type NoInfer = T extends infer S ? S : never; + /** * Create an express Router which provides an authorization route to allow * integration between the permission backend and other Backstage backend @@ -161,9 +171,16 @@ const applyConditions = ( * * @public */ -export const createPermissionIntegrationRouter = (options: { - resourceType: string; - rules: PermissionRule[]; +export const createPermissionIntegrationRouter = < + TResourceType extends string, + TResource, +>(options: { + resourceType: TResourceType; + // Do not infer value of TResourceType from supplied rules. + // instead only consider the resourceType parameter, and + // consider any rules whose resource type does not match + // to be an error. + rules: PermissionRule>[]; getResources: ( resourceRefs: string[], ) => Promise>; diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index f3a0519a70..6673fe76de 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -24,9 +24,10 @@ import { PermissionRule } from '../types'; export const createPermissionRule = < TResource, TQuery, + TResourceType extends string, TParams extends unknown[], >( - rule: PermissionRule, + rule: PermissionRule, ) => rule; /** @@ -38,8 +39,8 @@ export const createPermissionRule = < * @public */ export const makeCreatePermissionRule = - () => + () => ( - rule: PermissionRule, + rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts index d27c0a4243..17e27b6028 100644 --- a/plugins/permission-node/src/integration/util.test.ts +++ b/plugins/permission-node/src/integration/util.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { createPermissionRule } from './createPermissionRule'; import { createGetRule, isAndCriteria, @@ -25,19 +26,21 @@ describe('permission integration utils', () => { describe('createGetRule', () => { let getRule: ReturnType; - const testRule1 = { + const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', + resourceType: 'test-resource', apply: jest.fn(), toQuery: jest.fn(), - }; + }); - const testRule2 = { + const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', + resourceType: 'test-resource', apply: jest.fn(), toQuery: jest.fn(), - }; + }); beforeEach(() => { getRule = createGetRule([testRule1, testRule2]); diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts index 9102f092c4..68b7fff2b8 100644 --- a/plugins/permission-node/src/integration/util.ts +++ b/plugins/permission-node/src/integration/util.ts @@ -62,11 +62,11 @@ export const isNotCriteria = ( Object.prototype.hasOwnProperty.call(criteria, 'not'); export const createGetRule = ( - rules: PermissionRule[], + rules: PermissionRule[], ) => { const rulesMap = new Map(Object.values(rules).map(rule => [rule.name, rule])); - return (name: string): PermissionRule => { + return (name: string): PermissionRule => { const rule = rulesMap.get(name); if (!rule) { diff --git a/plugins/permission-node/src/policy/index.ts b/plugins/permission-node/src/policy/index.ts index 1d3fc4a737..f151cfb4fb 100644 --- a/plugins/permission-node/src/policy/index.ts +++ b/plugins/permission-node/src/policy/index.ts @@ -14,10 +14,4 @@ * limitations under the License. */ -export type { - ConditionalPolicyDecision, - DefinitivePolicyDecision, - PermissionPolicy, - PolicyAuthorizeQuery, - PolicyDecision, -} from './types'; +export type { PermissionPolicy, PolicyQuery } from './types'; diff --git a/plugins/permission-node/src/policy/types.ts b/plugins/permission-node/src/policy/types.ts index 19b12b8f28..0f469cfbfe 100644 --- a/plugins/permission-node/src/policy/types.ts +++ b/plugins/permission-node/src/policy/types.ts @@ -15,68 +15,25 @@ */ import { - AuthorizeQuery, - AuthorizeResult, - PermissionCondition, - PermissionCriteria, + Permission, + PolicyDecision, } from '@backstage/plugin-permission-common'; import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; /** - * An authorization request to be evaluated by the {@link PermissionPolicy}. + * A query to be evaluated by the {@link PermissionPolicy}. * * @remarks * - * This differs from {@link @backstage/permission-common#AuthorizeQuery} in that `resourceRef` - * should never be provided. This forces policies to be written in a way that's compatible with - * filtering collections of resources at data load time. + * Unlike other parts of the permission API, the policy does not accept a resource ref. This keeps + * the policy decoupled from the resource loading and condition applying logic. * * @public */ -export type PolicyAuthorizeQuery = Omit; - -/** - * A definitive result to an authorization request, returned by the {@link PermissionPolicy}. - * - * @remarks - * - * This indicates that the policy unconditionally allows (or denies) the request. - * - * @public - */ -export type DefinitivePolicyDecision = { - result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; +export type PolicyQuery = { + permission: Permission; }; -/** - * A conditional result to an authorization request, returned by the {@link PermissionPolicy}. - * - * @remarks - * - * This indicates that the policy allows authorization for the request, given that the returned - * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin - * which knows about the referenced permission rules. - * - * Similar to {@link @backstage/permission-common#AuthorizeDecision}, but with the plugin and resource - * identifiers needed to evaluate the returned conditions. - * @public - */ -export type ConditionalPolicyDecision = { - result: AuthorizeResult.CONDITIONAL; - pluginId: string; - resourceType: string; - conditions: PermissionCriteria; -}; - -/** - * The result of evaluating an authorization request with a {@link PermissionPolicy}. - * - * @public - */ -export type PolicyDecision = - | DefinitivePolicyDecision - | ConditionalPolicyDecision; - /** * A policy to evaluate authorization requests for any permissioned action performed in Backstage. * @@ -95,7 +52,7 @@ export type PolicyDecision = */ export interface PermissionPolicy { handle( - request: PolicyAuthorizeQuery, + request: PolicyQuery, user?: BackstageIdentityResponse, ): Promise; } diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index d5035f10dc..66b4a7b22b 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -35,10 +35,12 @@ import type { PermissionCriteria } from '@backstage/plugin-permission-common'; export type PermissionRule< TResource, TQuery, + TResourceType extends string, TParams extends unknown[] = unknown[], > = { name: string; description: string; + resourceType: TResourceType; /** * Apply this rule to a resource already loaded from a backing data source. The params are diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index fa1e950899..13597e9a1b 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-permission-react +## 0.3.4 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-plugin-api@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + ## 0.3.3 ### Patch Changes diff --git a/plugins/permission-react/api-report.md b/plugins/permission-react/api-report.md index 1bcb01fa32..17b29a463a 100644 --- a/plugins/permission-react/api-report.md +++ b/plugins/permission-react/api-report.md @@ -4,14 +4,15 @@ ```ts import { ApiRef } from '@backstage/core-plugin-api'; -import { AuthorizeDecision } from '@backstage/plugin-permission-common'; -import { AuthorizeQuery } from '@backstage/plugin-permission-common'; import { ComponentProps } from 'react'; import { Config } from '@backstage/config'; import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common'; +import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common'; import { IdentityApi } from '@backstage/core-plugin-api'; import { Permission } from '@backstage/plugin-permission-common'; import { ReactElement } from 'react'; +import { ResourcePermission } from '@backstage/plugin-permission-common'; import { Route } from 'react-router'; // @public (undocumented) @@ -24,7 +25,9 @@ export type AsyncPermissionResult = { // @public export class IdentityPermissionApi implements PermissionApi { // (undocumented) - authorize(request: AuthorizeQuery): Promise; + authorize( + request: EvaluatePermissionRequest, + ): Promise; // (undocumented) static create(options: { config: Config; @@ -35,7 +38,9 @@ export class IdentityPermissionApi implements PermissionApi { // @public export type PermissionApi = { - authorize(request: AuthorizeQuery): Promise; + authorize( + request: EvaluatePermissionRequest, + ): Promise; }; // @public @@ -44,17 +49,31 @@ export const permissionApiRef: ApiRef; // @public export const PermissionedRoute: ( props: ComponentProps & { - permission: Permission; - resourceRef?: string; errorComponent?: ReactElement | null; - }, + } & ( + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + } + ), ) => JSX.Element; // @public -export const usePermission: ( - permission: Permission, - resourceRef?: string | undefined, -) => AsyncPermissionResult; +export function usePermission( + input: + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + }, +): AsyncPermissionResult; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index c627a45660..3813970ebb 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-permission-common": "^0.5.2", + "@backstage/config": "^1.0.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-permission-common": "^0.5.3", "cross-fetch": "^3.1.5", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", @@ -44,8 +44,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@types/jest": "^26.0.7" diff --git a/plugins/permission-react/src/apis/IdentityPermissionApi.ts b/plugins/permission-react/src/apis/IdentityPermissionApi.ts index 6e5d54f61d..80d4eb8859 100644 --- a/plugins/permission-react/src/apis/IdentityPermissionApi.ts +++ b/plugins/permission-react/src/apis/IdentityPermissionApi.ts @@ -17,8 +17,8 @@ import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { PermissionApi } from './PermissionApi'; import { - AuthorizeQuery, - AuthorizeDecision, + EvaluatePermissionRequest, + EvaluatePermissionResponse, PermissionClient, } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; @@ -44,7 +44,9 @@ export class IdentityPermissionApi implements PermissionApi { return new IdentityPermissionApi(permissionClient, identity); } - async authorize(request: AuthorizeQuery): Promise { + async authorize( + request: EvaluatePermissionRequest, + ): Promise { const response = await this.permissionClient.authorize( [request], await this.identityApi.getCredentials(), diff --git a/plugins/permission-react/src/apis/PermissionApi.ts b/plugins/permission-react/src/apis/PermissionApi.ts index b17350c38e..d3a37421ef 100644 --- a/plugins/permission-react/src/apis/PermissionApi.ts +++ b/plugins/permission-react/src/apis/PermissionApi.ts @@ -15,19 +15,21 @@ */ import { - AuthorizeQuery, - AuthorizeDecision, + EvaluatePermissionRequest, + EvaluatePermissionResponse, } from '@backstage/plugin-permission-common'; import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; /** - * This API is used by various frontend utilities that allow developers to implement authorization wihtin their frontend + * This API is used by various frontend utilities that allow developers to implement authorization within their frontend * plugins. A plugin developer will likely not have to interact with this API or its implementations directly, but * rather with the aforementioned utility components/hooks. * @public */ export type PermissionApi = { - authorize(request: AuthorizeQuery): Promise; + authorize( + request: EvaluatePermissionRequest, + ): Promise; }; /** diff --git a/plugins/permission-react/src/components/PermissionedRoute.test.tsx b/plugins/permission-react/src/components/PermissionedRoute.test.tsx index 3d7d1b7af7..9159227925 100644 --- a/plugins/permission-react/src/components/PermissionedRoute.test.tsx +++ b/plugins/permission-react/src/components/PermissionedRoute.test.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { PermissionedRoute } from '.'; import { usePermission } from '../hooks'; import { renderInTestApp } from '@backstage/test-utils'; +import { createPermission } from '@backstage/plugin-permission-common'; jest.mock('../hooks', () => ({ usePermission: jest.fn(), @@ -26,10 +27,10 @@ const mockUsePermission = usePermission as jest.MockedFunction< typeof usePermission >; -const permission = { +const permission = createPermission({ name: 'access.something', - attributes: { action: 'read' as const }, -}; + attributes: { action: 'read' }, +}); describe('PermissionedRoute', () => { it('Does not render when loading', async () => { diff --git a/plugins/permission-react/src/components/PermissionedRoute.tsx b/plugins/permission-react/src/components/PermissionedRoute.tsx index 76017308aa..dd39c5bb4f 100644 --- a/plugins/permission-react/src/components/PermissionedRoute.tsx +++ b/plugins/permission-react/src/components/PermissionedRoute.tsx @@ -18,7 +18,11 @@ import React, { ComponentProps, ReactElement } from 'react'; import { Route } from 'react-router'; import { useApp } from '@backstage/core-plugin-api'; import { usePermission } from '../hooks'; -import { Permission } from '@backstage/plugin-permission-common'; +import { + isResourcePermission, + Permission, + ResourcePermission, +} from '@backstage/plugin-permission-common'; /** * Returns a React Router Route which only renders the element when authorized. If unauthorized, the Route will render a @@ -28,13 +32,25 @@ import { Permission } from '@backstage/plugin-permission-common'; */ export const PermissionedRoute = ( props: ComponentProps & { - permission: Permission; - resourceRef?: string; errorComponent?: ReactElement | null; - }, + } & ( + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + } + ), ) => { const { permission, resourceRef, errorComponent, ...otherProps } = props; - const permissionResult = usePermission(permission, resourceRef); + + const permissionResult = usePermission( + isResourcePermission(permission) + ? { permission, resourceRef } + : { permission }, + ); const app = useApp(); const { NotFoundErrorPage } = app.getComponents(); diff --git a/plugins/permission-react/src/hooks/usePermission.test.tsx b/plugins/permission-react/src/hooks/usePermission.test.tsx index d6c5b2ed33..2862b10f96 100644 --- a/plugins/permission-react/src/hooks/usePermission.test.tsx +++ b/plugins/permission-react/src/hooks/usePermission.test.tsx @@ -17,18 +17,21 @@ import React, { FC } from 'react'; import { render } from '@testing-library/react'; import { usePermission } from './usePermission'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + createPermission, +} from '@backstage/plugin-permission-common'; import { TestApiProvider } from '@backstage/test-utils'; import { PermissionApi, permissionApiRef } from '../apis'; import { SWRConfig } from 'swr'; -const permission = { +const permission = createPermission({ name: 'access.something', - attributes: { action: 'read' as const }, -}; + attributes: { action: 'read' }, +}); const TestComponent: FC = () => { - const { loading, allowed, error } = usePermission(permission); + const { loading, allowed, error } = usePermission({ permission }); return (
{loading && 'loading'} diff --git a/plugins/permission-react/src/hooks/usePermission.ts b/plugins/permission-react/src/hooks/usePermission.ts index cc27f82c59..069c2c8148 100644 --- a/plugins/permission-react/src/hooks/usePermission.ts +++ b/plugins/permission-react/src/hooks/usePermission.ts @@ -18,7 +18,9 @@ import { useApi } from '@backstage/core-plugin-api'; import { permissionApiRef } from '../apis'; import { AuthorizeResult, + isResourcePermission, Permission, + ResourcePermission, } from '@backstage/plugin-permission-common'; import useSWR from 'swr'; @@ -30,24 +32,45 @@ export type AsyncPermissionResult = { }; /** - * React hook utility for authorization. Given a - * {@link @backstage/plugin-permission-common#Permission} and an optional - * resourceRef, it will return whether or not access is allowed (for the given - * resource, if resourceRef is provided). See + * React hook utility for authorization. Given either a non-resource + * {@link @backstage/plugin-permission-common#Permission} or a + * {@link @backstage/plugin-permission-common#ResourcePermission} and an + * optional resourceRef, it will return whether or not access is allowed (for + * the given resource, if resourceRef is provided). See * {@link @backstage/plugin-permission-common/PermissionClient#authorize} for * more details. * + * The resourceRef field is optional to allow calling this hook with an + * entity that might be loading asynchronously, but when resourceRef is not + * supplied, the value of `allowed` will always be false. + * * Note: This hook uses stale-while-revalidate to help avoid flicker in UI * elements that would be conditionally rendered based on the `allowed` result * of this hook. * @public */ -export const usePermission = ( - permission: Permission, - resourceRef?: string, -): AsyncPermissionResult => { +export function usePermission( + input: + | { + permission: Exclude; + resourceRef?: never; + } + | { + permission: ResourcePermission; + resourceRef: string | undefined; + }, +): AsyncPermissionResult { const permissionApi = useApi(permissionApiRef); - const { data, error } = useSWR({ permission, resourceRef }, async args => { + const { data, error } = useSWR(input, async (args: typeof input) => { + // We could make the resourceRef parameter required to avoid this check, but + // it would make using this hook difficult in situations where the entity + // must be asynchronously loaded, so instead we short-circuit to a deny when + // no resourceRef is supplied, on the assumption that the resourceRef is + // still loading outside the hook. + if (isResourcePermission(args.permission) && !args.resourceRef) { + return AuthorizeResult.DENY; + } + const { result } = await permissionApi.authorize(args); return result; }); @@ -59,4 +82,4 @@ export const usePermission = ( return { loading: true, allowed: false }; } return { loading: false, allowed: data === AuthorizeResult.ALLOW }; -}; +} diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 21383f46b4..13bc583afa 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-proxy-backend +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.2.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.2.23 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index da81235a4e..b9db54f637 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.23", + "version": "0.2.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,8 +32,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -46,7 +46,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 35e0630b3c..b39d76c6d2 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-rollbar-backend +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + ## 0.1.26 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index c2292b5def..0f39b7b7ce 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.26", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,8 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "camelcase-keys": "^7.0.1", "compression": "^1.7.4", @@ -50,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 9d897bf066..4295d86daa 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-rollbar +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.4.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.4.2 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index c005cfcbf3..a0634ea57e 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.2", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 72527fba26..8c35a98a94 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-scaffolder-backend@1.0.1-next.0 + +## 0.2.5 + +### Patch Changes + +- 89c7e47967: Minor README update +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index d6a8b7dfd9..2c69da07f6 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.4", + "version": "0.2.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,19 +23,19 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-scaffolder-backend": "^0.18.0", - "@backstage/config": "^0.1.15", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", "fs-extra": "10.0.1", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index 18a6722663..8c501cf8f1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -115,7 +115,7 @@ describe('fetch:cookiecutter', () => { }); }); - // Mock when executeShellCommand is called it creats some new files in the mock filesystem + // Mock when executeShellCommand is called it creates some new files in the mock filesystem executeShellCommand.mockImplementation(async () => { mockFs({ [`${join(mockTmpDir, 'intermediate')}`]: { diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index d21805b9b5..e567e75099 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-scaffolder-backend@1.0.1-next.0 + +## 0.3.5 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 5fd07439e6..8b009ff889 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.3.4", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,17 +24,17 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/plugin-scaffolder-backend": "^0.18.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", "fs-extra": "^10.0.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 01bf358b9d..e356619bed 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.0.1-next.0 + +## 0.2.3 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.0.0 + - @backstage/config@1.0.0 + - @backstage/types@1.0.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 02395e62f6..dc1e9e95be 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.2", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,14 +23,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/plugin-scaffolder-backend": "^0.18.0", - "@backstage/types": "^0.1.3", + "@backstage/config": "^1.0.0", + "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "winston": "^3.2.1", "yeoman-environment": "^3.9.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.0", + "@backstage/backend-common": "^0.13.2-next.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index b39d57b897..7ff181b756 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-scaffolder-backend +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-scaffolder-common@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- 765639f98c: Added new `github:issues:label` action to apply labels to issues, and also output `pullRequestNumber` from `publish:github:pull-request`. +- efc73db10c: Use `better-sqlite3` instead of `@vscode/sqlite3` +- c8475ab3bb: Adding some documentation for exported things +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/plugin-scaffolder-common@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + ## 0.18.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6934f2ef83..89ca6b55bd 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -49,15 +49,10 @@ export const createBuiltinActions: ( // @public export interface CreateBuiltInActionsOptions { - // (undocumented) additionalTemplateFilters?: Record; - // (undocumented) catalogClient: CatalogApi; - // (undocumented) config: Config; - // (undocumented) integrations: ScmIntegrations; - // (undocumented) reader: UrlReader; } @@ -142,19 +137,27 @@ export function createGithubActionsDispatchAction(options: { token?: string | undefined; }>; -// @public (undocumented) +// @public +export function createGithubIssuesLabelAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction<{ + repoUrl: string; + number: number; + labels: string[]; + token?: string | undefined; +}>; + +// @public export interface CreateGithubPullRequestActionOptions { - // (undocumented) clientFactory?: ( input: CreateGithubPullRequestClientFactoryInput, ) => Promise; - // (undocumented) githubCredentialsProvider?: GithubCredentialsProvider; - // (undocumented) integrations: ScmIntegrationRegistry; } -// @public (undocumented) +// @public export type CreateGithubPullRequestClientFactoryInput = { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; @@ -263,6 +266,9 @@ export function createPublishGitlabAction(options: { repoVisibility?: 'internal' | 'private' | 'public' | undefined; sourcePath?: string | undefined; token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; }>; // @public @@ -278,7 +284,7 @@ export const createPublishGitlabMergeRequestAction: (options: { token?: string | undefined; }>; -// @public (undocumented) +// @public export function createRouter(options: RouterOptions): Promise; // @public @@ -298,11 +304,8 @@ export type CreateWorkerOptions = { // @public export interface CurrentClaimedTask { - // (undocumented) secrets?: TaskSecrets; - // (undocumented) spec: TaskSpec; - // (undocumented) taskId: string; } @@ -379,6 +382,7 @@ export interface OctokitWithPullRequestPluginClient { createPullRequest(options: createPullRequest.Options): Promise<{ data: { html_url: string; + number: number; }; } | null>; } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 1de4961b59..3ed587af2d 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "0.18.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,15 +34,15 @@ "build:assets": "node scripts/build-nunjucks.js" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-backend": "^0.24.0", - "@backstage/plugin-scaffolder-common": "^0.3.0", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", + "@backstage/types": "^1.0.0", "@gitbeaker/core": "^34.6.0", "@gitbeaker/node": "^35.1.0", "@octokit/webhooks": "^9.14.1", @@ -74,8 +74,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/nodegit/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/nodegit/index.ts deleted file mode 100644 index 79a32c3050..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/nodegit/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const mockIndex = { - addAll: jest.fn(), - write: jest.fn(), - writeTree: jest.fn().mockResolvedValue('mockoid'), -}; - -export const mockRepo = { - refreshIndex: jest.fn().mockResolvedValue(mockIndex), - createCommit: jest.fn(), -}; - -export const mockRemote = { - push: jest.fn(), -}; - -const Repository = { init: jest.fn().mockResolvedValue(mockRepo) }; -const Remote = { create: jest.fn().mockResolvedValue(mockRemote) }; -const Signature = { now: jest.fn() }; -const Cred = { - userpassPlaintextNew: jest.fn(), -}; - -export { Repository, Remote, Signature, Cred }; diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/octokit/index.ts b/plugins/scaffolder-backend/src/scaffolder/__mocks__/octokit/index.ts deleted file mode 100644 index de320ef498..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/octokit/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const mockGithubClient = { - rest: { - actions: { - createWorkflowDispatch: jest.fn(), - }, - repos: { - createInOrg: jest.fn(), - createForAuthenticatedUser: jest.fn(), - createWebhook: jest.fn(), - addCollaborator: jest.fn(), - replaceAllTopics: jest.fn(), - }, - users: { - getByUsername: jest.fn(), - }, - teams: { - addOrUpdateRepoPermissionsInOrg: jest.fn(), - }, - }, -}; - -export class Octokit { - constructor() { - return mockGithubClient; - } -} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index d7f15cdf13..93c5991ca9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -45,6 +45,7 @@ import { import { createGithubActionsDispatchAction, createGithubWebhookAction, + createGithubIssuesLabelAction, } from './github'; import { TemplateFilter } from '../../../lib'; import { TemplateAction } from '../types'; @@ -54,10 +55,26 @@ import { TemplateAction } from '../types'; * @public */ export interface CreateBuiltInActionsOptions { + /** + * The {@link @backstage/backend-common#UrlReader} interface that will be used in the default actions. + */ reader: UrlReader; + /** + * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. + */ integrations: ScmIntegrations; + /** + * The {@link @backstage/catalog-client#CatalogApi} that will be used in the default actions. + */ catalogClient: CatalogApi; + /** + * The {@link @backstage/config#Config} that will be used in the default actions. + */ config: Config; + /** + * Additional custom filters that will be passed to the nunjucks template engine for use in + * Template Manifests and also template skeleton files when using `fetch:template`. + */ additionalTemplateFilters?: Record; } @@ -129,6 +146,10 @@ export const createBuiltinActions = ( integrations, githubCredentialsProvider, }), + createGithubIssuesLabelAction({ + integrations, + githubCredentialsProvider, + }), ]; return actions as TemplateAction[]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts index 7850163e9d..0782ea40cd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -jest.mock('octokit'); import { TemplateAction } from '../../types'; import { createGithubActionsDispatchAction } from './githubActionsDispatch'; @@ -27,6 +26,21 @@ import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; +const mockOctokit = { + rest: { + actions: { + createWorkflowDispatch: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + describe('github:actions:dispatch', () => { const config = new ConfigReader({ integrations: { @@ -54,8 +68,6 @@ describe('github:actions:dispatch', () => { createTemporaryDirectory: jest.fn(), }; - const { mockGithubClient } = require('octokit'); - beforeEach(() => { jest.resetAllMocks(); githubCredentialsProvider = @@ -67,7 +79,7 @@ describe('github:actions:dispatch', () => { }); it('should call the githubApis for creating WorkflowDispatch without an input object', async () => { - mockGithubClient.rest.actions.createWorkflowDispatch.mockResolvedValue({ + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ data: { foo: 'bar', }, @@ -82,7 +94,7 @@ describe('github:actions:dispatch', () => { await action.handler(ctx); expect( - mockGithubClient.rest.actions.createWorkflowDispatch, + mockOctokit.rest.actions.createWorkflowDispatch, ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', @@ -92,7 +104,7 @@ describe('github:actions:dispatch', () => { }); it('should call the githubApis for creating WorkflowDispatch with an input object', async () => { - mockGithubClient.rest.actions.createWorkflowDispatch.mockResolvedValue({ + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ data: { foo: 'bar', }, @@ -108,7 +120,7 @@ describe('github:actions:dispatch', () => { await action.handler(ctx); expect( - mockGithubClient.rest.actions.createWorkflowDispatch, + mockOctokit.rest.actions.createWorkflowDispatch, ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts new file mode 100644 index 0000000000..5518662e45 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { createGithubIssuesLabelAction } from './githubIssuesLabel'; +import { + ScmIntegrations, + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, +} from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { PassThrough } from 'stream'; +import { TemplateAction } from '../..'; + +const mockOctokit = { + rest: { + issues: { + addLabels: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:issues:label', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + number: '1', + labels: ['label1', 'label2'], + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + beforeEach(() => { + jest.resetAllMocks(); + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubIssuesLabelAction({ + integrations, + githubCredentialsProvider, + }); + }); + + it('should call the githubApi for adding labels', async () => { + await action.handler(mockContext); + expect(mockOctokit.rest.issues.addLabels).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + issue_number: '1', + labels: ['label1', 'label2'], + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts new file mode 100644 index 0000000000..9c849edb6f --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts @@ -0,0 +1,109 @@ +/* + * 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 { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { createTemplateAction } from '../../createTemplateAction'; +import { assertError, InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; +import { getOctokitOptions } from './helpers'; +import { parseRepoUrl } from '../publish/util'; + +/** + * Adds labels to a pull request or issue on GitHub + * @public + */ +export function createGithubIssuesLabelAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}) { + const { integrations, githubCredentialsProvider } = options; + + return createTemplateAction<{ + repoUrl: string; + number: number; + labels: string[]; + token?: string; + }>({ + id: 'github:issues:label', + description: 'Adds labels to a pull request or issue on GitHub.', + schema: { + input: { + type: 'object', + required: ['repoUrl', 'number', 'labels'], + properties: { + repoUrl: { + title: 'Repository Location', + description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the repository name and 'owner' is an organization or username`, + type: 'string', + }, + number: { + title: 'Pull Request or issue number', + description: 'The pull request or issue number to add labels to', + type: 'number', + }, + labels: { + title: 'Labels', + description: 'The labels to add to the pull request or issue', + type: 'array', + items: { + type: 'string', + }, + }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The GITHUB_TOKEN to use for authorization to GitHub', + }, + }, + }, + }, + async handler(ctx) { + const { repoUrl, number, labels, token: providedToken } = ctx.input; + + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + ctx.logger.info(`Adding labels to ${number} issue on repo ${repo}`); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const client = new Octokit( + await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + repoUrl: repoUrl, + token: providedToken, + }), + ); + + try { + await client.rest.issues.addLabels({ + owner, + repo, + issue_number: number, + labels, + }); + } catch (e) { + assertError(e); + ctx.logger.warn( + `Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`, + ); + } + }, + }); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts index b80691fdcc..0798b04a93 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -jest.mock('octokit'); - import { createGithubWebhookAction } from './githubWebhook'; import { ScmIntegrations, @@ -27,6 +25,21 @@ import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; import { TemplateAction } from '../..'; +const mockOctokit = { + rest: { + repos: { + createWebhook: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + describe('github:repository:webhook:create', () => { const config = new ConfigReader({ integrations: { @@ -65,8 +78,6 @@ describe('github:repository:webhook:create', () => { createTemporaryDirectory: jest.fn(), }; - const { mockGithubClient } = require('octokit'); - it('should call the githubApi for creating repository Webhook', async () => { const repoUrl = 'github.com?repo=repo&owner=owner'; const webhookUrl = 'https://example.com/payload'; @@ -75,7 +86,7 @@ describe('github:repository:webhook:create', () => { }); await action.handler(ctx); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], @@ -97,7 +108,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], @@ -118,7 +129,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push', 'pull_request'], @@ -139,7 +150,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], @@ -160,7 +171,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], @@ -181,7 +192,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], @@ -202,7 +213,7 @@ describe('github:repository:webhook:create', () => { }, }); - expect(mockGithubClient.rest.repos.createWebhook).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', events: ['push'], diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts index 07614e8a03..c5788afc3a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts @@ -16,3 +16,4 @@ export { createGithubActionsDispatchAction } from './githubActionsDispatch'; export { createGithubWebhookAction } from './githubWebhook'; +export { createGithubIssuesLabelAction } from './githubIssuesLabel'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts index ec564e93dc..4e835657a3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts @@ -17,7 +17,6 @@ import { TemplateAction } from '../../types'; jest.mock('../helpers'); -jest.mock('octokit'); import { createPublishGithubAction } from './github'; import { @@ -34,6 +33,30 @@ import { } from '../helpers'; import { when } from 'jest-when'; +const mockOctokit = { + rest: { + users: { + getByUsername: jest.fn(), + }, + repos: { + addCollaborator: jest.fn(), + createInOrg: jest.fn(), + createForAuthenticatedUser: jest.fn(), + replaceAllTopics: jest.fn(), + }, + teams: { + addOrUpdateRepoPermissionsInOrg: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + describe('publish:github', () => { const config = new ConfigReader({ integrations: { @@ -62,8 +85,6 @@ describe('publish:github', () => { createTemporaryDirectory: jest.fn(), }; - const { mockGithubClient } = require('octokit'); - beforeEach(() => { jest.resetAllMocks(); githubCredentialsProvider = @@ -76,14 +97,14 @@ describe('publish:github', () => { }); it('should call the githubApis with the correct values for createInOrg', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'Organization' }, }); - mockGithubClient.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); await action.handler(mockContext); - expect(mockGithubClient.rest.repos.createInOrg).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ description: 'description', name: 'repo', org: 'owner', @@ -102,7 +123,7 @@ describe('publish:github', () => { repoVisibility: 'public', }, }); - expect(mockGithubClient.rest.repos.createInOrg).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ description: 'description', name: 'repo', org: 'owner', @@ -116,17 +137,17 @@ describe('publish:github', () => { }); it('should call the githubApis with the correct values for createForAuthenticatedUser', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: {}, }); await action.handler(mockContext); expect( - mockGithubClient.rest.repos.createForAuthenticatedUser, + mockOctokit.rest.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ description: 'description', name: 'repo', @@ -145,7 +166,7 @@ describe('publish:github', () => { }, }); expect( - mockGithubClient.rest.repos.createForAuthenticatedUser, + mockOctokit.rest.repos.createForAuthenticatedUser, ).toHaveBeenCalledWith({ description: 'description', name: 'repo', @@ -158,11 +179,11 @@ describe('publish:github', () => { }); it('should call initRepoAndPush with the correct values', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -182,11 +203,11 @@ describe('publish:github', () => { }); it('should call initRepoAndPush with the correct defaultBranch main', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -235,11 +256,11 @@ describe('publish:github', () => { githubCredentialsProvider, }); - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -279,11 +300,11 @@ describe('publish:github', () => { githubCredentialsProvider, }); - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -304,11 +325,11 @@ describe('publish:github', () => { }); it('should add access for the team when it starts with the owner', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -318,7 +339,7 @@ describe('publish:github', () => { await action.handler(mockContext); expect( - mockGithubClient.rest.teams.addOrUpdateRepoPermissionsInOrg, + mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg, ).toHaveBeenCalledWith({ org: 'owner', team_slug: 'blam', @@ -329,11 +350,11 @@ describe('publish:github', () => { }); it('should add outside collaborators when provided', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -348,7 +369,7 @@ describe('publish:github', () => { }, }); - expect(mockGithubClient.rest.repos.addCollaborator).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.addCollaborator).toHaveBeenCalledWith({ username: 'outsidecollaborator', owner: 'owner', repo: 'repo', @@ -357,11 +378,11 @@ describe('publish:github', () => { }); it('should add multiple collaborators when provided', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -392,7 +413,7 @@ describe('publish:github', () => { }; expect( - mockGithubClient.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[1], + mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[1], ).toEqual([ { ...commonProperties, @@ -402,7 +423,7 @@ describe('publish:github', () => { ]); expect( - mockGithubClient.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[2], + mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[2], ).toEqual([ { ...commonProperties, @@ -413,18 +434,18 @@ describe('publish:github', () => { }); it('should ignore failures when adding multiple collaborators', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', }, }); - when(mockGithubClient.rest.teams.addOrUpdateRepoPermissionsInOrg) + when(mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg) .calledWith({ org: 'owner', owner: 'owner', @@ -452,7 +473,7 @@ describe('publish:github', () => { }); expect( - mockGithubClient.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[2], + mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[2], ).toEqual([ { org: 'owner', @@ -465,18 +486,18 @@ describe('publish:github', () => { }); it('should add topics when provided', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', }, }); - mockGithubClient.rest.repos.replaceAllTopics.mockResolvedValue({ + mockOctokit.rest.repos.replaceAllTopics.mockResolvedValue({ data: { names: ['node.js'], }, @@ -490,7 +511,7 @@ describe('publish:github', () => { }, }); - expect(mockGithubClient.rest.repos.replaceAllTopics).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.replaceAllTopics).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', names: ['node.js'], @@ -498,18 +519,18 @@ describe('publish:github', () => { }); it('should lowercase topics when provided', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', }, }); - mockGithubClient.rest.repos.replaceAllTopics.mockResolvedValue({ + mockOctokit.rest.repos.replaceAllTopics.mockResolvedValue({ data: { names: ['backstage'], }, @@ -523,7 +544,7 @@ describe('publish:github', () => { }, }); - expect(mockGithubClient.rest.repos.replaceAllTopics).toHaveBeenCalledWith({ + expect(mockOctokit.rest.repos.replaceAllTopics).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', names: ['backstage'], @@ -531,11 +552,11 @@ describe('publish:github', () => { }); it('should call output with the remoteUrl and the repoContentsUrl', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -555,11 +576,11 @@ describe('publish:github', () => { }); it('should use main as default branch', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', html_url: 'https://github.com/html/url', @@ -585,11 +606,11 @@ describe('publish:github', () => { }); it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requireCodeOwnerReviews', async () => { - mockGithubClient.rest.users.getByUsername.mockResolvedValue({ + mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); - mockGithubClient.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ data: { name: 'repository', }, @@ -599,7 +620,7 @@ describe('publish:github', () => { expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ owner: 'owner', - client: mockGithubClient, + client: mockOctokit, repoName: 'repository', logger: mockContext.logger, defaultBranch: 'master', @@ -616,7 +637,7 @@ describe('publish:github', () => { expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ owner: 'owner', - client: mockGithubClient, + client: mockOctokit, repoName: 'repository', logger: mockContext.logger, defaultBranch: 'master', @@ -633,7 +654,7 @@ describe('publish:github', () => { expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ owner: 'owner', - client: mockGithubClient, + client: mockOctokit, repoName: 'repository', logger: mockContext.logger, defaultBranch: 'master', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts index 9956f16003..464498c0c4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts @@ -58,6 +58,7 @@ describe('createPublishGithubPullRequestAction', () => { status: 201, data: { html_url: 'https://github.com/myorg/myrepo/pull/123', + number: 123, }, }; }), @@ -123,13 +124,14 @@ describe('createPublishGithubPullRequestAction', () => { }); }); - it('creates outputs for the url', async () => { + it('creates outputs for the pull request url and number', async () => { await instance.handler(ctx); expect(ctx.output).toHaveBeenCalledWith( 'remoteUrl', 'https://github.com/myorg/myrepo/pull/123', ); + expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); afterEach(() => { mockFs.restore(); @@ -254,13 +256,14 @@ describe('createPublishGithubPullRequestAction', () => { }); }); - it('creates outputs for the url', async () => { + it('creates outputs for the pull request url and number', async () => { await instance.handler(ctx); expect(ctx.output).toHaveBeenCalledWith( 'remoteUrl', 'https://github.com/myorg/myrepo/pull/123', ); + expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); afterEach(() => { mockFs.restore(); @@ -322,13 +325,14 @@ describe('createPublishGithubPullRequestAction', () => { }); }); - it('creates outputs for the url', async () => { + it('creates outputs for the pull request url and number', async () => { await instance.handler(ctx); expect(ctx.output).toHaveBeenCalledWith( 'remoteUrl', 'https://github.com/myorg/myrepo/pull/123', ); + expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); afterEach(() => { mockFs.restore(); @@ -390,13 +394,14 @@ describe('createPublishGithubPullRequestAction', () => { }); }); - it('creates outputs for the url', async () => { + it('creates outputs for the pull request url and number', async () => { await instance.handler(ctx); expect(ctx.output).toHaveBeenCalledWith( 'remoteUrl', 'https://github.com/myorg/myrepo/pull/123', ); + expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); afterEach(() => { mockFs.restore(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 2ce8e1d81e..30293f2654 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -37,11 +37,17 @@ class GithubResponseError extends CustomErrorBase {} /** @public */ export interface OctokitWithPullRequestPluginClient { createPullRequest(options: createPullRequest.Options): Promise<{ - data: { html_url: string }; + data: { + html_url: string; + number: number; + }; } | null>; } -/** @public */ +/** + * The options passed to the client factory function. + * @public + */ export type CreateGithubPullRequestClientFactoryInput = { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; @@ -74,10 +80,22 @@ export const defaultClientFactory = async ({ return new OctokitPR(octokitOptions); }; -/** @public */ +/** + * The options passed to {@link createPublishGithubPullRequestAction} method + * @public + */ export interface CreateGithubPullRequestActionOptions { + /** + * An instance of {@link @backstage/integration#ScmIntegrationRegistry} that will be used in the action. + */ integrations: ScmIntegrationRegistry; + /** + * An instance of {@link @backstage/integration#GithubCredentialsProvider} that will be used to get credentials for the action. + */ githubCredentialsProvider?: GithubCredentialsProvider; + /** + * A method to return the Octokit client with the Pull Request Plugin. + */ clientFactory?: ( input: CreateGithubPullRequestClientFactoryInput, ) => Promise; @@ -154,6 +172,11 @@ export const createPublishGithubPullRequestAction = ({ title: 'Pull Request URL', description: 'Link to the pull request in Github', }, + pullRequestNumber: { + type: 'number', + title: 'Pull Request Number', + description: 'The pull request number', + }, }, }, }, @@ -248,6 +271,7 @@ export const createPublishGithubPullRequestAction = ({ } ctx.output('remoteUrl', response.data.html_url); + ctx.output('pullRequestNumber', response.data.number); } catch (e) { throw new GithubResponseError('Pull request creation failed', e); } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts index db4ad5a019..f427023c69 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ jest.mock('../helpers'); -jest.mock('@gitbeaker/node'); import { createPublishGitlabAction } from './gitlab'; import { ScmIntegrations } from '@backstage/integration'; @@ -23,6 +22,25 @@ import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; import { initRepoAndPush } from '../helpers'; +const mockGitlabClient = { + Namespaces: { + show: jest.fn(), + }, + Projects: { + create: jest.fn(), + }, + Users: { + current: jest.fn(), + }, +}; +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + describe('publish:gitlab', () => { const config = new ConfigReader({ integrations: { @@ -54,8 +72,6 @@ describe('publish:gitlab', () => { createTemporaryDirectory: jest.fn(), }; - const { mockGitlabClient } = require('@gitbeaker/node'); - beforeEach(() => { jest.resetAllMocks(); }); @@ -165,6 +181,7 @@ describe('publish:gitlab', () => { remoteUrl: 'http://mockurl.git', auth: { username: 'oauth2', password: 'tokenlols' }, logger: mockContext.logger, + commitMessage: 'initial commit', gitAuthorInfo: {}, }); }); @@ -189,6 +206,7 @@ describe('publish:gitlab', () => { remoteUrl: 'http://mockurl.git', auth: { username: 'oauth2', password: 'tokenlols' }, logger: mockContext.logger, + commitMessage: 'initial commit', gitAuthorInfo: {}, }); }); @@ -236,6 +254,7 @@ describe('publish:gitlab', () => { auth: { username: 'oauth2', password: 'tokenlols' }, logger: mockContext.logger, defaultBranch: 'master', + commitMessage: 'initial commit', gitAuthorInfo: { name: 'Test', email: 'example@example.com' }, }); }); @@ -278,10 +297,10 @@ describe('publish:gitlab', () => { dir: mockContext.workspacePath, remoteUrl: 'http://mockurl.git', auth: { username: 'oauth2', password: 'tokenlols' }, - logger: mockContext.logger, defaultBranch: 'master', - commitMessage: 'Test commit message', + commitMessage: 'initial commit', gitAuthorInfo: { email: undefined, name: undefined }, + logger: mockContext.logger, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index 9aa12803eb..a7c247f4d9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -40,6 +40,9 @@ export function createPublishGitlabAction(options: { repoVisibility?: 'private' | 'internal' | 'public'; sourcePath?: string; token?: string; + gitCommitMessage?: string; + gitAuthorName?: string; + gitAuthorEmail?: string; }>({ id: 'publish:gitlab', description: @@ -63,6 +66,21 @@ export function createPublishGitlabAction(options: { type: 'string', description: `Sets the default branch on the repository. The default value is 'master'`, }, + gitCommitMessage: { + title: 'Git Commit Message', + type: 'string', + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }, + gitAuthorName: { + title: 'Default Author Name', + type: 'string', + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }, + gitAuthorEmail: { + title: 'Default Author Email', + type: 'string', + description: `Sets the default author email for the commit.`, + }, sourcePath: { title: 'Source Path', description: @@ -95,8 +113,10 @@ export function createPublishGitlabAction(options: { repoUrl, repoVisibility = 'private', defaultBranch = 'master', + gitCommitMessage = 'initial commit', + gitAuthorName, + gitAuthorEmail, } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); if (!owner) { @@ -146,10 +166,13 @@ export function createPublishGitlabAction(options: { const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`; const gitAuthorInfo = { - name: config.getOptionalString('scaffolder.defaultAuthor.name'), - email: config.getOptionalString('scaffolder.defaultAuthor.email'), + name: gitAuthorName + ? gitAuthorName + : config.getOptionalString('scaffolder.defaultAuthor.name'), + email: gitAuthorEmail + ? gitAuthorEmail + : config.getOptionalString('scaffolder.defaultAuthor.email'), }; - await initRepoAndPush({ dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl: http_url_to_repo as string, @@ -159,9 +182,9 @@ export function createPublishGitlabAction(options: { password: token, }, logger: ctx.logger, - commitMessage: config.getOptionalString( - 'scaffolder.defaultCommitMessage', - ), + commitMessage: gitCommitMessage + ? gitCommitMessage + : config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 1b635b695c..0ef0846ddb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -25,6 +25,9 @@ import { ConfigReader } from '@backstage/config'; import { TaskContext, TaskSecrets } from './types'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +// The Stream module is lazy loaded, so make sure it's in the module cache before mocking fs +void winston.transports.Stream; + const realFiles = Object.fromEntries( [ resolvePackagePath( @@ -339,7 +342,7 @@ describe('DefaultWorkflowRunner', () => { expect(logger.error).not.toHaveBeenCalled(); }); - it('should keep the original types for the input and not parse things that arent meant to be parsed', async () => { + it('should keep the original types for the input and not parse things that are not meant to be parsed', async () => { const task = createMockTaskWithSpec({ apiVersion: 'scaffolder.backstage.io/v1beta3', steps: [ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 4cac2206fb..bb375e5a0f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -187,7 +187,7 @@ describe('StorageTaskBroker', () => { .mockRejectedValue(new Error('nah m8')); const intervalId = setInterval(() => { - broker.vacuumTasks({ timeoutS: 2 }).catch(fail); + broker.vacuumTasks({ timeoutS: 2 }); }, 500); for (;;) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 1399c25118..a92d6abdb8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -115,8 +115,17 @@ export class TaskManager implements TaskContext { * @public */ export interface CurrentClaimedTask { + /** + * The TaskSpec of the current claimed task. + */ spec: TaskSpec; + /** + * The uuid of the current claimed task. + */ taskId: string; + /** + * The secrets that are stored with the task. + */ secrets?: TaskSecrets; } @@ -135,6 +144,9 @@ export class StorageTaskBroker implements TaskBroker { ) {} private deferredDispatch = defer(); + /** + * {@inheritdoc TaskBroker.claim} + */ async claim(): Promise { for (;;) { const pendingTask = await this.storage.claimTask(); @@ -154,6 +166,9 @@ export class StorageTaskBroker implements TaskBroker { } } + /** + * {@inheritdoc TaskBroker.dispatch} + */ async dispatch( options: TaskBrokerDispatchOptions, ): Promise<{ taskId: string }> { @@ -164,10 +179,16 @@ export class StorageTaskBroker implements TaskBroker { }; } + /** + * {@inheritdoc TaskBroker.get} + */ async get(taskId: string): Promise { return this.storage.getTask(taskId); } + /** + * {@inheritdoc TaskBroker.event$} + */ event$(options: { taskId: string; after?: number; @@ -197,6 +218,9 @@ export class StorageTaskBroker implements TaskBroker { }); } + /** + * {@inheritdoc TaskBroker.vacuumTasks} + */ async vacuumTasks(options: { timeoutS: number }): Promise { const { tasks } = await this.storage.listStaleTasks(options); await Promise.all( diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index cf073b551b..e7413b3a87 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -62,7 +62,10 @@ function isSupportedTemplate(entity: TemplateEntityV1beta3) { return entity.apiVersion === 'scaffolder.backstage.io/v1beta3'; } -/** @public */ +/** + * A method to create a router for the scaffolder backend plugin. + * @public + */ export async function createRouter( options: RouterOptions, ): Promise { diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 5c3f4c60f2..52599fe72a 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-common +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Patch Changes + +- c8475ab3bb: Adding some documentation for exported things +- Updated dependencies + - @backstage/catalog-model@1.0.0 + - @backstage/types@1.0.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md index 6148440e6d..532e72d3fd 100644 --- a/plugins/scaffolder-common/api-report.md +++ b/plugins/scaffolder-common/api-report.md @@ -13,41 +13,28 @@ export type TaskSpec = TaskSpecV1beta3; // @public export interface TaskSpecV1beta3 { - // (undocumented) apiVersion: 'scaffolder.backstage.io/v1beta3'; - // (undocumented) output: { [name: string]: JsonValue; }; - // (undocumented) parameters: JsonObject; - // (undocumented) steps: TaskStep[]; - // (undocumented) templateInfo?: TemplateInfo; } // @public export interface TaskStep { - // (undocumented) action: string; - // (undocumented) id: string; - // (undocumented) if?: string | boolean; - // (undocumented) input?: JsonObject; - // (undocumented) name: string; } // @public export interface TemplateEntityV1beta3 extends Entity { - // (undocumented) apiVersion: 'scaffolder.backstage.io/v1beta3'; - // (undocumented) kind: 'Template'; - // (undocumented) spec: { type: string; parameters?: JsonObject | JsonObject[]; diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index acfe0c4da9..fab057ce62 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "0.3.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,10 +39,10 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/types": "^0.1.3" + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" } } diff --git a/plugins/scaffolder-common/src/TaskSpec.ts b/plugins/scaffolder-common/src/TaskSpec.ts index 326bb60ef6..e745964ff7 100644 --- a/plugins/scaffolder-common/src/TaskSpec.ts +++ b/plugins/scaffolder-common/src/TaskSpec.ts @@ -23,7 +23,13 @@ import { JsonValue, JsonObject } from '@backstage/types'; * @public */ export type TemplateInfo = { + /** + * The entityRef of the template + */ entityRef: string; + /** + * Where the template is stored, so we can resolve relative paths for things like `fetch:template` paths. + */ baseUrl?: string; }; @@ -33,10 +39,25 @@ export type TemplateInfo = { * @public */ export interface TaskStep { + /** + * A unqiue identifier for this step. + */ id: string; + /** + * A display name to show the user. + */ name: string; + /** + * The underlying action ID that will be called as part of running this step. + */ action: string; + /** + * Additional data that will be passed to the action. + */ input?: JsonObject; + /** + * When this is false, or if the templated value string evaluates to something that is falsy the step will be skipped. + */ if?: string | boolean; } @@ -47,10 +68,28 @@ export interface TaskStep { * @public */ export interface TaskSpecV1beta3 { + /** + * The apiVersion string of the TaskSpec. + */ apiVersion: 'scaffolder.backstage.io/v1beta3'; + /** + * This is a JSONSchema which is used to render a form in the frontend + * to collect user input and validate it against that schema. This can then be used in the `steps` part below to template + * variables passed from the user into each action in the template. + */ parameters: JsonObject; + /** + * A list of steps to be executed in sequence which are defined by the template. These steps are a list of the underlying + * javascript action and some optional input parameters that may or may not have been collected from the end user. + */ steps: TaskStep[]; + /** + * The output is an object where template authors can pull out information from template actions and return them in a known standard way. + */ output: { [name: string]: JsonValue }; + /** + * Some information about the template that is stored on the task spec. + */ templateInfo?: TemplateInfo; } diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 13b699de69..8ded484ee2 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -29,11 +29,32 @@ import schema from './Template.v1beta3.schema.json'; * @public */ export interface TemplateEntityV1beta3 extends Entity { + /** + * The apiVersion string of the TaskSpec. + */ apiVersion: 'scaffolder.backstage.io/v1beta3'; + /** + * The kind of the entity + */ kind: 'Template'; + /** + * The specification of the Template Entity + */ spec: { + /** + * The type that the Template will create. For example service, website or library. + */ type: string; + /** + * This is a JSONSchema or an array of JSONSchema's which is used to render a form in the frontend + * to collect user input and validate it against that schema. This can then be used in the `steps` part below to template + * variables passed from the user into each action in the template. + */ parameters?: JsonObject | JsonObject[]; + /** + * A list of steps to be executed in sequence which are defined by the template. These steps are a list of the underlying + * javascript action and some optional input parameters that may or may not have been collected from the end user. + */ steps: Array<{ id?: string; name?: string; @@ -41,7 +62,13 @@ export interface TemplateEntityV1beta3 extends Entity { input?: JsonObject; if?: string | boolean; }>; + /** + * The output is an object where template authors can pull out information from template actions and return them in a known standard way. + */ output?: { [name: string]: string }; + /** + * The owner entityRef of the TemplateEntity + */ owner?: string; }; } diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index fc8a82927a..c1f4cfed3a 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-scaffolder +## 1.0.1-next.0 + +### Patch Changes + +- d34900af81: Added a new `NextScaffolderRouter` which will eventually replace the exiting router +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-scaffolder-common@1.0.1-next.0 + - @backstage/integration-react@1.0.1-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 9a408928a1: **BREAKING**: Removed the unused `titleComponent` property of `groups` passed to the `ScaffolderPage`. The property was already ignored, but existing usage should migrated to use the `title` property instead, which now accepts any `ReactNode`. + +### Patch Changes + +- 9b7e361783: Remove beta labels +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- 20a262c214: The `ScaffolderPage` now uses the `CatalogFilterLayout`, which means the filters are put in a drawer on smaller screens. +- f24ef7864e: Minor typo fixes +- d8716924d6: Implement a template preview page (`/create/preview`) to test creating form UIs +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-permission-react@0.3.4 + - @backstage/catalog-model@1.0.0 + - @backstage/plugin-scaffolder-common@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + ## 0.15.0 ### Minor Changes diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 90fee1fc66..f1ab23c320 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -20,6 +20,7 @@ import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; +import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -126,6 +127,22 @@ export type LogEvent = { taskId: string; }; +// @alpha +export type NextRouterProps = { + components?: { + TemplateCardComponent?: React_2.ComponentType<{ + template: TemplateEntityV1beta3; + }>; + TaskPageComponent?: React_2.ComponentType<{}>; + }; + groups?: TemplateGroupFilter[]; +}; + +// @alpha +export const NextScaffolderPage: ( + props: PropsWithChildren, +) => JSX.Element; + // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< string, @@ -196,10 +213,10 @@ export type RouterProps = { TaskPageComponent?: ComponentType<{}>; }; groups?: Array<{ - title?: string; - titleComponent?: React_2.ReactNode; + title?: React_2.ReactNode; filter: (entity: Entity) => boolean; }>; + defaultPreviewTemplate?: string; }; // @public @@ -354,6 +371,12 @@ export type TaskPageProps = { loadingText?: string; }; +// @alpha (undocumented) +export type TemplateGroupFilter = { + title?: React_2.ReactNode; + filter: (entity: Entity) => boolean; +}; + // @public export type TemplateParameterSchema = { title: string; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index dcdeeac1c9..03a1443ca6 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "0.15.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,7 +9,8 @@ "publishConfig": { "access": "public", "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "frontend-plugin" @@ -24,7 +25,7 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "start": "backstage-cli package start", "lint": "backstage-cli package lint", "test": "backstage-cli package test", @@ -34,26 +35,30 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@types/json-schema": "^7.0.9", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-permission-react": "^0.3.3", - "@backstage/plugin-scaffolder-common": "^0.3.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", + "@codemirror/legacy-modes": "^0.19.0", + "@codemirror/panel": "^0.19.1", + "@codemirror/stream-parser": "^0.19.6", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@rjsf/core": "^3.2.1", "@rjsf/material-ui": "^3.2.1", + "@types/json-schema": "^7.0.9", + "@uiw/react-codemirror": "^4.5.1", "classnames": "^2.2.6", "git-url-parse": "^11.6.0", "humanize-duration": "^3.25.1", @@ -66,6 +71,7 @@ "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", "use-immer": "^0.6.0", + "yaml": "^1.9.2", "zen-observable": "^0.8.15" }, "peerDependencies": { @@ -73,11 +79,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", @@ -90,6 +96,7 @@ "msw": "^0.35.0" }, "files": [ - "dist" + "dist", + "alpha" ] } diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 4d37eafee4..a868c50d7a 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -51,7 +51,7 @@ type Props = { formData: Record; onChange: (e: IChangeEvent) => void; onReset: () => void; - onFinish: () => Promise; + onFinish?: () => Promise; widgets?: FormProps['widgets']; fields?: FormProps['fields']; }; @@ -163,6 +163,10 @@ export const MultistepJsonForm = (props: Props) => { }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); const handleCreate = async () => { + if (!onFinish) { + return; + } + setDisableButtons(true); try { await onFinish(); @@ -232,7 +236,7 @@ export const MultistepJsonForm = (props: Props) => { variant="contained" color="primary" onClick={handleCreate} - disabled={disableButtons} + disabled={!onFinish || disableButtons} > Create diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 261556d638..f46a4cc111 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -23,6 +23,7 @@ import { TemplatePage } from './TemplatePage'; import { TaskPage } from './TaskPage'; import { ActionsPage } from './ActionsPage'; import { SecretsContextProvider } from './secrets/SecretsContext'; +import { TemplatePreviewPage } from './TemplatePreviewPage'; import { FieldExtensionOptions, @@ -44,10 +45,10 @@ export type RouterProps = { TaskPageComponent?: ComponentType<{}>; }; groups?: Array<{ - title?: string; - titleComponent?: React.ReactNode; + title?: React.ReactNode; filter: (entity: Entity) => boolean; }>; + defaultPreviewTemplate?: string; }; /** @@ -56,7 +57,7 @@ export type RouterProps = { * @public */ export const Router = (props: RouterProps) => { - const { groups, components = {} } = props; + const { groups, components = {}, defaultPreviewTemplate } = props; const { TemplateCardComponent, TaskPageComponent } = components; @@ -104,6 +105,17 @@ export const Router = (props: RouterProps) => { /> } /> } /> + + + + } + /> ); }; diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 0b3fccc07c..a68e320c13 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -19,7 +19,6 @@ import { ContentHeader, CreateButton, Header, - Lifecycle, Page, SupportButton, } from '@backstage/core-components'; @@ -27,13 +26,13 @@ import { Entity } from '@backstage/catalog-model'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { useRouteRef } from '@backstage/core-plugin-api'; import { + CatalogFilterLayout, EntityKindPicker, EntityListProvider, EntitySearchBar, EntityTagPicker, UserListPicker, } from '@backstage/plugin-catalog-react'; -import { makeStyles } from '@material-ui/core'; import React, { ComponentType } from 'react'; import { registerComponentRouteRef } from '../../routes'; import { TemplateList } from '../TemplateList'; @@ -41,23 +40,12 @@ import { TemplateTypePicker } from '../TemplateTypePicker'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; import { usePermission } from '@backstage/plugin-permission-react'; -const useStyles = makeStyles(theme => ({ - contentWrapper: { - display: 'grid', - gridTemplateAreas: "'filters' 'grid'", - gridTemplateColumns: '250px 1fr', - gridColumnGap: theme.spacing(2), - }, -})); - export type ScaffolderPageProps = { TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta3 }> | undefined; groups?: Array<{ title?: React.ReactNode; - /** @deprcated use title instead as it accepts a string and react component */ - titleComponent?: React.ReactNode; filter: (entity: Entity) => boolean; }>; }; @@ -66,7 +54,6 @@ export const ScaffolderPageContents = ({ TemplateCardComponent, groups, }: ScaffolderPageProps) => { - const styles = useStyles(); const registerComponentLink = useRouteRef(registerComponentRouteRef); const otherTemplatesGroup = { title: groups ? 'Other Templates' : 'Templates', @@ -76,17 +63,15 @@ export const ScaffolderPageContents = ({ }, }; - const { allowed } = usePermission(catalogEntityCreatePermission); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); return (
- Create a New Component - - } + title="Create a New Component" subtitle="Create new software components using standard templates" /> @@ -104,8 +89,8 @@ export const ScaffolderPageContents = ({ -
-
+ +
-
+ + {groups && groups.map((group, index) => ( -
-
+ +
); diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index f73e32189c..27d89cb6a4 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -19,7 +19,6 @@ import { Content, ErrorPage, Header, - Lifecycle, Page, LogViewer, Progress, @@ -315,11 +314,7 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => {
- Task Activity - - } + title="Task Activity" subtitle={`Activity for task: ${taskId}`} /> diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx index e3cf6eac05..90d1c8cceb 100644 --- a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx @@ -30,7 +30,7 @@ import { Typography } from '@material-ui/core'; import { TemplateCard } from '../TemplateCard'; /** - * @deprecated this type is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list. + * @internal */ export type TemplateListProps = { TemplateCardComponent?: @@ -38,14 +38,12 @@ export type TemplateListProps = { | undefined; group?: { title?: React.ReactNode; - /** @deprecated use title instead, can be a string or a react component */ - titleComponent?: React.ReactNode; filter: (entity: Entity) => boolean; }; }; /** - * @deprecated this component is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list. + * @internal */ export const TemplateList = ({ TemplateCardComponent, @@ -58,13 +56,6 @@ export const TemplateList = ({ : entities; const titleComponent: React.ReactNode = (() => { - if (group?.titleComponent) { - // eslint-disable-next-line no-console - console.warn( - 'DEPRECATED: group.titleComponent is now deprecated. Use group.title instead, it can be a string or a react component', - ); - return group?.titleComponent; - } if (group && group.title) { if (typeof group.title === 'string') { return ; diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 5310a4c79f..fc1c4f222d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -27,13 +27,7 @@ import { SecretsContext } from '../secrets/SecretsContext'; import { rootRouteRef, scaffolderTaskRouteRef } from '../../routes'; import { MultistepJsonForm } from '../MultistepJsonForm'; -import { - Content, - Header, - InfoCard, - Lifecycle, - Page, -} from '@backstage/core-components'; +import { Content, Header, InfoCard, Page } from '@backstage/core-components'; import { ApiHolder, errorApiRef, @@ -183,11 +177,7 @@ export const TemplatePage = ({
- Create a New Component - - } + title="Create a New Component" subtitle="Create new software components using standard templates" /> diff --git a/plugins/scaffolder/src/components/TemplatePage/index.ts b/plugins/scaffolder/src/components/TemplatePage/index.ts index f796bd6500..b01da032e5 100644 --- a/plugins/scaffolder/src/components/TemplatePage/index.ts +++ b/plugins/scaffolder/src/components/TemplatePage/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TemplatePage } from './TemplatePage'; +export { createValidator, TemplatePage } from './TemplatePage'; diff --git a/plugins/scaffolder/src/components/TemplatePreviewPage/TemplatePreviewPage.tsx b/plugins/scaffolder/src/components/TemplatePreviewPage/TemplatePreviewPage.tsx new file mode 100644 index 0000000000..cb4f8e9896 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplatePreviewPage/TemplatePreviewPage.tsx @@ -0,0 +1,269 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useCallback, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import useDebounce from 'react-use/lib/useDebounce'; +import { Entity } from '@backstage/catalog-model'; +import { Content, Header, InfoCard, Page } from '@backstage/core-components'; +import { alertApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api'; +import { + catalogApiRef, + humanizeEntityRef, +} from '@backstage/plugin-catalog-react'; +import { JsonObject } from '@backstage/types'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { showPanel } from '@codemirror/panel'; +import { StreamLanguage } from '@codemirror/stream-parser'; +import { + FormControl, + Grid, + InputLabel, + LinearProgress, + makeStyles, + MenuItem, + Select, +} from '@material-ui/core'; +import { IChangeEvent } from '@rjsf/core'; +import CodeMirror from '@uiw/react-codemirror'; +import yaml from 'yaml'; +import { FieldExtensionOptions } from '../../extensions'; +import { TemplateParameterSchema } from '../../types'; +import { MultistepJsonForm } from '../MultistepJsonForm'; +import { createValidator } from '../TemplatePage'; + +const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI +parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + allowedKinds: + - Group + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com +`; + +type TemplateOption = { + label: string; + value: Entity; +}; + +const useStyles = makeStyles({ + templateSelect: { + marginBottom: '10px', + }, + grid: { + height: '100%', + }, + codeMirror: { + height: '95%', + }, +}); + +export const TemplatePreviewPage = ({ + defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, + customFieldExtensions = [], +}: { + defaultPreviewTemplate?: string; + customFieldExtensions?: FieldExtensionOptions[]; +}) => { + const classes = useStyles(); + const alertApi = useApi(alertApiRef); + const catalogApi = useApi(catalogApiRef); + const apiHolder = useApiHolder(); + const [selectedTemplate, setSelectedTemplate] = useState(''); + const [schema, setSchema] = useState({ + title: '', + steps: [], + }); + const [templateOptions, setTemplateOptions] = useState([]); + const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); + const [formState, setFormState] = useState({}); + + const { loading } = useAsync( + () => + catalogApi + .getEntities({ + filter: { kind: 'template' }, + fields: [ + 'kind', + 'metadata.namespace', + 'metadata.name', + 'metadata.title', + 'spec.parameters', + ], + }) + .then(({ items }) => + setTemplateOptions( + items.map(template => ({ + label: + template.metadata.title ?? + humanizeEntityRef(template, { defaultKind: 'template' }), + value: template, + })), + ), + ) + .catch(e => + alertApi.post({ + message: `Error loading exisiting templates: ${e.message}`, + severity: 'error', + }), + ), + [catalogApi], + ); + + const errorPanel = document.createElement('div'); + errorPanel.style.color = 'red'; + + useDebounce( + () => { + try { + const parsedTemplate = yaml.parse(templateYaml); + + setSchema({ + title: 'Preview', + steps: parsedTemplate.parameters.map((param: JsonObject) => ({ + title: param.title, + schema: param, + })), + }); + setFormState({}); + } catch (e) { + errorPanel.textContent = e.message; + } + }, + 250, + [setFormState, setSchema, templateYaml], + ); + + const handleSelectChange = useCallback( + selected => { + setSelectedTemplate(selected); + setTemplateYaml(yaml.stringify(selected.spec)); + }, + [setTemplateYaml], + ); + + const handleFormReset = () => setFormState({}); + const handleFormChange = useCallback( + (e: IChangeEvent) => setFormState(e.formData), + [setFormState], + ); + + const handleCodeChange = useCallback( + (code: string) => { + setTemplateYaml(code); + }, + [setTemplateYaml], + ); + + const customFieldComponents = Object.fromEntries( + customFieldExtensions.map(({ name, component }) => [name, component]), + ); + + const customFieldValidators = Object.fromEntries( + customFieldExtensions.map(({ name, validation }) => [name, validation]), + ); + + return ( + +
+ + {loading && } + + + + + Load Existing Template + + + + ({ dom: errorPanel, top: true })), + ]} + onChange={handleCodeChange} + /> + + + {schema && ( + + { + return { + ...step, + validate: createValidator( + step.schema, + customFieldValidators, + { apiHolder }, + ), + }; + })} + /> + + )} + + + + + ); +}; diff --git a/plugins/scaffolder/src/components/TemplatePreviewPage/index.ts b/plugins/scaffolder/src/components/TemplatePreviewPage/index.ts new file mode 100644 index 0000000000..cdf7bd1e82 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplatePreviewPage/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { TemplatePreviewPage } from './TemplatePreviewPage'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 22043d5761..7fac9f7840 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -138,7 +138,7 @@ describe('RepoUrlPicker', () => { 'ui:options': { requestUserCredentials: { secretsKey: 'testKey', - additionalScopes: { github: ['workflow:write'] }, + additionalScopes: { github: ['workflow'] }, }, }, }} @@ -164,7 +164,7 @@ describe('RepoUrlPicker', () => { additionalScope: { repoWrite: true, customScopes: { - github: ['workflow:write'], + github: ['workflow'], }, }, }); diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index bdca2a0952..fc132f644c 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -55,6 +55,11 @@ export { RepoUrlPickerFieldExtension, ScaffolderPage, scaffolderPlugin, + NextScaffolderPage, } from './plugin'; export * from './components'; export type { TaskPageProps } from './components/TaskPage'; + +/** next exports */ +export type { NextRouterProps } from './next'; +export type { TemplateGroupFilter } from './next'; diff --git a/plugins/scaffolder/src/next/Router/Router.test.tsx b/plugins/scaffolder/src/next/Router/Router.test.tsx new file mode 100644 index 0000000000..5094c786e0 --- /dev/null +++ b/plugins/scaffolder/src/next/Router/Router.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { TemplateListPage } from '../TemplateListPage'; +import { TemplateWizardPage } from '../TemplateWizardPage'; +import { Router } from './Router'; +import { renderInTestApp } from '@backstage/test-utils'; +import { + createScaffolderFieldExtension, + ScaffolderFieldExtensions, +} from '../../extensions'; +import { scaffolderPlugin } from '../../plugin'; + +jest.mock('../TemplateListPage', () => ({ + TemplateListPage: jest.fn(() => null), +})); + +jest.mock('../TemplateWizardPage', () => ({ + TemplateWizardPage: jest.fn(() => null), +})); + +describe('Router', () => { + beforeEach(() => { + (TemplateWizardPage as jest.Mock).mockClear(); + (TemplateListPage as jest.Mock).mockClear(); + }); + describe('/', () => { + it('should render the TemplateListPage', async () => { + await renderInTestApp(); + + expect(TemplateListPage).toHaveBeenCalled(); + }); + }); + + describe('/templates/:templateName', () => { + it('should render the TemplateWizard page', async () => { + await renderInTestApp(, { routeEntries: ['/templates/foo'] }); + + expect(TemplateWizardPage).toHaveBeenCalled(); + }); + + it('should extract the fieldExtensions and pass them through', async () => { + const mockComponent = () => null; + const CustomFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'custom', + component: mockComponent, + }), + ); + + await renderInTestApp( + + + + + , + { routeEntries: ['/templates/foo'] }, + ); + + const mock = TemplateWizardPage as jest.Mock; + const [{ customFieldExtensions }] = mock.mock.calls[0]; + + expect(customFieldExtensions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'custom', component: mockComponent }), + ]), + ); + }); + }); +}); diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx new file mode 100644 index 0000000000..580d41d27b --- /dev/null +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -0,0 +1,99 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { PropsWithChildren } from 'react'; +import { Routes, Route, useOutlet } from 'react-router'; +import { TemplateListPage } from '../TemplateListPage'; +import { SecretsContextProvider } from '../TemplateWizardPage/SecretsContext'; +import { TemplateWizardPage } from '../TemplateWizardPage'; +import { + FieldExtensionOptions, + FIELD_EXTENSION_WRAPPER_KEY, + FIELD_EXTENSION_KEY, + DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, +} from '../../extensions'; + +import { useElementFilter } from '@backstage/core-plugin-api'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { TemplateGroupFilter } from '../TemplateListPage/TemplateGroups'; + +/** + * The Props for the Scaffolder Router + * + * @alpha + */ +export type NextRouterProps = { + components?: { + TemplateCardComponent?: React.ComponentType<{ + template: TemplateEntityV1beta3; + }>; + TaskPageComponent?: React.ComponentType<{}>; + }; + groups?: TemplateGroupFilter[]; +}; + +/** + * The Scaffolder Router + * + * @alpha + */ +export const Router = (props: PropsWithChildren) => { + const { components: { TemplateCardComponent } = {} } = props; + + const outlet = useOutlet() || props.children; + + const customFieldExtensions = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: FIELD_EXTENSION_WRAPPER_KEY, + }) + .findComponentData({ + key: FIELD_EXTENSION_KEY, + }), + ); + + const fieldExtensions = [ + ...customFieldExtensions, + ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( + ({ name }) => + !customFieldExtensions.some( + customFieldExtension => customFieldExtension.name === name, + ), + ), + ]; + + return ( + + + } + /> + + + + + } + /> + + ); +}; diff --git a/plugins/scaffolder/src/next/Router/index.ts b/plugins/scaffolder/src/next/Router/index.ts new file mode 100644 index 0000000000..dac1db7b3a --- /dev/null +++ b/plugins/scaffolder/src/next/Router/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 { Router } from './Router'; +export type { NextRouterProps } from './Router'; diff --git a/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.test.tsx new file mode 100644 index 0000000000..f02ca51354 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.test.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { useEntityTypeFilter } from '@backstage/plugin-catalog-react'; +import { CategoryPicker } from './CategoryPicker'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { alertApiRef } from '@backstage/core-plugin-api'; +import { fireEvent } from '@testing-library/react'; + +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntityTypeFilter: jest.fn(), +})); + +describe('CategoryPicker', () => { + const mockAlertApi = { post: jest.fn() }; + + beforeEach(() => { + mockAlertApi.post.mockClear(); + }); + + it('should post the error to errorApi if an errors is returned', async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + error: new Error('something broked'), + }); + + await renderInTestApp( + + + , + ); + + expect(mockAlertApi.post).toHaveBeenCalledWith({ + message: expect.stringContaining('something broked'), + severity: 'error', + }); + }); + + it('should render loading if the hook is loading', async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + loading: true, + }); + + const { findByTestId } = await renderInTestApp( + + + , + ); + + expect(await findByTestId('progress')).toBeInTheDocument(); + }); + + it('should not render if there is no available types', async () => { + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: null, + }); + + const { queryByText } = await renderInTestApp( + + + , + ); + + expect(queryByText('Categories')).not.toBeInTheDocument(); + }); + + it('renders the autocomplete with the availableTypes', async () => { + const mockAvailableTypes = ['foo', 'bar']; + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole('button', { name: 'Open' }); + openButton.click(); + + expect(getByRole('checkbox', { name: 'Foo' })).toBeInTheDocument(); + expect(getByRole('checkbox', { name: 'Bar' })).toBeInTheDocument(); + }); + + it('should call setSelectedTypes when one of the options are called', async () => { + const mockAvailableTypes = ['foo', 'bar']; + const mockSetSelectedTypes = jest.fn(); + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + setSelectedTypes: mockSetSelectedTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole('button', { name: 'Open' }); + await fireEvent(openButton, new MouseEvent('click', { bubbles: true })); + + const fooCheckbox = getByRole('checkbox', { name: 'Foo' }); + await fireEvent(fooCheckbox, new MouseEvent('click', { bubbles: true })); + + expect(mockSetSelectedTypes).toHaveBeenCalledWith(['foo']); + + await fireEvent(openButton, new MouseEvent('click', { bubbles: true })); + + const barCheckbox = getByRole('checkbox', { name: 'Bar' }); + await fireEvent(barCheckbox, new MouseEvent('click', { bubbles: true })); + + expect(mockSetSelectedTypes).toHaveBeenCalledWith(['foo', 'bar']); + }); + + it('should render the selectedTypes already in the document', async () => { + const mockAvailableTypes = ['foo', 'bar']; + const mockSelectedTypes = ['foo']; + + (useEntityTypeFilter as jest.Mock).mockReturnValue({ + availableTypes: mockAvailableTypes, + selectedTypes: mockSelectedTypes, + }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const openButton = getByRole('button', { name: 'Open' }); + await fireEvent(openButton, new MouseEvent('click', { bubbles: true })); + + const fooCheckbox = getByRole('checkbox', { name: 'Foo' }); + expect(fooCheckbox).toBeChecked(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.tsx b/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.tsx new file mode 100644 index 0000000000..483751f2e2 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/CategoryPicker.tsx @@ -0,0 +1,85 @@ +/* + * 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 capitalize from 'lodash/capitalize'; +import { Progress } from '@backstage/core-components'; +import { + Box, + Checkbox, + FormControlLabel, + TextField, + Typography, +} from '@material-ui/core'; +import CheckBoxIcon from '@material-ui/icons/CheckBox'; +import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { Autocomplete } from '@material-ui/lab'; +import { useEntityTypeFilter } from '@backstage/plugin-catalog-react'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; + +const icon = ; +const checkedIcon = ; + +/** + * The Category Picker that is rendered on the left side for picking + * categories and filtering the template list. + */ +export const CategoryPicker = () => { + const alertApi = useApi(alertApiRef); + const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = + useEntityTypeFilter(); + + if (loading) return ; + + if (error) { + alertApi.post({ + message: `Failed to load entity types with error: ${error}`, + severity: 'error', + }); + return null; + } + + if (!availableTypes) return null; + + return ( + + Categories + setSelectedTypes(value)} + renderOption={(option, { selected }) => ( + + } + label={capitalize(option)} + /> + )} + size="small" + popupIcon={} + renderInput={params => } + /> + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx new file mode 100644 index 0000000000..e6022d16fb --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { RegisterExistingButton } from './RegisterExistingButton'; +import { usePermission } from '@backstage/plugin-permission-react'; + +jest.mock('@backstage/plugin-permission-react', () => ({ + usePermission: jest.fn(), +})); + +describe('RegisterExistingButton', () => { + beforeEach(() => { + (usePermission as jest.Mock).mockClear(); + }); + + it('should not render if to is unset', async () => { + (usePermission as jest.Mock).mockReturnValue({ allowed: true }); + + const { queryByText } = await renderInTestApp( + , + ); + + expect(await queryByText('Pick me')).not.toBeInTheDocument(); + }); + + it('should not render if permissions are not allowed', async () => { + (usePermission as jest.Mock).mockReturnValue({ allowed: false }); + const { queryByText } = await renderInTestApp( + , + ); + + expect(await queryByText('Pick me')).not.toBeInTheDocument(); + }); + + it('should render the button with the text', async () => { + (usePermission as jest.Mock).mockReturnValue({ allowed: true }); + const { queryByText } = await renderInTestApp( + , + ); + + expect(await queryByText('Pick me')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx new file mode 100644 index 0000000000..16e0a355a3 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx @@ -0,0 +1,68 @@ +/* + * 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 { BackstageTheme } from '@backstage/theme'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import React from 'react'; +import { Link as RouterLink, LinkProps } from 'react-router-dom'; +import AddCircleOutline from '@material-ui/icons/AddCircleOutline'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; +import { usePermission } from '@backstage/plugin-permission-react'; + +/** + * Properties for {@link RegisterExistingButton} + * + * @alpha + */ +export type RegisterExistingButtonProps = { + title: string; +} & Partial>; + +/** + * A button that helps users to register an existing component. + * @alpha + */ +export const RegisterExistingButton = (props: RegisterExistingButtonProps) => { + const { title, to } = props; + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); + const isXSScreen = useMediaQuery(theme => + theme.breakpoints.down('xs'), + ); + + if (!to || !allowed) { + return null; + } + + return isXSScreen ? ( + + + + ) : ( + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.test.tsx new file mode 100644 index 0000000000..287d813ca7 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.test.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { CardHeader } from './CardHeader'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { starredEntitiesApiRef } from '@backstage/plugin-catalog-react'; +import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog'; +import Observable from 'zen-observable'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + +describe('CardHeader', () => { + it('should select the correct theme from the theme provider from the header', () => { + // Can't really test what we want here. + // But we can check that we call the getPage theme with the right type of template at least. + const mockTheme = { + ...lightTheme, + getPageTheme: jest.fn(lightTheme.getPageTheme), + }; + + render( + + + + + , + ); + + expect(mockTheme.getPageTheme).toHaveBeenCalledWith({ themeId: 'service' }); + }); + + it('should render the type', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText('service')).toBeInTheDocument(); + }); + + it('should enable favoriting of the entity', async () => { + const starredEntitiesApi = { + starredEntitie$: () => new Observable(() => {}), + toggleStarred: jest.fn(async () => {}), + }; + + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob' }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByRole } = await renderInTestApp( + + + , + ); + + const favorite = getByRole('button', { name: 'favorite' }); + + await fireEvent.click(favorite); + + expect(starredEntitiesApi.toggleStarred).toHaveBeenCalledWith( + stringifyEntityRef(mockTemplate), + ); + }); + + it('should render the name of the entity', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText('bob')).toBeInTheDocument(); + }); + + it('should render the title of the entity in favor of the name if it is provided', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText('Iamtitle')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.tsx new file mode 100644 index 0000000000..3a5785a4f6 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardHeader.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { makeStyles, useTheme } from '@material-ui/core'; +import { ItemCardHeader } from '@backstage/core-components'; +import { BackstageTheme } from '@backstage/theme'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { FavoriteEntity } from '@backstage/plugin-catalog-react'; + +const useStyles = makeStyles( + () => ({ + header: { + backgroundImage: ({ cardBackgroundImage }) => cardBackgroundImage, + }, + subtitleWrapper: { + display: 'flex', + justifyContent: 'space-between', + }, + }), +); + +/** + * Props for the CardHeader component + */ +export interface CardHeaderProps { + template: TemplateEntityV1beta3; +} + +/** + * The Card Header with the background for the TemplateCard. + */ +export const CardHeader = (props: CardHeaderProps) => { + const { + template: { + metadata: { title, name }, + spec: { type }, + }, + } = props; + const { getPageTheme } = useTheme(); + const themeForType = getPageTheme({ themeId: type }); + + const styles = useStyles({ + cardBackgroundImage: themeForType.backgroundImage, + }); + + const SubtitleComponent = ( +
+
{type}
+
+ +
+
+ ); + + return ( + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx new file mode 100644 index 0000000000..45f4dc6317 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.test.tsx @@ -0,0 +1,239 @@ +/* + * 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 { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog'; +import { + entityRouteRef, + starredEntitiesApiRef, +} from '@backstage/plugin-catalog-react'; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { TemplateCard } from './TemplateCard'; +import React from 'react'; +import { rootRouteRef } from '../../../routes'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; + +describe('TemplateCard', () => { + it('should render the card title', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob' }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(getByText('bob')).toBeInTheDocument(); + }); + + it('should render the description as markdown', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob', description: 'hello **test**' }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + const description = getByText('hello'); + expect(description.querySelector('strong')).toBeInTheDocument(); + }); + + it('should render no descroption if none is provided through the template', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob' }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(getByText('No description')).toBeInTheDocument(); + }); + + it('should render the tags', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob', tags: ['cpp', 'react'] }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + for (const tag of mockTemplate.metadata.tags!) { + expect(getByText(tag)).toBeInTheDocument(); + } + }); + + it('should render a link to the owner', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob', tags: ['cpp', 'react'] }, + spec: { + steps: [], + type: 'service', + }, + relations: [ + { + targetRef: 'group:default/my-test-user', + type: RELATION_OWNED_BY, + }, + ], + }; + + const { getByRole } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/': rootRouteRef, + '/catalog/:kind/:namespace/:name': entityRouteRef, + }, + }, + ); + + expect(getByRole('link', { name: 'my-test-user' })).toBeInTheDocument(); + expect(getByRole('link', { name: 'my-test-user' })).toHaveAttribute( + 'href', + '/catalog/group/default/my-test-user', + ); + }); + + it('should render the choose button to navigate to the selected template', async () => { + const mockTemplate: TemplateEntityV1beta3 = { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'bob', tags: ['cpp', 'react'] }, + spec: { + steps: [], + type: 'service', + }, + }; + + const { getByRole } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/': rootRouteRef, + '/catalog/:kind/:namespace/:name': entityRouteRef, + }, + }, + ); + + expect(getByRole('button', { name: 'Choose' })).toBeInTheDocument(); + expect(getByRole('button', { name: 'Choose' })).toHaveAttribute( + 'href', + '/templates/bob', + ); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx new file mode 100644 index 0000000000..0e67ecd82a --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/TemplateCard.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { + Box, + Card, + CardActions, + CardContent, + Chip, + Divider, + makeStyles, +} from '@material-ui/core'; +import { CardHeader } from './CardHeader'; +import { MarkdownContent, UserIcon, Button } from '@backstage/core-components'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + EntityRefLinks, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { selectedTemplateRouteRef } from '../../../routes'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles(theme => ({ + box: { + overflow: 'hidden', + textOverflow: 'ellipsis', + display: '-webkit-box', + '-webkit-line-clamp': 10, + '-webkit-box-orient': 'vertical', + /** to make the styles for React Markdown not leak into the description */ + '& p:first-child': { + marginTop: 0, + marginBottom: theme.spacing(2), + }, + }, + label: { + color: theme.palette.text.secondary, + textTransform: 'uppercase', + fontWeight: 'bold', + letterSpacing: 0.5, + lineHeight: 1, + fontSize: '0.75rem', + }, + margin: { + marginBottom: theme.spacing(2), + }, + footer: { + display: 'flex', + justifyContent: 'space-between', + flex: 1, + alignItems: 'center', + }, + ownedBy: { + display: 'flex', + alignItems: 'center', + flex: 1, + color: theme.palette.link, + }, +})); + +/** + * The Props for the Template Card component + * @alpha + */ +export interface TemplateCardProps { + template: TemplateEntityV1beta3; + deprecated?: boolean; +} + +/** + * The Template Card component that is rendered in a list for each template + * @alpha + */ +export const TemplateCard = (props: TemplateCardProps) => { + const { template } = props; + const styles = useStyles(); + const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY); + const templateRoute = useRouteRef(selectedTemplateRouteRef); + const href = templateRoute({ templateName: template.metadata.name }); + + return ( + + + + + + + {(template.metadata.tags?.length ?? 0) > 0 && ( + <> + + + {template.metadata.tags?.map(tag => ( + + ))} + + + )} + + +
+
+ {ownedByRelations.length > 0 && ( + <> + + + + )} +
+ +
+
+
+ ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/index.ts b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/index.ts new file mode 100644 index 0000000000..99af0cbb50 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/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 { TemplateCard } from './TemplateCard'; +export type { TemplateCardProps } from './TemplateCard'; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.test.tsx new file mode 100644 index 0000000000..59c6989c0d --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.test.tsx @@ -0,0 +1,143 @@ +/* + * 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. + */ +jest.mock('./TemplateCard', () => ({ TemplateCard: jest.fn(() => null) })); + +import React from 'react'; +import { TemplateGroup } from './TemplateGroup'; +import { render } from '@testing-library/react'; +import { TemplateCard } from './TemplateCard'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + +describe('TemplateGroup', () => { + it('should return a message when no templates are passed in', async () => { + const { getByText } = render(); + + expect( + getByText(/No templates found that match your filter/), + ).toBeInTheDocument(); + }); + + it('should render a card for each template with the template being passed as a prop', () => { + const mockTemplates: TemplateEntityV1beta3[] = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'test' }, + spec: { + parameters: [], + steps: [], + type: 'website', + }, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'test2' }, + spec: { + parameters: [], + steps: [], + type: 'service', + }, + }, + ]; + + render(); + + expect(TemplateCard).toHaveBeenCalledTimes(2); + + for (const template of mockTemplates) { + expect(TemplateCard).toHaveBeenCalledWith( + expect.objectContaining({ template }), + {}, + ); + } + }); + + it('should use the passed in TemplateCard prop to render the template card', () => { + const mockTemplateCardComponent = jest.fn(() => null); + + const mockTemplates: TemplateEntityV1beta3[] = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'test' }, + spec: { + parameters: [], + steps: [], + type: 'website', + }, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'test2' }, + spec: { + parameters: [], + steps: [], + type: 'service', + }, + }, + ]; + + render( + , + ); + + expect(mockTemplateCardComponent).toHaveBeenCalledTimes(2); + + for (const template of mockTemplates) { + expect(mockTemplateCardComponent).toHaveBeenCalledWith( + expect.objectContaining({ template }), + {}, + ); + } + }); + + it('should render the title when no templates passed', () => { + const { getByText } = render(); + expect(getByText('Test')).toBeInTheDocument(); + }); + + it('should render the title when there are templates in the list', () => { + const mockTemplates: TemplateEntityV1beta3[] = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'test' }, + spec: { parameters: [], steps: [], type: 'website' }, + }, + ]; + + const { getByText } = render( + , + ); + + expect(getByText('Test')).toBeInTheDocument(); + }); + + it('should allow for passing through a user given title component', () => { + const TitleComponent =

Im a custom header

; + const { getByText } = render( + , + ); + + expect(getByText('Im a custom header')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.tsx new file mode 100644 index 0000000000..4344c195ae --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroup.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import React from 'react'; +import { + Content, + ContentHeader, + ItemCardGrid, + Link, +} from '@backstage/core-components'; +import { Typography } from '@material-ui/core'; +import { TemplateCard, TemplateCardProps } from './TemplateCard'; +import { stringifyEntityRef } from '@backstage/catalog-model'; + +export interface TemplateGroupProps { + templates: TemplateEntityV1beta3[]; + title: React.ReactNode; + components?: { + CardComponent?: React.ComponentType; + }; +} + +export const TemplateGroup = (props: TemplateGroupProps) => { + const { templates, title, components: { CardComponent } = {} } = props; + const titleComponent = + typeof title === 'string' ? : title; + + if (templates.length === 0) { + return ( + + {titleComponent} + + No templates found that match your filter. Learn more about{' '} + + adding templates + + . + + + ); + } + + const Card = CardComponent || TemplateCard; + + return ( + + {titleComponent} + + {templates.map(template => ( + + ))} + + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx new file mode 100644 index 0000000000..2d8cdc3150 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.test.tsx @@ -0,0 +1,172 @@ +/* + * 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. + */ + +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntityList: jest.fn(), +})); + +jest.mock('./TemplateGroup', () => ({ + TemplateGroup: jest.fn(() => null), +})); + +import React from 'react'; +import { render } from '@testing-library/react'; +import { useEntityList } from '@backstage/plugin-catalog-react'; +import { TemplateGroups } from './TemplateGroups'; +import { TestApiProvider } from '@backstage/test-utils'; +import { errorApiRef } from '@backstage/core-plugin-api'; +import { TemplateGroup } from './TemplateGroup'; + +describe('TemplateGroups', () => { + it('should return progress if the hook is loading', async () => { + (useEntityList as jest.Mock).mockReturnValue({ loading: true }); + + const { findByTestId } = render( + + + , + ); + + expect(await findByTestId('progress')).toBeInTheDocument(); + }); + + it('should use the error api if there is an error with the retrieval of entitylist', async () => { + const mockError = new Error('tings went poop'); + (useEntityList as jest.Mock).mockReturnValue({ + error: mockError, + }); + const errorApi = { + post: jest.fn(), + }; + render( + + + , + ); + + expect(errorApi.post).toHaveBeenCalledWith(mockError); + }); + + it('should return a no templates message if entities is unset', async () => { + (useEntityList as jest.Mock).mockReturnValue({ + entities: null, + loading: false, + error: null, + }); + + const { findByText } = render( + + + , + ); + + expect(await findByText(/No templates found/)).toBeInTheDocument(); + }); + + it('should return a no templates message if entities has no values in it', async () => { + (useEntityList as jest.Mock).mockReturnValue({ + entities: [], + loading: false, + error: null, + }); + + const { findByText } = render( + + + , + ); + + expect(await findByText(/No templates found/)).toBeInTheDocument(); + }); + + it('should call the template group with the components', async () => { + const mockEntities = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't1', + }, + spec: {}, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't2', + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + render( + + true }]} /> + , + ); + + expect(TemplateGroup).toHaveBeenCalledWith( + expect.objectContaining({ templates: mockEntities }), + {}, + ); + }); + + it('should apply the filter for each group', async () => { + const mockEntities = [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't1', + }, + spec: {}, + }, + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { + name: 't2', + }, + spec: {}, + }, + ]; + + (useEntityList as jest.Mock).mockReturnValue({ + entities: mockEntities, + loading: false, + error: null, + }); + + render( + + e.metadata.name === 't1' }]} + /> + , + ); + + expect(TemplateGroup).toHaveBeenCalledWith( + expect.objectContaining({ templates: [mockEntities[0]] }), + {}, + ); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx new file mode 100644 index 0000000000..f402c0ad19 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateGroups.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { TemplateGroup } from './TemplateGroup'; +import { Entity } from '@backstage/catalog-model'; +import { useEntityList } from '@backstage/plugin-catalog-react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { Progress, Link } from '@backstage/core-components'; +import { Typography } from '@material-ui/core'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; + +/** + * @alpha + */ +export type TemplateGroupFilter = { + title?: React.ReactNode; + filter: (entity: Entity) => boolean; +}; + +export interface TemplateGroupsProps { + groups: TemplateGroupFilter[]; + TemplateCardComponent?: React.ComponentType<{ + template: TemplateEntityV1beta3; + }>; +} + +export const TemplateGroups = (props: TemplateGroupsProps) => { + const { loading, error, entities } = useEntityList(); + const { groups, TemplateCardComponent } = props; + const errorApi = useApi(errorApiRef); + + if (loading) { + return ; + } + + if (error) { + errorApi.post(error); + return null; + } + + if (!entities || !entities.length) { + return ( + + No templates found that match your filter. Learn more about{' '} + + adding templates + + . + + ); + } + + return ( + <> + {groups.map(({ title, filter }, index) => ( + + filter(e), + )} + title={title} + components={{ CardComponent: TemplateCardComponent }} + /> + ))} + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx new file mode 100644 index 0000000000..e8037e8f98 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.test.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog'; +import { + catalogApiRef, + starredEntitiesApiRef, +} from '@backstage/plugin-catalog-react'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import React from 'react'; +import { rootRouteRef } from '../../routes'; +import { TemplateListPage } from './TemplateListPage'; + +describe('TemplateListPage', () => { + const mockCatalogApi = { + getEntities: async () => ({ + items: [ + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind: 'Template', + metadata: { name: 'blob', tags: ['blob'] }, + spec: { + type: 'service', + }, + }, + ], + }), + getEntityFacets: async () => ({ + facets: { 'spec.type': [{ value: 'service', count: 1 }] }, + }), + }; + + it('should render the search bar for templates', async () => { + const { getByPlaceholderText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(getByPlaceholderText('Search')).toBeInTheDocument(); + }); + + it('should render the all and starred filters', async () => { + const { getByRole } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(getByRole('menuitem', { name: 'All' })).toBeInTheDocument(); + expect(getByRole('menuitem', { name: 'Starred' })).toBeInTheDocument(); + }); + + it('should render the category picker', async () => { + const { getByText } = await renderInTestApp( + + + , + { mountedRoutes: { '/': rootRouteRef } }, + ); + + expect(getByText('Categories')).toBeInTheDocument(); + }); + + // eslint-disable-next-line jest/no-disabled-tests + it.skip('should render the EntityTag picker', async () => { + const { getByText } = await renderInTestApp( + + + , + ); + + expect(getByText('Tags')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx new file mode 100644 index 0000000000..1b18dce805 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -0,0 +1,100 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; + +import { + Content, + ContentHeader, + Header, + Page, + SupportButton, +} from '@backstage/core-components'; +import { + EntityKindPicker, + EntityListProvider, + EntitySearchBar, + EntityTagPicker, + CatalogFilterLayout, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { CategoryPicker } from './CategoryPicker'; +import { RegisterExistingButton } from './RegisterExistingButton'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { registerComponentRouteRef } from '../../routes'; +import { TemplateGroupFilter, TemplateGroups } from './TemplateGroups'; + +export type TemplateListPageProps = { + TemplateCardComponent?: React.ComponentType<{ + template: TemplateEntityV1beta3; + }>; + groups?: TemplateGroupFilter[]; +}; + +const defaultGroup: TemplateGroupFilter = { + title: 'All Templates', + filter: () => true, +}; + +export const TemplateListPage = (props: TemplateListPageProps) => { + const registerComponentLink = useRouteRef(registerComponentRouteRef); + const { TemplateCardComponent, groups = [defaultGroup] } = props; + + return ( + + +
+ + + + + Create new software components using standard templates. Different + templates create different kinds of components (services, + websites, documentation, ...). + + + + + + + + + + + + + + + ); +}; diff --git a/plugins/scaffolder/src/next/TemplateListPage/index.ts b/plugins/scaffolder/src/next/TemplateListPage/index.ts new file mode 100644 index 0000000000..0436cfeeeb --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateListPage/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 { TemplateListPage } from './TemplateListPage'; +export type { TemplateGroupFilter } from './TemplateGroups'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx new file mode 100644 index 0000000000..37d35c9015 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useContext } from 'react'; +import { + useTemplateSecrets, + SecretsContextProvider, + SecretsContext, +} from './SecretsContext'; +import { renderHook, act } from '@testing-library/react-hooks'; + +describe('SecretsContext', () => { + it('should allow the setting of secrets in the context', async () => { + const { result } = renderHook( + () => ({ + hook: useTemplateSecrets(), + context: useContext(SecretsContext), + }), + { + wrapper: ({ children }) => ( + {children} + ), + }, + ); + expect(result.current.context?.secrets.foo).toEqual(undefined); + + act(() => result.current.hook.setSecret({ foo: 'bar' })); + + expect(result.current.context?.secrets.foo).toEqual('bar'); + }); +}); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx new file mode 100644 index 0000000000..d6ca47d41c --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/SecretsContext.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { + useState, + useCallback, + useContext, + createContext, + PropsWithChildren, +} from 'react'; + +type SecretsContextContents = { + secrets: Record; + setSecrets: React.Dispatch>>; +}; + +/** + * The actual context object. + */ +export const SecretsContext = createContext( + undefined, +); + +/** + * The Context Provider that holds the state for the secrets. + * + * @alpha + */ +export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => { + const [secrets, setSecrets] = useState>({}); + + return ( + + {children} + + ); +}; + +/** + * Hook to access the secrets context. + * @alpha + */ +export const useTemplateSecrets = () => { + const value = useContext(SecretsContext); + if (!value) { + throw new Error( + 'useTemplateSecrets must be used within a SecretsContextProvider', + ); + } + + const { setSecrets } = value; + + const setSecret = useCallback( + (input: Record) => { + setSecrets(currentSecrets => ({ ...currentSecrets, ...input })); + }, + [setSecrets], + ); + + return { setSecret }; +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts new file mode 100644 index 0000000000..65b530dea1 --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/SecretsContext/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { + useTemplateSecrets, + SecretsContext, + SecretsContextProvider, +} from './SecretsContext'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx new file mode 100644 index 0000000000..4d160548ca --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FieldExtensionOptions } from '../../extensions'; + +export interface TemplateWizardPageProps { + customFieldExtensions: FieldExtensionOptions[]; +} + +export const TemplateWizardPage = (_props: TemplateWizardPageProps) => { + return null; +}; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/index.ts b/plugins/scaffolder/src/next/TemplateWizardPage/index.ts new file mode 100644 index 0000000000..146a7a9afc --- /dev/null +++ b/plugins/scaffolder/src/next/TemplateWizardPage/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { TemplateWizardPage } from './TemplateWizardPage'; diff --git a/plugins/scaffolder/src/next/index.ts b/plugins/scaffolder/src/next/index.ts new file mode 100644 index 0000000000..089c268b0b --- /dev/null +++ b/plugins/scaffolder/src/next/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './Router'; +export * from './TemplateListPage'; +export * from './TemplateWizardPage'; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index ffa0efaaa0..9bbb52a71f 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -150,3 +150,15 @@ export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide( name: 'EntityTagsPicker', }), ); + +/** + * @alpha + * The Router and main entrypoint to the Alpha Scaffolder plugin. + */ +export const NextScaffolderPage = scaffolderPlugin.provide( + createRoutableExtension({ + name: 'NextScaffolderPage', + component: () => import('./next/Router').then(m => m.Router), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index a542a8a473..bddd6a7b70 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -73,7 +73,7 @@ export type ScaffolderTaskOutput = { /** * The shape of each entry of parameters which gets rendered - * as a seperate step in the wizard input + * as a separate step in the wizard input * * @public */ diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 4bc49bbec7..de7cd608d8 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 0.1.3-next.0 + +### Patch Changes + +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.0 + - @backstage/plugin-search-backend-node@0.5.2 + - @backstage/plugin-search-common@0.3.2 + ## 0.1.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/api-report.md b/plugins/search-backend-module-elasticsearch/api-report.md index 265fdda686..10231f3a83 100644 --- a/plugins/search-backend-module-elasticsearch/api-report.md +++ b/plugins/search-backend-module-elasticsearch/api-report.md @@ -10,10 +10,10 @@ import { Client } from '@elastic/elasticsearch'; import { Config } from '@backstage/config'; import type { ConnectionOptions } from 'tls'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; // Warning: (ae-missing-release-tag) "ElasticSearchClientOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-search-backend-module-elasticsearch" does not have an export "ElasticSearchEngine" @@ -119,7 +119,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { getIndexer(type: string): Promise; newClient(create: (options: ElasticSearchClientOptions) => T): T; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // Warning: (ae-forgotten-export) The symbol "ElasticSearchQueryTranslator" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 1700cb09ae..3124762879 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "0.1.1", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,9 +23,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.15", - "@backstage/plugin-search-backend-node": "^0.5.1", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/config": "^1.0.0", + "@backstage/plugin-search-backend-node": "^0.5.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "@elastic/elasticsearch": "7.13.0", "@acuris/aws-es-connection": "^2.2.0", "aws-sdk": "^2.948.0", @@ -34,16 +34,13 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/cli": "^0.15.2", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/cli": "^0.16.1-next.0", "@elastic/elasticsearch-mock": "^1.0.0" }, "files": [ "dist", "config.d.ts" ], - "jest": { - "testEnvironment": "node" - }, "configSchema": "config.d.ts" } diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index f2ed5ed2b0..aec225a64b 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -21,9 +21,9 @@ import { import { Config } from '@backstage/config'; import { IndexableDocument, + IndexableResultSet, SearchEngine, SearchQuery, - SearchResultSet, } from '@backstage/plugin-search-common'; import { Client } from '@elastic/elasticsearch'; import esb from 'elastic-builder'; @@ -192,7 +192,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { return indexer; } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { elasticSearchQuery, documentTypes, pageSize } = this.translator(query); const queryIndices = documentTypes diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 73277da361..a7c12517c7 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.3.2-next.0 + +### Patch Changes + +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/api-report.md b/plugins/search-backend-module-pg/api-report.md index 39847a4935..71ceac996e 100644 --- a/plugins/search-backend-module-pg/api-report.md +++ b/plugins/search-backend-module-pg/api-report.md @@ -5,11 +5,11 @@ ```ts import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; // Warning: (ae-missing-release-tag) "ConcretePgSearchQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -88,7 +88,7 @@ export class PgSearchEngine implements SearchEngine { // (undocumented) getIndexer(type: string): Promise; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // (undocumented) setTranslator( translator: (query: SearchQuery) => ConcretePgSearchQuery, diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 64ad8ae6c2..fe235c29ff 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.3.1", + "version": "0.3.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,15 +23,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/plugin-search-backend-node": "^0.5.1", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/plugin-search-backend-node": "^0.5.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "lodash": "^4.17.21", "knex": "^1.0.2" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2" + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist", diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 8f75112837..ea363d8ea1 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -15,7 +15,10 @@ */ import { PluginDatabaseManager } from '@backstage/backend-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; -import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common'; +import { + SearchQuery, + IndexableResultSet, +} from '@backstage/plugin-search-common'; import { PgSearchEngineIndexer } from './PgSearchEngineIndexer'; import { DatabaseDocumentStore, @@ -81,7 +84,7 @@ export class PgSearchEngine implements SearchEngine { }); } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { pgQuery, pageSize } = this.translator(query); const rows = await this.databaseStore.transaction(async tx => diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index a4baecfe88..bf777380b0 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search-backend-node +## 0.5.3-next.0 + +### Patch Changes + +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.0 + +## 0.5.2 + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/errors@1.0.0 + - @backstage/plugin-search-common@0.3.2 + ## 0.5.1 ### Patch Changes diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index e4359eafac..ebc49df280 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -9,13 +9,13 @@ import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { DocumentDecoratorFactory } from '@backstage/plugin-search-common'; import { DocumentTypeInfo } from '@backstage/plugin-search-common'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { default as lunr_2 } from 'lunr'; import { QueryTranslator } from '@backstage/plugin-search-common'; import { Readable } from 'stream'; import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; import { Transform } from 'stream'; import { Writable } from 'stream'; @@ -87,7 +87,7 @@ export class LunrSearchEngine implements SearchEngine { // (undocumented) protected lunrIndices: Record; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // (undocumented) setTranslator(translator: LunrQueryTranslator): void; // (undocumented) diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 63d0167e8f..038087745f 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "0.5.1", + "version": "0.5.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,16 +23,16 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/errors": "^0.2.2", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "@types/lunr": "^2.3.3", "lodash": "^4.17.21", "lunr": "^2.3.9", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/cli": "^0.15.2" + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/search-backend-node/src/Scheduler.test.ts b/plugins/search-backend-node/src/Scheduler.test.ts index d5f671358a..de6add13fe 100644 --- a/plugins/search-backend-node/src/Scheduler.test.ts +++ b/plugins/search-backend-node/src/Scheduler.test.ts @@ -61,7 +61,7 @@ describe('Scheduler', () => { // Stop scheduling process testScheduler.stop(); - // Should't throw error, as it is stopped. + // Shouldn't throw error, as it is stopped. expect(() => testScheduler.addToSchedule(mockTask2, 4), ).not.toThrowError(); diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 46aaf47c60..b0bd2bca8f 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -932,7 +932,7 @@ describe('LunrSearchEngine', () => { }); inspectableSearchEngine.setDocStore({ 'existing-location': doc }); - // Mock methds called by close handler. + // Mock methods called by close handler. indexerMock.buildIndex.mockReturnValueOnce('expected-index'); indexerMock.getDocumentStore.mockReturnValueOnce({ 'new-location': doc, diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index b642647466..09e8729ba8 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -16,8 +16,8 @@ import { IndexableDocument, + IndexableResultSet, SearchQuery, - SearchResultSet, QueryTranslator, SearchEngine, } from '@backstage/plugin-search-common'; @@ -68,7 +68,7 @@ export class LunrSearchEngine implements SearchEngine { lunrQueryBuilder: q => { const termToken = lunr.tokenizer(term); - // Support for typeahead seach is based on https://github.com/olivernn/lunr.js/issues/256#issuecomment-295407852 + // Support for typeahead search is based on https://github.com/olivernn/lunr.js/issues/256#issuecomment-295407852 // look for an exact match and apply a large positive boost q.term(termToken, { usePipeline: true, @@ -147,7 +147,7 @@ export class LunrSearchEngine implements SearchEngine { return indexer; } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { lunrQueryBuilder, documentTypes, pageSize } = this.translator( query, ) as ConcreteLunrQuery; @@ -196,8 +196,8 @@ export class LunrSearchEngine implements SearchEngine { ? encodePageCursor({ page: page - 1 }) : undefined; - // Translate results into SearchResultSet - const realResultSet: SearchResultSet = { + // Translate results into IndexableResultSet + const realResultSet: IndexableResultSet = { results: results.slice(offset, offset + pageSize).map(d => { return { type: d.type, document: this.docStore[d.result.ref] }; }), diff --git a/plugins/search-backend-node/src/runPeriodically.test.ts b/plugins/search-backend-node/src/runPeriodically.test.ts index 4d8e991b9e..0f2ee44b8d 100644 --- a/plugins/search-backend-node/src/runPeriodically.test.ts +++ b/plugins/search-backend-node/src/runPeriodically.test.ts @@ -18,7 +18,11 @@ import { runPeriodically } from './runPeriodically'; jest.useFakeTimers(); describe('runPeriodically', () => { - const flushPromises = () => new Promise(setImmediate); + const flushPromises = async () => { + const promise = new Promise(resolve => process.nextTick(resolve)); + jest.runAllTicks(); + await promise; + }; const advanceTimersByTime = async (time: number) => { jest.advanceTimersByTime(time); // Advancing the time with jest doesn't run all promises, but only sync code diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 93e636223a..c51541a5d1 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-search-backend +## 0.5.0-next.0 + +### Minor Changes + +- 94ccd772d4: **BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents. + +### Patch Changes + +- 62ee65422c: Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. +- Updated dependencies + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + +## 0.4.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-search-backend-node@0.5.2 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-permission-node@0.5.5 + - @backstage/plugin-search-common@0.3.2 + ## 0.4.7 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 384c5c9296..e148cca418 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "0.4.7", + "version": "0.5.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,15 +23,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-auth-node": "^0.1.5", - "@backstage/plugin-permission-common": "^0.5.2", - "@backstage/plugin-permission-node": "^0.5.4", - "@backstage/plugin-search-backend-node": "^0.5.1", - "@backstage/plugin-search-common": "^0.3.1", - "@backstage/types": "^0.1.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-auth-node": "^0.2.0-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-node": "^0.5.6-next.0", + "@backstage/plugin-search-backend-node": "^0.5.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -43,7 +43,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts index ba63f8484e..47503312fb 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.test.ts @@ -16,8 +16,9 @@ import { ConfigReader } from '@backstage/config'; import { - AuthorizeDecision, + EvaluatePermissionResponse, AuthorizeResult, + createPermission, PermissionAuthorizer, } from '@backstage/plugin-permission-common'; import { @@ -78,28 +79,32 @@ describe('AuthorizedSearchEngine', () => { const defaultTypes: Record = { [typeUsers]: { - visibilityPermission: { + visibilityPermission: createPermission({ name: 'search.users.read', attributes: { action: 'read' }, - }, + resourceType: 'test-user', + }), }, [typeTemplates]: { - visibilityPermission: { + visibilityPermission: createPermission({ name: 'search.templates.read', attributes: { action: 'read' }, - }, + resourceType: 'test-template', + }), }, [typeServices]: { - visibilityPermission: { + visibilityPermission: createPermission({ name: 'search.services.read', attributes: { action: 'read' }, - }, + resourceType: 'test-service', + }), }, [typeGroups]: { - visibilityPermission: { + visibilityPermission: createPermission({ name: 'search.groups.read', attributes: { action: 'read' }, - }, + resourceType: 'test-group', + }), }, }; @@ -238,7 +243,7 @@ describe('AuthorizedSearchEngine', () => { } return { result: AuthorizeResult.CONDITIONAL, - } as AuthorizeDecision; + } as EvaluatePermissionResponse; } return { @@ -289,7 +294,7 @@ describe('AuthorizedSearchEngine', () => { return { result: AuthorizeResult.CONDITIONAL, - } as AuthorizeDecision; + } as EvaluatePermissionResponse; }), ); @@ -331,7 +336,9 @@ describe('AuthorizedSearchEngine', () => { result: AuthorizeResult.ALLOW, }; } - return { result: AuthorizeResult.CONDITIONAL } as AuthorizeDecision; + return { + result: AuthorizeResult.CONDITIONAL, + } as EvaluatePermissionResponse; }), ); @@ -408,7 +415,9 @@ describe('AuthorizedSearchEngine', () => { : AuthorizeResult.ALLOW, }; } - return { result: AuthorizeResult.CONDITIONAL } as AuthorizeDecision; + return { + result: AuthorizeResult.CONDITIONAL, + } as EvaluatePermissionResponse; }), ); @@ -490,7 +499,9 @@ describe('AuthorizedSearchEngine', () => { if (query.resourceRef) { return { result: AuthorizeResult.ALLOW }; } - return { result: AuthorizeResult.CONDITIONAL } as AuthorizeDecision; + return { + result: AuthorizeResult.CONDITIONAL, + } as EvaluatePermissionResponse; }), ); diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index 2e65e18a84..a529bf04c6 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -18,19 +18,20 @@ import { compact, zipObject } from 'lodash'; import qs from 'qs'; import DataLoader from 'dataloader'; import { - AuthorizeDecision, - AuthorizeQuery, + EvaluatePermissionResponse, + EvaluatePermissionRequest, AuthorizeResult, + isResourcePermission, PermissionAuthorizer, } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, + IndexableResult, + IndexableResultSet, QueryRequestOptions, QueryTranslator, SearchEngine, SearchQuery, - SearchResult, - SearchResultSet, } from '@backstage/plugin-search-common'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -85,11 +86,11 @@ export class AuthorizedSearchEngine implements SearchEngine { async query( query: SearchQuery, options: QueryRequestOptions, - ): Promise { + ): Promise { const queryStartTime = Date.now(); const authorizer = new DataLoader( - (requests: readonly AuthorizeQuery[]) => + (requests: readonly EvaluatePermissionRequest[]) => this.permissions.authorize(requests.slice(), options), { // Serialize the permission name and resourceRef as @@ -144,7 +145,7 @@ export class AuthorizedSearchEngine implements SearchEngine { const { page } = decodePageCursor(query.pageCursor); const targetResults = (page + 1) * this.pageSize; - let filteredResults: SearchResult[] = []; + let filteredResults: IndexableResult[] = []; let nextPageCursor: string | undefined; let latencyBudgetExhausted = false; @@ -183,9 +184,12 @@ export class AuthorizedSearchEngine implements SearchEngine { } private async filterResults( - results: SearchResult[], - typeDecisions: Record, - authorizer: DataLoader, + results: IndexableResult[], + typeDecisions: Record, + authorizer: DataLoader< + EvaluatePermissionRequest, + EvaluatePermissionResponse + >, ) { return compact( await Promise.all( @@ -201,6 +205,23 @@ export class AuthorizedSearchEngine implements SearchEngine { return result; } + // We only reach this point in the code for types where the initial + // authorization returned CONDITIONAL -- ALLOWs return early + // immediately above, and types where the decision was DENY get + // filtered out entirely when querying. + // + // This means the call to isResourcePermission here is mostly about + // narrowing the type of permission - the only way to get here with a + // non-resource permission is if the PermissionPolicy returns a + // CONDITIONAL decision for a non-resource permission, which can't + // happen - it would throw an error during validation in the + // permission-backend. + if (!isResourcePermission(permission)) { + throw new Error( + `Unexpected conditional decision returned for non-resource permission "${permission.name}"`, + ); + } + return authorizer .load({ permission, resourceRef }) .then(decision => diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index bdf46a240b..2efab91256 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -19,7 +19,6 @@ import { ConfigReader } from '@backstage/config'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { IndexBuilder, - LunrSearchEngine, SearchEngine, } from '@backstage/plugin-search-backend-node'; import express from 'express'; @@ -39,8 +38,19 @@ describe('createRouter', () => { beforeAll(async () => { const logger = getVoidLogger(); - const searchEngine = new LunrSearchEngine({ logger }); - const indexBuilder = new IndexBuilder({ logger, searchEngine }); + mockSearchEngine = { + getIndexer: jest.fn(), + setTranslator: jest.fn(), + query: jest.fn().mockResolvedValue({ + results: [], + nextPageCursor: '', + previousPageCursor: '', + }), + }; + const indexBuilder = new IndexBuilder({ + logger, + searchEngine: mockSearchEngine, + }); const router = await createRouter({ engine: indexBuilder.getSearchEngine(), @@ -56,7 +66,7 @@ describe('createRouter', () => { }); beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); describe('GET /query', () => { @@ -101,6 +111,42 @@ describe('createRouter', () => { }); }); + it('removes backend-only properties from search documents', async () => { + mockSearchEngine.query.mockResolvedValue({ + results: [ + { + type: 'software-catalog', + document: { + text: 'foo', + title: 'bar baz', + location: '/catalog/default/component/example', + authorization: { + resourceRef: 'component:default/example', + }, + }, + }, + ], + nextPageCursor: '', + previousPageCursor: '', + }); + + const response = await request(app).get('/query'); + + expect(response.status).toEqual(200); + expect(response.body).toMatchObject({ + results: [ + { + type: 'software-catalog', + document: { + text: 'foo', + title: 'bar baz', + location: '/catalog/default/component/example', + }, + }, + ], + }); + }); + describe('search result filtering', () => { beforeAll(async () => { const logger = getVoidLogger(); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 92e4e5526f..ff91465cf4 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -26,6 +26,7 @@ import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-no import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, + IndexableResultSet, SearchResultSet, } from '@backstage/plugin-search-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; @@ -89,6 +90,17 @@ export async function createRouter( }), }); + const toSearchResults = (resultSet: IndexableResultSet): SearchResultSet => ({ + ...resultSet, + results: resultSet.results.map(result => ({ + ...result, + document: { + ...result.document, + authorization: undefined, + }, + })), + }); + const router = Router(); router.get( '/query', @@ -116,7 +128,7 @@ export async function createRouter( try { const resultSet = await engine?.query(query, { token }); - res.send(filterResultSet(resultSet)); + res.send(filterResultSet(toSearchResults(resultSet))); } catch (err) { throw new Error( `There was a problem performing the search query. ${err}`, diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 3b8a46644e..ab43b6ed2c 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-common +## 0.3.3-next.0 + +### Patch Changes + +- c8b295f2fb: - Introduce `SearchDocument` type. This type contains the subset of `IndexableDocument` properties relevant to the frontend, and is intended to be used for documents returned to the frontend from the search API. + - `SearchResultSet` is now a wrapper for documents of type `SearchDocument`, and is intended to be used in the frontend. This isn't a breaking change, since `IndexableDocument`s are valid `SearchDocument`s, so the old and new types are compatible. + - Introduce `IndexableResultSet` type, which wraps `IndexableDocument` instances in the same way as `SearchResultSet`. + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + ## 0.3.1 ### Patch Changes diff --git a/plugins/search-common/api-report.md b/plugins/search-common/api-report.md index 8ab25d5e42..bd5a56c5cc 100644 --- a/plugins/search-common/api-report.md +++ b/plugins/search-common/api-report.md @@ -30,14 +30,17 @@ export type DocumentTypeInfo = { }; // @beta -export interface IndexableDocument { +export type IndexableDocument = SearchDocument & { authorization?: { resourceRef: string; }; - location: string; - text: string; - title: string; -} +}; + +// @beta (undocumented) +export type IndexableResult = Result; + +// @beta (undocumented) +export type IndexableResultSet = ResultSet; // @beta export type QueryRequestOptions = { @@ -47,13 +50,38 @@ export type QueryRequestOptions = { // @beta export type QueryTranslator = (query: SearchQuery) => unknown; +// @beta (undocumented) +export interface Result { + // (undocumented) + document: TDocument; + // (undocumented) + type: string; +} + +// @beta (undocumented) +export interface ResultSet { + // (undocumented) + nextPageCursor?: string; + // (undocumented) + previousPageCursor?: string; + // (undocumented) + results: Result[]; +} + +// @beta +export interface SearchDocument { + location: string; + text: string; + title: string; +} + // @beta export interface SearchEngine { getIndexer(type: string): Promise; query( query: SearchQuery, options?: QueryRequestOptions, - ): Promise; + ): Promise; setTranslator(translator: QueryTranslator): void; } @@ -70,20 +98,8 @@ export interface SearchQuery { } // @beta (undocumented) -export interface SearchResult { - // (undocumented) - document: IndexableDocument; - // (undocumented) - type: string; -} +export type SearchResult = Result; // @beta (undocumented) -export interface SearchResultSet { - // (undocumented) - nextPageCursor?: string; - // (undocumented) - previousPageCursor?: string; - // (undocumented) - results: SearchResult[]; -} +export type SearchResultSet = ResultSet; ``` diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index af7c14cfae..f37d3ac103 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "0.3.1", + "version": "0.3.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -39,11 +39,11 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/types": "^0.1.3", - "@backstage/plugin-permission-common": "^0.5.2" + "@backstage/types": "^1.0.0", + "@backstage/plugin-permission-common": "^0.5.3" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "jest": { "roots": [ diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 51ce45617b..4eedabeb71 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -31,26 +31,45 @@ export interface SearchQuery { /** * @beta */ -export interface SearchResult { +export interface Result { type: string; - document: IndexableDocument; + document: TDocument; } /** * @beta */ -export interface SearchResultSet { - results: SearchResult[]; +export interface ResultSet { + results: Result[]; nextPageCursor?: string; previousPageCursor?: string; } /** - * Base properties that all indexed documents must include, as well as some - * common properties that documents are encouraged to use where appropriate. * @beta */ -export interface IndexableDocument { +export type SearchResult = Result; + +/** + * @beta + */ +export type SearchResultSet = ResultSet; + +/** + * @beta + */ +export type IndexableResult = Result; + +/** + * @beta + */ +export type IndexableResultSet = ResultSet; + +/** + * Base properties that all search documents must include. + * @beta + */ +export interface SearchDocument { /** * The primary name of the document (e.g. name, title, identifier, etc). */ @@ -66,7 +85,16 @@ export interface IndexableDocument { * is clicked). */ location: string; +} +/** + * Properties related to indexing of documents. This type is only useful for + * backends working directly with documents being inserted or retrieved from + * search indexes. When dealing with documents in the frontend, use + * {@link SearchDocument}. + * @beta + */ +export type IndexableDocument = SearchDocument & { /** * Optional authorization information to be used when determining whether this * search result should be visible to a given user. @@ -77,7 +105,7 @@ export interface IndexableDocument { */ resourceRef: string; }; -} +}; /** * Information about a specific document type. Intended to be used in the @@ -178,5 +206,5 @@ export interface SearchEngine { query( query: SearchQuery, options?: QueryRequestOptions, - ): Promise; + ): Promise; } diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 93e853d59a..7f7735f8b2 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-search +## 0.7.5-next.0 + +### Patch Changes + +- 38e01f2f70: Switch to `SearchDocument` type in `DefaultResultListItem` props +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.7.4 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-search-common@0.3.2 + ## 0.7.3 ### Patch Changes diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index 0a277dab85..1bdd918802 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -9,13 +9,13 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { AsyncState } from 'react-use/lib/useAsync'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; -import { IndexableDocument } from '@backstage/plugin-search-common'; import { InputBaseProps } from '@material-ui/core'; import { JsonObject } from '@backstage/types'; import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common'; import { SearchResultSet } from '@backstage/plugin-search-common'; @@ -31,7 +31,7 @@ export const DefaultResultListItem: ({ }: { icon?: ReactNode; secondaryAction?: ReactNode; - result: IndexableDocument; + result: SearchDocument; lineClamp?: number | undefined; }) => JSX.Element; diff --git a/plugins/search/package.json b/plugins/search/package.json index 8ef7804ce6..5d3123eb55 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "0.7.3", + "version": "0.7.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,15 +33,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -56,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx index d9fbfe315e..46045f91ee 100644 --- a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx @@ -15,7 +15,7 @@ */ import React, { ReactNode } from 'react'; -import { IndexableDocument } from '@backstage/plugin-search-common'; +import { SearchDocument } from '@backstage/plugin-search-common'; import { ListItem, ListItemIcon, @@ -29,7 +29,7 @@ import TextTruncate from 'react-text-truncate'; type Props = { icon?: ReactNode; secondaryAction?: ReactNode; - result: IndexableDocument; + result: SearchDocument; lineClamp?: number; }; diff --git a/plugins/search/src/components/SearchFilter/hooks.ts b/plugins/search/src/components/SearchFilter/hooks.ts index 7b27ac1fe2..217e65d1c0 100644 --- a/plugins/search/src/components/SearchFilter/hooks.ts +++ b/plugins/search/src/components/SearchFilter/hooks.ts @@ -43,7 +43,7 @@ export const useAsyncFilterValues = ( // for the lifetime of the hook/component. if (valuesMemo.current[inputValue] === undefined) { valuesMemo.current[inputValue] = callback(inputValue).then(values => { - // Overrite the value for future immediate returns. + // Override the value for future immediate returns. valuesMemo.current[inputValue] = values; return values; }); diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 6722e39091..272edc9986 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-sentry +## 0.3.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.41 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.40 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index e3223e8a68..d7a3f17ac3 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.3.40", + "version": "0.3.42-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index de938695c2..aa0afe0375 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-shortcuts +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.2.4 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/types@1.0.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index b647c05906..20ed09074b 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.2.3", + "version": "0.2.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,10 +24,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/src/api/LocalStoredShortcuts.test.ts b/plugins/shortcuts/src/api/LocalStoredShortcuts.test.ts index a1f7d04d3b..c8401e8b1a 100644 --- a/plugins/shortcuts/src/api/LocalStoredShortcuts.test.ts +++ b/plugins/shortcuts/src/api/LocalStoredShortcuts.test.ts @@ -21,19 +21,22 @@ import { LocalStoredShortcuts } from './LocalStoredShortcuts'; import { ShortcutApi } from './ShortcutApi'; describe('LocalStoredShortcuts', () => { - // eslint-disable-next-line jest/no-done-callback - it('should observe shortcuts', async done => { + it('should observe shortcuts', async () => { const shortcutApi: ShortcutApi = new LocalStoredShortcuts( MockStorageApi.create(), ); const shortcut: Shortcut = { id: 'id', title: 'title', url: '/url' }; await shortcutApi.add(shortcut); - shortcutApi.shortcut$().subscribe(data => { - expect(data).toEqual( - expect.arrayContaining([{ ...shortcut, id: expect.anything() }]), - ); - done(); + + await new Promise(resolve => { + const subscription = shortcutApi.shortcut$().subscribe(data => { + expect(data).toEqual( + expect.arrayContaining([{ ...shortcut, id: expect.anything() }]), + ); + subscription.unsubscribe(); + resolve(); + }); }); }); diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 89c8ba8b2e..91bab9f82e 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-sonarqube +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.3 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 0c89758706..690c025d87 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.3.2", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,10 +36,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index e3d497bfc0..cf333b6d41 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-splunk-on-call +## 0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.3.27 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + ## 0.3.26 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 2637965bc8..7be62edcce 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.26", + "version": "0.3.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/stack-overflow-backend/.eslintrc.js b/plugins/stack-overflow-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/stack-overflow-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md new file mode 100644 index 0000000000..f2eaf660c2 --- /dev/null +++ b/plugins/stack-overflow-backend/README.md @@ -0,0 +1,44 @@ +# Stack Overflow + +A plugin that provides stack overflow specific functionality that can be used in different ways (e.g. for search) to compose your Backstage App. + +## Getting started + +Before we begin, make sure: + +- You have created your own standalone Backstage app using @backstage/create-app and not using a fork of the backstage repository. If you haven't setup Backstage already, start [here](https://backstage.io/docs/getting-started/). + +To use any of the functionality this plugin provides, you need to start by configuring your App with the following config: + +```yaml +stackoverflow: + baseUrl: https://api.stackexchange.com/2.2 # alternative: your internal stack overflow instance +``` + +## Areas of Responsibility + +This stack overflow backend plugin is primarily responsible for the following: + +- Provides a `StackOverflowQuestionsCollatorFactory`, which can be used in the search backend to index stack overflow questions to your Backstage Search. + +### Index Stack Overflow Questions to search + +Before you are able to start index stack overflow questions to search, you need to go through the [search getting started guide](https://backstage.io/docs/features/search/getting-started). + +When you have your `packages/backend/src/plugins/search.ts` file ready to make modifications, add the following code snippet to add the `StackOverflowQuestionsCollatorFactory`. Note that you can modify the `requestParams`. + +> Note: if your baseUrl is set to the external stack overflow api `https://api.stackexchange.com/2.2`, you can find optional and required parameters under the official API documentation under [`Usage of /questions GET`](https://api.stackexchange.com/docs/questions) + +```ts +indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + factory: StackOverflowQuestionsCollatorFactory.fromConfig(env.config, { + logger: env.logger, + requestParams: { + tagged: ['backstage'], + site: 'stackoverflow', + pagesize: 100, + }, + }), +}); +``` diff --git a/plugins/stack-overflow-backend/api-report.md b/plugins/stack-overflow-backend/api-report.md new file mode 100644 index 0000000000..fc4c610d3e --- /dev/null +++ b/plugins/stack-overflow-backend/api-report.md @@ -0,0 +1,52 @@ +## API Report File for "@backstage/plugin-stack-overflow-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { Config } from '@backstage/config'; +import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; +import { IndexableDocument } from '@backstage/plugin-search-common'; +import { Logger } from 'winston'; +import { Readable } from 'stream'; + +// @public +export interface StackOverflowDocument extends IndexableDocument { + // (undocumented) + answers: number; + // (undocumented) + tags: string[]; +} + +// @public +export class StackOverflowQuestionsCollatorFactory + implements DocumentCollatorFactory +{ + // (undocumented) + execute(): AsyncGenerator; + // (undocumented) + static fromConfig( + config: Config, + options: StackOverflowQuestionsCollatorFactoryOptions, + ): StackOverflowQuestionsCollatorFactory; + // (undocumented) + getCollator(): Promise; + // (undocumented) + protected requestParams: StackOverflowQuestionsRequestParams; + // (undocumented) + readonly type: string; +} + +// @public +export type StackOverflowQuestionsCollatorFactoryOptions = { + baseUrl?: string; + requestParams: StackOverflowQuestionsRequestParams; + logger: Logger; +}; + +// @public +export type StackOverflowQuestionsRequestParams = { + [key: string]: string | string[] | number; +}; +``` diff --git a/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx b/plugins/stack-overflow-backend/config.d.ts similarity index 66% rename from plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx rename to plugins/stack-overflow-backend/config.d.ts index d210d551fb..7e7211ca33 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx +++ b/plugins/stack-overflow-backend/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * 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. @@ -14,14 +14,15 @@ * limitations under the License. */ -import { Grid } from '@material-ui/core'; -import React from 'react'; - -/** @public */ -export function EntityListContainer(props: { children: React.ReactNode }) { - return ( - - {props.children} - - ); +export interface Config { + /** + * Configuration options for the stack overflow plugin + */ + stackoverflow: { + /** + * The base url of the Stack Overflow API used for the plugin + * @visibility backend + */ + baseUrl: string; + }; } diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json new file mode 100644 index 0000000000..bf8c000f44 --- /dev/null +++ b/plugins/stack-overflow-backend/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-stack-overflow-backend", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/stack-overflow-backend" + }, + "keywords": [ + "backstage", + "stack-overflow" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/config": "^1.0.0", + "qs": "^6.9.4", + "cross-fetch": "^3.1.5", + "winston": "^3.2.1" + }, + "files": [ + "dist", + "config" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/__mocks__/azure-devops-node-api/GitApi/index.ts b/plugins/stack-overflow-backend/src/index.ts similarity index 76% rename from plugins/scaffolder-backend/src/scaffolder/__mocks__/azure-devops-node-api/GitApi/index.ts rename to plugins/stack-overflow-backend/src/index.ts index 6b6e313cae..6e461b8183 100644 --- a/plugins/scaffolder-backend/src/scaffolder/__mocks__/azure-devops-node-api/GitApi/index.ts +++ b/plugins/stack-overflow-backend/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,12 +14,10 @@ * limitations under the License. */ -export const mockGitApi = { - createRepository: jest.fn(), -}; +/** + * Stack Overflow backend plugin + * + * @packageDocumentation + */ -export class GitApi { - constructor() { - return mockGitApi; - } -} +export * from './search'; diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts new file mode 100644 index 0000000000..98fc86c2ea --- /dev/null +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -0,0 +1,114 @@ +/* + * 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 { + IndexableDocument, + DocumentCollatorFactory, +} from '@backstage/plugin-search-common'; +import { Config } from '@backstage/config'; +import { Readable } from 'stream'; +import fetch from 'cross-fetch'; +import qs from 'qs'; +import { Logger } from 'winston'; + +/** + * Extended IndexableDocument with stack overflow specific properties + * + * @public + */ +export interface StackOverflowDocument extends IndexableDocument { + answers: number; + tags: string[]; +} + +/** + * Type representing the request parameters accepted by the {@link StackOverflowQuestionsCollatorFactory} + * + * @public + */ +export type StackOverflowQuestionsRequestParams = { + [key: string]: string | string[] | number; +}; + +/** + * Options for {@link StackOverflowQuestionsCollatorFactory} + * + * @public + */ +export type StackOverflowQuestionsCollatorFactoryOptions = { + baseUrl?: string; + requestParams: StackOverflowQuestionsRequestParams; + logger: Logger; +}; + +/** + * Search collator responsible for collecting stack overflow questions to index. + * + * @public + */ +export class StackOverflowQuestionsCollatorFactory + implements DocumentCollatorFactory +{ + protected requestParams: StackOverflowQuestionsRequestParams; + private readonly baseUrl: string | undefined; + private readonly logger: Logger; + public readonly type: string = 'stack-overflow'; + + private constructor(options: StackOverflowQuestionsCollatorFactoryOptions) { + this.baseUrl = options.baseUrl; + this.requestParams = options.requestParams; + this.logger = options.logger; + } + + static fromConfig( + config: Config, + options: StackOverflowQuestionsCollatorFactoryOptions, + ) { + const baseUrl = + config.getOptionalString('stackoverflow.baseUrl') || + 'https://api.stackexchange.com/2.2'; + return new StackOverflowQuestionsCollatorFactory({ ...options, baseUrl }); + } + + async getCollator() { + return Readable.from(this.execute()); + } + + async *execute(): AsyncGenerator { + if (!this.baseUrl) { + this.logger.debug( + `No stackoverflow.baseUrl configured in your app-config.yaml`, + ); + } + const params = qs.stringify(this.requestParams, { + arrayFormat: 'comma', + addQueryPrefix: true, + }); + + const res = await fetch(`${this.baseUrl}/questions${params}`); + const data = await res.json(); + + for (const question of data.items) { + yield { + title: question.title, + location: question.link, + text: question.owner.display_name, + tags: question.tags, + answers: question.answer_count, + }; + } + } +} diff --git a/plugins/stack-overflow-backend/src/search/index.ts b/plugins/stack-overflow-backend/src/search/index.ts new file mode 100644 index 0000000000..ed3e05cb28 --- /dev/null +++ b/plugins/stack-overflow-backend/src/search/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 * from './StackOverflowQuestionsCollatorFactory'; diff --git a/plugins/stack-overflow/.eslintrc.js b/plugins/stack-overflow/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/stack-overflow/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/stack-overflow/README.md b/plugins/stack-overflow/README.md new file mode 100644 index 0000000000..6beb843c7d --- /dev/null +++ b/plugins/stack-overflow/README.md @@ -0,0 +1,52 @@ +# Stack Overflow + +A plugin that provides stack overflow specific functionality that can be used in different ways (e.g. for homepage and search) to compose your Backstage App. + +## Getting started + +Before we begin, make sure: + +- You have created your own standalone Backstage app using @backstage/create-app and not using a fork of the backstage repository. If you haven't setup Backstage already, start [here](https://backstage.io/docs/getting-started/). + +To use any of the functionality this plugin provides, you need to start by configuring your App with the following config: + +```yaml +stackoverflow: + baseUrl: https://api.stackexchange.com/2.2 # alternative: your internal stack overflow instance +``` + +## Areas of Responsibility + +This stack overflow frontend plugin is primarily responsible for the following: + +- Exposing various stack-overflow related components like `` which can be used for composing the search page, and `` which can be used for composing the homepage. + +#### Use specific search result list item for Stack Overflow Question + +When you have your `packages/app/src/components/search/SearchPage.tsx` file ready to make modifications, add the following code snippet to add the `StackOverflowSearchResultListItem` when the type of the search results are `stack-overflow`. + +```tsx + case 'stack-overflow': + return ( + + ); +``` + +#### Use Stack Overflow Questions on your homepage + +Before you are able to add the stack overflow question component to your homepage, you need to go through the [homepage getting started guide](https://backstage.io/docs/getting-started/homepage). When its ready, add the following code snippet to your `packages/app/src/components/home/HomePage.tsx` file. + +```tsx + + + +``` diff --git a/plugins/stack-overflow/api-report.md b/plugins/stack-overflow/api-report.md new file mode 100644 index 0000000000..277b0272d1 --- /dev/null +++ b/plugins/stack-overflow/api-report.md @@ -0,0 +1,46 @@ +## API Report File for "@backstage/plugin-stack-overflow" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { ComponentRenderer } from '@backstage/plugin-home'; +import { ReactNode } from 'react'; + +// @public +export const HomePageStackOverflowQuestions: ( + props: ComponentRenderer & { + title?: string | undefined; + } & StackOverflowQuestionsContentProps, +) => JSX.Element; + +// @public +export const stackOverflowPlugin: BackstagePlugin<{}, {}>; + +// @public +export type StackOverflowQuestion = { + title: string; + link: string; + owner: Record; + tags: string[]; + answer_count: number; +}; + +// @public +export type StackOverflowQuestionsContentProps = { + requestParams: StackOverflowQuestionsRequestParams; +}; + +// @public +export type StackOverflowQuestionsRequestParams = { + [key: string]: string | string[] | number; +}; + +// @public +export const StackOverflowSearchResultListItem: (props: { + result: any; + icon?: ReactNode; +}) => JSX.Element; +``` diff --git a/plugins/stack-overflow/config.d.ts b/plugins/stack-overflow/config.d.ts new file mode 100644 index 0000000000..811893231e --- /dev/null +++ b/plugins/stack-overflow/config.d.ts @@ -0,0 +1,28 @@ +/* + * 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 stack overflow plugin + */ + stackoverflow: { + /** + * The base url of the Stack Overflow API used for the plugin + * @visibility frontend + */ + baseUrl: string; + }; +} diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json new file mode 100644 index 0000000000..9c83ed4856 --- /dev/null +++ b/plugins/stack-overflow/package.json @@ -0,0 +1,59 @@ +{ + "name": "@backstage/plugin-stack-overflow", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "scripts": { + "build": "backstage-cli package start", + "start": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/plugin-home": "^0.4.20-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/config": "^1.0.0", + "@backstage/theme": "^0.2.15", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "qs": "^6.9.4", + "cross-fetch": "^3.1.5", + "lodash": "^4.17.21", + "react-use": "^17.2.4", + "@testing-library/jest-dom": "^5.10.1" + }, + "peerDependencies": { + "@types/react": "^16.13.1 || ^17.0.0", + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.16.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", + "@testing-library/react": "^12.1.3", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@testing-library/user-event": "^13.1.8", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "msw": "^0.35.0" + }, + "files": [ + "dist", + "config" + ] +} diff --git a/plugins/stack-overflow/src/home/StackOverflowQuestions/Content.tsx b/plugins/stack-overflow/src/home/StackOverflowQuestions/Content.tsx new file mode 100644 index 0000000000..c347399474 --- /dev/null +++ b/plugins/stack-overflow/src/home/StackOverflowQuestions/Content.tsx @@ -0,0 +1,87 @@ +/* + * 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 { Link } from '@backstage/core-components'; +import { + IconButton, + List, + ListItem, + ListItemText, + ListItemSecondaryAction, +} from '@material-ui/core'; +import OpenInNewIcon from '@material-ui/icons/OpenInNew'; +import useAsync from 'react-use/lib/useAsync'; +import qs from 'qs'; +import React from 'react'; +import { + StackOverflowQuestion, + StackOverflowQuestionsContentProps, +} from '../../types'; + +/** + * A component to display a list of stack overflow questions on the homepage. + * + * @public + */ + +export const Content = (props: StackOverflowQuestionsContentProps) => { + const { requestParams } = props; + const configApi = useApi(configApiRef); + const baseUrl = + configApi.getOptionalString('stackoverflow.baseUrl') || + 'https://api.stackexchange.com/2.2'; + + const { value, loading, error } = useAsync(async (): Promise< + StackOverflowQuestion[] + > => { + const params = qs.stringify(requestParams, { addQueryPrefix: true }); + const response = await fetch(`${baseUrl}/questions${params}`); + const data = await response.json(); + return data.items; + }, []); + + if (loading) { + return

loading...

; + } + + if (error || !value || !value.length) { + return

could not load questions

; + } + + const getSecondaryText = (answer_count: Number) => + answer_count > 1 ? `${answer_count} answers` : `${answer_count} answer`; + + return ( + + {value.map(question => ( + + + + + + + + + + + ))} + + ); +}; diff --git a/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx b/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx new file mode 100644 index 0000000000..e19e0f807b --- /dev/null +++ b/plugins/stack-overflow/src/home/StackOverflowQuestions/StackOverflowQuestions.stories.tsx @@ -0,0 +1,62 @@ +/* + * 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 { HomePageStackOverflowQuestions } from '../../plugin'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigReader } from '@backstage/config'; +import { Grid } from '@material-ui/core'; +import React, { ComponentType } from 'react'; + +export default { + title: 'Plugins/Home/Components/StackOverflow', + component: HomePageStackOverflowQuestions, + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + <> + + + + , + ), + ], +}; + +export const Default = () => { + return ( + + + + ); +}; diff --git a/plugins/stack-overflow/src/home/StackOverflowQuestions/index.ts b/plugins/stack-overflow/src/home/StackOverflowQuestions/index.ts new file mode 100644 index 0000000000..1faa9a2426 --- /dev/null +++ b/plugins/stack-overflow/src/home/StackOverflowQuestions/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 { Content } from './Content'; diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx b/plugins/stack-overflow/src/index.ts similarity index 60% rename from plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx rename to plugins/stack-overflow/src/index.ts index 8e595fe5cd..264a0d5d7c 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx +++ b/plugins/stack-overflow/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * 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. @@ -13,15 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Grid } from '@material-ui/core'; -import React from 'react'; - -/** @public */ -export function FilteredEntityLayout(props: { children: React.ReactNode }) { - return ( - - {props.children} - - ); -} +/** + * Stack Overflow frontend plugin + * + * @packageDocumentation + */ +export { + stackOverflowPlugin, + StackOverflowSearchResultListItem, + HomePageStackOverflowQuestions, +} from './plugin'; +export type { + StackOverflowQuestion, + StackOverflowQuestionsContentProps, + StackOverflowQuestionsRequestParams, +} from './types'; diff --git a/plugins/stack-overflow/src/plugin.test.ts b/plugins/stack-overflow/src/plugin.test.ts new file mode 100644 index 0000000000..b25cfe01ee --- /dev/null +++ b/plugins/stack-overflow/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * 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 { stackOverflowPlugin } from './plugin'; + +describe('stack-overflow', () => { + it('should export plugin', () => { + expect(stackOverflowPlugin).toBeDefined(); + }); +}); diff --git a/plugins/stack-overflow/src/plugin.ts b/plugins/stack-overflow/src/plugin.ts new file mode 100644 index 0000000000..dee48cdf8e --- /dev/null +++ b/plugins/stack-overflow/src/plugin.ts @@ -0,0 +1,61 @@ +/* + * 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 { + createPlugin, + createComponentExtension, +} from '@backstage/core-plugin-api'; +import { createCardExtension } from '@backstage/plugin-home'; +import { StackOverflowQuestionsContentProps } from './types'; + +/** + * The Backstage plugin that holds stack overflow specific components + * + * @public + */ +export const stackOverflowPlugin = createPlugin({ + id: 'stack-overflow', +}); + +/** + * A component to display a stack overflow search result + * + * @public + */ +export const StackOverflowSearchResultListItem = stackOverflowPlugin.provide( + createComponentExtension({ + name: 'StackOverflowResultListItem', + component: { + lazy: () => + import('./search/StackOverflowSearchResultListItem').then( + m => m.StackOverflowSearchResultListItem, + ), + }, + }), +); + +/** + * A component to display a list of stack overflow questions on the homepage. + * + * @public + */ +export const HomePageStackOverflowQuestions = stackOverflowPlugin.provide( + createCardExtension({ + name: 'HomePageStackOverflowQuestions', + title: 'Stack Overflow Questions', + components: () => import('./home/StackOverflowQuestions'), + }), +); diff --git a/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.stories.tsx b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.stories.tsx new file mode 100644 index 0000000000..1239cabcbe --- /dev/null +++ b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.stories.tsx @@ -0,0 +1,39 @@ +/* + * 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 { StackOverflowSearchResultListItem } from '../../plugin'; +import { wrapInTestApp } from '@backstage/test-utils'; +import React, { ComponentType } from 'react'; + +export default { + title: 'Plugins/Search/StackOverflowResultListItem', + component: StackOverflowSearchResultListItem, + decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], +}; + +export const Default = () => { + return ( + + ); +}; diff --git a/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.test.tsx b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.test.tsx new file mode 100644 index 0000000000..61e901591e --- /dev/null +++ b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { StackOverflowSearchResultListItem } from './StackOverflowSearchResultListItem'; + +describe('', () => { + it('should render without exploding', async () => { + await renderInTestApp( + , + ); + expect( + screen.getByText(/Customizing Spotify backstage UI/i), + ).toBeInTheDocument(); + expect(screen.getByText(/Tag: backstage/i)).toBeInTheDocument(); + expect( + screen.getByText(/Customizing Spotify backstage UI/i).closest('a'), + ).toHaveAttribute('href', 'https://stackoverflow.com/questions/7'); + }); +}); diff --git a/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx new file mode 100644 index 0000000000..c793939593 --- /dev/null +++ b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import _unescape from 'lodash/unescape'; +import { Link } from '@backstage/core-components'; +import { + Divider, + ListItem, + ListItemText, + ListItemIcon, + Box, + Chip, +} from '@material-ui/core'; + +type StackOverflowSearchResultListItemProps = { + result: any; // TODO(emmaindal): type to StackOverflowDocument. + icon?: React.ReactNode; +}; + +export const StackOverflowSearchResultListItem = ( + props: StackOverflowSearchResultListItemProps, +) => { + const { location, title, text, answers, tags } = props.result; + + return ( + + + {props.icon && {props.icon}} + + + + {tags && + tags.map((tag: string) => ( + + ))} + + + + + ); +}; diff --git a/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/index.ts b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/index.ts new file mode 100644 index 0000000000..499f7273aa --- /dev/null +++ b/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/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 { StackOverflowSearchResultListItem } from './StackOverflowSearchResultListItem'; diff --git a/plugins/stack-overflow/src/setupTests.ts b/plugins/stack-overflow/src/setupTests.ts new file mode 100644 index 0000000000..9bb3e72355 --- /dev/null +++ b/plugins/stack-overflow/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/stack-overflow/src/types.ts b/plugins/stack-overflow/src/types.ts new file mode 100644 index 0000000000..c3fa08cc4f --- /dev/null +++ b/plugins/stack-overflow/src/types.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +/** + * Type representing a stack overflow question + * + * @public + */ +export type StackOverflowQuestion = { + title: string; + link: string; + owner: Record; + tags: string[]; + answer_count: number; +}; + +/** + * Props for HomePageStackOverflowQuestions + * + * @public + */ +export type StackOverflowQuestionsContentProps = { + requestParams: StackOverflowQuestionsRequestParams; +}; + +/** + * Type representing the request parameters accepted by the HomePageStackOverflowQuestions component + * + * @public + */ +export type StackOverflowQuestionsRequestParams = { + [key: string]: string | string[] | number; +}; diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 2b6eb28a27..3f11cd5ed2 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.15-next.0 + +### Patch Changes + +- e0a51384ac: build(deps): bump `ajv` from 7.0.3 to 8.10.0 +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + - @backstage/plugin-tech-insights-node@0.2.9-next.0 + +## 0.1.14 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.4 + - @backstage/plugin-tech-insights-node@0.2.8 + ## 0.1.13 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 344cec8d45..1f4b7de469 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.13", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,20 +34,19 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-tech-insights-common": "^0.2.3", - "@backstage/plugin-tech-insights-node": "^0.2.7", - "ajv": "^7.0.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-tech-insights-common": "^0.2.4", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.0", + "ajv": "^8.10.0", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", "luxon": "^2.0.2", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@types/node-cron": "^3.0.1" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 714557dfe0..0e0715e16a 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-tech-insights-backend +## 0.2.11-next.0 + +### Patch Changes + +- c47509e1a0: Implemented changes suggested by Deepsource.io including multiple double non-null assertion operators and unexpected awaits for non-promise values. +- 2fe58c7285: Improved the Tech-Insights documentation: + + - `lifecycle` examples used `ttl` when it should be `timeToLive` + - Added list of included FactRetrievers + - Added full backend example using all included FactRetrievers + - Added boolean scorecard example image showing results of backend example + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-tech-insights-node@0.2.9-next.0 + +## 0.2.10 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.4 + - @backstage/plugin-tech-insights-node@0.2.8 + ## 0.2.9 ### Patch Changes diff --git a/plugins/tech-insights-backend/README.md b/plugins/tech-insights-backend/README.md index 275adaa15c..55653e097b 100644 --- a/plugins/tech-insights-backend/README.md +++ b/plugins/tech-insights-backend/README.md @@ -35,6 +35,7 @@ export default async function createPlugin( config: env.config, database: env.database, discovery: env.discovery, + scheduler: env.scheduler, factRetrievers: [], // Fact retrievers registrations you want tech insights to use }); @@ -92,8 +93,8 @@ FactRetrieverRegistration also accepts an optional `lifecycle` configuration val ```ts const maxItems = { maxItems: 7 }; // Deletes all but 7 latest facts for each id/entity pair -const ttl = { ttl: 1209600000 }; // (2 weeks) Deletes items older than 2 weeks -const ttlWithAHumanReadableValue = { ttl: { weeks: 2 } }; // Deletes items older than 2 weeks +const ttl = { timeToLive: 1209600000 }; // (2 weeks) Deletes items older than 2 weeks +const ttlWithAHumanReadableValue = { timeToLive: { weeks: 2 } }; // Deletes items older than 2 weeks ``` To register these fact retrievers to your application you can modify the example `techInsights.ts` file shown above like this: @@ -235,3 +236,130 @@ const myFactCheckerFactory = new JsonRulesEngineFactCheckerFactory({ }), ``` + +## Included FactRetrievers + +There are three FactRetrievers that come out of the box with Tech Insights: + +- `entityMetadataFactRetriever`: Generates facts which indicate the completeness of entity metadata +- `entityOwnershipFactRetriever`: Generates facts which indicate the quality of data in the spec.owner field +- `techdocsFactRetriever`: Generates facts related to the completeness of techdocs configuration for entities + +## Backend Example + +Here's an example backend setup that will use the three included fact retrievers so you can get an idea of how this all works. This will be the entire contents of your `techInsights.ts` file found at `\packages\backend\src\plugins` as per [Adding the plugin to your `packages/backend`](#adding-the-plugin-to-your-packagesbackend) + +```ts +import { + createRouter, + buildTechInsightsContext, + createFactRetrieverRegistration, + entityOwnershipFactRetriever, + entityMetadataFactRetriever, + techdocsFactRetriever, +} from '@backstage/plugin-tech-insights-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; +import { + JsonRulesEngineFactCheckerFactory, + JSON_RULE_ENGINE_CHECK_TYPE, +} from '@backstage/plugin-tech-insights-backend-module-jsonfc'; + +const ttlTwoWeeks = { timeToLive: { weeks: 2 } }; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const techInsightsContext = await buildTechInsightsContext({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + factRetrievers: [ + createFactRetrieverRegistration({ + cadence: '0 */6 * * *', // Run every 6 hours - https://crontab.guru/#0_*/6_*_*_* + factRetriever: entityOwnershipFactRetriever, + lifecycle: ttlTwoWeeks, + }), + createFactRetrieverRegistration({ + cadence: '0 */6 * * *', + factRetriever: entityMetadataFactRetriever, + lifecycle: ttlTwoWeeks, + }), + createFactRetrieverRegistration({ + cadence: '0 */6 * * *', + factRetriever: techdocsFactRetriever, + lifecycle: ttlTwoWeeks, + }), + ], + factCheckerFactory: new JsonRulesEngineFactCheckerFactory({ + logger: env.logger, + checks: [ + { + id: 'groupOwnerCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Group Owner Check', + description: + 'Verifies that a Group has been set as the owner for this entity', + factIds: ['entityOwnershipFactRetriever'], + rule: { + conditions: { + all: [ + { + fact: 'hasGroupOwner', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + { + id: 'titleCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'Title Check', + description: + 'Verifies that a Title, used to improve readability, has been set for this entity', + factIds: ['entityMetadataFactRetriever'], + rule: { + conditions: { + all: [ + { + fact: 'hasTitle', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + { + id: 'techDocsCheck', + type: JSON_RULE_ENGINE_CHECK_TYPE, + name: 'TechDocs Check', + description: + 'Verifies that TechDocs has been enabled for this entity', + factIds: ['techdocsFactRetriever'], + rule: { + conditions: { + all: [ + { + fact: 'hasAnnotationBackstageIoTechdocsRef', + operator: 'equal', + value: true, + }, + ], + }, + }, + }, + ], + }), + }); + + return await createRouter({ + ...techInsightsContext, + logger: env.logger, + config: env.config, + }); +} +``` diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index d56e3c8082..210aa50af9 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -14,6 +14,7 @@ import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node' import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TechInsightCheck } from '@backstage/plugin-tech-insights-node'; import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; @@ -26,11 +27,9 @@ export const buildTechInsightsContext: < ) => Promise>; // @public -export function createFactRetrieverRegistration({ - cadence, - factRetriever, - lifecycle, -}: FactRetrieverRegistrationOptions): FactRetrieverRegistration; +export function createFactRetrieverRegistration( + options: FactRetrieverRegistrationOptions, +): FactRetrieverRegistration; // @public export function createRouter< @@ -94,6 +93,8 @@ export interface TechInsightsOptions< factRetrievers: FactRetrieverRegistration[]; // (undocumented) logger: Logger; + // (undocumented) + scheduler: PluginTaskScheduler; } // (No @packageDocumentation comment for this package) diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index 8eed1fb1ed..475e5e98ae 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.2.9", + "version": "0.2.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,32 +34,32 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-tech-insights-common": "^0.2.3", - "@backstage/plugin-tech-insights-node": "^0.2.7", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-tech-insights-common": "^0.2.4", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", "knex": "^1.0.2", "lodash": "^4.17.21", "luxon": "^2.0.2", - "node-cron": "^3.0.0", "semver": "^7.3.5", "uuid": "^8.3.2", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", - "@types/node-cron": "^3.0.0", "@types/semver": "^7.3.8", - "supertest": "^6.1.3" + "supertest": "^6.1.3", + "wait-for-expect": "^3.0.2" }, "files": [ "dist", diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts index 8d1f7180a3..0617c0aedd 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.test.ts @@ -22,20 +22,15 @@ import { } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistry } from './FactRetrieverRegistry'; import { FactRetrieverEngine } from './FactRetrieverEngine'; -import { getVoidLogger } from '@backstage/backend-common'; +import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; -import { schedule } from 'node-cron'; +import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { TaskScheduler } from '@backstage/backend-tasks'; -jest.mock('node-cron', () => { - const original = jest.requireActual('node-cron'); - return { - ...original, - schedule: jest.fn(), - }; -}); +jest.useFakeTimers(); const testFactRetriever: FactRetriever = { - id: 'test-factretriever', + id: 'test_factretriever', version: '0.0.1', entityFilter: [{ kind: 'component' }], schema: { @@ -59,94 +54,171 @@ const testFactRetriever: FactRetriever = { ]; }), }; -const cadence = '1 * * * *'; +const defaultCadence = '1 * * * *'; describe('FactRetrieverEngine', () => { let engine: FactRetrieverEngine; - let factSchemaAssertionCallback: ( + type FactSchemaAssertionCallback = ( factSchemaDefinition: FactSchemaDefinition, ) => void; - let factInsertionAssertionCallback: (facts: TechInsightFact[]) => void; - const mockRepository: TechInsightsStore = { - insertFacts: (facts: TechInsightFact[]) => { - factInsertionAssertionCallback(facts); - return Promise.resolve(); - }, - insertFactSchema: (def: FactSchemaDefinition) => { - factSchemaAssertionCallback(def); - return Promise.resolve(); - }, - } as unknown as TechInsightsStore; + jest.setTimeout(15000); - const mockFactRetrieverRegistry: FactRetrieverRegistry = { - listRetrievers(): FactRetriever[] { - return [testFactRetriever]; - }, - listRegistrations(): FactRetrieverRegistration[] { - return [{ factRetriever: testFactRetriever, cadence }]; - }, - } as unknown as FactRetrieverRegistry; + type FactInsertionAssertionCallback = ({ + facts, + id, + }: { + id: string; + facts: TechInsightFact[]; + }) => void; - const defaultEngineConfig = { - factRetrieverContext: { - logger: getVoidLogger(), - config: ConfigReader.fromConfigs([]), - discovery: { - getBaseUrl: (_: string) => Promise.resolve('http://mock.url'), - getExternalBaseUrl: (_: string) => Promise.resolve('http://mock.url'), + function createMockRepository( + insertCallback: FactInsertionAssertionCallback, + assertionCallback: FactSchemaAssertionCallback, + ): TechInsightsStore { + return { + async insertFacts(f: { facts: TechInsightFact[]; id: string }) { + insertCallback(f); }, - }, - factRetrieverRegistry: mockFactRetrieverRegistry, - repository: mockRepository, - }; - - it('Should update fact retriever schemas on initialization', async () => { - factSchemaAssertionCallback = ({ id, schema, version, entityFilter }) => { - expect(id).toEqual('test-factretriever'); - expect(version).toEqual('0.0.1'); - expect(entityFilter).toEqual([{ kind: 'component' }]); - expect(schema).toEqual({ - testnumberfact: { - type: 'integer', - description: '', - }, - }); - }; - - engine = await FactRetrieverEngine.create(defaultEngineConfig); - }); - it('Should insert facts when scheduled step is run', async () => { - (schedule as jest.Mock).mockImplementation( - (cronCadence: string, retrieverAction: Function) => { - return { - cadence: cronCadence, - triggerScheduledJobNow: retrieverAction, - }; + async insertFactSchema(def: FactSchemaDefinition) { + assertionCallback(def); }, - ); + } as unknown as TechInsightsStore; + } - factSchemaAssertionCallback = () => {}; - factInsertionAssertionCallback = facts => { - expect(facts).toHaveLength(1); - expect(facts[0]).toEqual({ - ref: 'test-factretriever', - entity: { - namespace: 'a', - kind: 'a', - name: 'a', - }, - facts: { - testnumberfact: 1, - }, - }); - }; - engine = await FactRetrieverEngine.create(defaultEngineConfig); - engine.schedule(); - const job: any = engine.getJob('test-factretriever'); - job.triggerScheduledJobNow(); - expect(job.cadence!!).toEqual(cadence); - expect(testFactRetriever.handler).toHaveBeenCalledWith( - expect.objectContaining({ entityFilter: testFactRetriever.entityFilter }), - ); + function createMockFactRetrieverRegistry( + cadence?: string, + factRetriever?: FactRetriever, + ): FactRetrieverRegistry { + const cron = cadence ?? defaultCadence; + const retriever: FactRetriever = factRetriever + ? factRetriever + : testFactRetriever; + return { + listRetrievers(): FactRetriever[] { + return [retriever]; + }, + listRegistrations(): FactRetrieverRegistration[] { + return [{ factRetriever: retriever, cadence: cron }]; + }, + get: (_: string): FactRetrieverRegistration => { + return { factRetriever: retriever, cadence: cron }; + }, + } as unknown as FactRetrieverRegistry; + } + + const databases = TestDatabases.create({ + ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], }); + + async function createEngine( + databaseId: TestDatabaseId, + insert: FactInsertionAssertionCallback, + schema: FactSchemaAssertionCallback, + cadence?: string, + factRetriever?: FactRetriever, + ): Promise { + const knex = await databases.init(databaseId); + const databaseManager: Partial = { + forPlugin: (_: string) => ({ + getClient: async () => knex, + }), + }; + const manager = databaseManager as DatabaseManager; + const scheduler = new TaskScheduler(manager, getVoidLogger()); + return await FactRetrieverEngine.create({ + factRetrieverContext: { + logger: getVoidLogger(), + config: ConfigReader.fromConfigs([]), + discovery: { + getBaseUrl: (_: string) => Promise.resolve('http://mock.url'), + getExternalBaseUrl: (_: string) => Promise.resolve('http://mock.url'), + }, + }, + factRetrieverRegistry: createMockFactRetrieverRegistry( + cadence, + factRetriever, + ), + repository: createMockRepository(insert, schema), + scheduler: scheduler.forPlugin('tech-insights'), + }); + } + + it.each(databases.eachSupportedId())( + 'Should update fact retriever schemas on initialization with %s', + async databaseId => { + const schemaAssertionCallback = jest.fn((def: FactSchemaDefinition) => { + expect(def.id).toEqual('test_factretriever'); + expect(def.version).toEqual('0.0.1'); + expect(def.entityFilter).toEqual([{ kind: 'component' }]); + expect(def.schema).toEqual({ + testnumberfact: { + type: 'integer', + description: '', + }, + }); + }); + + engine = await createEngine( + databaseId, + () => {}, + schemaAssertionCallback, + ); + expect(schemaAssertionCallback).toBeCalled(); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'Should insert facts when scheduled step is run with %s', + async databaseId => { + function insertCallback({ + facts, + id, + }: { + id: string; + facts: TechInsightFact[]; + }) { + expect(facts).toHaveLength(1); + expect(id).toEqual('test_factretriever'); + expect(facts[0]).toEqual({ + entity: { + namespace: 'a', + kind: 'a', + name: 'a', + }, + facts: { + testnumberfact: 1, + }, + }); + } + + const handler = jest.fn(); + engine = await createEngine( + databaseId, + insertCallback, + () => {}, + undefined, + { ...testFactRetriever, handler }, + ); + await engine.schedule(); + const job: FactRetrieverRegistration = engine.getJobRegistration( + testFactRetriever.id, + ); + expect(job.cadence!!).toEqual(defaultCadence); + + await engine.triggerJob(job.factRetriever.id); + jest.advanceTimersByTime(5000); + + const handlerParam = await new Promise(resolve => + handler.mockImplementation(resolve), + ); + + await expect(handlerParam).toEqual( + expect.objectContaining({ + entityFilter: testFactRetriever.entityFilter, + }), + ); + }, + 60_000, + ); }); diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index 85e7e680c7..25e7610809 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -17,12 +17,14 @@ import { FactLifecycle, FactRetriever, FactRetrieverContext, + FactRetrieverRegistration, TechInsightFact, TechInsightsStore, } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistry } from './FactRetrieverRegistry'; -import { schedule, validate, ScheduledTask } from 'node-cron'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { Duration } from 'luxon'; function randomDailyCron() { const rand = (min: number, max: number) => @@ -37,27 +39,33 @@ function duration(startTimestamp: [number, number]): string { } export class FactRetrieverEngine { - private scheduledJobs = new Map(); - - constructor( + private constructor( private readonly repository: TechInsightsStore, private readonly factRetrieverRegistry: FactRetrieverRegistry, private readonly factRetrieverContext: FactRetrieverContext, private readonly logger: Logger, + private readonly scheduler: PluginTaskScheduler, private readonly defaultCadence?: string, + private readonly defaultTimeout?: Duration, ) {} - static async create({ - repository, - factRetrieverRegistry, - factRetrieverContext, - defaultCadence, - }: { + static async create(options: { repository: TechInsightsStore; factRetrieverRegistry: FactRetrieverRegistry; factRetrieverContext: FactRetrieverContext; + scheduler: PluginTaskScheduler; defaultCadence?: string; + defaultTimeout?: Duration; }) { + const { + repository, + factRetrieverRegistry, + factRetrieverContext, + scheduler, + defaultCadence, + defaultTimeout, + } = options; + await Promise.all( factRetrieverRegistry .listRetrievers() @@ -69,39 +77,50 @@ export class FactRetrieverEngine { factRetrieverRegistry, factRetrieverContext, factRetrieverContext.logger, + scheduler, defaultCadence, + defaultTimeout, ); } - schedule() { + async schedule() { const registrations = this.factRetrieverRegistry.listRegistrations(); const newRegs: string[] = []; - registrations.forEach(registration => { - const { factRetriever, cadence, lifecycle } = registration; - if (!this.scheduledJobs.has(factRetriever.id)) { + + await Promise.all( + registrations.map(async registration => { + const { factRetriever, cadence, lifecycle, timeout } = registration; const cronExpression = cadence || this.defaultCadence || randomDailyCron(); - if (!validate(cronExpression)) { + const timeLimit = + timeout || this.defaultTimeout || Duration.fromObject({ minutes: 5 }); + try { + await this.scheduler.scheduleTask({ + id: factRetriever.id, + frequency: { cron: cronExpression }, + fn: this.createFactRetrieverHandler(factRetriever, lifecycle), + timeout: timeLimit, + }); + newRegs.push(factRetriever.id); + } catch (e) { this.logger.warn( - `Validation failed for cron expression ${cronExpression} when trying to schedule fact retriever ${factRetriever.id}`, + `Failed to schedule fact retriever ${factRetriever.id}, ${e}`, ); - return; } - const job = schedule( - cronExpression, - this.createFactRetrieverHandler(factRetriever, lifecycle), - ); - this.scheduledJobs.set(factRetriever.id, job); - newRegs.push(factRetriever.id); - } - }); + }), + ); + this.logger.info( - `Scheduled ${newRegs.length} fact retrievers to Fact Retriever Engine.`, + `Scheduled ${newRegs.length}/${registrations.length} fact retrievers into the tech-insights engine`, ); } - getJob(ref: string) { - return this.scheduledJobs.get(ref); + getJobRegistration(ref: string): FactRetrieverRegistration { + return this.factRetrieverRegistry.get(ref); + } + + async triggerJob(ref: string): Promise { + await this.scheduler.triggerTask(ref); } private createFactRetrieverHandler( diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts index 719f8bf504..b122f52f0a 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverRegistry.ts @@ -39,14 +39,14 @@ export class FactRetrieverRegistry { this.retrievers.set(registration.factRetriever.id, registration); } - get(retrieverReference: string): FactRetriever { + get(retrieverReference: string): FactRetrieverRegistration { const registration = this.retrievers.get(retrieverReference); if (!registration) { throw new NotFoundError( `Tech insight fact retriever with identifier '${retrieverReference}' is not registered.`, ); } - return registration.factRetriever; + return registration; } listRetrievers(): FactRetriever[] { diff --git a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts index bd85b3c873..de49888e1b 100644 --- a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts @@ -61,11 +61,10 @@ export type FactRetrieverRegistrationOptions = { * \{ maxItems: 7 \} -- This fact retriever will leave 7 newest items in the database when it is run * */ -export function createFactRetrieverRegistration({ - cadence, - factRetriever, - lifecycle, -}: FactRetrieverRegistrationOptions): FactRetrieverRegistration { +export function createFactRetrieverRegistration( + options: FactRetrieverRegistrationOptions, +): FactRetrieverRegistration { + const { cadence, factRetriever, lifecycle } = options; return { cadence, factRetriever, diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts index 535aede115..26b1e4274b 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.ts @@ -53,7 +53,7 @@ export const entityMetadataFactRetriever: FactRetriever = { return entities.items.map((entity: Entity) => { return { entity: { - namespace: entity.metadata.namespace!!, + namespace: entity.metadata.namespace!, kind: entity.kind, name: entity.metadata.name, }, diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts index 8f61f182ad..b9a91e67f7 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts @@ -51,7 +51,7 @@ export const entityOwnershipFactRetriever: FactRetriever = { return entities.items.map((entity: Entity) => { return { entity: { - namespace: entity.metadata.namespace!!, + namespace: entity.metadata.namespace!, kind: entity.kind, name: entity.metadata.name, }, diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts index c1c3ba5d20..2717f94475 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.ts @@ -49,7 +49,7 @@ export const techdocsFactRetriever: FactRetriever = { return entities.items.map((entity: Entity) => { return { entity: { - namespace: entity.metadata.namespace!!, + namespace: entity.metadata.namespace!, kind: entity.kind, name: entity.metadata.name, }, diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts index 0b81ad490f..c69432d070 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts @@ -116,10 +116,11 @@ const additionalFacts = [ ]; describe('Tech Insights database', () => { + const databases = TestDatabases.create(); let store: TechInsightsStore; let testDbClient: Knex; beforeAll(async () => { - testDbClient = await TestDatabases.create().init('SQLITE_3'); + testDbClient = await databases.init('SQLITE_3'); store = ( await initializePersistenceContext(testDbClient, { diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts index ddb90b75cd..3507e280a1 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts @@ -62,7 +62,7 @@ export class TechInsightsDatabase implements TechInsightsStore { return Object.values(groupedSchemas) .map(schemas => { const sorted = rsort(schemas.map(it => it.version)); - return schemas.find(it => it.version === sorted[0])!!; + return schemas.find(it => it.version === sorted[0])!; }) .map((it: RawDbFactSchemaRow) => ({ ...omit(it, 'schema'), @@ -188,7 +188,7 @@ export class TechInsightsDatabase implements TechInsightsStore { throw new Error(`No schema found for ${id}. `); } const sorted = rsort(existingSchemas.map(it => it.version)); - return existingSchemas.find(it => it.version === sorted[0])!!; + return existingSchemas.find(it => it.version === sorted[0])!; } private async deleteExpiredFactsByDate( diff --git a/plugins/tech-insights-backend/src/service/router.test.ts b/plugins/tech-insights-backend/src/service/router.test.ts index 0b7d3b7c45..df2d471516 100644 --- a/plugins/tech-insights-backend/src/service/router.test.ts +++ b/plugins/tech-insights-backend/src/service/router.test.ts @@ -15,7 +15,11 @@ */ import { buildTechInsightsContext } from './techInsightsContextBuilder'; import { createRouter } from './router'; -import { getVoidLogger } from '@backstage/backend-common'; +import { + DatabaseManager, + getVoidLogger, + PluginDatabaseManager, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import request from 'supertest'; import express from 'express'; @@ -23,6 +27,7 @@ import { PersistenceContext } from './persistence/persistenceContext'; import { TechInsightsStore } from '@backstage/plugin-tech-insights-node'; import { DateTime } from 'luxon'; import { Knex } from 'knex'; +import { TaskScheduler } from '@backstage/backend-tasks'; describe('Tech Insights router tests', () => { let app: express.Express; @@ -44,18 +49,26 @@ describe('Tech Insights router tests', () => { }); beforeAll(async () => { - const techInsightsContext = await buildTechInsightsContext({ - database: { - getClient: () => { - return Promise.resolve({ - migrate: { - latest: () => {}, - }, - }) as unknown as Promise; - }, + const pluginDatabase: PluginDatabaseManager = { + getClient: () => { + return Promise.resolve({ + migrate: { + latest: () => {}, + }, + }) as unknown as Promise; }, + }; + const databaseManager: Partial = { + forPlugin: () => pluginDatabase, + }; + const manager = databaseManager as DatabaseManager; + const techInsightsContext = await buildTechInsightsContext({ + database: pluginDatabase, logger: getVoidLogger(), factRetrievers: [], + scheduler: new TaskScheduler(manager, getVoidLogger()).forPlugin( + 'tech-insights', + ), config: ConfigReader.fromConfigs([]), discovery: { getBaseUrl: (_: string) => Promise.resolve('http://mock.url'), diff --git a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts index 75e24b7c06..4f8f2ed928 100644 --- a/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts +++ b/plugins/tech-insights-backend/src/service/techInsightsContextBuilder.ts @@ -33,6 +33,7 @@ import { PersistenceContext, } from './persistence/persistenceContext'; import { CheckResult } from '@backstage/plugin-tech-insights-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; /** * @public @@ -61,6 +62,7 @@ export interface TechInsightsOptions< config: Config; discovery: PluginEndpointDiscovery; database: PluginDatabaseManager; + scheduler: PluginTaskScheduler; } /** @@ -101,6 +103,7 @@ export const buildTechInsightsContext = async < discovery, database, logger, + scheduler, } = options; const factRetrieverRegistry = new FactRetrieverRegistry(factRetrievers); @@ -111,6 +114,7 @@ export const buildTechInsightsContext = async < ); const factRetrieverEngine = await FactRetrieverEngine.create({ + scheduler, repository: persistenceContext.techInsightsStore, factRetrieverRegistry, factRetrieverContext: { @@ -120,7 +124,7 @@ export const buildTechInsightsContext = async < }, }); - factRetrieverEngine.schedule(); + await factRetrieverEngine.schedule(); if (factCheckerFactory) { const factChecker = factCheckerFactory.construct( diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index e895a82e6c..d39e8d9d18 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-tech-insights-common +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index 0b89054c50..d1fef2ee61 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "dependencies": { "@types/luxon": "^2.0.5", "luxon": "^2.0.2", - "@backstage/types": "^0.1.3" + "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2-next.0" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index 3ae704f321..5eacdda007 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-insights-node +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.2-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/config@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.4 + ## 0.2.7 ### Patch Changes diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index 45426587e3..7d9f2e00b7 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -6,6 +6,7 @@ import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { Config } from '@backstage/config'; import { DateTime } from 'luxon'; +import { Duration } from 'luxon'; import { DurationLike } from 'luxon'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -66,6 +67,7 @@ export type FactRetrieverContext = { export type FactRetrieverRegistration = { factRetriever: FactRetriever; cadence?: string; + timeout?: Duration; lifecycle?: FactLifecycle; }; diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index b523d63669..f2a3b267eb 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.2.7", + "version": "0.2.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,15 +33,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/plugin-tech-insights-common": "^0.2.3", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/config": "^1.0.0", + "@backstage/plugin-tech-insights-common": "^0.2.4", "@types/luxon": "^2.0.5", "luxon": "^2.0.2", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2" + "@backstage/cli": "^0.16.1-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index 24b6764ba4..a5df4bae85 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { DateTime, DurationLike } from 'luxon'; +import { DateTime, Duration, DurationLike } from 'luxon'; import { Config } from '@backstage/config'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Logger } from 'winston'; @@ -243,6 +242,13 @@ export type FactRetrieverRegistration = { */ cadence?: string; + /** + * A duration to determine how long the fact retriever should be allowed to run, + * defaults to 5 minutes. + * + */ + timeout?: Duration; + /** * Fact lifecycle definition * diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index c29e76c294..f8663f785f 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-tech-insights +## 0.1.14-next.0 + +### Patch Changes + +- 2fe58c7285: Improved the Tech-Insights documentation: + + - `lifecycle` examples used `ttl` when it should be `timeToLive` + - Added list of included FactRetrievers + - Added full backend example using all included FactRetrievers + - Added boolean scorecard example image showing results of backend example + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.1.13 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.4 + ## 0.1.12 ### Patch Changes diff --git a/plugins/tech-insights/README.md b/plugins/tech-insights/README.md index db69808df1..068f88c035 100644 --- a/plugins/tech-insights/README.md +++ b/plugins/tech-insights/README.md @@ -47,12 +47,8 @@ const serviceEntityPage = ( It is not obligatory to pass title and description props to `EntityTechInsightsScorecardContent`. If those are left out, default values from `defaultCheckResultRenderers` in `CheckResultRenderer` will be taken, hence `Boolean scorecard` and `This card represents an overview of default boolean Backstage checks`. -### Customize scorecards overview title and description: +## Boolean Scorecard Example -```tsx -// packages/app/src/components/catalog/EntityPage.tsx +If you follow the [Backend Example](https://github.com/backstage/backstage/tree/master/plugins/tech-insights-backend#backend-example), once the needed facts have been generated the boolean scorecard will look like this: -## Links - -- [The Backstage homepage](https://backstage.io) -``` +![Boolean Scorecard Example](./docs/boolean-scorecard-example.png) diff --git a/plugins/tech-insights/docs/boolean-scorecard-example.png b/plugins/tech-insights/docs/boolean-scorecard-example.png new file mode 100644 index 0000000000..f7860afc2c Binary files /dev/null and b/plugins/tech-insights/docs/boolean-scorecard-example.png differ diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index a868080345..def63e1f30 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.1.12", + "version": "0.1.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,11 @@ "backstage": { "role": "frontend-plugin" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/tech-insights" + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", @@ -23,14 +28,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-tech-insights-common": "^0.2.3", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-tech-insights-common": "^0.2.4", "@backstage/theme": "^0.2.15", - "@backstage/types": "^0.1.3", + "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -42,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 9e6f3758e1..e438182e85 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-radar +## 0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.5.10 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.5.9 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index b63a7d3606..377bcd539e 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.9", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,8 +34,8 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx index 38d289cf1e..6b8bd944b8 100644 --- a/plugins/tech-radar/src/components/RadarComponent.test.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx @@ -51,7 +51,7 @@ describe('RadarComponent', () => { jest.useFakeTimers(); const errorApi = { post: () => {} }; - const { getByTestId, queryByTestId } = render( + const { getByTestId, findByTestId } = render( { }); expect(getByTestId('progress')).toBeInTheDocument(); - await waitFor(() => queryByTestId('tech-radar-svg')); + await findByTestId('tech-radar-svg'); jest.useRealTimers(); }); diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx index e26d44aa1a..e7cca7d45f 100644 --- a/plugins/tech-radar/src/components/RadarComponent.tsx +++ b/plugins/tech-radar/src/components/RadarComponent.tsx @@ -28,7 +28,7 @@ const useTechRadarLoader = (id: string | undefined) => { const { error, value, loading } = useAsync( async () => techRadarApi.load(id), - [techRadarApi], + [techRadarApi, id], ); useEffect(() => { diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx index 490a61ee76..c0ebde17c8 100644 --- a/plugins/tech-radar/src/components/RadarPage.test.tsx +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -59,7 +59,7 @@ describe('RadarPage', () => { svgProps: { 'data-testid': 'tech-radar-svg' }, }; - const { getByTestId, queryByTestId } = render( + const { getByTestId, findByTestId } = render( wrapInTestApp( @@ -74,7 +74,7 @@ describe('RadarPage', () => { }); expect(getByTestId('progress')).toBeInTheDocument(); - await waitFor(() => queryByTestId('tech-radar-svg')); + await findByTestId('tech-radar-svg'); jest.useRealTimers(); }); @@ -86,7 +86,7 @@ describe('RadarPage', () => { }; jest.spyOn(mockClient, 'load'); - const { getByText, getByTestId } = await renderInTestApp( + const { getByText, findByTestId } = await renderInTestApp( @@ -94,12 +94,10 @@ describe('RadarPage', () => { , ); - await waitFor(() => getByTestId('tech-radar-svg')); - + await expect(findByTestId('tech-radar-svg')).resolves.toBeInTheDocument(); expect( getByText('Pick the recommended technologies for your projects'), ).toBeInTheDocument(); - expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); expect(mockClient.load).toBeCalledWith(undefined); }); @@ -112,7 +110,7 @@ describe('RadarPage', () => { }; jest.spyOn(mockClient, 'load'); - const { getByTestId } = await renderInTestApp( + const { findByTestId } = await renderInTestApp( @@ -120,9 +118,7 @@ describe('RadarPage', () => { , ); - await waitFor(() => getByTestId('tech-radar-svg')); - - expect(getByTestId('tech-radar-svg')).toBeInTheDocument(); + await expect(findByTestId('tech-radar-svg')).resolves.toBeInTheDocument(); expect(mockClient.load).toBeCalledWith('myId'); }); diff --git a/plugins/tech-radar/src/utils/polyfills/getBBox.ts b/plugins/tech-radar/src/utils/polyfills/getBBox.ts index 7102d3d653..6e52995e80 100644 --- a/plugins/tech-radar/src/utils/polyfills/getBBox.ts +++ b/plugins/tech-radar/src/utils/polyfills/getBBox.ts @@ -34,6 +34,7 @@ class GetBBoxPolyfill { Object.defineProperty(window.Element.prototype, 'getBBox', { writable: false, + configurable: true, value: () => ({ x, y, width, height }), }); } diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 63bb881971..997f97bd17 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-techdocs-backend +## 1.0.1-next.0 + +### Patch Changes + +- 7762d54200: Fixed a bug affecting those with cache enabled that would result in empty content being cached if the first attempt to load a static asset from storage were made via a `HEAD` request, rather than a `GET` request. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-techdocs-node@1.0.1-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 700d93ff41: Removed deprecated exports, including: + + - deprecated config `generators` is now deleted and fully replaced with `techdocs.generator` + - deprecated config `generators.techdocs` is now deleted and fully replaced with `techdocs.generator.runIn` + - deprecated config `techdocs.requestUrl` is now deleted + - deprecated config `techdocs.storageUrl` is now deleted + - deprecated `createHttpResponse` is now deleted and calls to `/sync/:namespace/:kind/:name` needs to be done by an EventSource. + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-catalog-common@1.0.0 + - @backstage/plugin-techdocs-node@1.0.0 + - @backstage/plugin-search-common@0.3.2 + ## 0.14.2 ### Patch Changes diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 8d531e0536..d4782ff406 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -46,17 +46,6 @@ export interface Config { pullImage?: boolean; }; - /** - * Techdocs generator information - * @deprecated Replaced with techdocs.generator - */ - generators?: { - /** - * @deprecated Use techdocs.generator.runIn - */ - techdocs: 'local' | 'docker'; - }; - /** * Techdocs publisher information */ @@ -252,19 +241,6 @@ export interface Config { readTimeout?: number; }; - /** - * @example http://localhost:7007/api/techdocs - * @visibility frontend - * @deprecated - */ - requestUrl?: string; - - /** - * @example http://localhost:7007/api/techdocs/static/docs - * @deprecated - */ - storageUrl?: string; - /** * (Optional and not recommended) Prior to version [0.x.y] of TechDocs, docs * sites could only be accessed over paths with case-sensitive entity triplets diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 782a63355f..b278f8919a 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "0.14.2", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,15 +34,16 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-catalog-common": "^0.2.2", - "@backstage/plugin-search-common": "^0.3.1", - "@backstage/plugin-techdocs-node": "^0.11.12", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-techdocs-node": "^1.0.1-next.0", "@types/express": "^4.17.6", "dockerode": "^3.3.1", "express": "^4.17.1", @@ -55,9 +56,9 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.21", - "@backstage/cli": "^0.15.2", - "@backstage/plugin-search-backend-node": "0.5.1", + "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/plugin-search-backend-node": "0.5.3-next.0", "@types/dockerode": "^3.3.0", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts index 312674a5bf..b6a0f7db56 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts @@ -85,6 +85,15 @@ describe('createCacheMiddleware', () => { expect(cache.set).not.toHaveBeenCalled(); }); + it('checks cache for head requests', async () => { + cache.get.mockResolvedValueOnce(getMockHttpResponseFor('xyz')); + + await request(app).head('/static/docs/foo.html').expect(200); + + await waitForSocketClose(); + expect(cache.set).not.toHaveBeenCalled(); + }); + it('sets cache when content is cacheable', async () => { const expectedPath = 'default/api/xyz/index.html'; await request(app) @@ -105,5 +114,13 @@ describe('createCacheMiddleware', () => { await waitForSocketClose(); expect(cache.set).not.toHaveBeenCalled(); }); + + it('does not set cache on head requests', async () => { + const expectedPath = 'default/api/xyz/index.html'; + await request(app).head(`/static/docs/${expectedPath}`).expect(200); + + await waitForSocketClose(); + expect(cache.set).not.toHaveBeenCalled(); + }); }); }); diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts index cb59681304..51cce6d220 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.ts @@ -36,6 +36,7 @@ export const createCacheMiddleware = ({ cacheMiddleware.use(async (req, res, next) => { const socket = res.socket; const isCacheable = req.path.startsWith('/static/docs/'); + const isGetRequest = req.method === 'GET'; // Continue early if this is non-cacheable, or there's no socket. if (!isCacheable || !socket) { @@ -69,7 +70,12 @@ export const createCacheMiddleware = ({ socket.on('close', async hadError => { const content = Buffer.concat(chunks); const head = content.toString('utf8', 0, 12); - if (writeToCache && !hadError && head.match(/HTTP\/\d\.\d 200/)) { + if ( + isGetRequest && + writeToCache && + !hadError && + head.match(/HTTP\/\d\.\d 200/) + ) { await cache.set(reqPath, content); } }); diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index 7529eaca66..bf647322cf 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -30,6 +30,7 @@ import { Logger } from 'winston'; import pLimit from 'p-limit'; import { Config } from '@backstage/config'; import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common'; +import { Permission } from '@backstage/plugin-permission-common'; import { CatalogApi, CatalogClient, @@ -73,7 +74,8 @@ type EntityInfo = { */ export class DefaultTechDocsCollator { public readonly type: string = 'techdocs'; - public readonly visibilityPermission = catalogEntityReadPermission; + public readonly visibilityPermission: Permission = + catalogEntityReadPermission; private constructor( private readonly legacyPathCasing: boolean, diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts index 291f21a0d1..659f4783ce 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts @@ -203,7 +203,7 @@ describe('DefaultTechDocsCollatorFactory', () => { const pipeline = TestPipeline.withSubject(collator); const { documents } = await pipeline.execute(); - // Only 1 entity with TechDocs configured multipled by 3 pages. + // Only 1 entity with TechDocs configured multiplied by 3 pages. expect(documents).toHaveLength(3); }); diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts index 41d637e319..915b027854 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts @@ -31,6 +31,7 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common'; +import { Permission } from '@backstage/plugin-permission-common'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { TechDocsDocument } from '@backstage/plugin-techdocs-node'; import unescape from 'lodash/unescape'; @@ -74,7 +75,8 @@ type EntityInfo = { */ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { public readonly type: string = 'techdocs'; - public readonly visibilityPermission = catalogEntityReadPermission; + public readonly visibilityPermission: Permission = + catalogEntityReadPermission; private discovery: PluginEndpointDiscovery; private locationTemplate: string; diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 2e707c9513..f3f9892f44 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -21,7 +21,6 @@ import { PluginEndpointDiscovery, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; -import { NotModifiedError } from '@backstage/errors'; import { GeneratorBuilder, PreparerBuilder, @@ -31,12 +30,7 @@ import express, { Response } from 'express'; import request from 'supertest'; import { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer'; import { CachedEntityLoader } from './CachedEntityLoader'; -import { - createEventStream, - createHttpResponse, - createRouter, - RouterOptions, -} from './router'; +import { createEventStream, createRouter, RouterOptions } from './router'; import { TechDocsCache } from '../cache'; import { DocsBuildStrategy } from './DocsBuildStrategy'; @@ -160,120 +154,6 @@ describe('createRouter', () => { }); describe('GET /sync/:namespace/:kind/:name', () => { - describe('accept application/json', () => { - it('should return not found if entity is not found', async () => { - const app = await createApp(outOfTheBoxOptions); - - MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined); - - const response = await request(app) - .get('/sync/default/Component/test') - .send(); - - expect(response.status).toBe(404); - }); - - it('should return not found if entity has no uid', async () => { - const app = await createApp(outOfTheBoxOptions); - - MockCachedEntityLoader.prototype.load.mockResolvedValue( - entityWithoutMetadata, - ); - - const response = await request(app) - .get('/sync/default/Component/test') - .send(); - - expect(response.status).toBe(404); - }); - - it('should not check for an update when shouldBuild returns false', async () => { - const app = await createApp(outOfTheBoxOptions); - - docsBuildStrategy.shouldBuild.mockResolvedValue(false); - MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); - MockDocsSynchronizer.prototype.doCacheSync.mockImplementation( - async ({ responseHandler }) => - responseHandler.finish({ updated: false }), - ); - - const response = await request(app) - .get('/sync/default/Component/test') - .send(); - - expect(response.status).toBe(304); - }); - - it('should error if build is required and is missing preparer', async () => { - const app = await createApp(recommendedOptions); - - docsBuildStrategy.shouldBuild.mockResolvedValue(true); - MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); - - const response = await request(app) - .get('/sync/default/Component/test') - .send(); - - expect(response.status).toBe(500); - expect(response.text).toMatch( - /Invalid configuration\. docsBuildStrategy\.shouldBuild returned 'true', but no 'preparer' was provided to the router initialization./, - ); - - expect(MockDocsSynchronizer.prototype.doSync).toBeCalledTimes(0); - }); - - it('should execute synchronization', async () => { - const app = await createApp(outOfTheBoxOptions); - - docsBuildStrategy.shouldBuild.mockResolvedValue(true); - MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); - MockDocsSynchronizer.prototype.doSync.mockImplementation( - async ({ responseHandler }) => - responseHandler.finish({ updated: true }), - ); - - await request(app).get('/sync/default/Component/test').send(); - - expect(MockDocsSynchronizer.prototype.doSync).toBeCalledTimes(1); - expect(MockDocsSynchronizer.prototype.doSync).toBeCalledWith({ - responseHandler: { - log: expect.any(Function), - error: expect.any(Function), - finish: expect.any(Function), - }, - entity, - generators, - preparers, - }); - }); - - it('should return on updated', async () => { - const app = await createApp(outOfTheBoxOptions); - - docsBuildStrategy.shouldBuild.mockResolvedValue(true); - MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); - MockDocsSynchronizer.prototype.doSync.mockImplementation( - async ({ responseHandler }) => { - const { log, finish } = responseHandler; - - log('Some log'); - - finish({ updated: true }); - }, - ); - - const response = await request(app) - .get('/sync/default/Component/test') - .send(); - - expect(response.status).toBe(201); - expect(response.get('content-type')).toMatch(/application\/json/); - expect(response.text).toEqual( - '{"message":"Docs updated or did not need updating"}', - ); - }); - }); - describe('accept text/event-stream', () => { it('should return not found if entity is not found', async () => { const app = await createApp(outOfTheBoxOptions); @@ -559,48 +439,3 @@ data: {"updated":true} expect(res.end).toBeCalledTimes(1); }); }); - -describe('createHttpResponse', () => { - const res: jest.Mocked = { - status: jest.fn(), - json: jest.fn(), - } as any; - - let handlers: DocsSynchronizerSyncOpts; - - beforeEach(() => { - res.status.mockImplementation(() => res); - handlers = createHttpResponse(res); - }); - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return CREATED if updated', async () => { - handlers.finish({ updated: true }); - - expect(res.status).toBeCalledTimes(1); - expect(res.status).toBeCalledWith(201); - - expect(res.json).toBeCalledTimes(1); - expect(res.json).toBeCalledWith({ - message: 'Docs updated or did not need updating', - }); - }); - - it('should return NOT_MODIFIED if not updated', async () => { - expect(() => handlers.finish({ updated: false })).toThrowError( - NotModifiedError, - ); - }); - - it('should throw custom error', async () => { - expect(() => handlers.error(new Error('Some Error'))).toThrowError( - /Some Error/, - ); - }); - - it('should ignore logs', async () => { - expect(() => handlers.log('Some Message')).not.toThrow(); - }); -}); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index e404c1b6d2..148143e460 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -20,7 +20,7 @@ import { import { CatalogClient } from '@backstage/catalog-client'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { NotFoundError, NotModifiedError } from '@backstage/errors'; +import { NotFoundError } from '@backstage/errors'; import { GeneratorBuilder, getLocationForEntity, @@ -208,15 +208,7 @@ export async function createRouter( throw new NotFoundError('Entity metadata UID missing'); } - let responseHandler: DocsSynchronizerSyncOpts; - if (req.header('accept') !== 'text/event-stream') { - console.warn( - "The call to /sync/:namespace/:kind/:name wasn't done by an EventSource. This behavior is deprecated and will be removed soon. Make sure to update the @backstage/plugin-techdocs package in the frontend to the latest version.", - ); - responseHandler = createHttpResponse(res); - } else { - responseHandler = createEventStream(res); - } + const responseHandler: DocsSynchronizerSyncOpts = createEventStream(res); // By default, techdocs-backend will only try to build documentation for an entity if techdocs.builder is set to // 'local'. If set to 'external', it will assume that an external process (e.g. CI/CD pipeline @@ -345,26 +337,3 @@ export function createEventStream( }, }; } - -/** - * @deprecated use event-stream implementation of the sync endpoint - */ -export function createHttpResponse( - res: Response, -): DocsSynchronizerSyncOpts { - return { - log: () => {}, - error: e => { - throw e; - }, - finish: ({ updated }) => { - if (!updated) { - throw new NotModifiedError(); - } - - res - .status(201) - .json({ message: 'Docs updated or did not need updating' }); - }, - }; -} diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index 4ab4c084e0..f0ee42ba4a 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -60,11 +60,10 @@ export async function startStandaloneServer( logger.debug('Creating application...'); const preparers = new Preparers(); - const directoryPreparer = new DirectoryPreparer( - config, + const directoryPreparer = DirectoryPreparer.fromConfig(config, { logger, - mockUrlReader, - ); + reader: mockUrlReader, + }); preparers.register('dir', directoryPreparer); const dockerClient = new Docker(); diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 4f98aa3ea2..7e1461a8e7 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-techdocs-node +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search-common@0.3.3-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 700d93ff41: - `DirectoryPreparer` now uses private constructor. Use static fromConfig method to instantiate. + - `UrlPreparer` now uses private constructor. Use static fromConfig method to instantiate. + +### Patch Changes + +- f24ef7864e: Minor typo fixes +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + - @backstage/plugin-search-common@0.3.2 + ## 0.11.12 ### Patch Changes diff --git a/plugins/techdocs-node/__mocks__/@azure/storage-blob.ts b/plugins/techdocs-node/__mocks__/@azure/storage-blob.ts deleted file mode 100644 index 1d63e7266a..0000000000 --- a/plugins/techdocs-node/__mocks__/@azure/storage-blob.ts +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2020 The 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 { - BlobUploadCommonResponse, - ContainerGetPropertiesResponse, -} from '@azure/storage-blob'; -import { EventEmitter } from 'events'; -import { IStorageFilesMock } from '../../src/testUtils/types'; - -const storage = global.storageFilesMock as IStorageFilesMock; - -export class BlockBlobClient { - private readonly blobName; - - constructor(blobName: string) { - this.blobName = blobName; - } - - uploadFile(source: string): Promise { - storage.writeFile(this.blobName, source); - return Promise.resolve({ - _response: { - request: { - url: `https://example.blob.core.windows.net`, - } as any, - status: 200, - headers: {} as any, - }, - }); - } - - exists() { - return storage.fileExists(this.blobName); - } - - download() { - const emitter = new EventEmitter(); - setTimeout(() => { - if (storage.fileExists(this.blobName)) { - emitter.emit('data', storage.readFile(this.blobName)); - emitter.emit('end'); - } else { - emitter.emit( - 'error', - new Error(`The file ${this.blobName} does not exist!`), - ); - } - }, 0); - return Promise.resolve({ - readableStreamBody: emitter, - }); - } -} - -class BlockBlobClientFailUpload extends BlockBlobClient { - uploadFile(): Promise { - return Promise.resolve({ - _response: { - request: { - url: `https://example.blob.core.windows.net`, - } as any, - status: 500, - headers: {} as any, - }, - }); - } -} - -class ContainerClientIterator { - private containerName: string; - - constructor(containerName) { - this.containerName = containerName; - } - - async next() { - if ( - this.containerName === 'delete_stale_files_success' || - this.containerName === 'delete_stale_files_error' - ) { - return { - value: { - segment: { - blobItems: [{ name: `stale_file.png` }], - }, - }, - }; - } - return { - value: { - segment: { - blobItems: [], - }, - }, - }; - } -} - -export class ContainerClient { - getProperties(): Promise { - return Promise.resolve({ - _response: { - request: { - url: `https://example.blob.core.windows.net`, - } as any, - status: 200, - headers: {} as any, - parsedHeaders: {}, - }, - }); - } - - getBlockBlobClient(blobName: string) { - return new BlockBlobClient(blobName); - } - - listBlobsFlat() { - return { - byPage: () => { - return new ContainerClientIterator(this.containerName); - }, - }; - } - - deleteBlob() { - if (this.containerName === 'delete_stale_files_error') { - throw new Error('Message'); - } - } -} - -class ContainerClientFailGetProperties extends ContainerClient { - getProperties(): Promise { - return Promise.resolve({ - _response: { - request: { - url: `https://example.blob.core.windows.net`, - } as any, - status: 404, - headers: {} as any, - parsedHeaders: {}, - }, - }); - } -} - -class ContainerClientFailUpload extends ContainerClient { - getBlockBlobClient(blobName: string) { - return new BlockBlobClientFailUpload(blobName); - } -} - -export class BlobServiceClient { - private readonly url; - private readonly credential; - - constructor(url: string, credential?: StorageSharedKeyCredential) { - storage.emptyFiles(); - this.url = url; - this.credential = credential; - } - - getContainerClient(containerName: string) { - if (containerName === 'bad_container') { - return new ContainerClientFailGetProperties(); - } - if (this.credential.accountName === 'bad_account_credentials') { - return new ContainerClientFailUpload(); - } - return new ContainerClient(); - } -} - -export class StorageSharedKeyCredential { - private readonly accountName; - private readonly accountKey; - - constructor(accountName: string, accountKey: string) { - this.accountName = accountName; - this.accountKey = accountKey; - } -} diff --git a/plugins/techdocs-node/__mocks__/@google-cloud/storage.ts b/plugins/techdocs-node/__mocks__/@google-cloud/storage.ts deleted file mode 100644 index e87ccb69c1..0000000000 --- a/plugins/techdocs-node/__mocks__/@google-cloud/storage.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020 The 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 { Readable } from 'stream'; -import { IStorageFilesMock } from '../../src/testUtils/types'; - -const storage = global.storageFilesMock as IStorageFilesMock; - -class GCSFile { - private readonly path: string; - - constructor(path: string) { - this.path = path; - } - - exists() { - return new Promise(async (resolve, reject) => { - if (storage.fileExists(this.path)) { - resolve([true]); - } else { - reject(); - } - }); - } - - createReadStream() { - const readable = new Readable(); - readable._read = () => {}; - - process.nextTick(() => { - if (storage.fileExists(this.path)) { - if (readable.eventNames().includes('pipe')) { - readable.emit('pipe'); - } - readable.emit('data', storage.readFile(this.path)); - readable.emit('end'); - } else { - readable.emit( - 'error', - new Error(`The file ${this.path} does not exist!`), - ); - } - }); - - return readable; - } - - delete() { - return Promise.resolve(); - } -} - -class Bucket { - private readonly bucketName; - - constructor(bucketName: string) { - this.bucketName = bucketName; - } - - async getMetadata() { - if (this.bucketName === 'bad_bucket_name') { - throw Error('Bucket does not exist'); - } - return ''; - } - - upload(source: string, { destination }) { - return new Promise(async resolve => { - storage.writeFile(destination, source); - resolve(null); - }); - } - - file(destinationFilePath: string) { - if (this.bucketName === 'delete_stale_files_error') { - throw Error('Message'); - } - return new GCSFile(destinationFilePath); - } - - getFilesStream() { - const readable = new Readable(); - readable._read = () => {}; - - process.nextTick(() => { - if ( - this.bucketName === 'delete_stale_files_success' || - this.bucketName === 'delete_stale_files_error' - ) { - readable.emit('data', { name: 'stale-file.png' }); - } - readable.emit('end'); - }); - - return readable; - } -} - -export class Storage { - constructor() { - storage.emptyFiles(); - } - - bucket(bucketName) { - return new Bucket(bucketName); - } -} diff --git a/plugins/techdocs-node/__mocks__/@trendyol-js/openstack-swift-sdk.ts b/plugins/techdocs-node/__mocks__/@trendyol-js/openstack-swift-sdk.ts deleted file mode 100644 index 0f11950a66..0000000000 --- a/plugins/techdocs-node/__mocks__/@trendyol-js/openstack-swift-sdk.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import fs from 'fs-extra'; -import os from 'os'; -import path from 'path'; -import { - ContainerMetaResponse, - DownloadResponse, - NotFound, - ObjectMetaResponse, - UploadResponse, -} from '@trendyol-js/openstack-swift-sdk'; -import { Stream, Readable } from 'stream'; - -const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; - -const checkFileExists = async (Key: string): Promise => { - // Key will always have / as file separator irrespective of OS since cloud providers expects /. - // Normalize Key to OS specific path before checking if file exists. - const filePath = path.join(rootDir, Key); - - try { - await fs.access(filePath, fs.constants.F_OK); - return true; - } catch (err) { - return false; - } -}; - -const streamToBuffer = (stream: Stream | Readable): Promise => { - return new Promise((resolve, reject) => { - try { - const chunks: any[] = []; - stream.on('data', chunk => chunks.push(chunk)); - stream.on('error', reject); - stream.on('end', () => resolve(Buffer.concat(chunks))); - } catch (e) { - throw new Error(`Unable to parse the response data ${e.message}`); - } - }); -}; - -export class SwiftClient { - async getMetadata(_containerName: string, file: string) { - const fileExists = await checkFileExists(file); - if (fileExists) { - return new ObjectMetaResponse({ - fullPath: file, - }); - } - return new NotFound(); - } - - async getContainerMetadata(containerName: string) { - if (containerName === 'mock') { - return new ContainerMetaResponse({ - size: 10, - }); - } - return new NotFound(); - } - - async upload(_containerName: string, destination: string, stream: Readable) { - try { - const filePath = path.join(rootDir, destination); - const fileBuffer = await streamToBuffer(stream); - - await fs.writeFile(filePath, fileBuffer); - const fileExists = await checkFileExists(destination); - - if (fileExists) { - return new UploadResponse(filePath); - } - const errorMessage = `Unable to upload file(s) to OpenStack Swift.`; - throw new Error(errorMessage); - } catch (error) { - const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${error}`; - throw new Error(errorMessage); - } - } - - async download(_containerName: string, file: string) { - const filePath = path.join(rootDir, file); - const fileExists = await checkFileExists(file); - if (!fileExists) { - return new NotFound(); - } - return new DownloadResponse([], fs.createReadStream(filePath)); - } -} diff --git a/plugins/techdocs-node/__mocks__/aws-sdk.ts b/plugins/techdocs-node/__mocks__/aws-sdk.ts deleted file mode 100644 index ebb3c6840b..0000000000 --- a/plugins/techdocs-node/__mocks__/aws-sdk.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2020 The 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 { EventEmitter } from 'events'; -import { ReadStream } from 'fs'; -import { IStorageFilesMock } from '../src/testUtils/types'; - -export { Credentials } from 'aws-sdk'; - -const storage = global.storageFilesMock as IStorageFilesMock; - -export class S3 { - constructor() { - storage.emptyFiles(); - } - - headObject({ Key }: { Key: string }) { - return { - promise: async () => { - if (!storage.fileExists(Key)) { - throw new Error('File does not exist'); - } - }, - }; - } - - getObject({ Key }: { Key: string }) { - return { - promise: async () => storage.fileExists(Key), - createReadStream: () => { - const emitter = new EventEmitter(); - process.nextTick(() => { - if (storage.fileExists(Key)) { - emitter.emit('data', Buffer.from(storage.readFile(Key))); - emitter.emit('end'); - } else { - emitter.emit('error', new Error(`The file ${Key} does not exist!`)); - } - }); - return emitter; - }, - }; - } - - headBucket({ Bucket }) { - return { - promise: async () => { - if (Bucket === 'errorBucket') { - throw new Error('Bucket does not exist'); - } - return {}; - }, - }; - } - - upload({ Key, Body }: { Key: string; Body: ReadStream }) { - return { - promise: () => - new Promise(async resolve => { - const chunks = []; - Body.on('data', chunk => { - chunks.push(chunk); - }); - Body.once('end', () => { - storage.writeFile(Key, Buffer.concat(chunks)); - resolve(null); - }); - }), - }; - } - - listObjectsV2({ Bucket }) { - return { - promise: () => { - if ( - Bucket === 'delete_stale_files_success' || - Bucket === 'delete_stale_files_error' - ) { - return Promise.resolve({ - Contents: [{ Key: 'stale_file.png' }], - }); - } - return Promise.resolve({}); - }, - }; - } - - deleteObject({ Bucket }) { - return { - promise: () => { - if (Bucket === 'delete_stale_files_error') { - throw new Error('Message'); - } - return Promise.resolve(); - }, - }; - } -} - -export default { - S3, -}; diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index 4c88ab0fc0..350e67e1c3 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -19,8 +19,6 @@ import { Writable } from 'stream'; // @public export class DirectoryPreparer implements PreparerBase { - // @deprecated - constructor(config: Config, _logger: Logger | null, reader: UrlReader); static fromConfig( config: Config, { logger, reader }: PreparerConfig, @@ -250,8 +248,6 @@ export const transformDirLocation: ( // @public export class UrlPreparer implements PreparerBase { - // @deprecated - constructor(reader: UrlReader, logger: Logger); static fromConfig({ reader, logger }: PreparerConfig): UrlPreparer; prepare(entity: Entity, options?: PreparerOptions): Promise; } diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 6db2637180..16861f0b7e 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-node", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.11.12", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -42,12 +42,12 @@ "dependencies": { "@azure/identity": "^2.0.1", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/plugin-search-common": "^0.3.1", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.0", "@google-cloud/storage": "^5.6.0", "@trendyol-js/openstack-swift-sdk": "^0.0.5", "@types/express": "^4.17.6", @@ -64,7 +64,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", @@ -72,10 +72,5 @@ "@types/recursive-readdir": "^2.2.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" - }, - "jest": { - "roots": [ - ".." - ] } } diff --git a/plugins/techdocs-node/src/stages/prepare/dir.test.ts b/plugins/techdocs-node/src/stages/prepare/dir.test.ts index 2bf3fe29fc..829c414ddb 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.test.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.test.ts @@ -52,11 +52,10 @@ const mockUrlReader: jest.Mocked = { describe('directory preparer', () => { it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => { - const directoryPreparer = new DirectoryPreparer( - mockConfig, + const directoryPreparer = DirectoryPreparer.fromConfig(mockConfig, { logger, - mockUrlReader, - ); + reader: mockUrlReader, + }); const mockEntity = createMockEntity({ 'backstage.io/managed-by-location': @@ -69,11 +68,10 @@ describe('directory preparer', () => { }); it('should reject when techdocs-ref is absolute', async () => { - const directoryPreparer = new DirectoryPreparer( - mockConfig, + const directoryPreparer = DirectoryPreparer.fromConfig(mockConfig, { logger, - mockUrlReader, - ); + reader: mockUrlReader, + }); const mockEntity = createMockEntity({ 'backstage.io/managed-by-location': @@ -87,11 +85,10 @@ describe('directory preparer', () => { }); it('should reject when managed-by-location has an unknown type', async () => { - const directoryPreparer = new DirectoryPreparer( - mockConfig, + const directoryPreparer = DirectoryPreparer.fromConfig(mockConfig, { logger, - mockUrlReader, - ); + reader: mockUrlReader, + }); const mockEntity = createMockEntity({ 'backstage.io/managed-by-location': diff --git a/plugins/techdocs-node/src/stages/prepare/dir.ts b/plugins/techdocs-node/src/stages/prepare/dir.ts index 42bfa5b903..c0298c0dab 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.ts @@ -39,12 +39,6 @@ export class DirectoryPreparer implements PreparerBase { private readonly scmIntegrations: ScmIntegrationRegistry; private readonly reader: UrlReader; - /** @deprecated use static fromConfig method instead */ - constructor(config: Config, _logger: Logger | null, reader: UrlReader) { - this.reader = reader; - this.scmIntegrations = ScmIntegrations.fromConfig(config); - } - /** * Returns a directory preparer instance * @param config - A backstage config @@ -57,6 +51,15 @@ export class DirectoryPreparer implements PreparerBase { return new DirectoryPreparer(config, logger, reader); } + private constructor( + config: Config, + _logger: Logger | null, + reader: UrlReader, + ) { + this.reader = reader; + this.scmIntegrations = ScmIntegrations.fromConfig(config); + } + /** {@inheritDoc PreparerBase.prepare} */ async prepare( entity: Entity, diff --git a/plugins/techdocs-node/src/stages/prepare/preparers.ts b/plugins/techdocs-node/src/stages/prepare/preparers.ts index 575294e91a..a8c175125a 100644 --- a/plugins/techdocs-node/src/stages/prepare/preparers.ts +++ b/plugins/techdocs-node/src/stages/prepare/preparers.ts @@ -44,18 +44,17 @@ export class Preparers implements PreparerBuilder { ): Promise { const preparers = new Preparers(); - const urlPreparer = new UrlPreparer(reader, logger); + const urlPreparer = UrlPreparer.fromConfig({ reader, logger }); preparers.register('url', urlPreparer); /** * Dir preparer is a syntactic sugar for users to define techdocs-ref annotation. * When using dir preparer, the docs will be fetched using URL Reader. */ - const directoryPreparer = new DirectoryPreparer( - backstageConfig, + const directoryPreparer = DirectoryPreparer.fromConfig(backstageConfig, { logger, reader, - ); + }); preparers.register('dir', directoryPreparer); return preparers; diff --git a/plugins/techdocs-node/src/stages/prepare/url.ts b/plugins/techdocs-node/src/stages/prepare/url.ts index 8026dca41e..0d2907d7d1 100644 --- a/plugins/techdocs-node/src/stages/prepare/url.ts +++ b/plugins/techdocs-node/src/stages/prepare/url.ts @@ -34,12 +34,6 @@ export class UrlPreparer implements PreparerBase { private readonly logger: Logger; private readonly reader: UrlReader; - /** @deprecated use static fromConfig method instead */ - constructor(reader: UrlReader, logger: Logger) { - this.logger = logger; - this.reader = reader; - } - /** * Returns a directory preparer instance * @param config - A URL preparer config containing the a logger and reader @@ -48,6 +42,11 @@ export class UrlPreparer implements PreparerBase { return new UrlPreparer(reader, logger); } + private constructor(reader: UrlReader, logger: Logger) { + this.logger = logger; + this.reader = reader; + } + /** {@inheritDoc PreparerBase.prepare} */ async prepare( entity: Entity, diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index 326e587a83..6372093c4b 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -21,12 +21,112 @@ import express from 'express'; import request from 'supertest'; import mockFs from 'mock-fs'; import path from 'path'; -import fs from 'fs-extra'; +import fs, { ReadStream } from 'fs-extra'; +import { EventEmitter } from 'events'; import { AwsS3Publish } from './awsS3'; +import { storageRootDir } from '../../testUtils/StorageFilesMock'; -// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock aws-sdk client library +jest.mock('aws-sdk', () => { + const { StorageFilesMock } = require('../../testUtils/StorageFilesMock'); + const storage = new StorageFilesMock(); -const rootDir = (global as any).rootDir; // Set by setupTests.ts + return { + __esModule: true, + Credentials: jest.requireActual('aws-sdk').Credentials, + default: { + S3: class { + constructor() { + storage.emptyFiles(); + } + + headObject({ Key }: { Key: string }) { + return { + promise: async () => { + if (!storage.fileExists(Key)) { + throw new Error('File does not exist'); + } + }, + }; + } + + getObject({ Key }: { Key: string }) { + return { + promise: async () => storage.fileExists(Key), + createReadStream: () => { + const emitter = new EventEmitter(); + process.nextTick(() => { + if (storage.fileExists(Key)) { + emitter.emit('data', Buffer.from(storage.readFile(Key))); + emitter.emit('end'); + } else { + emitter.emit( + 'error', + new Error(`The file ${Key} does not exist!`), + ); + } + }); + return emitter; + }, + }; + } + + headBucket({ Bucket }: { Bucket: string }) { + return { + promise: async () => { + if (Bucket === 'errorBucket') { + throw new Error('Bucket does not exist'); + } + return {}; + }, + }; + } + + upload({ Key, Body }: { Key: string; Body: ReadStream }) { + return { + promise: () => + new Promise(async resolve => { + const chunks = new Array(); + Body.on('data', chunk => { + chunks.push(chunk as Buffer); + }); + Body.once('end', () => { + storage.writeFile(Key, Buffer.concat(chunks)); + resolve(null); + }); + }), + }; + } + + listObjectsV2({ Bucket }: { Bucket: string }) { + return { + promise: () => { + if ( + Bucket === 'delete_stale_files_success' || + Bucket === 'delete_stale_files_error' + ) { + return Promise.resolve({ + Contents: [{ Key: 'stale_file.png' }], + }); + } + return Promise.resolve({}); + }, + }; + } + + deleteObject({ Bucket }: { Bucket: string }) { + return { + promise: () => { + if (Bucket === 'delete_stale_files_error') { + throw new Error('Message'); + } + return Promise.resolve(); + }, + }; + } + }, + }, + }; +}); const getEntityRootDir = (entity: Entity) => { const { @@ -34,7 +134,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name); + return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name); }; const logger = getVoidLogger(); @@ -54,7 +154,6 @@ const createPublisherFromConfig = ({ } = {}) => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'awsS3', awsS3: { @@ -214,7 +313,7 @@ describe('AwsS3Publish', () => { it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( - rootDir, + storageRootDir, 'wrong', 'path', 'to', diff --git a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts index 3e5e420e4b..6d5848b0d8 100644 --- a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts @@ -23,10 +23,207 @@ import mockFs from 'mock-fs'; import path from 'path'; import fs from 'fs-extra'; import { AzureBlobStoragePublish } from './azureBlobStorage'; +import { EventEmitter } from 'events'; +import { + BlobUploadCommonResponse, + ContainerGetPropertiesResponse, +} from '@azure/storage-blob'; +import { + storageRootDir, + StorageFilesMock, +} from '../../testUtils/StorageFilesMock'; -// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock Azure client library +jest.mock('@azure/identity', () => ({ + __esModule: true, + DefaultAzureCredential: class {}, +})); -const rootDir = (global as any).rootDir; // Set by setupTests.ts +jest.mock('@azure/storage-blob', () => { + class BlockBlobClient { + constructor( + private readonly blobName: string, + private readonly storage: StorageFilesMock, + ) {} + + uploadFile(source: string): Promise { + this.storage.writeFile(this.blobName, source); + return Promise.resolve({ + _response: { + request: { + url: `https://example.blob.core.windows.net`, + } as any, + status: 200, + headers: {} as any, + }, + }); + } + + exists() { + return this.storage.fileExists(this.blobName); + } + + download() { + const emitter = new EventEmitter(); + setTimeout(() => { + if (this.storage.fileExists(this.blobName)) { + emitter.emit('data', this.storage.readFile(this.blobName)); + emitter.emit('end'); + } else { + emitter.emit( + 'error', + new Error(`The file ${this.blobName} does not exist!`), + ); + } + }, 0); + return Promise.resolve({ + readableStreamBody: emitter, + }); + } + } + + class BlockBlobClientFailUpload extends BlockBlobClient { + uploadFile(): Promise { + return Promise.resolve({ + _response: { + request: { + url: `https://example.blob.core.windows.net`, + } as any, + status: 500, + headers: {} as any, + }, + }); + } + } + + class ContainerClientIterator { + private containerName: string; + + constructor(containerName: string) { + this.containerName = containerName; + } + + async next() { + if ( + this.containerName === 'delete_stale_files_success' || + this.containerName === 'delete_stale_files_error' + ) { + return { + value: { + segment: { + blobItems: [{ name: `stale_file.png` }], + }, + }, + }; + } + return { + value: { + segment: { + blobItems: [], + }, + }, + }; + } + } + + class ContainerClient { + constructor( + private readonly containerName: string, + protected readonly storage: StorageFilesMock, + ) {} + + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: { + url: `https://example.blob.core.windows.net`, + } as any, + status: 200, + headers: {} as any, + parsedHeaders: {}, + }, + }); + } + + getBlockBlobClient(blobName: string) { + return new BlockBlobClient(blobName, this.storage); + } + + listBlobsFlat() { + return { + byPage: () => { + return new ContainerClientIterator(this.containerName); + }, + }; + } + + deleteBlob() { + if (this.containerName === 'delete_stale_files_error') { + throw new Error('Message'); + } + } + } + + class ContainerClientFailGetProperties extends ContainerClient { + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: { + url: `https://example.blob.core.windows.net`, + } as any, + status: 404, + headers: {} as any, + parsedHeaders: {}, + }, + }); + } + } + + class ContainerClientFailUpload extends ContainerClient { + getBlockBlobClient(blobName: string) { + return new BlockBlobClientFailUpload(blobName, this.storage); + } + } + + class BlobServiceClient { + storage = new StorageFilesMock(); + + constructor( + public readonly url: string, + private readonly credential?: StorageSharedKeyCredential, + ) { + this.storage.emptyFiles(); + } + + getContainerClient(containerName: string) { + if (containerName === 'bad_container') { + return new ContainerClientFailGetProperties( + containerName, + this.storage, + ); + } + if (this.credential?.accountName === 'bad_account_credentials') { + return new ContainerClientFailUpload(containerName, this.storage); + } + return new ContainerClient(containerName, this.storage); + } + } + + class StorageSharedKeyCredential { + readonly accountName; + readonly accountKey; + + constructor(accountName: string, accountKey: string) { + this.accountName = accountName; + this.accountKey = accountKey; + } + } + + return { + __esModule: true, + BlobServiceClient, + StorageSharedKeyCredential, + }; +}); const getEntityRootDir = (entity: Entity) => { const { @@ -34,7 +231,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name); + return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name); }; const logger = getVoidLogger(); @@ -51,7 +248,6 @@ const createPublisherFromConfig = ({ } = {}) => { const config = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'azureBlobStorage', azureBlobStorage: { @@ -179,7 +375,7 @@ describe('AzureBlobStoragePublish', () => { it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( - rootDir, + storageRootDir, 'wrong', 'path', 'to', diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 6dac092cec..b347f185c6 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -22,11 +22,119 @@ import request from 'supertest'; import mockFs from 'mock-fs'; import path from 'path'; import fs from 'fs-extra'; +import { Readable } from 'stream'; import { GoogleGCSPublish } from './googleStorage'; +import { + storageRootDir, + StorageFilesMock, +} from '../../testUtils/StorageFilesMock'; -// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock Google Cloud Storage client library +jest.mock('@google-cloud/storage', () => { + class GCSFile { + constructor( + private readonly filePath: string, + private readonly storage: StorageFilesMock, + ) {} -const rootDir = (global as any).rootDir; // Set by setupTests.ts + exists() { + return new Promise(async (resolve, reject) => { + if (this.storage.fileExists(this.filePath)) { + resolve([true]); + } else { + reject(); + } + }); + } + + createReadStream() { + const readable = new Readable(); + readable._read = () => {}; + + process.nextTick(() => { + if (this.storage.fileExists(this.filePath)) { + if (readable.eventNames().includes('pipe')) { + readable.emit('pipe'); + } + readable.emit('data', this.storage.readFile(this.filePath)); + readable.emit('end'); + } else { + readable.emit( + 'error', + new Error(`The file ${this.filePath} does not exist!`), + ); + } + }); + + return readable; + } + + delete() { + return Promise.resolve(); + } + } + + class Bucket { + constructor( + private readonly bucketName: string, + private readonly storage: StorageFilesMock, + ) {} + + async getMetadata() { + if (this.bucketName === 'bad_bucket_name') { + throw Error('Bucket does not exist'); + } + return ''; + } + + upload(source: string, { destination }: { destination: string }) { + return new Promise(async resolve => { + this.storage.writeFile(destination, source); + resolve(null); + }); + } + + file(destinationFilePath: string) { + if (this.bucketName === 'delete_stale_files_error') { + throw Error('Message'); + } + return new GCSFile(destinationFilePath, this.storage); + } + + getFilesStream() { + const readable = new Readable(); + readable._read = () => {}; + + process.nextTick(() => { + if ( + this.bucketName === 'delete_stale_files_success' || + this.bucketName === 'delete_stale_files_error' + ) { + readable.emit('data', { name: 'stale-file.png' }); + } + readable.emit('end'); + }); + + return readable; + } + } + + class Storage { + storage = new StorageFilesMock(); + + constructor() { + this.storage.emptyFiles(); + } + + bucket(bucketName: string) { + return new Bucket(bucketName, this.storage); + } + } + + return { + __esModule: true, + Storage, + }; +}); const getEntityRootDir = (entity: Entity) => { const { @@ -34,7 +142,7 @@ const getEntityRootDir = (entity: Entity) => { metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name); + return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name); }; const logger = getVoidLogger(); @@ -52,7 +160,6 @@ const createPublisherFromConfig = ({ } = {}) => { const config = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'googleGcs', googleGcs: { @@ -194,7 +301,7 @@ describe('GoogleGCSPublish', () => { it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( - rootDir, + storageRootDir, 'wrong', 'path', 'to', diff --git a/plugins/techdocs-node/src/stages/publish/helpers.test.ts b/plugins/techdocs-node/src/stages/publish/helpers.test.ts index 7acdb47ea7..ceb1b02a20 100644 --- a/plugins/techdocs-node/src/stages/publish/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/publish/helpers.test.ts @@ -216,7 +216,7 @@ describe('getCloudPathForLocalPath', () => { ); }); - it('should add trailing seperator to root directory', () => { + it('should add trailing separator to root directory', () => { const localPath = 'index/html'; const rootPath = 'backstage-data/techdocs'; const remoteBucket = getCloudPathForLocalPath( @@ -230,7 +230,7 @@ describe('getCloudPathForLocalPath', () => { ); }); - it('should remove leading seperator from root directory', () => { + it('should remove leading separator from root directory', () => { const localPath = 'index/html'; const rootPath = '/backstage-data/techdocs/'; const remoteBucket = getCloudPathForLocalPath( @@ -244,7 +244,7 @@ describe('getCloudPathForLocalPath', () => { ); }); - it('should ignore seperator if root directory is explicitly defined', () => { + it('should ignore separator if root directory is explicitly defined', () => { const localPath = 'index/html'; const rootPath = '/'; const remoteBucket = getCloudPathForLocalPath( diff --git a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts index aeb4e120f5..51b67d6167 100644 --- a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts +++ b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts @@ -24,12 +24,106 @@ import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; import mockFs from 'mock-fs'; -import os from 'os'; +import fs from 'fs-extra'; import path from 'path'; import { OpenStackSwiftPublish } from './openStackSwift'; import { PublisherBase, TechDocsMetadata } from './types'; +import { storageRootDir } from '../../testUtils/StorageFilesMock'; +import { Stream, Readable } from 'stream'; -// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock @trendyol-js/openstack-swift-sdk client library +jest.mock('@trendyol-js/openstack-swift-sdk', () => { + const { + ContainerMetaResponse, + DownloadResponse, + NotFound, + ObjectMetaResponse, + UploadResponse, + }: typeof import('@trendyol-js/openstack-swift-sdk') = jest.requireActual( + '@trendyol-js/openstack-swift-sdk', + ); + + const checkFileExists = async (Key: string): Promise => { + // Key will always have / as file separator irrespective of OS since cloud providers expects /. + // Normalize Key to OS specific path before checking if file exists. + const filePath = path.join(storageRootDir, Key); + + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + return false; + } + }; + + const streamToBuffer = (stream: Stream | Readable): Promise => { + return new Promise((resolve, reject) => { + try { + const chunks: any[] = []; + stream.on('data', chunk => chunks.push(chunk)); + stream.on('error', reject); + stream.on('end', () => resolve(Buffer.concat(chunks))); + } catch (e) { + throw new Error(`Unable to parse the response data ${e.message}`); + } + }); + }; + + return { + __esModule: true, + SwiftClient: class { + async getMetadata(_containerName: string, file: string) { + const fileExists = await checkFileExists(file); + if (fileExists) { + return new ObjectMetaResponse({ + fullPath: file, + }); + } + return new NotFound(); + } + + async getContainerMetadata(containerName: string) { + if (containerName === 'mock') { + return new ContainerMetaResponse({ + size: 10, + }); + } + return new NotFound(); + } + + async upload( + _containerName: string, + destination: string, + stream: Readable, + ) { + try { + const filePath = path.join(storageRootDir, destination); + const fileBuffer = await streamToBuffer(stream); + + await fs.writeFile(filePath, fileBuffer); + const fileExists = await checkFileExists(destination); + + if (fileExists) { + return new UploadResponse(filePath); + } + const errorMessage = `Unable to upload file(s) to OpenStack Swift.`; + throw new Error(errorMessage); + } catch (error) { + const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${error}`; + throw new Error(errorMessage); + } + } + + async download(_containerName: string, file: string) { + const filePath = path.join(storageRootDir, file); + const fileExists = await checkFileExists(file); + if (!fileExists) { + return new NotFound(); + } + return new DownloadResponse([], fs.createReadStream(filePath)); + } + }, + }; +}); const createMockEntity = (annotations = {}): Entity => { return { @@ -51,15 +145,13 @@ const createMockEntityName = (): CompoundEntityRef => ({ namespace: 'test-namespace', }); -const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; - const getEntityRootDir = (entity: Entity) => { const { kind, metadata: { namespace, name }, } = entity; - return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name); + return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name); }; const getPosixEntityRootDir = (entity: Entity) => { @@ -84,7 +176,6 @@ beforeEach(() => { mockFs.restore(); const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'openStackSwift', openStackSwift: { @@ -114,7 +205,6 @@ describe('OpenStackSwiftPublish', () => { it('should reject incorrect config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'openStackSwift', openStackSwift: { @@ -181,7 +271,7 @@ describe('OpenStackSwiftPublish', () => { it('should fail to publish a directory', async () => { const wrongPathToGeneratedDirectory = path.join( - rootDir, + storageRootDir, 'wrong', 'path', 'to', diff --git a/plugins/techdocs-node/src/stages/publish/publish.test.ts b/plugins/techdocs-node/src/stages/publish/publish.test.ts index a47da828a7..ab300412a4 100644 --- a/plugins/techdocs-node/src/stages/publish/publish.test.ts +++ b/plugins/techdocs-node/src/stages/publish/publish.test.ts @@ -31,17 +31,18 @@ const discovery: jest.Mocked = { getExternalBaseUrl: jest.fn(), }; +jest.mock('@azure/identity', () => ({ + __esModule: true, + DefaultAzureCredential: class {}, +})); + describe('Publisher', () => { beforeEach(() => { jest.resetModules(); // clear the cache }); it('should create local publisher by default', async () => { - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7007', - }, - }); + const mockConfig = new ConfigReader({}); const publisher = await Publisher.fromConfig(mockConfig, { logger, @@ -53,7 +54,6 @@ describe('Publisher', () => { it('should create local publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'local', }, @@ -70,7 +70,6 @@ describe('Publisher', () => { it('should create google gcs publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'googleGcs', googleGcs: { @@ -91,7 +90,6 @@ describe('Publisher', () => { it('should create AWS S3 publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'awsS3', awsS3: { @@ -115,7 +113,6 @@ describe('Publisher', () => { it('should create Azure Blob Storage publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'azureBlobStorage', azureBlobStorage: { @@ -143,7 +140,6 @@ describe('Publisher', () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'azureBlobStorage', azureBlobStorage: { @@ -166,7 +162,6 @@ describe('Publisher', () => { it('should create Open Stack Swift publisher from config', async () => { const mockConfig = new ConfigReader({ techdocs: { - requestUrl: 'http://localhost:7007', publisher: { type: 'openStackSwift', openStackSwift: { diff --git a/plugins/techdocs-node/src/testUtils/StorageFilesMock.ts b/plugins/techdocs-node/src/testUtils/StorageFilesMock.ts index 4a0e183bdb..dd28110d00 100644 --- a/plugins/techdocs-node/src/testUtils/StorageFilesMock.ts +++ b/plugins/techdocs-node/src/testUtils/StorageFilesMock.ts @@ -19,12 +19,13 @@ import path from 'path'; import fs from 'fs-extra'; import { IStorageFilesMock } from './types'; -const rootDir: string = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; +export const storageRootDir: string = + os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; const encoding = 'utf8'; export class StorageFilesMock implements IStorageFilesMock { - static rootDir = rootDir; + static rootDir = storageRootDir; private files: Record; @@ -37,20 +38,20 @@ export class StorageFilesMock implements IStorageFilesMock { } public fileExists(targetPath: string): boolean { - const filePath = path.join(rootDir, targetPath); + const filePath = path.join(storageRootDir, targetPath); const posixPath = filePath.split(path.posix.sep).join(path.sep); return this.files[posixPath] !== undefined; } public readFile(targetPath: string): Buffer { - const filePath = path.join(rootDir, targetPath); + const filePath = path.join(storageRootDir, targetPath); return Buffer.from(this.files[filePath] ?? '', encoding); } public writeFile(targetPath: string, sourcePath: string): void; public writeFile(targetPath: string, sourceBuffer: Buffer): void; public writeFile(targetPath: string, source: string | Buffer): void { - const filePath = path.join(rootDir, targetPath); + const filePath = path.join(storageRootDir, targetPath); if (typeof source === 'string') { this.files[filePath] = fs.readFileSync(source).toString(encoding); } else { diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 882638caeb..d56b90cd12 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-techdocs +## 1.0.1-next.0 + +### Patch Changes + +- fe53fe97d7: Fix permalink scrolling for anchors where the id starts with a number. +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-search@0.7.5-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + - @backstage/integration-react@1.0.1-next.0 + +## 1.0.0 + +### Major Changes + +- b58c70c223: This package has been promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +### Minor Changes + +- 700d93ff41: Removed deprecated exports, including: + + - deprecated `DocsResultListItem` is now deleted and fully replaced with `TechDocsSearchResultListItem` + - deprecated `TechDocsPage` is now deleted and fully replaced with `TechDocsReaderPage` + - deprecated `TechDocsPageHeader` is now deleted and fully replaced with `TechDocsReaderPageHeader` + - deprecated `TechDocsPageHeaderProps` is now deleted and fully replaced with `TechDocsReaderPageHeaderProps` + - deprecated `TechDocsPageRenderFunction` is now deleted and fully replaced with `TechDocsReaderPageRenderFunction` + - deprecated config `techdocs.requestUrl` is now deleted and fully replaced with the discoveryApi + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- c689d7a94c: Switched to using `CatalogFilterLayout` from `@backstage/plugin-catalog-react`. +- f24ef7864e: Minor typo fixes +- 06af9e8d17: Long sidebars will no longer overflow the footer and will properly show a scrollbar when needed. +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/integration-react@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/plugin-search@0.7.4 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.15.1 ### Patch Changes diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index f7ef6e984a..ecab23e225 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -41,11 +41,6 @@ export type DocsCardGridProps = { entities: Entity[] | undefined; }; -// @public @deprecated (undocumented) -export const DocsResultListItem: ( - props: TechDocsSearchResultListItemProps, -) => JSX.Element; - // @public export const DocsTable: { (props: DocsTableProps): JSX.Element | null; @@ -185,6 +180,7 @@ export type TabsConfig = TabConfig[]; // @public export interface TechDocsApi { + // (undocumented) getApiOrigin(): Promise; // (undocumented) getEntityMetadata( @@ -243,23 +239,9 @@ export type TechDocsMetadata = { site_description: string; }; -// @public @deprecated (undocumented) -export const TechDocsPage: (props: TechDocsReaderPageProps) => JSX.Element; - // @public export const TechdocsPage: () => JSX.Element; -// @public @deprecated (undocumented) -export const TechDocsPageHeader: ( - props: TechDocsReaderPageHeaderProps, -) => JSX.Element; - -// @public @deprecated (undocumented) -export type TechDocsPageHeaderProps = TechDocsReaderPageHeaderProps; - -// @public @deprecated (undocumented) -export type TechDocsPageRenderFunction = TechDocsReaderPageRenderFunction; - // @public export const TechDocsPageWrapper: ( props: TechDocsPageWrapperProps, @@ -348,6 +330,7 @@ export type TechDocsSearchResultListItemProps = { // @public export interface TechDocsStorageApi { + // (undocumented) getApiOrigin(): Promise; // (undocumented) getBaseUrl( diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 5274a31ed0..82ae8dd87a 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -33,13 +33,6 @@ export interface Config { */ legacyUseCaseSensitiveTripletPaths?: boolean; - /** - * @example http://localhost:7007/api/techdocs - * @visibility frontend - * @deprecated - */ - requestUrl?: string; - sanitizer?: { /** * Allows iframe tag only for listed hosts diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index e02e4e1fa1..8ab87deadd 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "0.15.1", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,16 +35,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", - "@backstage/integration-react": "^0.1.25", - "@backstage/plugin-catalog": "^0.10.0", - "@backstage/plugin-catalog-react": "^0.9.0", - "@backstage/plugin-search": "^0.7.3", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", + "@backstage/integration-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-search": "^0.7.5-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -65,10 +64,10 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index b349431b0d..51dd82e643 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -49,9 +49,6 @@ export type SyncResult = 'cached' | 'updated'; * @public */ export interface TechDocsStorageApi { - /** - * Set to techdocs.requestUrl as the URL for techdocs-backend API. - */ getApiOrigin(): Promise; getStorageUrl(): Promise; getBuilder(): Promise; @@ -73,9 +70,6 @@ export interface TechDocsStorageApi { * @public */ export interface TechDocsApi { - /** - * Set to techdocs.requestUrl as the URL for techdocs-backend API. - */ getApiOrigin(): Promise; getTechDocsMetadata(entityId: CompoundEntityRef): Promise; getEntityMetadata( diff --git a/plugins/techdocs/src/client.test.ts b/plugins/techdocs/src/client.test.ts index a88c724cc8..8236b93c55 100644 --- a/plugins/techdocs/src/client.test.ts +++ b/plugins/techdocs/src/client.test.ts @@ -35,9 +35,7 @@ const mockEntity = { describe('TechDocsStorageClient', () => { const mockBaseUrl = 'http://backstage:9191/api/techdocs'; - const configApi = new MockConfigApi({ - techdocs: { requestUrl: 'http://backstage:9191/api/techdocs' }, - }); + const configApi = new MockConfigApi({}); const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); const identityApi: jest.Mocked = { getCredentials: jest.fn(), @@ -223,7 +221,7 @@ describe('TechDocsStorageClient', () => { const promise = storageApi.syncEntityDocs(mockEntity).then(); // flush the event loop - await new Promise(setImmediate); + await new Promise(r => setTimeout(r)); const instance = MockedEventSource.mock .instances[0] as jest.Mocked; @@ -250,7 +248,7 @@ describe('TechDocsStorageClient', () => { const promise = storageApi.syncEntityDocs(mockEntity).then(); // flush the event loop - await new Promise(setImmediate); + await new Promise(r => setTimeout(r)); const instance = MockedEventSource.mock .instances[0] as jest.Mocked; diff --git a/plugins/techdocs/src/client.ts b/plugins/techdocs/src/client.ts index f260f00547..69a5a91efc 100644 --- a/plugins/techdocs/src/client.ts +++ b/plugins/techdocs/src/client.ts @@ -47,10 +47,7 @@ export class TechDocsClient implements TechDocsApi { } async getApiOrigin(): Promise { - return ( - this.configApi.getOptionalString('techdocs.requestUrl') ?? - (await this.discoveryApi.getBaseUrl('techdocs')) - ); + return await this.discoveryApi.getBaseUrl('techdocs'); } /** @@ -126,10 +123,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi { } async getApiOrigin(): Promise { - return ( - this.configApi.getOptionalString('techdocs.requestUrl') ?? - (await this.discoveryApi.getBaseUrl('techdocs')) - ); + return await this.discoveryApi.getBaseUrl('techdocs'); } async getStorageUrl(): Promise { diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 4dcff501ed..bdbb4877ac 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -23,11 +23,7 @@ import { TableProps, } from '@backstage/core-components'; import { - EntityListContainer, - FilterContainer, - FilteredEntityLayout, -} from '@backstage/plugin-catalog'; -import { + CatalogFilterLayout, EntityListProvider, EntityOwnerPicker, EntityTagPicker, @@ -65,17 +61,17 @@ export const DefaultTechDocsHome = (props: DefaultTechDocsHomeProps) => { - - + + - - + + - - + + diff --git a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx index 311f83544a..0891ddcfd3 100644 --- a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx +++ b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx @@ -45,7 +45,7 @@ describe('Entity Docs Card Grid', () => { jest.resetAllMocks(); }); - it('should render all entities passed ot it', async () => { + it('should render all entities passed to it', async () => { const { findByText, findAllByRole } = render( wrapInTestApp( ({ - items: [ - { - apiVersion: 'version', - kind: 'User', - metadata: { - name: 'owned', - namespace: 'default', - }, - }, - ], - }), -} as Partial; - -describe('Legacy TechDocs Home', () => { - const configApi: ConfigApi = new ConfigReader({ - organization: { - name: 'My Company', - }, - }); - - const apiRegistry = TestApiRegistry.from( - [catalogApiRef, mockCatalogApi], - [configApiRef, configApi], - ); - - it('should render a TechDocs home page', async () => { - await renderInTestApp( - - - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - // Header - expect(await screen.findByText('Documentation')).toBeInTheDocument(); - expect( - await screen.findByText(/Documentation available in My Company/i), - ).toBeInTheDocument(); - - // Explore Content - expect(await screen.findByTestId('docs-explore')).toBeDefined(); - }); -}); diff --git a/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx deleted file mode 100644 index 536c2c80c8..0000000000 --- a/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; - -/** - * @deprecated Use {@link TechDocsCustomHome} instead. - */ -export const LegacyTechDocsHome = () => { - const tabsConfig = [ - { - label: 'Overview', - panels: [ - { - title: 'Overview', - description: - 'Explore your internal technical ecosystem through documentation.', - panelType: 'DocsCardGrid' as PanelType, - filterPredicate: () => true, - }, - // uncomment this if you would like to have a secondary panel with owned documents - // { - // title: 'Owned', - // description: 'Explore your owned internal documentation.', - // panelType: 'DocsCardGrid' as PanelType, - // filterPredicate: 'ownedByUser', - // }, - ], - }, - { - label: 'Owned Documents', - panels: [ - { - title: 'Owned documents', - description: 'Access your documentation.', - panelType: 'DocsTable' as PanelType, - // ownedByUser filters out entities owned by signed in user - filterPredicate: 'ownedByUser', - }, - ], - }, - ]; - return ; -}; diff --git a/plugins/techdocs/src/reader/README.md b/plugins/techdocs/src/reader/README.md index fb9f613651..63464dfa81 100644 --- a/plugins/techdocs/src/reader/README.md +++ b/plugins/techdocs/src/reader/README.md @@ -9,7 +9,7 @@ Transformers are functions that optionally takes in parameters from the Reader.t ```typescript export const updateH1Text = (): Transformer => { return dom => { - // Change the first occurance of H1 to say "TechDocs!" + // Change the first occurrence of H1 to say "TechDocs!" dom.querySelector('h1')?.innerHTML = 'TechDocs!'; return dom; diff --git a/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx b/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx deleted file mode 100644 index 4bfda57ace..0000000000 --- a/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2020 The 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, { useCallback, useState } from 'react'; -import { useParams } from 'react-router-dom'; -import useAsync from 'react-use/lib/useAsync'; -import { techdocsApiRef } from '../../api'; -import { TechDocsNotFound } from './TechDocsNotFound'; -import { useApi } from '@backstage/core-plugin-api'; -import { Page, Content } from '@backstage/core-components'; -import { Reader } from './Reader'; -import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader'; - -/** - * @deprecated Use {@link TechDocsReaderPage} instead. - */ -export const LegacyTechDocsPage = () => { - const [documentReady, setDocumentReady] = useState(false); - const { namespace, kind, name } = useParams(); - - const techdocsApi = useApi(techdocsApiRef); - - const { value: techdocsMetadataValue } = useAsync(() => { - if (documentReady) { - return techdocsApi.getTechDocsMetadata({ kind, namespace, name }); - } - - return Promise.resolve(undefined); - }, [kind, namespace, name, techdocsApi, documentReady]); - - const { value: entityMetadataValue, error: entityMetadataError } = - useAsync(() => { - return techdocsApi.getEntityMetadata({ kind, namespace, name }); - }, [kind, namespace, name, techdocsApi]); - - const onReady = useCallback(() => { - setDocumentReady(true); - }, [setDocumentReady]); - - if (entityMetadataError) { - return ; - } - - return ( - - - - - - - ); -}; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx index 184ab24b6d..1bfd0e6f51 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx @@ -18,12 +18,13 @@ import React, { useCallback, useState } from 'react'; import { useOutlet } from 'react-router'; import { useParams } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; +import { Reader } from './Reader'; +import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader'; import { techdocsApiRef } from '../../api'; -import { LegacyTechDocsPage } from './LegacyTechDocsPage'; import { TechDocsEntityMetadata, TechDocsMetadata } from '../../types'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { useApi, useApp } from '@backstage/core-plugin-api'; -import { Page } from '@backstage/core-components'; +import { Page, Content } from '@backstage/core-components'; /** * Helper function that gives the children of {@link TechDocsReaderPage} access to techdocs and entity metadata @@ -79,7 +80,32 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { if (entityMetadataError) return ; - if (!children) return outlet || ; + if (!children) + return ( + outlet || ( + + + + + + + ) + ); return ( @@ -94,16 +120,3 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { ); }; - -/** - * @public - * @deprecated use {@link TechDocsReaderPage} instead - */ -export const TechDocsPage = TechDocsReaderPage; - -/** - * @public - * @deprecated use {@link TechDocsReaderPageRenderFunction} instead - */ - -export type TechDocsPageRenderFunction = TechDocsReaderPageRenderFunction; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.tsx index 5867641c48..f6a3352e7d 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.tsx @@ -122,16 +122,3 @@ export const TechDocsReaderPageHeader = (
); }; - -/** - * @public - * @deprecated use {@link TechDocsReaderPageHeader} instead - */ -export const TechDocsPageHeader = TechDocsReaderPageHeader; - -/** - * @public - * @deprecated use {@link TechDocsReaderPageHeader} instead - */ - -export type TechDocsPageHeaderProps = TechDocsReaderPageHeaderProps; diff --git a/plugins/techdocs/src/reader/components/index.ts b/plugins/techdocs/src/reader/components/index.ts index 3124a92f93..8e660767ad 100644 --- a/plugins/techdocs/src/reader/components/index.ts +++ b/plugins/techdocs/src/reader/components/index.ts @@ -17,10 +17,8 @@ export * from './Reader'; export type { TechDocsReaderPageProps, - TechDocsPageRenderFunction, TechDocsReaderPageRenderFunction, } from './TechDocsReaderPage'; -export { TechDocsPage } from './TechDocsReaderPage'; export * from './TechDocsReaderPageHeader'; export * from './TechDocsStateIndicator'; diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts index b06762399b..896f909be4 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.test.ts @@ -35,11 +35,25 @@ describe('scrollIntoAnchor', () => { it('scroll to the hash anchor element', async () => { const scrollIntoView = jest.fn(); dom.querySelector.mockReturnValue({ scrollIntoView }); - const hash = '#hash'; - window.location.hash = hash; + window.location.hash = '#hash'; transformer(dom as unknown as Element); jest.advanceTimersByTime(200); - expect(dom.querySelector).toHaveBeenCalledWith(`#${hash.slice(1)}`); + expect(dom.querySelector).toHaveBeenCalledWith( + expect.stringMatching('[id="hash"]'), + ); + expect(scrollIntoView).toHaveBeenCalledWith(); + window.location.hash = ''; + }); + + it('works for anchor starting with number', async () => { + const scrollIntoView = jest.fn(); + dom.querySelector.mockReturnValue({ scrollIntoView }); + window.location.hash = '#1-hash'; + transformer(dom as unknown as Element); + jest.advanceTimersByTime(200); + expect(dom.querySelector).toHaveBeenCalledWith( + expect.stringMatching('[id="1-hash"]'), + ); expect(scrollIntoView).toHaveBeenCalledWith(); window.location.hash = ''; }); diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts index 0fa977865e..7ba02bcdfc 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoAnchor.ts @@ -22,7 +22,8 @@ export const scrollIntoAnchor = (): Transformer => { // Scroll to the desired anchor on initial navigation if (window.location.hash) { const hash = window.location.hash.slice(1); - dom?.querySelector(`#${hash}`)?.scrollIntoView(); + // fix invalid selector error for anchor starting with number + dom?.querySelector(`[id="${hash}"]`)?.scrollIntoView(); } }, 200); return dom; diff --git a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx index 152fa2e3d3..db5751654d 100644 --- a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx @@ -101,9 +101,3 @@ export const TechDocsSearchResultListItem = ( ); }; - -/** - * @public - * @deprecated use {@link TechDocsSearchResultListItem} instead - */ -export const DocsResultListItem = TechDocsSearchResultListItem; diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index a805551a0e..11332cbd41 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-todo-backend +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/catalog-client@1.0.1-next.0 + +## 0.1.27 + +### Patch Changes + +- 89c7e47967: Minor README update +- Updated dependencies + - @backstage/backend-common@0.13.1 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/errors@1.0.0 + ## 0.1.26 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index c1840bffba..34c9b17c43 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.26", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.0", - "@backstage/catalog-client": "^0.9.0", - "@backstage/catalog-model": "^0.13.0", - "@backstage/config": "^0.1.15", - "@backstage/errors": "^0.2.2", - "@backstage/integration": "^0.8.0", + "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/catalog-client": "^1.0.1-next.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/config": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/integration": "^1.0.1-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -43,7 +43,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", + "@backstage/cli": "^0.16.1-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 2012e58694..dfc814eda3 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-todo +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.0 + - @backstage/core-components@0.9.3-next.0 + +## 0.2.5 + +### Patch Changes + +- 89c7e47967: Minor README update +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + - @backstage/plugin-catalog-react@1.0.0 + - @backstage/catalog-model@1.0.0 + - @backstage/errors@1.0.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index c4d0f0a644..bb6b2cb2bc 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.2.4", + "version": "0.2.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.13.0", - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", - "@backstage/errors": "^0.2.2", - "@backstage/plugin-catalog-react": "^0.9.0", + "@backstage/catalog-model": "^1.0.1-next.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/errors": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -45,10 +45,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 35213632c1..6af1500ce2 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-user-settings +## 0.4.3-next.0 + +### Patch Changes + +- cf647f6a72: Wired up the OneLogin provider to be visible in the Settings UI when configured correctly. + + Previously it wasn't visible at all. + +- Updated dependencies + - @backstage/core-components@0.9.3-next.0 + +## 0.4.2 + +### Patch Changes + +- a422d7ce5e: chore(deps): bump `@testing-library/react` from 11.2.6 to 12.1.3 +- Updated dependencies + - @backstage/core-components@0.9.2 + - @backstage/core-plugin-api@1.0.0 + ## 0.4.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 25f3f1027b..85e74328a6 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.4.1", + "version": "0.4.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,8 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.1", - "@backstage/core-plugin-api": "^0.8.0", + "@backstage/core-components": "^0.9.3-next.0", + "@backstage/core-plugin-api": "^1.0.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.15.2", - "@backstage/core-app-api": "^0.6.0", - "@backstage/dev-utils": "^0.2.25", - "@backstage/test-utils": "^0.3.0", + "@backstage/cli": "^0.16.1-next.0", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx index 7025167658..019967cf55 100644 --- a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx +++ b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx @@ -24,6 +24,7 @@ import { microsoftAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, + oneloginAuthApiRef, } from '@backstage/core-plugin-api'; type Props = { @@ -80,6 +81,14 @@ export const DefaultProviderSettings = ({ configuredProviders }: Props) => ( icon={Star} /> )} + {configuredProviders.includes('onelogin') && ( + + )} {configuredProviders.includes('atlassian') && ( { const rendered = await renderInTestApp( , ); - expect(rendered.findAllByText('testTitle')).toBeTruthy(); + expect(await rendered.findAllByText('testTitle')).toBeTruthy(); }); it('should render empty state', async () => { @@ -32,6 +32,6 @@ describe('Trend', () => { const rendered = await renderInTestApp( , ); - expect(rendered.findAllByText('testTitle')).toBeTruthy(); + expect(await rendered.findAllByText('testTitle')).toBeTruthy(); }); }); diff --git a/scripts/generate-changeset-feedback.js b/scripts/generate-changeset-feedback.js new file mode 100644 index 0000000000..b614459a0e --- /dev/null +++ b/scripts/generate-changeset-feedback.js @@ -0,0 +1,278 @@ +/* + * 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. + */ + +const fs = require('fs'); +const { execFile: execFileCb } = require('child_process'); +const { promisify } = require('util'); +const { + basename, + resolve: resolvePath, + relative: relativePath, +} = require('path'); + +const execFile = promisify(execFileCb); + +// Tells whether a path relative to the package directory has an effect +// on the published package +function isPublishedPath(path) { + if (path.startsWith('dev/')) { + return false; + } + if (path.includes('__mocks__')) { + return false; + } + if (path.includes('__fixtures__')) { + return false; + } + // Don't count manual modifications to the changelog + if (path === 'CHANGELOG.md') { + return false; + } + // API report changes by themselves don't count + if (path === 'api-report.md') { + return false; + } + // Lint changes don't count + if (path === '.eslintrc.js') { + return false; + } + + const name = basename(path); + if (name.startsWith('setupTests.')) { + return false; + } + if (name.includes('.test.')) { + return false; + } + if (name.includes('.stories.')) { + return false; + } + return true; +} + +async function listChangedFiles(ref) { + if (!ref) { + throw new Error('ref is required'); + } + + const { stdout } = await execFile('git', ['diff', '--name-only', ref]); + return stdout.trim().split(/\r?\n/); +} + +async function listPackages() { + const { stdout } = await execFile('yarn', ['-s', 'workspaces', 'info']); + return Object.entries(JSON.parse(stdout)).map(([name, info]) => ({ + name, + path: info.location, + })); +} + +async function loadChangesets(filePaths) { + const changesets = []; + for (const filePath of filePaths) { + if (!filePath.startsWith('.changeset/') || !filePath.endsWith('.md')) { + continue; + } + try { + const content = await fs.promises.readFile(filePath, 'utf8'); + let lines = content.split(/\r?\n/); + + lines = lines.slice(lines.findIndex(line => line === '---') + 1); + lines = lines.slice( + 0, + lines.findIndex(line => line === '---'), + ); + + const bumps = new Map(); + bumps.toJSON = () => Object.fromEntries(bumps); + for (const line of lines) { + const match = line.match(/^'(.*)': (patch|minor|major)$/); + if (!match) { + throw new Error(`Invalid changeset line: ${line}`); + } + + bumps.set(match[1], match[2]); + } + + changesets.push({ + filePath, + bumps, + }); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + } + + return changesets; +} + +async function listChangedPackages(changedFiles, packages) { + const changedPackageMap = new Map(); + for (const filePath of changedFiles) { + for (const pkg of packages) { + if (filePath.startsWith(`${pkg.path}/`)) { + const pkgPath = relativePath(pkg.path, filePath); + if (!isPublishedPath(pkgPath)) { + break; + } + const entry = changedPackageMap.get(pkg.name); + if (entry) { + entry.files.push(pkgPath); + } else { + const pkgJson = require(resolvePath(pkg.path, 'package.json')); + + changedPackageMap.set(pkg.name, { + ...pkg, + version: pkgJson.version, + isStable: !pkgJson.version.startsWith('0.'), + isPrivate: Boolean(pkgJson.private), + files: [pkgPath], + }); + } + break; + } + } + } + return Array.from(changedPackageMap.values()); +} + +function formatSection(prefix = [], generator, suffix = ['']) { + const lines = Array.from(generator()); + if (lines.length === 0) { + return ''; + } + + return [...[prefix].flat(), ...lines, ...[suffix].flat(), '', ''].join('\n'); +} + +function formatSummary(changedPackages, changesets) { + const changedNames = new Set(changedPackages.map(pkg => pkg.name)); + + let output = ''; + + output += formatSection( + `## Missing Changesets + +The following package(s) are changed by this PR but do not have a changeset: +`, + function* section() { + for (const pkg of changedPackages) { + if (changesets.some(c => c.bumps.get(pkg.name))) { + continue; + } + if (pkg.isPrivate) { + continue; + } + yield `- **${pkg.name}**`; + } + }, + ` +See [CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#creating-changesets) for more information about how to add changesets. +`, + ); + + output += formatSection( + `## Unexpected Changesets + +The following changeset(s) reference packages that have not been changed in this PR: +`, + function* section() { + for (const c of changesets) { + const missing = Array.from(c.bumps.keys()).filter( + b => !changedNames.has(b), + ); + if (missing.length > 0) { + yield `- **${c.filePath}**: ${missing.join(', ')}`; + } + } + }, + ` +Note that only changes that affect the published package require changesets, for example changes to tests and storybook stories do not require changesets. +`, + ); + + output += formatSection( + `## Unnecessary Changesets + +The following package(s) are private and do not need a changeset: +`, + function* section() { + for (const pkg of changedPackages) { + if (changesets.some(c => c.bumps.get(pkg.name)) && pkg.isPrivate) { + yield `- **${pkg.name}**`; + } + } + }, + ); + + output += formatSection( + `## Changed Packages + +| Package Name | Package Path | Changeset Bump | Current Version | +|:-------------|:-------------|:--------------:|:----------------|`, + function* section() { + const bumpMap = { + undefined: -1, + patch: 0, + minor: 1, + major: 2, + }; + + for (const pkg of changedPackages) { + const maxBump = + changesets + .map(c => c.bumps.get(pkg.name)) + .reduce( + (max, bump) => (bumpMap[bump] > bumpMap[max] ? bump : max), + undefined, + ) ?? 'none'; + yield `| ${pkg.name} | ${pkg.path} | **${maxBump}** | \`v${pkg.version}\` |`; + } + }, + ); + + return output; +} + +async function main() { + const [diffRef = 'origin/master'] = process.argv.slice(2); + const changedFiles = await listChangedFiles(diffRef); + const packages = await listPackages(); + + const changesets = await loadChangesets(changedFiles); + const changedPackages = await listChangedPackages(changedFiles, packages); + + process.stderr.write( + JSON.stringify( + { + changesets, + changedPackages, + }, + null, + 2, + ), + ); + + const summary = formatSummary(changedPackages, changesets); + process.stdout.write(summary); +} + +main().catch(error => { + console.error(error.stack); + process.exit(1); +}); diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index c4a790b2cc..ad783c4609 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -259,7 +259,7 @@ async function updateBackstageReleaseVersion(repo, type) { } } else if (type === 'patch') { if (preMode) { - throw new Error(`Unexpected pre mode ${preMode} on branch ${branchName}`); + throw new Error(`Unexpected pre mode ${preMode} on current branch`); } nextVersion = semver.inc(currentVersion, 'patch'); } diff --git a/storybook/.storybook/main.js b/storybook/.storybook/main.js index bd58156d2d..2356e4e41b 100644 --- a/storybook/.storybook/main.js +++ b/storybook/.storybook/main.js @@ -9,6 +9,7 @@ const BACKSTAGE_CORE_STORIES = [ 'plugins/org', 'plugins/search', 'plugins/home', + 'plugins/stack-overflow', ]; module.exports = ({ args }) => { diff --git a/storybook/yarn.lock b/storybook/yarn.lock index c53f5fe7be..fabc3b643d 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -6016,9 +6016,9 @@ minimatch@^3.0.2, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2" @@ -6801,15 +6801,10 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -prismjs@^1.21.0: - version "1.26.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47" - integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ== - -prismjs@~1.25.0: - version "1.25.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@^1.21.0, prismjs@~1.27.0: + version "1.27.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== process-nextick-args@~2.0.0: version "2.0.1" @@ -7241,13 +7236,13 @@ readdirp@~3.6.0: picomatch "^2.2.1" refractor@^3.1.0: - version "3.5.0" - resolved "https://registry.npmjs.org/refractor/-/refractor-3.5.0.tgz#334586f352dda4beaf354099b48c2d18e0819aec" - integrity sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg== + version "3.6.0" + resolved "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" + integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== dependencies: hastscript "^6.0.0" parse-entities "^2.0.0" - prismjs "~1.25.0" + prismjs "~1.27.0" regenerate-unicode-properties@^9.0.0: version "9.0.0" diff --git a/yarn.lock b/yarn.lock index 2a01e72aeb..0e6629ecf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,6 +9,13 @@ dependencies: aws4 "^1.11.0" +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + "@apidevtools/json-schema-ref-parser@^9.0.6": version "9.0.6" resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" @@ -114,7 +121,7 @@ resolved "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz#dcccebb88406e5c76e0e1d52e8cc4c43a68b3ee7" integrity sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg== -"@azure/core-auth@^1.1.3", "@azure/core-auth@^1.3.0": +"@azure/core-auth@^1.3.0": version "1.3.2" resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.2.tgz#6a2c248576c26df365f6c7881ca04b7f6d08e3d0" integrity sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA== @@ -135,36 +142,36 @@ "@azure/logger" "^1.0.0" tslib "^2.2.0" -"@azure/core-http@^1.2.0": - version "1.2.2" - resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-1.2.2.tgz#a6f7717184fd2657d3acabd1d64dfdc0bd531ce3" - integrity sha512-9eu2OcbR7e44gqBy4U1Uv8NTWgLIMwKXMEGgO2MahsJy5rdTiAhs5fJHQffPq8uX2MFh21iBODwO9R/Xlov88A== +"@azure/core-http@^2.0.0": + version "2.2.4" + resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-2.2.4.tgz#df5a5b4138dbbc4299879f2fc6f257d0a5f0401e" + integrity sha512-QmmJmexXKtPyc3/rsZR/YTLDvMatzbzAypJmLzvlfxgz/SkgnqV/D4f6F2LsK6tBj1qhyp8BoXiOebiej0zz3A== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-auth" "^1.1.3" - "@azure/core-tracing" "1.0.0-preview.9" + "@azure/core-asynciterator-polyfill" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.13" "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" "@types/node-fetch" "^2.5.0" - "@types/tunnel" "^0.0.1" - form-data "^3.0.0" - node-fetch "^2.6.0" + "@types/tunnel" "^0.0.3" + form-data "^4.0.0" + node-fetch "^2.6.7" process "^0.11.10" tough-cookie "^4.0.0" - tslib "^2.0.0" + tslib "^2.2.0" tunnel "^0.0.6" uuid "^8.3.0" xml2js "^0.4.19" -"@azure/core-lro@^1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@azure/core-lro/-/core-lro-1.0.3.tgz#1ddfb4ecdb81ce87b5f5d972ffe2acbbc46e524e" - integrity sha512-Py2crJ84qx1rXkzIwfKw5Ni4WJuzVU7KAF6i1yP3ce8fbynUeu8eEWS4JGtSQgU7xv02G55iPDROifmSDbxeHA== +"@azure/core-lro@^2.2.0": + version "2.2.4" + resolved "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.2.4.tgz#42fbf4ae98093c59005206a4437ddcd057c57ca1" + integrity sha512-e1I2v2CZM0mQo8+RSix0x091Av493e4bnT22ds2fcQGslTHzM2oTbswkB65nP4iEpCxBrFxOSDPKExmTmjCVtQ== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - events "^3.0.0" - tslib "^2.0.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" "@azure/core-paging@^1.1.1": version "1.1.3" @@ -188,15 +195,6 @@ tslib "^2.2.0" uuid "^8.3.0" -"@azure/core-tracing@1.0.0-preview.10": - version "1.0.0-preview.10" - resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.10.tgz#e7060272145dddad4486765030d1b037cd52a8ea" - integrity sha512-iIwjtMwQnsxB7cYkugMx+s4W1nfy3+pT/ceo+uW1fv4YDgYe84nh+QP0fEC9IH/3UATLSWbIBemdMHzk2APUrw== - dependencies: - "@opencensus/web-types" "0.0.7" - "@opentelemetry/api" "^0.10.2" - tslib "^2.0.0" - "@azure/core-tracing@1.0.0-preview.13": version "1.0.0-preview.13" resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644" @@ -205,15 +203,6 @@ "@opentelemetry/api" "^1.0.1" tslib "^2.2.0" -"@azure/core-tracing@1.0.0-preview.9": - version "1.0.0-preview.9" - resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.9.tgz#84f3b85572013f9d9b85e1e5d89787aa180787eb" - integrity sha512-zczolCLJ5QG42AEPQ+Qg9SRYNUyB+yZ5dzof4YEc+dyWczO9G2sBqbAjLB7IqrsdHN2apkiB2oXeDKCsq48jug== - dependencies: - "@opencensus/web-types" "0.0.7" - "@opentelemetry/api" "^0.10.2" - tslib "^2.0.0" - "@azure/core-util@^1.0.0-beta.1": version "1.0.0-beta.1" resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.0.0-beta.1.tgz#2efd2c74b4b0a38180369f50fe274a3c4cd36e98" @@ -271,30 +260,37 @@ dependencies: debug "^4.1.1" -"@azure/msal-node@^1.1.0", "@azure/msal-node@^1.3.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.4.0.tgz#660685804fbdc533b10cc699f16323e27ec582c6" - integrity sha512-Ek6hqOFUi5QEAxZ55awM8y1N+9SzS9Qh8ijF4RDLtFuHzqP7xXmMnVC1lae45FlH55DUOo7dg/smuDJnb4kw6g== +"@azure/msal-common@^6.1.0": + version "6.1.0" + resolved "https://registry.npmjs.org/@azure/msal-common/-/msal-common-6.1.0.tgz#00b0c625d10f4c8e48e7b48a353f7d8d20a5ceca" + integrity sha512-IGjAHttOgKDPQr0Qxx1NjABR635ZNuN7LHjxI0Y7SEA2thcaRGTccy+oaXTFabM/rZLt4F2VrPKUX4BnR9hW9g== dependencies: - "@azure/msal-common" "^5.2.0" + debug "^4.1.1" + +"@azure/msal-node@^1.1.0", "@azure/msal-node@^1.3.0": + version "1.7.0" + resolved "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.7.0.tgz#9026971b1fd953f15bfddde9c43e180356a80d77" + integrity sha512-qDkW+Z4b0SGkkYrM1x+0s5WJ3z96vgiNZ20iwpmtCUHVfSrDiGFB8s8REKVaz7JZJi2L1s0vQRbUahly8EoGnw== + dependencies: + "@azure/msal-common" "^6.1.0" axios "^0.21.4" + https-proxy-agent "^5.0.0" jsonwebtoken "^8.5.1" uuid "^8.3.0" "@azure/storage-blob@^12.5.0": - version "12.5.0" - resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.5.0.tgz#1ddd8837d9a15ebe355e795375d13b406f2cb496" - integrity sha512-DgoefgODst2IPkkQsNdhtYdyJgSsAZC1pEujO6aD5y7uFy5GnzhYliobSrp204jYRyK5XeJ9iiePmy/SPtTbLA== + version "12.9.0" + resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.9.0.tgz#4cbd8b4c7a47dd064867430db892f4ef2d8f17ab" + integrity sha512-ank38FdCLfJ+EoeMzCz3hkYJuZAd63ARvDKkxZYRDb+beBYf+/+gx8jNTqkq/hfyUl4dJQ/a7tECU0Y0F98CHg== dependencies: "@azure/abort-controller" "^1.0.0" - "@azure/core-http" "^1.2.0" - "@azure/core-lro" "^1.0.2" + "@azure/core-http" "^2.0.0" + "@azure/core-lro" "^2.2.0" "@azure/core-paging" "^1.1.1" - "@azure/core-tracing" "1.0.0-preview.10" + "@azure/core-tracing" "1.0.0-preview.13" "@azure/logger" "^1.0.0" - "@opentelemetry/api" "^0.10.2" events "^3.0.0" - tslib "^2.0.0" + tslib "^2.2.0" "@babel/code-frame@7.0.0": version "7.0.0" @@ -303,7 +299,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== @@ -315,7 +311,12 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz#31560f9f29fdf1868de8cb55049538a1b9732a60" integrity sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q== -"@babel/core@^7.1.0", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.15.5", "@babel/core@^7.7.5": +"@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== + +"@babel/core@^7.1.0", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.15.5": version "7.16.12" resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz#5edc53c1b71e54881315923ae2aedea2522bb784" integrity sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg== @@ -336,7 +337,28 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.14.0", "@babel/generator@^7.16.0", "@babel/generator@^7.16.8": +"@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.17.8" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" + integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.8" + "@babel/parser" "^7.17.8" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + +"@babel/generator@^7.14.0", "@babel/generator@^7.16.0", "@babel/generator@^7.16.8", "@babel/generator@^7.7.2": version "7.16.8" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe" integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw== @@ -345,6 +367,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.17.3", "@babel/generator@^7.17.7": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" @@ -370,6 +401,16 @@ browserslist "^4.17.5" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7": version "7.16.10" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz#8a6959b9cc818a88815ba3c5474619e9c0f2c21c" @@ -470,6 +511,20 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + "@babel/helper-optimise-call-expression@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" @@ -509,6 +564,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== + dependencies: + "@babel/types" "^7.17.0" + "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09" @@ -552,6 +614,15 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helpers@^7.17.8": + version "7.17.8" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" + integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" + "@babel/highlight@^7.0.0", "@babel/highlight@^7.16.7": version "7.16.10" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" @@ -566,11 +637,16 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.3", "@babel/parser@^7.16.7": +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.3", "@babel/parser@^7.16.7": version "7.16.12" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6" integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A== +"@babel/parser@^7.17.3", "@babel/parser@^7.17.8": + version "7.17.8" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" + integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" @@ -841,7 +917,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.7": +"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": version "7.16.7" resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== @@ -1295,10 +1371,10 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.17.2" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" - integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.17.7" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825" + integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA== dependencies: regenerator-runtime "^0.13.4" @@ -1326,7 +1402,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.10", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.10", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.16.10" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f" integrity sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw== @@ -1342,6 +1418,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.17.3": + version "7.17.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@7.16.0": version "7.16.0" resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" @@ -1358,6 +1450,149 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@babel/types@^7.17.0": + version "7.17.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@backstage/catalog-client@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@backstage/catalog-client/-/catalog-client-1.0.0.tgz#05f9ee3b771ca17e4800f5116d63bd183fe0c4d6" + integrity sha512-m2FiV9+gQElYrZMICFpr3Dj7H34as9Jly1onF/yFhKi4DSovZpgemTG6I0KQuNuEwmrqYTkTd6kDDYvpUis9Ow== + dependencies: + "@backstage/catalog-model" "^1.0.0" + "@backstage/errors" "^1.0.0" + cross-fetch "^3.1.5" + +"@backstage/catalog-model@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-1.0.0.tgz#0aa8694a3182aaf4232725842da751bf5f78bd68" + integrity sha512-hjvTzs+P1/TG42FhX6jJvRIBbYZJC0vE9Qqn9MTb4n8vaoZuBT5jYTnK6K80gpEyNfbznpD5vsE8ss5lmzWz+Q== + dependencies: + "@backstage/config" "^1.0.0" + "@backstage/errors" "^1.0.0" + "@backstage/types" "^1.0.0" + ajv "^7.0.3" + json-schema "^0.4.0" + lodash "^4.17.21" + uuid "^8.0.0" + +"@backstage/config@^0.1.15": + version "0.1.15" + resolved "https://registry.npmjs.org/@backstage/config/-/config-0.1.15.tgz#4bad122ad861be5bd61a60639f92d2494fa245c5" + integrity sha512-eNJEYYSEu9MkrkBYiMpUBWEc3Bu64YgB9pZZGCMW7/9350tV2wbylEdoBJHslilJlJhiUyTXBckn8Ua7DOH7rw== + dependencies: + "@backstage/types" "^0.1.3" + lodash "^4.17.21" + +"@backstage/core-components@^0.9.0", "@backstage/core-components@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@backstage/core-components/-/core-components-0.9.2.tgz#9a3d79a15039256bbc007e5daa08c983050e0238" + integrity sha512-kh0FB0FmjC55W+xSEkKrAc7D6hvbYLY7N1UUd6M4VBghYXD61Y8RrJFKmBM3bAfPgYaryQNjYgA0BsoTo53PJA== + dependencies: + "@backstage/config" "^1.0.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/errors" "^1.0.0" + "@backstage/theme" "^0.2.15" + "@material-table/core" "^3.1.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + "@react-hookz/web" "^13.0.0" + "@types/react-sparklines" "^1.7.0" + "@types/react-text-truncate" "^0.14.0" + ansi-regex "^6.0.1" + classnames "^2.2.6" + d3-selection "^3.0.0" + d3-shape "^3.0.0" + d3-zoom "^3.0.0" + dagre "^0.8.5" + history "^5.0.0" + immer "^9.0.1" + lodash "^4.17.21" + pluralize "^8.0.0" + prop-types "^15.7.2" + qs "^6.9.4" + rc-progress "3.2.4" + react-helmet "6.1.0" + react-hook-form "^7.12.2" + react-markdown "^8.0.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-sparklines "^1.7.0" + react-syntax-highlighter "^15.4.5" + react-text-truncate "^0.18.0" + react-use "^17.3.2" + react-virtualized-auto-sizer "^1.0.6" + react-window "^1.8.6" + remark-gfm "^3.0.1" + zen-observable "^0.8.15" + zod "^3.11.6" + +"@backstage/integration-react@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@backstage/integration-react/-/integration-react-1.0.0.tgz#8075e65c6b5387631d27a9c242e7a4fff6f92417" + integrity sha512-htL4Ovm4Thg07IQ7OM18ydUUWGDiTFUn0ozUcNoYQHXdmYtONHpc4/W+THWOYC+0VYdTBSEKz9JidSMsFs3EGQ== + dependencies: + "@backstage/config" "^1.0.0" + "@backstage/core-components" "^0.9.2" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/integration" "^1.0.0" + "@backstage/theme" "^0.2.15" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + react-use "^17.2.4" + +"@backstage/integration@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@backstage/integration/-/integration-1.0.0.tgz#e307cfddea014bfb0eb2281a5ae25ea0b742e9cf" + integrity sha512-DpEoSCQyurLSNGLAAfZzwb8IGEoxm6po6ZkKq9Gt9I63NbLhixBEnCcoPbm5+ld4MVNwg+FgzIoktsjdBt3JIw== + dependencies: + "@backstage/config" "^1.0.0" + "@octokit/auth-app" "^3.4.0" + "@octokit/rest" "^18.5.3" + cross-fetch "^3.1.5" + git-url-parse "^11.6.0" + lodash "^4.17.21" + luxon "^2.0.2" + +"@backstage/plugin-catalog-react@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-1.0.0.tgz#4f42c070ffe5c9690e45a5288e18bacd8d4e0e66" + integrity sha512-F4HwGPD2/U6D219ivXRaRSsKUwOHrA5+yb+d6kHG0T+N5WdcBEhBv6a2bypGjkEEu/7oeTbQKjK2CKIGMlwbPA== + dependencies: + "@backstage/catalog-client" "^1.0.0" + "@backstage/catalog-model" "^1.0.0" + "@backstage/core-components" "^0.9.2" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/errors" "^1.0.0" + "@backstage/integration" "^1.0.0" + "@backstage/plugin-permission-common" "^0.5.3" + "@backstage/plugin-permission-react" "^0.3.4" + "@backstage/theme" "^0.2.15" + "@backstage/types" "^1.0.0" + "@backstage/version-bridge" "^1.0.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + classnames "^2.2.6" + jwt-decode "^3.1.0" + lodash "^4.17.21" + qs "^6.9.4" + react-router "6.0.0-beta.0" + react-use "^17.2.4" + yaml "^1.10.0" + zen-observable "^0.8.15" + +"@backstage/types@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@backstage/types/-/types-0.1.3.tgz#6613d8cbdf97d42d31cd1e66a833df533e7ccf14" + integrity sha512-fJVi4oVrlO+G3PRv1fYSll9/X4pE11HLnkI//Geare9sP6wSfp/2zXpLYfKVsG0e24jOl7Swkc8lwLkQ90zMaQ== + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1560,13 +1795,90 @@ human-id "^1.0.2" prettier "^1.19.1" -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== +"@codemirror/autocomplete@^0.19.0": + version "0.19.14" + resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.19.14.tgz#5f61b0fced56e7960791063d03b21d11a63f0ec5" + integrity sha512-4PqJG7GGTePc+FQF387RFebDV4ERvKj23gQBmzNtu64ZSHlYEGulwP5EIIfulBiaWEmei9TYVaMFmTdNfofpRQ== dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.4" + "@codemirror/text" "^0.19.2" + "@codemirror/tooltip" "^0.19.12" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.0" + +"@codemirror/basic-setup@^0.19.1": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.19.1.tgz#17b27d02f15c628eb62a85d01e3e1b1958933eb4" + integrity sha512-gLjD7YgZU/we6BzS/ecCmD3viw83dsgv5ZUaSydYbYx9X4w4w9RqYnckcJ+0GDyHfNr5Jtfv2Z5ZtFQnBj0UDA== + dependencies: + "@codemirror/autocomplete" "^0.19.0" + "@codemirror/closebrackets" "^0.19.0" + "@codemirror/commands" "^0.19.0" + "@codemirror/comment" "^0.19.0" + "@codemirror/fold" "^0.19.0" + "@codemirror/gutter" "^0.19.0" + "@codemirror/highlight" "^0.19.0" + "@codemirror/history" "^0.19.0" + "@codemirror/language" "^0.19.0" + "@codemirror/lint" "^0.19.0" + "@codemirror/matchbrackets" "^0.19.0" + "@codemirror/rectangular-selection" "^0.19.0" + "@codemirror/search" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.31" + +"@codemirror/closebrackets@^0.19.0": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/closebrackets/-/closebrackets-0.19.1.tgz#c93219d6800c27a880e569135b468cf4a05d903f" + integrity sha512-ZiLXT6u+VuBK5QnfBbt/Vmfd9Pg6449wn1DIOWFZHUOldg5eFn3VGGjYY2XWuHQz5WuK+7dXamV2KE885O1gyA== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.2" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.44" + +"@codemirror/commands@^0.19.0": + version "0.19.8" + resolved "https://registry.npmjs.org/@codemirror/commands/-/commands-0.19.8.tgz#1f99c1a8bf200d17c4d6997379099459f3678107" + integrity sha512-65LIMSGUGGpY3oH6mzV46YWRrgao6NmfJ+AuC7jNz3K5NPnH6GCV1H5I6SwOFyVbkiygGyd0EFwrWqywTBD1aw== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/matchbrackets" "^0.19.0" + "@codemirror/state" "^0.19.2" + "@codemirror/text" "^0.19.6" + "@codemirror/view" "^0.19.22" + "@lezer/common" "^0.15.0" + +"@codemirror/comment@^0.19.0": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/comment/-/comment-0.19.1.tgz#7def8345eeb9095ef1ef33676fbde1ab4fe33fad" + integrity sha512-uGKteBuVWAC6fW+Yt8u27DOnXMT/xV4Ekk2Z5mRsiADCZDqYvryrJd6PLL5+8t64BVyocwQwNfz1UswYS2CtFQ== + dependencies: + "@codemirror/state" "^0.19.9" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/fold@^0.19.0": + version "0.19.3" + resolved "https://registry.npmjs.org/@codemirror/fold/-/fold-0.19.3.tgz#de55d44a7313f2a8920aefb6ebf9eff34715d8d4" + integrity sha512-8hT+Eq2G68mL0yPRvSD2ewhnLQAX6sbUJmtGVKFcj8oAXtfpYCX8LIcfXsuI19Qs7gZkOSpqZvn+KKj8IhZoAw== + dependencies: + "@codemirror/gutter" "^0.19.0" + "@codemirror/language" "^0.19.0" + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.22" + +"@codemirror/gutter@^0.19.0", "@codemirror/gutter@^0.19.4": + version "0.19.9" + resolved "https://registry.npmjs.org/@codemirror/gutter/-/gutter-0.19.9.tgz#bbb69f4d49570d9c1b3f3df5d134980c516cd42b" + integrity sha512-PFrtmilahin1g6uL27aG5tM/rqR9DZzZYZsIrCXA5Uc2OFTFqx4owuhoU9hqfYxHp5ovfvBwQ+txFzqS4vog6Q== + dependencies: + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.23" "@codemirror/highlight@^0.19.0": version "0.19.6" @@ -1580,6 +1892,14 @@ "@lezer/common" "^0.15.0" style-mod "^4.0.0" +"@codemirror/history@^0.19.0": + version "0.19.2" + resolved "https://registry.npmjs.org/@codemirror/history/-/history-0.19.2.tgz#25e3fda755f77ac1223a6ae6e9d7899f5919265e" + integrity sha512-unhP4t3N2smzmHoo/Yio6ueWi+il8gm9VKrvi6wlcdGH5fOfVDNkmjHQ495SiR+EdOG35+3iNebSPYww0vN7ow== + dependencies: + "@codemirror/state" "^0.19.2" + "@codemirror/view" "^0.19.0" + "@codemirror/language@^0.19.0": version "0.19.7" resolved "https://registry.npmjs.org/@codemirror/language/-/language-0.19.7.tgz#9eef8e827692d93a701b18db9d46a42be34ecca6" @@ -1591,24 +1911,83 @@ "@lezer/common" "^0.15.5" "@lezer/lr" "^0.15.0" -"@codemirror/rangeset@^0.19.0": - version "0.19.2" - resolved "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.2.tgz#d7a999e4273c00fecef4aba8535a426073cdcddf" - integrity sha512-5d+X8LtmeZtfFtKrSx57bIHRUpKv2HD0b74clp4fGA7qJLLfYehF6FGkJJxJb8lKsqAga1gdjjWr0jiypmIxoQ== +"@codemirror/legacy-modes@^0.19.0": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-0.19.1.tgz#7dc3b5df1842060648f75764ab6919fcfce3ea1a" + integrity sha512-vYPLsD/ON+3SXhlGj9Qb3fpFNNU3Ya/AtDiv/g3OyqVzhh5vs5rAnOvk8xopGWRwppdhlNPD9VyXjiOmZUQtmQ== + dependencies: + "@codemirror/stream-parser" "^0.19.0" + +"@codemirror/lint@^0.19.0": + version "0.19.6" + resolved "https://registry.npmjs.org/@codemirror/lint/-/lint-0.19.6.tgz#0379688da3e16739db4a6304c73db857ca85d7ec" + integrity sha512-Pbw1Y5kHVs2J+itQ0uez3dI4qY9ApYVap7eNfV81x1/3/BXgBkKfadaw0gqJ4h4FDG7OnJwb0VbPsjJQllHjaA== + dependencies: + "@codemirror/gutter" "^0.19.4" + "@codemirror/panel" "^0.19.0" + "@codemirror/rangeset" "^0.19.1" + "@codemirror/state" "^0.19.4" + "@codemirror/tooltip" "^0.19.16" + "@codemirror/view" "^0.19.22" + crelt "^1.0.5" + +"@codemirror/matchbrackets@^0.19.0": + version "0.19.4" + resolved "https://registry.npmjs.org/@codemirror/matchbrackets/-/matchbrackets-0.19.4.tgz#50b5188eb2d53f32598dca906bf5fd66626a9ebc" + integrity sha512-VFkaOKPNudAA5sGP1zikRHCEKU0hjYmkKpr04pybUpQvfTvNJXlReCyP0rvH/1iEwAGPL990ZTT+QrLdu4MeEA== + dependencies: + "@codemirror/language" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + "@lezer/common" "^0.15.0" + +"@codemirror/panel@^0.19.0", "@codemirror/panel@^0.19.1": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/panel/-/panel-0.19.1.tgz#bf77d27b962cf16357139e50864d0eb69d634441" + integrity sha512-sYeOCMA3KRYxZYJYn5PNlt9yNsjy3zTNTrbYSfVgjgL9QomIVgOJWPO5hZ2sTN8lufO6lw0vTBsIPL9MSidmBg== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/rangeset@^0.19.0", "@codemirror/rangeset@^0.19.1", "@codemirror/rangeset@^0.19.5": + version "0.19.9" + resolved "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.9.tgz#e80895de93c39dc7899f5be31d368c9d88aa4efc" + integrity sha512-V8YUuOvK+ew87Xem+71nKcqu1SXd5QROMRLMS/ljT5/3MCxtgrRie1Cvild0G/Z2f1fpWxzX78V0U4jjXBorBQ== dependencies: "@codemirror/state" "^0.19.0" -"@codemirror/state@^0.19.0", "@codemirror/state@^0.19.3": - version "0.19.6" - resolved "https://registry.npmjs.org/@codemirror/state/-/state-0.19.6.tgz#d631f041d39ce41b7891b099fca26cb1fdb9763e" - integrity sha512-sqIQZE9VqwQj7D4c2oz9mfLhlT1ElAzGB5lO1lE33BPyrdNy1cJyCIOecT4cn4VeJOFrnjOeu+IftZ3zqdFETw== +"@codemirror/rectangular-selection@^0.19.0": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/rectangular-selection/-/rectangular-selection-0.19.1.tgz#5a88ece4fb68ce5682539497db8a64fc015aae63" + integrity sha512-9ElnqOg3mpZIWe0prPRd1SZ48Q9QB3bR8Aocq8UtjboJSUG8ABhRrbuTZMW/rMqpBPSjVpCe9xkCCkEQMYQVmw== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/text" "^0.19.4" + "@codemirror/view" "^0.19.0" + +"@codemirror/search@^0.19.0": + version "0.19.9" + resolved "https://registry.npmjs.org/@codemirror/search/-/search-0.19.9.tgz#b9548dfeb2028e6d5ecd1965f8f0ee50e2925603" + integrity sha512-h3MuwbUbiyOp6Np3IB5r4LH0w4inZvbtLO1Ipmz8RhElcGRiYr11Q6Bim8ocLfe08RmZT6B5EkTj1E8eNlugQQ== + dependencies: + "@codemirror/panel" "^0.19.0" + "@codemirror/rangeset" "^0.19.0" + "@codemirror/state" "^0.19.3" + "@codemirror/text" "^0.19.0" + "@codemirror/view" "^0.19.34" + crelt "^1.0.5" + +"@codemirror/state@^0.19.0", "@codemirror/state@^0.19.2", "@codemirror/state@^0.19.3", "@codemirror/state@^0.19.4", "@codemirror/state@^0.19.9": + version "0.19.9" + resolved "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz#b797f9fbc204d6dc7975485e231693c09001b0dd" + integrity sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw== dependencies: "@codemirror/text" "^0.19.0" -"@codemirror/stream-parser@^0.19.2": - version "0.19.2" - resolved "https://registry.npmjs.org/@codemirror/stream-parser/-/stream-parser-0.19.2.tgz#793428e55aa7b9daa64cb733973e5d5e3d9a2306" - integrity sha512-hBKRQlyu8GUOrY33xZ6/1kAfNZ8ZUm6cX9a7mPx8zAAqnpz/fpksC/qJRrkg1mPMBwxm+JG4fqAwDGJ3gLVniQ== +"@codemirror/stream-parser@^0.19.0", "@codemirror/stream-parser@^0.19.2", "@codemirror/stream-parser@^0.19.6": + version "0.19.7" + resolved "https://registry.npmjs.org/@codemirror/stream-parser/-/stream-parser-0.19.7.tgz#99a9696307b90677f6e3abe29ea6d77c1c0a8db5" + integrity sha512-4ExcbKksmU4PIT8s11/dPESgMNLQqlMlbQzRQ1CyMcmygcuQk+58zQ84nv/X17OCUd2ephZ1DKEaHHACifzCiA== dependencies: "@codemirror/highlight" "^0.19.0" "@codemirror/language" "^0.19.0" @@ -1617,17 +1996,34 @@ "@lezer/common" "^0.15.0" "@lezer/lr" "^0.15.0" -"@codemirror/text@^0.19.0": - version "0.19.5" - resolved "https://registry.npmjs.org/@codemirror/text/-/text-0.19.5.tgz#75033af2476214e79eae22b81ada618815441c18" - integrity sha512-Syu5Xc7tZzeUAM/y4fETkT0zgGr48rDG+w4U38bPwSIUr+L9S/7w2wDE1WGNzjaZPz12F6gb1gxWiSTg9ocLow== +"@codemirror/text@^0.19.0", "@codemirror/text@^0.19.2", "@codemirror/text@^0.19.4", "@codemirror/text@^0.19.6": + version "0.19.6" + resolved "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz#9adcbd8137f69b75518eacd30ddb16fd67bbac45" + integrity sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA== -"@codemirror/view@^0.19.0": - version "0.19.27" - resolved "https://registry.npmjs.org/@codemirror/view/-/view-0.19.27.tgz#76e5dc19ecb4ce53e9fef1d29245040d7ff64183" - integrity sha512-Uz/LecEf7CyvMWaQBlKtbJCYn0hRnEZ2yYvuZVy9YMhmvGmES6ec7FaKw7lDFFOMLwLbBThc9kfw4DCHreHN1w== +"@codemirror/theme-one-dark@^0.19.1": + version "0.19.1" + resolved "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-0.19.1.tgz#648b9cbe37186a2b7bd2a83fb483dc7aa18ce218" + integrity sha512-8gc4c2k2o/EhyHoWkghCxp5vyDT96JaFGtRy35PHwIom0LZdx7aU4AbDUnITvwiFB+0+i54VO+WQjBqgTyJvqg== dependencies: - "@codemirror/rangeset" "^0.19.0" + "@codemirror/highlight" "^0.19.0" + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/tooltip@^0.19.12", "@codemirror/tooltip@^0.19.16": + version "0.19.16" + resolved "https://registry.npmjs.org/@codemirror/tooltip/-/tooltip-0.19.16.tgz#6ba2c43f9d8e3d943d9d7bbae22bf800f7726a22" + integrity sha512-zxKDHryUV5/RS45AQL+wOeN+i7/l81wK56OMnUPoTSzCWNITfxHn7BToDsjtrRKbzHqUxKYmBnn/4hPjpZ4WJQ== + dependencies: + "@codemirror/state" "^0.19.0" + "@codemirror/view" "^0.19.0" + +"@codemirror/view@^0.19.0", "@codemirror/view@^0.19.22", "@codemirror/view@^0.19.23", "@codemirror/view@^0.19.31", "@codemirror/view@^0.19.34", "@codemirror/view@^0.19.44", "@codemirror/view@^0.19.45": + version "0.19.47" + resolved "https://registry.npmjs.org/@codemirror/view/-/view-0.19.47.tgz#2163c3016d7680bf50dd695c0e3abdaf80f45363" + integrity sha512-SfbagKvJQl5dtt+9wYpo9sa3ZkMgUxTq+/hXDf0KVwIx+zu3cJIqfEm9xSx6yXkq7it7RsPGHaPasApNffF/8g== + dependencies: + "@codemirror/rangeset" "^0.19.5" "@codemirror/state" "^0.19.3" "@codemirror/text" "^0.19.0" style-mod "^4.0.0" @@ -1744,16 +2140,16 @@ ts-node "^9" tslib "^2" -"@eslint/eslintrc@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" - integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w== +"@eslint/eslintrc@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" + integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== dependencies: ajv "^6.12.4" debug "^4.3.2" espree "^9.3.1" globals "^13.9.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.0.4" @@ -1783,12 +2179,12 @@ query-string "^7.0.0" xcase "^2.0.1" -"@gitbeaker/core@^35.4.0": - version "35.4.0" - resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-35.4.0.tgz#ebc3e46f9d8e17f85f81ccfe163cffdcd429545a" - integrity sha512-VjRmC/j1umhn0+f+DbrbxXDAc0uvGd32tJ4MCFQYkxw9ndr/0Rlg8iYvYN/zWyqtgr3SeTxMyS3Z9MtbWWhjBA== +"@gitbeaker/core@^35.5.0": + version "35.5.0" + resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-35.5.0.tgz#e744ca9115133f8070a40b3bbfedf5a11801d341" + integrity sha512-ExAIDoUWmaY/QBNoUNkxEvvg79kF9ccFkEoFnHdKYvrqX2517OJCSoWoslGt9/XaSEcE9sTm7lYZrCmqExXsWg== dependencies: - "@gitbeaker/requester-utils" "^35.4.0" + "@gitbeaker/requester-utils" "^35.5.0" form-data "^4.0.0" li "^1.3.0" mime "^3.0.0" @@ -1796,12 +2192,12 @@ xcase "^2.0.1" "@gitbeaker/node@^35.1.0": - version "35.4.0" - resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-35.4.0.tgz#096a28fd936dad9220625a917502063adf566acf" - integrity sha512-5isY4/ePtjIs7uFwVW8U5oNNwntyCa3eTs3+tb6CywtLGdlnenk0eNJjZ2Srn8xJOKsOCCpQkT5hP1AOKSAlAQ== + version "35.5.0" + resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-35.5.0.tgz#9cb38c6532cf8cb32e0de7fa8dac6327adaca471" + integrity sha512-gjGSqnNLNz4aP28dX8Cy97n7MtGUaSewoc+oJAkPDgpA0y4gWhgxpzEEwXrhhPsQA4Cxig0CLpPHMTi1Q1fVvg== dependencies: - "@gitbeaker/core" "^35.4.0" - "@gitbeaker/requester-utils" "^35.4.0" + "@gitbeaker/core" "^35.5.0" + "@gitbeaker/requester-utils" "^35.5.0" delay "^5.0.0" got "11.8.3" xcase "^2.0.1" @@ -1815,19 +2211,19 @@ qs "^6.10.1" xcase "^2.0.1" -"@gitbeaker/requester-utils@^35.4.0": - version "35.4.0" - resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-35.4.0.tgz#918be27441024c841d3f9e09c498e6b24de291e2" - integrity sha512-NkJ55oMM7TNMWUNnzeYLdLOIUFv1LBBN/PCM2u7J5+67gpck03YfWAkgrODIqBePFyrfm19pdka3SUlNBP0G2g== +"@gitbeaker/requester-utils@^35.5.0": + version "35.5.0" + resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-35.5.0.tgz#22fb09698da018607c9ce70a62114747bd92752e" + integrity sha512-5weE7X7kfIuUHGWl0XfSt68kFMVXUg3SrUZW7MOS2Y0w2tj/H+6eomzJ7mYcJmI7ttNGIDGYhqxftPh8zLpi0w== dependencies: form-data "^4.0.0" qs "^6.10.1" xcase "^2.0.1" "@google-cloud/common@^3.7.0": - version "3.7.0" - resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.7.0.tgz#ee3fba75aeaa614978aebf8740380670026592aa" - integrity sha512-oFgpKLjH9JTOAyQd3kB36iSuH8wNSpDKb1TywlB6zcsG0xmJFxLutmfPhz03KUxRMNQOZ1K1Gc9BYvJifVnGVA== + version "3.10.0" + resolved "https://registry.npmjs.org/@google-cloud/common/-/common-3.10.0.tgz#454d1155bb512109cd83c6183aabbd39f9aabda7" + integrity sha512-XMbJYMh/ZSaZnbnrrOFfR/oQrb0SxG4qh6hDisWCoEbFcBHV0qHQo4uXfeMCzolx2Mfkh6VDaOGg+hyJsmxrlw== dependencies: "@google-cloud/projectify" "^2.0.0" "@google-cloud/promisify" "^2.0.0" @@ -1835,7 +2231,7 @@ duplexify "^4.1.1" ent "^2.2.0" extend "^3.0.2" - google-auth-library "^7.0.2" + google-auth-library "^7.14.0" retry-request "^4.2.2" teeny-request "^7.0.0" @@ -1998,34 +2394,34 @@ tslib "~2.3.0" "@graphql-codegen/typescript-resolvers@^2.4.3": - version "2.4.3" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-2.4.3.tgz#556dbaf23eac0ff9c321d3ce7126d96a839f793f" - integrity sha512-4m3E0zKLSXjGirZcYHHaZ0bxjy/gxvuumShFCKFmYTkHwTfqBaeh/pMhWqLkwC9wimrH6mQoPIYSQHLaF6Eqng== + version "2.6.1" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-2.6.1.tgz#5963da60de79cfb847b55b2433cd9f65a33d7aad" + integrity sha512-tb8qiIYRDbypHvEVBi04OlItABPeormhd6gh26Q0FO/dD+tLx1V85A6wnHC4jExWPP9+/3PwHJL1pq/0n1kxlA== dependencies: - "@graphql-codegen/plugin-helpers" "^2.3.2" - "@graphql-codegen/typescript" "^2.4.2" - "@graphql-codegen/visitor-plugin-common" "2.5.2" + "@graphql-codegen/plugin-helpers" "^2.4.0" + "@graphql-codegen/typescript" "^2.4.8" + "@graphql-codegen/visitor-plugin-common" "2.7.4" "@graphql-tools/utils" "^8.1.1" auto-bind "~4.0.0" tslib "~2.3.0" -"@graphql-codegen/typescript@^2.4.2": - version "2.4.5" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-2.4.5.tgz#80ca7a343511a27cd587c0b220b0a0dd99a4dc4b" - integrity sha512-Ytb8phNHKl/v/wxudsMOAV1dmzIbckWHm2J83PLNOvnu9CGEhgsd67vfe3ZoF95VU2BKSG8BXGa6uL9z2xDmuA== +"@graphql-codegen/typescript@^2.4.2", "@graphql-codegen/typescript@^2.4.8": + version "2.4.8" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-2.4.8.tgz#e8110baba9713cde72d57a5c95aa27400363ed9a" + integrity sha512-tVsHIkuyenBany7c5IMU1yi4S1er2hgyXJGNY7PcyhpJMx0eChmbqz1VTiZxDEwi8mDBS2mn3TaSJMh6xuJM5g== dependencies: "@graphql-codegen/plugin-helpers" "^2.4.0" "@graphql-codegen/schema-ast" "^2.4.1" - "@graphql-codegen/visitor-plugin-common" "2.7.1" + "@graphql-codegen/visitor-plugin-common" "2.7.4" auto-bind "~4.0.0" tslib "~2.3.0" -"@graphql-codegen/visitor-plugin-common@2.5.2": - version "2.5.2" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.5.2.tgz#90aa4add41e17bca83f1c7c8ad674f2a06065efd" - integrity sha512-qDMraPmumG+vEGAz42/asRkdgIRmQWH5HTc320UX+I6CY6eE/Ey85cgzoqeQGLV8gu4sj3UkNx/3/r79eX4u+Q== +"@graphql-codegen/visitor-plugin-common@2.7.1": + version "2.7.1" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.1.tgz#bdcded24d1d6f329d32b90704397d6001374b893" + integrity sha512-Ew/+0GwGwL3ZSXS0cYz6lVvE7S5SkbTr7caRpLa4FpAdLGNskCgdA1n6W5tX5c7qsaYstqlxp30hJTfgTsYS6A== dependencies: - "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-codegen/plugin-helpers" "^2.4.0" "@graphql-tools/optimize" "^1.0.1" "@graphql-tools/relay-operation-optimizer" "^6.3.7" "@graphql-tools/utils" "^8.3.0" @@ -2036,10 +2432,10 @@ parse-filepath "^1.0.2" tslib "~2.3.0" -"@graphql-codegen/visitor-plugin-common@2.7.1": - version "2.7.1" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.1.tgz#bdcded24d1d6f329d32b90704397d6001374b893" - integrity sha512-Ew/+0GwGwL3ZSXS0cYz6lVvE7S5SkbTr7caRpLa4FpAdLGNskCgdA1n6W5tX5c7qsaYstqlxp30hJTfgTsYS6A== +"@graphql-codegen/visitor-plugin-common@2.7.4": + version "2.7.4" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.7.4.tgz#fbc8aec9df0035b8f29fa64a6356cbb02062da5d" + integrity sha512-aaDoEudDD+B7DK/UwDSL2Fzej75N9hNJ3N8FQuTIeDyw6FNGWUxmkjVBLQGlzfnYfK8IYkdfYkrPn3Skq0pVxA== dependencies: "@graphql-codegen/plugin-helpers" "^2.4.0" "@graphql-tools/optimize" "^1.0.1" @@ -2215,15 +2611,23 @@ valid-url "1.0.9" "@graphql-tools/load@^7.3.0", "@graphql-tools/load@^7.4.1": - version "7.5.1" - resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-7.5.1.tgz#8c7f846d2185ddc1d44fdfbf1ed9cb678f69e40b" - integrity sha512-j9XcLYZPZdl/TzzqA83qveJmwcCxgGizt5L1+C1/Z68brTEmQHLdQCOR3Ma3ewESJt6DU05kSTu2raKaunkjRg== + version "7.5.5" + resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-7.5.5.tgz#d61e5bdee59b6c8aa9631f74f0b2a70d512e5b31" + integrity sha512-qPasit140nwTbMQbFCfZcgaS7q/0+xMQGdkMGU11rtHt6/jMgJIKDUU8/fJGKltNY3EeHlEdVtZmggZD7Rr6bA== dependencies: - "@graphql-tools/schema" "8.3.1" - "@graphql-tools/utils" "^8.6.0" + "@graphql-tools/schema" "8.3.5" + "@graphql-tools/utils" "8.6.5" p-limit "3.1.0" tslib "~2.3.0" +"@graphql-tools/merge@8.2.6", "@graphql-tools/merge@^8.2.1": + version "8.2.6" + resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.6.tgz#7fb615fa9c143c3151ff025e501d52bd48186d19" + integrity sha512-dkwTm4czMISi/Io47IVvq2Fl9q4TIGKpJ0VZjuXYdEFkECyH6A5uwxZfPVandZG+gQs8ocFFoa6RisiUJLZrJw== + dependencies: + "@graphql-tools/utils" "8.6.5" + tslib "~2.3.0" + "@graphql-tools/merge@^6.0.0", "@graphql-tools/merge@^6.2.12": version "6.2.14" resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.14.tgz#694e2a2785ba47558e5665687feddd2935e9d94e" @@ -2233,14 +2637,6 @@ "@graphql-tools/utils" "^7.7.0" tslib "~2.2.0" -"@graphql-tools/merge@^8.2.1": - version "8.2.1" - resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.1.tgz#bf83aa06a0cfc6a839e52a58057a84498d0d51ff" - integrity sha512-Q240kcUszhXiAYudjuJgNuLgy9CryDP3wp83NOZQezfA6h3ByYKU7xI6DiKrdjyVaGpYN3ppUmdj0uf5GaXzMA== - dependencies: - "@graphql-tools/utils" "^8.5.1" - tslib "~2.3.0" - "@graphql-tools/mock@^8.1.2": version "8.5.1" resolved "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.5.1.tgz#379d18eafdcb65486beb8f9247b33b7b693c53aa" @@ -2293,13 +2689,13 @@ relay-compiler "12.0.0" tslib "~2.3.0" -"@graphql-tools/schema@8.3.1", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.1.1", "@graphql-tools/schema@^8.1.2", "@graphql-tools/schema@^8.3.1": - version "8.3.1" - resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.1.tgz#1ee9da494d2da457643b3c93502b94c3c4b68c74" - integrity sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ== +"@graphql-tools/schema@8.3.5", "@graphql-tools/schema@^8.0.0", "@graphql-tools/schema@^8.1.1", "@graphql-tools/schema@^8.1.2", "@graphql-tools/schema@^8.3.1": + version "8.3.5" + resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.5.tgz#ebec0c0c7f53e591d34375d6f81d0660852fcaca" + integrity sha512-3mJ/K7TdL+fnEUtCUqF4qkh1fcNMzaxgwKgO9fSYSTS7zyT16hbi5XSulSTshygHgaD2u+MO588iR4ZJcbZcIg== dependencies: - "@graphql-tools/merge" "^8.2.1" - "@graphql-tools/utils" "^8.5.1" + "@graphql-tools/merge" "8.2.6" + "@graphql-tools/utils" "8.6.5" tslib "~2.3.0" value-or-promise "1.0.11" @@ -2369,13 +2765,20 @@ dependencies: tslib "~2.3.0" -"@graphql-tools/utils@8.6.2", "@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.3.0", "@graphql-tools/utils@^8.5.1", "@graphql-tools/utils@^8.5.2", "@graphql-tools/utils@^8.5.3", "@graphql-tools/utils@^8.6.0": +"@graphql-tools/utils@8.6.2": version "8.6.2" resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.2.tgz#095408135f091aac68fe18a0a21b708e685500da" integrity sha512-x1DG0cJgpJtImUlNE780B/dfp8pxvVxOD6UeykFH5rHes26S4kGokbgU8F1IgrJ1vAPm/OVBHtd2kicTsPfwdA== dependencies: tslib "~2.3.0" +"@graphql-tools/utils@8.6.5", "@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.3.0", "@graphql-tools/utils@^8.5.1", "@graphql-tools/utils@^8.5.2", "@graphql-tools/utils@^8.5.3", "@graphql-tools/utils@^8.6.0": + version "8.6.5" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.5.tgz#ac04571b03f854c7a938b2ab700516a6c6d32335" + integrity sha512-mjOtaWiS2WIqRz/cq5gaeM3sVrllcu2xbtHROw1su1v3xWa3D3dKgn8Lrl7+tvWs5WUVySsBss/VZ3WdoPkCrA== + dependencies: + tslib "~2.3.0" + "@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.5.0", "@graphql-tools/utils@^7.7.0", "@graphql-tools/utils@^7.7.1", "@graphql-tools/utils@^7.8.1", "@graphql-tools/utils@^7.9.0": version "7.10.0" resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz#07a4cb5d1bec1ff1dc1d47a935919ee6abd38699" @@ -2506,162 +2909,160 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" + jest-mock "^27.5.1" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + v8-to-istanbul "^8.1.0" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" slash "^3.0.0" source-map "^0.6.1" write-file-atomic "^3.0.0" @@ -2688,6 +3089,17 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@jimp/bmp@^0.10.3": version "0.10.3" resolved "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.10.3.tgz#79a23678e8389865c62e77b0dccc3e069dfc27f0" @@ -3013,6 +3425,24 @@ resolved "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.5" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" + integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.11" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jsdevtools/ono@^7.1.3": version "7.1.3" resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" @@ -3031,9 +3461,9 @@ ioredis "^4.28.5" "@kubernetes/client-node@^0.16.0": - version "0.16.1" - resolved "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.16.1.tgz#c78ef667579777c1a532983922807e228dbc9b90" - integrity sha512-/Ah+3gFSjXFeqDMGGTyYBKug44Eu2D2qowKLdiZqxCkHdSNgy+CNk6FU1Vy80WrTvGkF/CZr4az6O5AopAiJEw== + version "0.16.3" + resolved "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.16.3.tgz#a26a5abbd6e45603b4f75f0baff00e19853e5be7" + integrity sha512-L7IckuyuPfhd+/Urib8MRas9D6sfKEq8IaITYcaE6LlU+Y8MeD7MTbuW6Yb2WdeRuFN8HPSS47mxPnOUNYBXEg== dependencies: "@types/js-yaml" "^4.0.1" "@types/node" "^10.12.0" @@ -3050,9 +3480,9 @@ openid-client "^4.1.1" request "^2.88.0" rfc4648 "^1.3.0" - shelljs "^0.8.4" + shelljs "^0.8.5" stream-buffers "^3.0.2" - tar "^6.0.2" + tar "^6.1.11" tmp-promise "^3.0.2" tslib "^1.9.3" underscore "^1.9.1" @@ -3958,9 +4388,9 @@ integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== "@microsoft/microsoft-graph-types@^2.6.0": - version "2.15.0" - resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.15.0.tgz#1705ea1ce84c3de4705957392d7f0e3ae465c9f8" - integrity sha512-EyuOpZs55HUoC37Ujrp6IRgE5ghf/wtDrlWuJm7J/DKoB7B/Iek7eXdavTygx2uBeDZ5b4jXXvwl4PiDLlEcsw== + version "2.16.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.16.0.tgz#5329890d230c4fdd9f57f39e26dfade0882c94f3" + integrity sha512-Qvxv9mpXb/F4xlESEkSLjREHj3dAixTkH3LVO6Ct6sllc5RWrQxPxaSGqW9IpcLU6jI49f2XNSGLotVef3Irdg== "@microsoft/tsdoc-config@~0.15.2": version "0.15.2" @@ -4558,28 +4988,11 @@ fast-deep-equal "^3.1.3" lodash.clonedeep "^4.5.0" -"@opencensus/web-types@0.0.7": - version "0.0.7" - resolved "https://registry.npmjs.org/@opencensus/web-types/-/web-types-0.0.7.tgz#4426de1fe5aa8f624db395d2152b902874f0570a" - integrity sha512-xB+w7ZDAu3YBzqH44rCmG9/RlrOmFuDPt/bpf17eJr8eZSrLt7nc7LnWdxM9Mmoj/YKMHpxRg28txu3TcpiL+g== - -"@opentelemetry/api@^0.10.2": - version "0.10.2" - resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-0.10.2.tgz#9647b881f3e1654089ff7ea59d587b2d35060654" - integrity sha512-GtpMGd6vkzDMYcpu2t9LlhEgMy/SzBwRnz48EejlRArYqZzqSzAsKmegUK7zHgl+EOIaK9mKHhnRaQu3qw20cA== - dependencies: - "@opentelemetry/context-base" "^0.10.2" - "@opentelemetry/api@^1.0.1": version "1.0.4" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz#a167e46c10d05a07ab299fc518793b0cff8f6924" integrity sha512-BuJuXRSJNQ3QoKA6GWWDyuLpOUck+9hAXNMCnrloc1aWVoy6Xq6t9PUV08aBZ4Lutqq2LEHM486bpZqoViScog== -"@opentelemetry/context-base@^0.10.2": - version "0.10.2" - resolved "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.10.2.tgz#55bea904b2b91aa8a8675df9eaba5961bddb1def" - integrity sha512-hZNKjKOYsckoOEgBziGMnBcX0M7EtstnCmwz5jZUOUYwlZ+/xxX6z3jPu1XVO2Jivk0eLfuP9GP+vFD49CMetw== - "@panva/asn1.js@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" @@ -4670,15 +5083,15 @@ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-3.2.1.tgz#84fbf322485aee3a84101e189161f0687779ec8d" integrity sha512-8UiDeDbjCImFSfOegGu13otQ7OdP9FOYpcLjeouppnhs+MPeIEAtYS+jCcBKmi3reyTagC15/KVSRhde1wS1vg== -"@roadiehq/backstage-plugin-buildkite@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-1.4.0.tgz#f268ba2b6f43f68158af379d55fc7e19842c1b43" - integrity sha512-bG16kyn5e/QzceGy7PXs/wb09/p7W1O+kev7V/PGECHlxgj/Gg575maFsyZU/bIGTQM3cUavwvj0pzfdXjFD3Q== +"@roadiehq/backstage-plugin-buildkite@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-2.0.0.tgz#e5da049a2e15c533228e26da1af537b3783fe5de" + integrity sha512-qurSWt+n505BXFBTzoTSXn9BPmhtLjt+SPiVYjD3KSqJ7hwOoJg3rr1wuCWigUDj1R0Kx9TgaxNscETGBzUUgQ== dependencies: - "@backstage/catalog-model" "^0.13.0" + "@backstage/catalog-model" "^1.0.0" "@backstage/core-components" "^0.9.0" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/plugin-catalog-react" "^0.9.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/plugin-catalog-react" "^1.0.0" "@backstage/theme" "^0.2.6" "@material-ui/core" "^4.12.1" "@material-ui/icons" "^4.11.2" @@ -4689,16 +5102,16 @@ react-router-dom "6.0.0-beta.0" react-use "^17.2.4" -"@roadiehq/backstage-plugin-github-insights@^1.6.0": - version "1.6.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.6.0.tgz#0ccd63ff08f720f09d0076ba3210ef2373bb62b2" - integrity sha512-TV7hrxxhJy0yN+jERVKALYW36nwvNWCpCOcBGgoBepbFCOhiUgc3g9EjRp0b0z+uUSywFkEPUYH1B1CXGzDLdw== +"@roadiehq/backstage-plugin-github-insights@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-2.0.0.tgz#1137f4f3ad5744559bf095ee868371f5fd8c60ec" + integrity sha512-6rLMWq8F6hYNAkuF+gOKyAZ/A2MGVHuNJArBJCUVhw6b/tmSxQcj78y62GaXB1Zz9KERhSk8OZf94m0+EE8C5g== dependencies: - "@backstage/catalog-model" "^0.13.0" + "@backstage/catalog-model" "^1.0.0" "@backstage/core-components" "^0.9.0" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/integration-react" "^0.1.10" - "@backstage/plugin-catalog-react" "^0.9.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/integration-react" "^1.0.0" + "@backstage/plugin-catalog-react" "^1.0.0" "@backstage/theme" "^0.2.7" "@date-io/core" "2.10.7" "@material-ui/core" "^4.11.0" @@ -4713,15 +5126,15 @@ react-use "^17.2.4" zustand "3.6.9" -"@roadiehq/backstage-plugin-github-pull-requests@^1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-1.5.0.tgz#30266e037831c11842cd8e1456008fede2c6b580" - integrity sha512-jlNYV/RC0HVwkp8lCmPAhps6KpRP4OuTrsrIXt557EHWb+kuwEAV5jKaSkINvUOjIPAnOB7SA83xXWZW1Y9lig== +"@roadiehq/backstage-plugin-github-pull-requests@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-2.0.0.tgz#99ba670e1bd9bdc954b43901883aaf9832ae8942" + integrity sha512-3R9MmWGqmd7cVEZtiUShr10M8j+aM0SQUaZG1EuW+AzlErA2Zo1pcSBwKuIOcotWbyQzVsruKEPI2BrvJkJikQ== dependencies: - "@backstage/catalog-model" "^0.13.0" + "@backstage/catalog-model" "^1.0.0" "@backstage/core-components" "^0.9.0" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/plugin-catalog-react" "^0.9.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/plugin-catalog-react" "^1.0.0" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@octokit/rest" "^18.5.3" @@ -4734,15 +5147,15 @@ react-router "6.0.0-beta.0" react-use "^17.2.4" -"@roadiehq/backstage-plugin-travis-ci@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.4.0.tgz#165b401b4b4eaae93ebb1148854c8343392f9950" - integrity sha512-tYNpfybmKiOULYoL7YV2DJ1l0e8UcbZsc3B0pSUbchPedoWlvgpXoG5BmttTZcCW8MI0e1kK2FWqwnVkvygRhQ== +"@roadiehq/backstage-plugin-travis-ci@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-2.0.0.tgz#d86eba878bbaa6ada77e9eda932ff25bd9121021" + integrity sha512-nrqWysSLBq4t9jVkQb5rz1pfNt2Ojlj+s0004FSNzPa8PPD+5XUhFZiS9Mi4kJVUC9BLW5SVdJzWtWEGbxyLPw== dependencies: - "@backstage/catalog-model" "^0.13.0" + "@backstage/catalog-model" "^1.0.0" "@backstage/core-components" "^0.9.0" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/plugin-catalog-react" "^0.9.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/plugin-catalog-react" "^1.0.0" "@backstage/theme" "^0.2.9" "@material-ui/core" "^4.11.3" "@material-ui/icons" "^4.11.2" @@ -4887,13 +5300,6 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== - dependencies: - "@sinonjs/commons" "^1.7.0" - "@sinonjs/fake-timers@^7.0.4", "@sinonjs/fake-timers@^7.1.0": version "7.1.2" resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" @@ -4901,6 +5307,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/samsam@^6.0.2": version "6.0.2" resolved "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz#a0117d823260f282c04bff5f8704bdc2ac6910bb" @@ -4915,20 +5328,20 @@ resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@spotify/eslint-config-base@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-12.0.0.tgz#0b1e41bb436d5c1c20714703629514d64c3c0f06" - integrity sha512-5Uud/TmzakqmdUNCZpD8JFQRa2VG3dVd3DanSMpU/nVdu6K5LyX8EMU3Tz1vGP18Wih8iAu/sBSJhntNzw7e6w== +"@spotify/eslint-config-base@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-13.0.0.tgz#bb748bb2b705ffb5085f873aa0daf94dfad59985" + integrity sha512-BrnexUcUQkp6XUw8HWSmE4LpWtJGgEC6A7vrSkgpgKJtZaYkpw8O+Xnk60DA266ecbFHYbQD6ngqKHlvjNB+pA== "@spotify/eslint-config-react@^12.0.0": version "12.0.0" resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-12.0.0.tgz#5b8d4bc3b81a8ec2824648f482f1f6c3cf711893" integrity sha512-lNHZRtJesNA273OJHBVUGAg2JYyVDZ+bsT7h3OwnX1HYgejJ3YcKPSziPM8TGFAN8DruH3tHFfaM63uAIA1+uw== -"@spotify/eslint-config-typescript@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz#4c7af3f74a47668bec0c860b72e2a0103e78a138" - integrity sha512-nMVll8ZkN/W8+IHn6Iz3YzCKW0qhrn3TVfyxkAr3qmXm5cex+GzyUdZEuxb8rdN2inZL6A1Il2NFfO5p/UKxog== +"@spotify/eslint-config-typescript@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz#908a51e05de7ec9d2e3b136f9831e0ddb36c7f89" + integrity sha512-pR9hptdnu39FCofS9y4SbLRJOgGNhBsX6N24HD3LxbLnVedSF2kpqMmgaBw3W2T932oXSrJlf5nsCePWVsUTjQ== "@spotify/prettier-config@^12.0.0": version "12.0.0" @@ -5105,13 +5518,13 @@ pretty-format "^27.0.2" "@testing-library/jest-dom@^5.10.1": - version "5.14.1" - resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz#8501e16f1e55a55d675fe73eecee32cdaddb9766" - integrity sha512-dfB7HVIgTNCxH22M1+KU6viG5of2ldoA5ly8Ar8xkezKHKXjRvznCdbMbqjYGgO2xjRbwnR+rR8MLUIqF3kKbQ== + version "5.16.2" + resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.2.tgz#f329b36b44aa6149cd6ced9adf567f8b6aa1c959" + integrity sha512-6ewxs1MXWwsBFZXIk4nKKskWANelkdUehchEOokHsN8X7c2eKXGw+77aRV63UU8f/DTSVUPLaGxdrj4lN7D/ug== dependencies: "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" - aria-query "^4.2.2" + aria-query "^5.0.0" chalk "^3.0.0" css "^3.0.0" css.escape "^1.5.1" @@ -5140,9 +5553,9 @@ "@types/react-dom" "*" "@testing-library/user-event@^13.1.8": - version "13.1.8" - resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.8.tgz#9cbf342b88d837ee188f9f9f4df6d1beaaf179c2" - integrity sha512-M04HgOlJvxILf5xyrkJaEQfFOtcvhy3usLldQIEg9zgFIYQofSmFGVfFlS7BWowqlBGLrItwGMlPXCoBgoHSiw== + version "13.5.0" + resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== dependencies: "@babel/runtime" "^7.12.5" @@ -5239,7 +5652,7 @@ dependencies: "@types/node" "*" -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": +"@types/babel__core@^7.0.0": version "7.1.9" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== @@ -5250,6 +5663,17 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" +"@types/babel__core@^7.1.14": + version "7.1.18" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8" + integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__generator@*": version "7.6.1" resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" @@ -5554,15 +5978,7 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint@*": - version "6.1.8" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-6.1.8.tgz#7e868f89bc1e520323d405940e49cb912ede5bba" - integrity sha512-CJBhm9pYdUS8cFVbXACWlLxZWFBTQMiM0eI6RYxng3u9oQ9gHdQ5PN89DHPrK4RISRzX62nRsteUlbBgEIdSug== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/eslint@^7.28.2": +"@types/eslint@*", "@types/eslint@^7.28.2": version "7.29.0" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== @@ -5609,7 +6025,7 @@ "@types/express" "*" "@types/xml2js" "*" -"@types/express@*", "@types/express@4.17.13", "@types/express@^4.17.6": +"@types/express@*", "@types/express@4.17.13", "@types/express@^4.17.13", "@types/express@^4.17.6": version "4.17.13" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== @@ -5835,9 +6251,9 @@ "@types/json-schema" "*" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json-stable-stringify@^1.0.32": version "1.0.32" @@ -5960,11 +6376,6 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node-cron@^3.0.0", "@types/node-cron@^3.0.1": - version "3.0.1" - resolved "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.1.tgz#e01a874d4c2aa1a02ebc64cfd1cd8ebdbad7a996" - integrity sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg== - "@types/node-fetch@^2.5.0", "@types/node-fetch@^2.5.12", "@types/node-fetch@^2.5.7": version "2.5.12" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" @@ -6092,10 +6503,10 @@ resolved "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== -"@types/prettier@^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.0.tgz#dc85454b953178cc6043df5208b9e949b54a3bc4" - integrity sha512-/rM+sWiuOZ5dvuVzV37sUuklsbg+JPOP8d+nNFlo2ZtfpzPiPvh1/gc8liWOLBqe+sR+ZM7guPaIcTt6UZTo7Q== +"@types/prettier@^2.1.5": + version "2.4.3" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf" + integrity sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w== "@types/prop-types@*", "@types/prop-types@^15.7.3": version "15.7.4" @@ -6103,9 +6514,9 @@ integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== "@types/puppeteer@^5.4.4": - version "5.4.4" - resolved "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.4.tgz#e92abeccc4f46207c3e1b38934a1246be080ccd0" - integrity sha512-3Nau+qi69CN55VwZb0ATtdUAlYlqOOQ3OfQfq0Hqgc4JMFXiQT/XInlwQ9g6LbicDslE6loIFsXFklGh5XmI6Q== + version "5.4.5" + resolved "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.5.tgz#154e3850a77bfd3967f036680de8ddc88eb3a12b" + integrity sha512-lxCjpDEY+DZ66+W3x5Af4oHnEmUXt0HuaRzkBGE2UZiZEp/V1d3StpLPlmNVu/ea091bdNmVPl44lu8Wy/0ZCA== dependencies: "@types/node" "*" @@ -6470,10 +6881,10 @@ resolved "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== -"@types/tunnel@^0.0.1": - version "0.0.1" - resolved "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c" - integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A== +"@types/tunnel@^0.0.3": + version "0.0.3" + resolved "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" + integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA== dependencies: "@types/node" "*" @@ -6625,23 +7036,15 @@ eslint-utils "^3.0.0" "@typescript-eslint/parser@^5.9.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.13.0.tgz#0394ed8f2f849273c0bf4b811994d177112ced5c" - integrity sha512-GdrU4GvBE29tm2RqWOM0P5QfCtgCyN4hXICj/X9ibKED16136l9ZpoJvCL5pSKtmJzA+NRDzQ312wWMejCVVfg== + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508" + integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA== dependencies: - "@typescript-eslint/scope-manager" "5.13.0" - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/typescript-estree" "5.13.0" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/typescript-estree" "5.16.0" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.13.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.13.0.tgz#cf6aff61ca497cb19f0397eea8444a58f46156b6" - integrity sha512-T4N8UvKYDSfVYdmJq7g2IPJYCRzwtp74KyDZytkR4OL3NRupvswvmJQJ4CX5tDSurW2cvCc1Ia1qM7d0jpa7IA== - dependencies: - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/visitor-keys" "5.13.0" - "@typescript-eslint/scope-manager@5.14.0": version "5.14.0" resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.14.0.tgz#ea518962b42db8ed0a55152ea959c218cb53ca7b" @@ -6650,6 +7053,14 @@ "@typescript-eslint/types" "5.14.0" "@typescript-eslint/visitor-keys" "5.14.0" +"@typescript-eslint/scope-manager@5.16.0": + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.16.0.tgz#7e7909d64bd0c4d8aef629cdc764b9d3e1d3a69a" + integrity sha512-P+Yab2Hovg8NekLIR/mOElCDPyGgFZKhGoZA901Yax6WR6HVeGLbsqJkZ+Cvk5nts/dAlFKm8PfL43UZnWdpIQ== + dependencies: + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.0" + "@typescript-eslint/scope-manager@5.9.0": version "5.9.0" resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz#02dfef920290c1dcd7b1999455a3eaae7a1a3117" @@ -6667,34 +7078,21 @@ debug "^4.3.2" tsutils "^3.21.0" -"@typescript-eslint/types@5.13.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.13.0.tgz#da1de4ae905b1b9ff682cab0bed6b2e3be9c04e5" - integrity sha512-LmE/KO6DUy0nFY/OoQU0XelnmDt+V8lPQhh8MOVa7Y5k2gGRd6U9Kp3wAjhB4OHg57tUO0nOnwYQhRRyEAyOyg== - "@typescript-eslint/types@5.14.0": version "5.14.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.14.0.tgz#96317cf116cea4befabc0defef371a1013f8ab11" integrity sha512-BR6Y9eE9360LNnW3eEUqAg6HxS9Q35kSIs4rp4vNHRdfg0s+/PgHgskvu5DFTM7G5VKAVjuyaN476LCPrdA7Mw== +"@typescript-eslint/types@5.16.0": + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.16.0.tgz#5827b011982950ed350f075eaecb7f47d3c643ee" + integrity sha512-oUorOwLj/3/3p/HFwrp6m/J2VfbLC8gjW5X3awpQJ/bSG+YRGFS4dpsvtQ8T2VNveV+LflQHjlLvB6v0R87z4g== + "@typescript-eslint/types@5.9.0": version "5.9.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz#e5619803e39d24a03b3369506df196355736e1a3" integrity sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg== -"@typescript-eslint/typescript-estree@5.13.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.13.0.tgz#b37c07b748ff030a3e93d87c842714e020b78141" - integrity sha512-Q9cQow0DeLjnp5DuEDjLZ6JIkwGx3oYZe+BfcNuw/POhtpcxMTy18Icl6BJqTSd+3ftsrfuVb7mNHRZf7xiaNA== - dependencies: - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/visitor-keys" "5.13.0" - debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.14.0": version "5.14.0" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" @@ -6708,6 +7106,19 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.16.0": + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.16.0.tgz#32259459ec62f5feddca66adc695342f30101f61" + integrity sha512-SE4VfbLWUZl9MR+ngLSARptUv2E8brY0luCdgmUevU6arZRY/KxYoLI/3V/yxaURR8tLRN7bmZtJdgmzLHI6pQ== + dependencies: + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/visitor-keys" "5.16.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + "@typescript-eslint/typescript-estree@5.9.0": version "5.9.0" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz#0e5c6f03f982931abbfbc3c1b9df5fbf92a3490f" @@ -6733,13 +7144,17 @@ eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.13.0": - version "5.13.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.13.0.tgz#f45ff55bcce16403b221ac9240fbeeae4764f0fd" - integrity sha512-HLKEAS/qA1V7d9EzcpLFykTePmOQqOFim8oCvhY3pZgQ8Hi38hYpHd9e5GN6nQBFQNecNhws5wkS9Y5XIO0s/g== +"@typescript-eslint/utils@^5.10.0": + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.16.0.tgz#42218b459d6d66418a4eb199a382bdc261650679" + integrity sha512-iYej2ER6AwmejLWMWzJIHy3nPJeGDuCqf8Jnb+jAQVoPpmWzwQOfa9hWVB8GIQE5gsCv/rfN4T+AYb/V06WseQ== dependencies: - "@typescript-eslint/types" "5.13.0" - eslint-visitor-keys "^3.0.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.16.0" + "@typescript-eslint/types" "5.16.0" + "@typescript-eslint/typescript-estree" "5.16.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" "@typescript-eslint/visitor-keys@5.14.0": version "5.14.0" @@ -6749,6 +7164,14 @@ "@typescript-eslint/types" "5.14.0" eslint-visitor-keys "^3.0.0" +"@typescript-eslint/visitor-keys@5.16.0": + version "5.16.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.16.0.tgz#f27dc3b943e6317264c7492e390c6844cd4efbbb" + integrity sha512-jqxO8msp5vZDhikTwq9ubyMHqZ67UIvawohr4qF3KhlpL7gzSjOd+8471H3nh5LyABkaI85laEKKU8SnGUK5/g== + dependencies: + "@typescript-eslint/types" "5.16.0" + eslint-visitor-keys "^3.0.0" + "@typescript-eslint/visitor-keys@5.9.0": version "5.9.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz#7585677732365e9d27f1878150fab3922784a1a6" @@ -6757,6 +7180,17 @@ "@typescript-eslint/types" "5.9.0" eslint-visitor-keys "^3.0.0" +"@uiw/react-codemirror@^4.5.1": + version "4.5.1" + resolved "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.5.1.tgz#f2af4e761f13b729a3ec7c7c858b34432cdf46a7" + integrity sha512-90SNE89uJOQgcZwV0fuZjCgDeH7pjagCqwOcYHvDG5Qedp2nKmb4/2mgDCtLBxTGKxb73ktUOkbdu3L05ck1Yg== + dependencies: + "@babel/runtime" "^7.17.2" + "@codemirror/basic-setup" "^0.19.1" + "@codemirror/state" "^0.19.9" + "@codemirror/theme-one-dark" "^0.19.1" + "@codemirror/view" "^0.19.45" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -7053,19 +7487,19 @@ ajv@^6.10.0, ajv@^6.10.1, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv uri-js "^4.2.2" ajv@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2" - integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ== + version "7.2.4" + resolved "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f" + integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: - version "8.9.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz#738019146638824dea25edcf299dcba1b0e7eb18" - integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.8.0: + version "8.11.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -7188,14 +7622,6 @@ any-promise@^1.0.0: resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -7212,10 +7638,10 @@ apollo-datasource@^3.3.1: apollo-server-caching "^3.3.0" apollo-server-env "^4.2.1" -apollo-reporting-protobuf@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.0.tgz#2fc0f7508e488851eda8a6e7c8cc3b5a156ab44b" - integrity sha512-51Jwrg0NvHJfKz7TIGU8+Os3rUAqWtXeKRsRtKYtTeMSBPNhzz8UoGjAB3XyVmUXRE3IRmLtDPDRFL7qbxMI/w== +apollo-reporting-protobuf@^3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.1.tgz#8c8761f9ac4375fd8490262d6144057cec6ce0b3" + integrity sha512-tyvj3Vj71TCh6c8PtdHOLgHHBSJ05DF/A/Po3q8yfHTBkOPcOJZE/GGN/PT/pwKg7HHxKcAeHDw7+xciVvGx0w== dependencies: "@apollo/protobufjs" "1.2.2" @@ -7226,10 +7652,10 @@ apollo-server-caching@^3.3.0: dependencies: lru-cache "^6.0.0" -apollo-server-core@^3.6.3: - version "3.6.3" - resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.3.tgz#6b12ffa1af8bc8799930f72360090834915033d1" - integrity sha512-TFJmAlI6vPp1MHOSXqYkE6leAyMekWv/D/3ma11uETkcd3EPjERGmxtTXPJElMVEkOK9BEElYKthCrH7bjYLuw== +apollo-server-core@^3.6.4, apollo-server-core@^3.6.5: + version "3.6.5" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.5.tgz#f8a30bab49d7ccbd9500b85968accae0d8631927" + integrity sha512-9lOTHnJnPV4TiVwt4DMl3HDWGebHxoJIU2tonOuIkRxOUjwCjsDsHfkIrPA3vutmXIb7R3OPxh/mofdDpm7DjQ== dependencies: "@apollographql/apollo-tools" "^0.5.1" "@apollographql/graphql-playground-html" "1.6.29" @@ -7237,12 +7663,12 @@ apollo-server-core@^3.6.3: "@graphql-tools/schema" "^8.0.0" "@josephg/resolvable" "^1.0.0" apollo-datasource "^3.3.1" - apollo-reporting-protobuf "^3.3.0" + apollo-reporting-protobuf "^3.3.1" apollo-server-caching "^3.3.0" apollo-server-env "^4.2.1" apollo-server-errors "^3.3.1" - apollo-server-plugin-base "^3.5.1" - apollo-server-types "^3.5.1" + apollo-server-plugin-base "^3.5.2" + apollo-server-types "^3.5.2" async-retry "^1.2.1" fast-json-stable-stringify "^2.1.0" graphql-tag "^2.11.0" @@ -7264,10 +7690,10 @@ apollo-server-errors@^3.3.1: resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz#ba5c00cdaa33d4cbd09779f8cb6f47475d1cd655" integrity sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA== -apollo-server-express@^3.0.0, apollo-server-express@^3.6.3: - version "3.6.3" - resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.3.tgz#5daf58bf0bdf0107ded7cd52c7e6ce6cd32c8b44" - integrity sha512-3CjahZ+n+1T7pHH1qW1B6Ns0BzwOMeupAp2u0+M8ruOmE/e7VKn0OSOQQckZ8Z2AcWxWeno9K89fIv3PoSYgYA== +apollo-server-express@^3.0.0, apollo-server-express@^3.6.4: + version "3.6.5" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.5.tgz#e623ee6f2e2d4cc2b4075baf7624a899a21ec1a6" + integrity sha512-mV1Dif//BUq1WFlCWfaSlhsuxhcJ/gGD8HdQ6/s3JEspStS7SgmeSUIwJYl3vw8IA/8j7GNg/EINkc57x2UbdA== dependencies: "@types/accepts" "^1.3.5" "@types/body-parser" "1.19.2" @@ -7275,35 +7701,35 @@ apollo-server-express@^3.0.0, apollo-server-express@^3.6.3: "@types/express" "4.17.13" "@types/express-serve-static-core" "4.17.28" accepts "^1.3.5" - apollo-server-core "^3.6.3" - apollo-server-types "^3.5.1" + apollo-server-core "^3.6.5" + apollo-server-types "^3.5.2" body-parser "^1.19.0" cors "^2.8.5" parseurl "^1.3.3" -apollo-server-plugin-base@^3.5.1: - version "3.5.1" - resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.1.tgz#73fc1591522e36e32eff3d033975333e30cf1a7c" - integrity sha512-wgDHz3lLrCqpecDky3z6AOQ0vik0qs0Cya/Ti6n3ESYXJ9MdK3jE/QunATIrOYYJaa+NKl9V7YwU+/bojNfFuQ== +apollo-server-plugin-base@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.2.tgz#18a08353f5ea207a07f2c1e66dd7ef2335ce8c84" + integrity sha512-SwIf1waDmNDb0kmn57QR++InwK6Iv/X2slpm/aFIoqFBe91r6uJfakJvQZuh8dLEgk68gxqFsT8zHRpxBclE+g== dependencies: - apollo-server-types "^3.5.1" + apollo-server-types "^3.5.2" -apollo-server-types@^3.5.1: - version "3.5.1" - resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.1.tgz#73fc8aa82b3175fde3906fa3d6786ee4d3e8c982" - integrity sha512-zG7xLl4mmHuZMAYOfjWKHY/IC/GgIkJ3HnYuR7FRrnPpRA9Yt5Kf1M1rjm1Esuqzpb/dt8pM7cX40QaIQObCYQ== +apollo-server-types@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.2.tgz#903d499a70b9010764cbab4d704ddb8c086aa06f" + integrity sha512-vhcbIWsBkoNibABOym4AAPBoNDjokhjUQokKYdwZMeqrb850PMQdNJFrGyXT5onP408Ghv4O8PfgBuPQmeJhVQ== dependencies: - apollo-reporting-protobuf "^3.3.0" + apollo-reporting-protobuf "^3.3.1" apollo-server-caching "^3.3.0" apollo-server-env "^4.2.1" apollo-server@^3.0.0: - version "3.6.3" - resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.3.tgz#0ba0ddb2835ccf27056d20b6f5b83b0ce9545a79" - integrity sha512-kNvOiDNkIaO+MsfR9v40Vz4ArlDdc9VwVKGJy5dniLW9AoDa/tSF99m8ItfGoMypqlRPMgrNGxkMuToBnvYXNQ== + version "3.6.4" + resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.4.tgz#e1bc966eb7d03944274056af4b17b380f0ac8696" + integrity sha512-PIEDWtfiiiKt0uEMJ7/qiyULPat/ichDN/h9GrrroOFiz/tfU/yJXuHpoq8R/uzVyn4GpEc4OoibC2zOr59zig== dependencies: - apollo-server-core "^3.6.3" - apollo-server-express "^3.6.3" + apollo-server-core "^3.6.4" + apollo-server-express "^3.6.4" express "^4.17.1" aproba@^1.0.3: @@ -7645,18 +8071,18 @@ available-typed-arrays@^1.0.2: array-filter "^1.0.0" aws-sdk-mock@^5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-5.2.1.tgz#126d4d5362c96b7d1d0bd87708a99d626c19ffd4" - integrity sha512-dY7zA1p/lX335V4/aOJ2L8ggXC3a5zokTJFZlZVW3uU+Zej7u+V7WrEcN5TVaJAnk4auT263T6EK/OHW4WjKhw== + version "5.6.2" + resolved "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-5.6.2.tgz#664771462953ca8d3806d5a50a63b4a6dbd5290f" + integrity sha512-GRJg8kjRJFLm2aLiPkYSqe/RreHqlqncAeFtWdAbtSxzBdct9EaV6rqSqjyWXKNJG45Rzn2Ojo3F6qVIgkQnSg== dependencies: aws-sdk "^2.928.0" sinon "^11.1.1" traverse "^0.6.6" aws-sdk@^2.840.0, aws-sdk@^2.928.0, aws-sdk@^2.948.0: - version "2.1088.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1088.0.tgz#e8ea761eff2b5c42aca6136c5a01fa9b8066f0a2" - integrity sha512-KtGJTvMPJL6QynasbSMNvz7Onc5ejebY6NzuUvgrw6sNohNJDR/3J/0e016ocQwvEq79MNK4v4EsxNi9eMELtg== + version "2.1100.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1100.0.tgz#20bbabc12fbc316067ba02af66bf371a455af9e3" + integrity sha512-StLSQCYFmFPxjoMntIb+8jUZ0vzmq3xkrwG5e/4qU1bSGWCmhhjvz6c+4j38AnIy8MFV1+tV8RArbhLUEV2dGw== dependencies: buffer "4.9.2" events "1.1.1" @@ -7720,18 +8146,18 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" slash "^3.0.0" babel-plugin-dynamic-import-node@^2.3.3: @@ -7741,21 +8167,21 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@istanbuljs/load-nyc-config" "^1.0.0" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -7851,12 +8277,12 @@ babel-preset-fbjs@^3.4.0: "@babel/plugin-transform-template-literals" "^7.0.0" babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== dependencies: - babel-plugin-jest-hoist "^26.6.2" + babel-plugin-jest-hoist "^27.5.1" babel-preset-current-node-syntax "^1.0.0" babel-runtime@^6.26.0: @@ -7954,11 +8380,6 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" -bdd-lazy-var@^2.6.0: - version "2.6.1" - resolved "https://registry.npmjs.org/bdd-lazy-var/-/bdd-lazy-var-2.6.1.tgz#ca03fb36d68c5a507c0ba9a4d53160b899e6b7cb" - integrity sha512-X3ADwcFji/IHIrYJhTTpaiWhoOx4pl4whdAx1dmvdeUPsMUb7fVYFvf/Q33VEAEAVkEwi5rgNSZ0Y9oOVeQV+A== - before-after-hook@^2.1.0, before-after-hook@^2.2.0: version "2.2.2" resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" @@ -8538,7 +8959,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -8576,13 +8997,6 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -8792,10 +9206,10 @@ circleci-api@^4.0.0: dependencies: axios "^0.21.1" -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== class-utils@^0.3.5: version "0.3.6" @@ -9479,11 +9893,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: safe-buffer "~5.1.1" cookie-parser@^1.4.5: - version "1.4.5" - resolved "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49" - integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw== + version "1.4.6" + resolved "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz#3ac3a7d35a7a03bbc7e365073a26074824214594" + integrity sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA== dependencies: - cookie "0.4.0" + cookie "0.4.1" cookie-signature "1.0.6" cookie-signature@1.0.6: @@ -9491,11 +9905,6 @@ cookie-signature@1.0.6: resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - cookie@0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" @@ -9658,6 +10067,11 @@ create-require@^1.1.0: resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +crelt@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" + integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== + cron@^1.8.2: version "1.8.2" resolved "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce" @@ -9796,12 +10210,12 @@ css-in-js-utils@^2.0.0: isobject "^3.0.1" css-loader@^6.5.1: - version "6.6.0" - resolved "https://registry.npmjs.org/css-loader/-/css-loader-6.6.0.tgz#c792ad5510bd1712618b49381bd0310574fafbd3" - integrity sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg== + version "6.7.1" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== dependencies: icss-utils "^5.1.0" - postcss "^8.4.5" + postcss "^8.4.7" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" @@ -10675,6 +11089,11 @@ diff-sequences@^26.6.2: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff2html@^2.7.0: version "2.12.2" resolved "https://registry.npmjs.org/diff2html/-/diff2html-2.12.2.tgz#356d35f9c87c42ebd11558bedf1c99c5b00886e8" @@ -11005,10 +11424,10 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emittery@^0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" - integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^7.0.1: version "7.0.3" @@ -11094,10 +11513,10 @@ engine.io@~3.5.0: engine.io-parser "~2.2.0" ws "~7.4.2" -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -11203,70 +11622,75 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild-android-arm64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.22.tgz#fb051169a63307d958aec85ad596cfc7d7770303" - integrity sha512-k1Uu4uC4UOFgrnTj2zuj75EswFSEBK+H6lT70/DdS4mTAOfs2ECv2I9ZYvr3w0WL0T4YItzJdK7fPNxcPw6YmQ== +esbuild-android-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.27.tgz#b868bbd9955a92309c69df628d8dd1945478b45c" + integrity sha512-LuEd4uPuj/16Y8j6kqy3Z2E9vNY9logfq8Tq+oTE2PZVuNs3M1kj5Qd4O95ee66yDGb3isaOCV7sOLDwtMfGaQ== -esbuild-darwin-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.22.tgz#615ea0a9de67b57a293a7128d7ac83ee307a856d" - integrity sha512-d8Ceuo6Vw6HM3fW218FB6jTY6O3r2WNcTAU0SGsBkXZ3k8SDoRLd3Nrc//EqzdgYnzDNMNtrWegK2Qsss4THhw== +esbuild-android-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.27.tgz#e7d6430555e8e9c505fd87266bbc709f25f1825c" + integrity sha512-E8Ktwwa6vX8q7QeJmg8yepBYXaee50OdQS3BFtEHKrzbV45H4foMOeEE7uqdjGQZFBap5VAqo7pvjlyA92wznQ== -esbuild-darwin-arm64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.22.tgz#82054dcfcecb15ccfd237093b8008e7745a99ad9" - integrity sha512-YAt9Tj3SkIUkswuzHxkaNlT9+sg0xvzDvE75LlBo4DI++ogSgSmKNR6B4eUhU5EUUepVXcXdRIdqMq9ppeRqfw== +esbuild-darwin-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.27.tgz#4dc7484127564e89b4445c0a560a3cb50b3d68e1" + integrity sha512-czw/kXl/1ZdenPWfw9jDc5iuIYxqUxgQ/Q+hRd4/3udyGGVI31r29LCViN2bAJgGvQkqyLGVcG03PJPEXQ5i2g== -esbuild-freebsd-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.22.tgz#778a818c5b078d5cdd6bb6c0e0797217d196999b" - integrity sha512-ek1HUv7fkXMy87Qm2G4IRohN+Qux4IcnrDBPZGXNN33KAL0pEJJzdTv0hB/42+DCYWylSrSKxk3KUXfqXOoH4A== +esbuild-darwin-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.27.tgz#469e59c665f84a8ed323166624c5e7b9b2d22ac1" + integrity sha512-BEsv2U2U4o672oV8+xpXNxN9bgqRCtddQC6WBh4YhXKDcSZcdNh7+6nS+DM2vu7qWIWNA4JbRG24LUUYXysimQ== -esbuild-freebsd-arm64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.22.tgz#18da93b9f3db2e036f72383bfe73b28b73bb332c" - integrity sha512-zPh9SzjRvr9FwsouNYTqgqFlsMIW07O8mNXulGeQx6O5ApgGUBZBgtzSlBQXkHi18WjrosYfsvp5nzOKiWzkjQ== +esbuild-freebsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.27.tgz#895df03bf5f87094a56c9a5815bf92e591903d70" + integrity sha512-7FeiFPGBo+ga+kOkDxtPmdPZdayrSzsV9pmfHxcyLKxu+3oTcajeZlOO1y9HW+t5aFZPiv7czOHM4KNd0tNwCA== -esbuild-linux-32@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.22.tgz#d0d5d9f5bb3536e17ac097e9512019c65b7c0234" - integrity sha512-SnpveoE4nzjb9t2hqCIzzTWBM0RzcCINDMBB67H6OXIuDa4KqFqaIgmTchNA9pJKOVLVIKd5FYxNiJStli21qg== +esbuild-freebsd-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.27.tgz#0b72a41a6b8655e9a8c5608f2ec1afdcf6958441" + integrity sha512-8CK3++foRZJluOWXpllG5zwAVlxtv36NpHfsbWS7TYlD8S+QruXltKlXToc/5ZNzBK++l6rvRKELu/puCLc7jA== -esbuild-linux-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.22.tgz#2773d540971999ea7f38107ef92fca753f6a8c30" - integrity sha512-Zcl9Wg7gKhOWWNqAjygyqzB+fJa19glgl2JG7GtuxHyL1uEnWlpSMytTLMqtfbmRykIHdab797IOZeKwk5g0zg== +esbuild-linux-32@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.27.tgz#43b8ba3803b0bbe7f051869c6a8bf6de1e95de28" + integrity sha512-qhNYIcT+EsYSBClZ5QhLzFzV5iVsP1YsITqblSaztr3+ZJUI+GoK8aXHyzKd7/CKKuK93cxEMJPpfi1dfsOfdw== -esbuild-linux-arm64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.22.tgz#5d4480ce6d6bffab1dd76a23158f5a5ab33e7ba4" - integrity sha512-8q/FRBJtV5IHnQChO3LHh/Jf7KLrxJ/RCTGdBvlVZhBde+dk3/qS9fFsUy+rs3dEi49aAsyVitTwlKw1SUFm+A== +esbuild-linux-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.27.tgz#dc8072097327ecfadba1735562824ce8c05dd0bd" + integrity sha512-ESjck9+EsHoTaKWlFKJpPZRN26uiav5gkI16RuI8WBxUdLrrAlYuYSndxxKgEn1csd968BX/8yQZATYf/9+/qg== -esbuild-linux-arm@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.22.tgz#c6391b3f7c8fa6d3b99a7e893ce0f45f3a921eef" - integrity sha512-soPDdbpt/C0XvOOK45p4EFt8HbH5g+0uHs5nUKjHVExfgR7du734kEkXR/mE5zmjrlymk5AA79I0VIvj90WZ4g== +esbuild-linux-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.27.tgz#c52b58cbe948426b1559910f521b0a3f396f10b8" + integrity sha512-no6Mi17eV2tHlJnqBHRLekpZ2/VYx+NfGxKcBE/2xOMYwctsanCaXxw4zapvNrGE9X38vefVXLz6YCF8b1EHiQ== -esbuild-linux-mips64le@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.22.tgz#2c8dabac355c502e86c38f9f292b3517d8e181f3" - integrity sha512-SiNDfuRXhGh1JQLLA9JPprBgPVFOsGuQ0yDfSPTNxztmVJd8W2mX++c4FfLpAwxuJe183mLuKf7qKCHQs5ZnBQ== +esbuild-linux-arm@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.27.tgz#df869dbd67d4ee3a04b3c7273b6bd2b233e78a18" + integrity sha512-JnnmgUBdqLQO9hoNZQqNHFWlNpSX82vzB3rYuCJMhtkuaWQEmQz6Lec1UIxJdC38ifEghNTBsF9bbe8dFilnCw== -esbuild-linux-ppc64le@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.22.tgz#69d71b2820d5c94306072dac6094bae38e77d1c0" - integrity sha512-6t/GI9I+3o1EFm2AyN9+TsjdgWCpg2nwniEhjm2qJWtJyJ5VzTXGUU3alCO3evopu8G0hN2Bu1Jhz2YmZD0kng== +esbuild-linux-mips64le@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.27.tgz#a2b646d9df368b01aa970a7b8968be6dd6b01d19" + integrity sha512-NolWP2uOvIJpbwpsDbwfeExZOY1bZNlWE/kVfkzLMsSgqeVcl5YMen/cedRe9mKnpfLli+i0uSp7N+fkKNU27A== -esbuild-linux-riscv64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.22.tgz#c0ec0fc3a23624deebf657781550d2329cec4213" - integrity sha512-AyJHipZKe88sc+tp5layovquw5cvz45QXw5SaDgAq2M911wLHiCvDtf/07oDx8eweCyzYzG5Y39Ih568amMTCQ== +esbuild-linux-ppc64le@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.27.tgz#9a21af766a0292578a3009c7408b8509cac7cefd" + integrity sha512-/7dTjDvXMdRKmsSxKXeWyonuGgblnYDn0MI1xDC7J1VQXny8k1qgNp6VmrlsawwnsymSUUiThhkJsI+rx0taNA== -esbuild-linux-s390x@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.22.tgz#ec2af4572d63336cfb27f5a5c851fb1b6617dd91" - integrity sha512-Sz1NjZewTIXSblQDZWEFZYjOK6p8tV6hrshYdXZ0NHTjWE+lwxpOpWeElUGtEmiPcMT71FiuA9ODplqzzSxkzw== +esbuild-linux-riscv64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.27.tgz#344a27f91568056a5903ad5841b447e00e78d740" + integrity sha512-D+aFiUzOJG13RhrSmZgrcFaF4UUHpqj7XSKrIiCXIj1dkIkFqdrmqMSOtSs78dOtObWiOrFCDDzB24UyeEiNGg== + +esbuild-linux-s390x@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.27.tgz#73a7309bd648a07ef58f069658f989a5096130db" + integrity sha512-CD/D4tj0U4UQjELkdNlZhQ8nDHU5rBn6NGp47Hiz0Y7/akAY5i0oGadhEIg0WCY/HYVXFb3CsSPPwaKcTOW3bg== esbuild-loader@^2.18.0: version "2.18.0" @@ -11280,60 +11704,61 @@ esbuild-loader@^2.18.0: tapable "^2.2.0" webpack-sources "^2.2.0" -esbuild-netbsd-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.22.tgz#0e283278e9fdbaa7f0930f93ee113d7759cd865e" - integrity sha512-TBbCtx+k32xydImsHxvFgsOCuFqCTGIxhzRNbgSL1Z2CKhzxwT92kQMhxort9N/fZM2CkRCPPs5wzQSamtzEHA== +esbuild-netbsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.27.tgz#482a587cdbd18a6c264a05136596927deb46c30a" + integrity sha512-h3mAld69SrO1VoaMpYl3a5FNdGRE/Nqc+E8VtHOag4tyBwhCQXxtvDDOAKOUQexBGca0IuR6UayQ4ntSX5ij1Q== -esbuild-openbsd-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.22.tgz#2a73bba04e16d8ef278fbe2be85248e12a2f2cc2" - integrity sha512-vK912As725haT313ANZZZN+0EysEEQXWC/+YE4rQvOQzLuxAQc2tjbzlAFREx3C8+uMuZj/q7E5gyVB7TzpcTA== +esbuild-openbsd-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.27.tgz#e99f8cdc63f1628747b63edd124d53cf7796468d" + integrity sha512-xwSje6qIZaDHXWoPpIgvL+7fC6WeubHHv18tusLYMwL+Z6bEa4Pbfs5IWDtQdHkArtfxEkIZz77944z8MgDxGw== -esbuild-sunos-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.22.tgz#8fe03513b8b2e682a6d79d5e3ca5849651a3c1d8" - integrity sha512-/mbJdXTW7MTcsPhtfDsDyPEOju9EOABvCjeUU2OJ7fWpX/Em/H3WYDa86tzLUbcVg++BScQDzqV/7RYw5XNY0g== +esbuild-sunos-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.27.tgz#8611d825bcb8239c78d57452e83253a71942f45c" + integrity sha512-/nBVpWIDjYiyMhuqIqbXXsxBc58cBVH9uztAOIfWShStxq9BNBik92oPQPJ57nzWXRNKQUEFWr4Q98utDWz7jg== -esbuild-windows-32@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.22.tgz#a75df61e3e49df292a1842be8e877a3153ee644f" - integrity sha512-1vRIkuvPTjeSVK3diVrnMLSbkuE36jxA+8zGLUOrT4bb7E/JZvDRhvtbWXWaveUc/7LbhaNFhHNvfPuSw2QOQg== +esbuild-windows-32@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.27.tgz#c06374206d4d92dd31d4fda299b09f51a35e82f6" + integrity sha512-Q9/zEjhZJ4trtWhFWIZvS/7RUzzi8rvkoaS9oiizkHTTKd8UxFwn/Mm2OywsAfYymgUYm8+y2b+BKTNEFxUekw== -esbuild-windows-64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.22.tgz#d06cf8bbe4945b8bf95a730d871e54a22f635941" - integrity sha512-AxjIDcOmx17vr31C5hp20HIwz1MymtMjKqX4qL6whPj0dT9lwxPexmLj6G1CpR3vFhui6m75EnBEe4QL82SYqw== +esbuild-windows-64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.27.tgz#756631c1d301dfc0d1a887deed2459ce4079582f" + integrity sha512-b3y3vTSl5aEhWHK66ngtiS/c6byLf6y/ZBvODH1YkBM+MGtVL6jN38FdHUsZasCz9gFwYs/lJMVY9u7GL6wfYg== -esbuild-windows-arm64@0.14.22: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.22.tgz#f8b1b05c548073be8413a5ecb12d7c2f6e717227" - integrity sha512-5wvQ+39tHmRhNpu2Fx04l7QfeK3mQ9tKzDqqGR8n/4WUxsFxnVLfDRBGirIfk4AfWlxk60kqirlODPoT5LqMUg== +esbuild-windows-arm64@0.14.27: + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.27.tgz#ad7e187193dcd18768b16065a950f4441d7173f4" + integrity sha512-I/reTxr6TFMcR5qbIkwRGvldMIaiBu2+MP0LlD7sOlNXrfqIl9uNjsuxFPGEG4IRomjfQ5q8WT+xlF/ySVkqKg== esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.6: - version "0.14.22" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.22.tgz#2b55fde89d7aa5aaaad791816d58ff9dfc5ed085" - integrity sha512-CjFCFGgYtbFOPrwZNJf7wsuzesx8kqwAffOlbYcFDLFuUtP8xloK1GH+Ai13Qr0RZQf9tE7LMTHJ2iVGJ1SKZA== + version "0.14.27" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1" + integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q== optionalDependencies: - esbuild-android-arm64 "0.14.22" - esbuild-darwin-64 "0.14.22" - esbuild-darwin-arm64 "0.14.22" - esbuild-freebsd-64 "0.14.22" - esbuild-freebsd-arm64 "0.14.22" - esbuild-linux-32 "0.14.22" - esbuild-linux-64 "0.14.22" - esbuild-linux-arm "0.14.22" - esbuild-linux-arm64 "0.14.22" - esbuild-linux-mips64le "0.14.22" - esbuild-linux-ppc64le "0.14.22" - esbuild-linux-riscv64 "0.14.22" - esbuild-linux-s390x "0.14.22" - esbuild-netbsd-64 "0.14.22" - esbuild-openbsd-64 "0.14.22" - esbuild-sunos-64 "0.14.22" - esbuild-windows-32 "0.14.22" - esbuild-windows-64 "0.14.22" - esbuild-windows-arm64 "0.14.22" + esbuild-android-64 "0.14.27" + esbuild-android-arm64 "0.14.27" + esbuild-darwin-64 "0.14.27" + esbuild-darwin-arm64 "0.14.27" + esbuild-freebsd-64 "0.14.27" + esbuild-freebsd-arm64 "0.14.27" + esbuild-linux-32 "0.14.27" + esbuild-linux-64 "0.14.27" + esbuild-linux-arm "0.14.27" + esbuild-linux-arm64 "0.14.27" + esbuild-linux-mips64le "0.14.27" + esbuild-linux-ppc64le "0.14.27" + esbuild-linux-riscv64 "0.14.27" + esbuild-linux-s390x "0.14.27" + esbuild-netbsd-64 "0.14.27" + esbuild-openbsd-64 "0.14.27" + esbuild-sunos-64 "0.14.27" + esbuild-windows-32 "0.14.27" + esbuild-windows-64 "0.14.27" + esbuild-windows-arm64 "0.14.27" escalade@^3.1.1: version "3.1.1" @@ -11421,6 +11846,15 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-deprecation@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/eslint-plugin-deprecation/-/eslint-plugin-deprecation-1.3.2.tgz#a8125d28c56158cdfa1a685197e6be8ed86f189e" + integrity sha512-z93wbx9w7H/E3ogPw6AZMkkNJ6m51fTZRNZPNQqxQLmx+KKt7aLkMU9wN67s71i+VVHN4tLOZ3zT3QLbnlC0Mg== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + tslib "^2.3.1" + tsutils "^3.21.0" + eslint-plugin-graphql@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" @@ -11450,12 +11884,12 @@ eslint-plugin-import@^2.25.4: resolve "^1.20.0" tsconfig-paths "^3.12.0" -eslint-plugin-jest@^25.3.4: - version "25.3.4" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.3.4.tgz#2031dfe495be1463330f8b80096ddc91f8e6387f" - integrity sha512-CCnwG71wvabmwq/qkz0HWIqBHQxw6pXB1uqt24dxqJ9WB34pVg49bL1sjXphlJHgTMWGhBjN1PicdyxDxrfP5A== +eslint-plugin-jest@^26.1.2: + version "26.1.2" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.2.tgz#0f1a15c62889fffc3f78a773749d672f1bedb15f" + integrity sha512-1bXCoRODPkGN06n9KAMls4Jm0eyS+0Q/LWcIxhqWR2ycV0Z7lnx2c10idk4dtFIJY5xStgiIr5snC6/rxcXpbw== dependencies: - "@typescript-eslint/experimental-utils" "^5.0.0" + "@typescript-eslint/utils" "^5.10.0" eslint-plugin-jsx-a11y@^6.5.1: version "6.5.1" @@ -11555,24 +11989,23 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint-webpack-plugin@^2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" - integrity sha512-V+LPY/T3kur5QO3u+1s34VDTcRxjXWPUGM4hlmTb5DwVD0OQz631yGTxJZf4SpAqAjdbBVe978S8BJeHpAdOhQ== +eslint-webpack-plugin@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" + integrity sha512-xSucskTN9tOkfW7so4EaiFIkulWLXwCB/15H917lR6pTv0Zot6/fetFucmENRb7J5whVSFKIvwnrnsa78SG2yg== dependencies: "@types/eslint" "^7.28.2" - arrify "^2.0.1" jest-worker "^27.3.1" micromatch "^4.0.4" normalize-path "^3.0.0" schema-utils "^3.1.1" eslint@^8.6.0: - version "8.10.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d" - integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw== + version "8.11.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" + integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA== dependencies: - "@eslint/eslintrc" "^1.2.0" + "@eslint/eslintrc" "^1.2.1" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -11733,65 +12166,65 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" "example-app@link:packages/app": - version "0.2.68" + version "0.2.70-next.0" dependencies: - "@backstage/app-defaults" "^0.2.1" - "@backstage/catalog-model" "^0.13.0" - "@backstage/cli" "^0.15.2" - "@backstage/core-app-api" "^0.6.0" - "@backstage/core-components" "^0.9.1" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/integration-react" "^0.1.25" - "@backstage/plugin-airbrake" "^0.3.2" - "@backstage/plugin-apache-airflow" "^0.1.10" - "@backstage/plugin-api-docs" "^0.8.2" - "@backstage/plugin-azure-devops" "^0.1.18" - "@backstage/plugin-badges" "^0.2.26" - "@backstage/plugin-catalog" "^0.10.0" - "@backstage/plugin-catalog-common" "^0.2.2" - "@backstage/plugin-catalog-graph" "^0.2.14" - "@backstage/plugin-catalog-import" "^0.8.5" - "@backstage/plugin-catalog-react" "^0.9.0" - "@backstage/plugin-circleci" "^0.3.2" - "@backstage/plugin-cloudbuild" "^0.3.2" - "@backstage/plugin-code-coverage" "^0.1.29" - "@backstage/plugin-cost-insights" "^0.11.24" - "@backstage/plugin-explore" "^0.3.33" - "@backstage/plugin-gcalendar" "^0.1.0" - "@backstage/plugin-gcp-projects" "^0.3.21" - "@backstage/plugin-github-actions" "^0.5.2" - "@backstage/plugin-gocd" "^0.1.8" - "@backstage/plugin-graphiql" "^0.2.34" - "@backstage/plugin-home" "^0.4.18" - "@backstage/plugin-jenkins" "^0.7.1" - "@backstage/plugin-kafka" "^0.3.2" - "@backstage/plugin-kubernetes" "^0.6.2" - "@backstage/plugin-lighthouse" "^0.3.2" - "@backstage/plugin-newrelic" "^0.3.20" - "@backstage/plugin-newrelic-dashboard" "^0.1.10" - "@backstage/plugin-org" "^0.5.2" - "@backstage/plugin-pagerduty" "0.3.29" - "@backstage/plugin-permission-react" "^0.3.3" - "@backstage/plugin-rollbar" "^0.4.2" - "@backstage/plugin-scaffolder" "^0.15.0" - "@backstage/plugin-search" "^0.7.3" - "@backstage/plugin-search-common" "^0.3.1" - "@backstage/plugin-sentry" "^0.3.40" - "@backstage/plugin-shortcuts" "^0.2.3" - "@backstage/plugin-tech-insights" "^0.1.12" - "@backstage/plugin-tech-radar" "^0.5.9" - "@backstage/plugin-techdocs" "^0.15.1" - "@backstage/plugin-todo" "^0.2.4" - "@backstage/plugin-user-settings" "^0.4.1" + "@backstage/app-defaults" "^1.0.1-next.0" + "@backstage/catalog-model" "^1.0.1-next.0" + "@backstage/cli" "^0.16.1-next.0" + "@backstage/core-app-api" "^1.0.1-next.0" + "@backstage/core-components" "^0.9.3-next.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/integration-react" "^1.0.1-next.0" + "@backstage/plugin-airbrake" "^0.3.4-next.0" + "@backstage/plugin-apache-airflow" "^0.1.12-next.0" + "@backstage/plugin-api-docs" "^0.8.4-next.0" + "@backstage/plugin-azure-devops" "^0.1.20-next.0" + "@backstage/plugin-badges" "^0.2.28-next.0" + "@backstage/plugin-catalog" "^1.0.1-next.0" + "@backstage/plugin-catalog-common" "^1.0.1-next.0" + "@backstage/plugin-catalog-graph" "^0.2.16-next.0" + "@backstage/plugin-catalog-import" "^0.8.7-next.0" + "@backstage/plugin-catalog-react" "^1.0.1-next.0" + "@backstage/plugin-circleci" "^0.3.4-next.0" + "@backstage/plugin-cloudbuild" "^0.3.4-next.0" + "@backstage/plugin-code-coverage" "^0.1.31-next.0" + "@backstage/plugin-cost-insights" "^0.11.26-next.0" + "@backstage/plugin-explore" "^0.3.35-next.0" + "@backstage/plugin-gcalendar" "^0.2.1-next.0" + "@backstage/plugin-gcp-projects" "^0.3.23-next.0" + "@backstage/plugin-github-actions" "^0.5.4-next.0" + "@backstage/plugin-gocd" "^0.1.10-next.0" + "@backstage/plugin-graphiql" "^0.2.36-next.0" + "@backstage/plugin-home" "^0.4.20-next.0" + "@backstage/plugin-jenkins" "^0.7.3-next.0" + "@backstage/plugin-kafka" "^0.3.4-next.0" + "@backstage/plugin-kubernetes" "^0.6.4-next.0" + "@backstage/plugin-lighthouse" "^0.3.4-next.0" + "@backstage/plugin-newrelic" "^0.3.22-next.0" + "@backstage/plugin-newrelic-dashboard" "^0.1.12-next.0" + "@backstage/plugin-org" "^0.5.4-next.0" + "@backstage/plugin-pagerduty" "0.3.31-next.0" + "@backstage/plugin-permission-react" "^0.3.4" + "@backstage/plugin-rollbar" "^0.4.4-next.0" + "@backstage/plugin-scaffolder" "^1.0.1-next.0" + "@backstage/plugin-search" "^0.7.5-next.0" + "@backstage/plugin-search-common" "^0.3.3-next.0" + "@backstage/plugin-sentry" "^0.3.42-next.0" + "@backstage/plugin-shortcuts" "^0.2.5-next.0" + "@backstage/plugin-tech-insights" "^0.1.14-next.0" + "@backstage/plugin-tech-radar" "^0.5.11-next.0" + "@backstage/plugin-techdocs" "^1.0.1-next.0" + "@backstage/plugin-todo" "^0.2.6-next.0" + "@backstage/plugin-user-settings" "^0.4.3-next.0" "@backstage/theme" "^0.2.15" "@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.4.0" - "@roadiehq/backstage-plugin-github-insights" "^1.6.0" - "@roadiehq/backstage-plugin-github-pull-requests" "^1.5.0" - "@roadiehq/backstage-plugin-travis-ci" "^1.4.0" + "@roadiehq/backstage-plugin-buildkite" "^2.0.0" + "@roadiehq/backstage-plugin-github-insights" "^2.0.0" + "@roadiehq/backstage-plugin-github-pull-requests" "^2.0.0" + "@roadiehq/backstage-plugin-travis-ci" "^2.0.0" history "^5.0.0" prop-types "^15.7.2" react "^17.0.2" @@ -11802,12 +12235,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: 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" - integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== - -execa@4.1.0, execa@^4.0.0: +execa@4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -11905,17 +12333,15 @@ expand-template@^2.0.3: resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" express-prom-bundle@^6.3.6: version "6.4.1" @@ -12645,7 +13071,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -13039,10 +13465,10 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -google-auth-library@^7.0.0, google-auth-library@^7.0.2, google-auth-library@^7.6.1: - version "7.12.0" - resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.12.0.tgz#7965db6bc20cb31f2df05a08a296bbed6af69426" - integrity sha512-RS/whvFPMoF1hQNxnoVET3DWKPBt1Xgqe2rY0k+Jn7TNhoHlwdnSe7Rlcbo2Nub3Mt2lUVz26X65aDQrWp6x8w== +google-auth-library@^7.0.0, google-auth-library@^7.14.0, google-auth-library@^7.6.1: + version "7.14.0" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.0.tgz#9d6a20592f7b4d4c463cd3e93934c4b1711d5dc6" + integrity sha512-or8r7qUqGVI3W8lVSdPh0ZpeFyQHeE73g5c0p+bLNTTUFXJ+GSeDQmZRZ2p4H8cF/RJYa4PNvi/A1ar1uVNLFA== dependencies: arrify "^2.0.0" base64-js "^1.3.0" @@ -13250,11 +13676,6 @@ grouped-queue@^2.0.0: resolved "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz#a2c6713f2171e45db2c300a3a9d7c119d694dac8" integrity sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - gtoken@^5.0.4: version "5.1.0" resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz#4ba8d2fc9a8459098f76e7e8fd7beaa39fda9fe4" @@ -13673,9 +14094,9 @@ http-proxy-agent@^5.0.0: debug "4" http-proxy-middleware@^2.0.0: - version "2.0.3" - resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.3.tgz#5df04f69a89f530c2284cd71eeaa51ba52243289" - integrity sha512-1bloEwnrHMnCoO/Gcwbz7eSVvW50KPES01PecpagI+YLNLci4AcuKJrujW4Mc3sBLpFxMSlsLNHS5Nl/lvrTPA== + version "2.0.4" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" + integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" @@ -13833,11 +14254,6 @@ ignore@^3.3.5: resolved "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -14799,14 +15215,20 @@ istanbul-lib-coverage@^3.0.0: resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== +istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== dependencies: - "@babel/core" "^7.7.5" + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" + istanbul-lib-coverage "^3.2.0" semver "^6.3.0" istanbul-lib-report@^3.0.0: @@ -14827,7 +15249,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: +istanbul-reports@^3.1.3: version "3.1.4" resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -14857,57 +15279,87 @@ jenkins@^0.28.1: dependencies: papi "^0.29.0" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" exit "^0.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" prompts "^2.0.1" - yargs "^15.4.1" + yargs "^16.2.0" -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" chalk "^4.0.0" + ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" jest-css-modules@^2.1.0: version "2.1.0" @@ -14916,7 +15368,7 @@ jest-css-modules@^2.1.0: dependencies: identity-obj-proxy "3.0.0" -jest-diff@^26.0.0, jest-diff@^26.6.2: +jest-diff@^26.0.0: version "26.6.2" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -14926,138 +15378,151 @@ jest-diff@^26.0.0, jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "^27.5.1" + jest-util "^27.5.1" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/graceful-fs" "^4.1.2" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" walker "^1.0.7" optionalDependencies: - fsevents "^2.1.2" + fsevents "^2.3.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" -jest-matcher-utils@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" - integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" slash "^3.0.0" - stack-utils "^2.0.2" + stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -15065,121 +15530,125 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" source-map-support "^0.5.6" - throat "^5.0.0" + throat "^6.0.1" -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" - cjs-module-lexer "^0.6.0" + cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - exit "^0.1.2" + execa "^5.0.0" glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== dependencies: "@types/node" "*" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" natural-compare "^1.4.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" semver "^7.3.2" jest-transform-yaml@^1.0.0: @@ -15189,41 +15658,41 @@ jest-transform-yaml@^1.0.0: dependencies: js-yaml "4.1.0" -jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" + "@jest/types" "^27.5.1" + camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^26.3.0" + jest-get-type "^27.5.1" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.2" + jest-util "^27.5.1" string-length "^4.0.1" jest-when@^3.1.0: @@ -15231,15 +15700,6 @@ jest-when@^3.1.0: resolved "https://registry.npmjs.org/jest-when/-/jest-when-3.5.1.tgz#33ab6f923661cf878cd08fe9df64b507934603db" integrity sha512-o+HiaIVCg1IC95sMDKHU9G5v5N5l3UHqXvJpf0PgAMThZeQo4Hf5Sgoj+wpCBRGg4/KtzSAZZZEKNiLqE0i4eQ== -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - jest-worker@^27.3.1, jest-worker@^27.4.1: version "27.4.6" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz#5d2d93db419566cb680752ca0792780e71b3273e" @@ -15249,14 +15709,23 @@ jest-worker@^27.3.1, jest-worker@^27.4.1: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^26.0.1: - version "26.6.3" - resolved "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== +jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: - "@jest/core" "^26.6.3" + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== + dependencies: + "@jest/core" "^27.5.1" import-local "^3.0.2" - jest-cli "^26.6.3" + jest-cli "^27.5.1" jimp@^0.10.3: version "0.10.3" @@ -15421,7 +15890,7 @@ jscodeshift@^0.13.0: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@^16.4.0, jsdom@^16.5.2: +jsdom@^16.5.2, jsdom@^16.6.0: version "16.7.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -15600,11 +16069,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.2, json5@^2.1.3, json5@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + version "2.2.1" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonc-parser@^3.0.0: version "3.0.0" @@ -16655,6 +17122,13 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" +magic-string@^0.26.1: + version "0.26.1" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.26.1.tgz#ba9b651354fa9512474199acecf9c6dbe93f97fd" + integrity sha512-ndThHmvgtieXe8J/VGPjG+Apu7v7ItcD5mhEIvOscWjPF/ccOiLxHaSuCAS2G+3x4GKsAbT8u7zdyamupui8Tg== + dependencies: + sourcemap-codec "^1.4.8" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -17000,7 +17474,7 @@ media-typer@0.3.0: vinyl "^2.0.1" vinyl-file "^3.0.0" -memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.1: +memfs@^3.1.2, memfs@^3.4.1: version "3.4.1" resolved "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== @@ -17351,7 +17825,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" parse-entities "^3.0.0" -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.1.10: version "3.1.10" resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -17391,10 +17865,10 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": - version "1.51.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-db@~1.33.0: version "1.33.0" @@ -17409,11 +17883,11 @@ mime-types@2.1.18: mime-db "~1.33.0" mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.34" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.51.0" + mime-db "1.52.0" mime@1.6.0, mime@^1.3.4: version "1.6.0" @@ -17468,9 +17942,9 @@ min-indent@^1.0.0: integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= mini-css-extract-plugin@^2.4.2: - version "2.5.3" - resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz#c5c79f9b22ce9b4f164e9492267358dbe35376d9" - integrity sha512-YseMB8cs8U/KCaAGQoqYmfUuhhGW0a9p9XvWXrxVOkE3/IiISTLw4ALNt7JR5B2eYauFM+PQGSbXMDmVbR7Tfw== + version "2.6.0" + resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" + integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== dependencies: schema-utils "^4.0.0" @@ -17514,10 +17988,10 @@ minimist-options@4.1.0, minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: + version "1.2.6" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minimisted@^2.0.0: version "2.0.1" @@ -17662,7 +18136,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@^0.5.31, moment-timezone@^0.5.x: +moment-timezone@^0.5.x: version "0.5.34" resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== @@ -17937,13 +18411,6 @@ node-cache@^5.1.2: dependencies: clone "2.x" -node-cron@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/node-cron/-/node-cron-3.0.0.tgz#b33252803e430f9cd8590cf85738efa1497a9522" - integrity sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA== - dependencies: - moment-timezone "^0.5.31" - node-dir@^0.1.17: version "0.1.17" resolved "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -17969,9 +18436,9 @@ node-fetch@2.6.7, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node- whatwg-url "^5.0.0" node-forge@^1.0.0, node-forge@^1.2.0: - version "1.2.1" - resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" - integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== + version "1.3.0" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== node-gyp@^5.0.2: version "5.1.0" @@ -18066,18 +18533,6 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - node-releases@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -18670,11 +19125,6 @@ p-cancelable@^2.0.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-each-series@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" - integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== - p-filter@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" @@ -19008,6 +19458,16 @@ parse-json@^5.0.0: json-parse-better-errors "^1.0.1" lines-and-columns "^1.1.6" +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parse-package-name@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/parse-package-name/-/parse-package-name-0.1.0.tgz#3f44dd838feb4c2be4bf318bae4477d7706bade4" @@ -19477,6 +19937,11 @@ pirates@^4.0.0, pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + pixelmatch@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" @@ -19821,10 +20286,10 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.1.0, postcss@^8.4.5: - version "8.4.7" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz#f99862069ec4541de386bf57f5660a6c7a0875a8" - integrity sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A== +postcss@^8.1.0, postcss@^8.4.7: + version "8.4.12" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: nanoid "^3.3.1" picocolors "^1.0.0" @@ -19944,12 +20409,21 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + printj@~1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@^1.25.0, prismjs@~1.27.0: +prismjs@^1.27.0, prismjs@~1.27.0: version "1.27.0" resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== @@ -20487,9 +20961,9 @@ react-helmet@6.1.0: react-side-effect "^2.1.0" react-hook-form@^7.12.2, react-hook-form@^7.13.0: - version "7.27.1" - resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.27.1.tgz#fe5fbcb6bf58751f66d9569e998d671480cc57f6" - integrity sha512-N3a7A6zIQ8DJeThisVZGtOUabTbJw+7DHJidmB9w8m3chckv2ZWKb5MHps9d2pPJqmCDoWe53Bos56bYmJms5w== + version "7.28.1" + resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.28.1.tgz#95fc37be6c6b9d57212eb7eca055fb36d079b3b7" + integrity sha512-mgwxvXuvt3FMY/mdnWbPc++Zf1U5xYzkhOaL05mtFMLvXc9MvUhMUlKtUVuO12sOrgT3nPXBgVFawtiJ4ONrgg== react-hot-loader@^4.13.0: version "4.13.0" @@ -20655,15 +21129,15 @@ react-sparklines@^1.7.0: prop-types "^15.5.10" react-syntax-highlighter@^15.4.5: - version "15.4.5" - resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.5.tgz#db900d411d32a65c8e90c39cd64555bf463e712e" - integrity sha512-RC90KQTxZ/b7+9iE6s9nmiFLFjWswUcfULi4GwVzdFVKVMQySkJWBuOmJFfjwjMVCo0IUUuJrWebNKyviKpwLQ== + version "15.5.0" + resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== dependencies: "@babel/runtime" "^7.3.1" highlight.js "^10.4.1" lowlight "^1.17.0" - prismjs "^1.25.0" - refractor "^3.2.0" + prismjs "^1.27.0" + refractor "^3.6.0" react-test-renderer@^16.13.1: version "16.13.1" @@ -20707,27 +21181,7 @@ react-universal-interface@^0.6.2: resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== -react-use@^17.2.4: - version "17.2.4" - resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.4.tgz#1f89be3db0a8237c79253db0a15e12bbe3cfeff1" - integrity sha512-vQGpsAM0F5UIlshw5UI8ULGPS4yn5rm7/qvn3T1Gnkrz7YRMEEMh+ynKcmRloOyiIeLvKWiQjMiwRGtdbgs5qQ== - dependencies: - "@types/js-cookie" "^2.2.6" - "@xobotyi/scrollbar-width" "^1.9.5" - copy-to-clipboard "^3.3.1" - fast-deep-equal "^3.1.3" - fast-shallow-equal "^1.0.0" - js-cookie "^2.2.1" - nano-css "^5.3.1" - react-universal-interface "^0.6.2" - resize-observer-polyfill "^1.5.1" - screenfull "^5.1.0" - set-harmonic-interval "^1.0.1" - throttle-debounce "^3.0.1" - ts-easing "^0.2.0" - tslib "^2.1.0" - -react-use@^17.3.1, react-use@^17.3.2: +react-use@^17.2.4, react-use@^17.3.1, react-use@^17.3.2: version "17.3.2" resolved "https://registry.npmjs.org/react-use/-/react-use-17.3.2.tgz#448abf515f47c41c32455024db28167cb6e53be8" integrity sha512-bj7OD0/1wL03KyWmzFXAFe425zziuTf7q8olwCYBfOeFHY1qfO1FAMjROQLsLZYwG4Rx63xAfb7XAbBrJsZmEw== @@ -21076,7 +21530,7 @@ reflect-metadata@^0.1.13: resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -refractor@^3.2.0: +refractor@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== @@ -21413,7 +21867,12 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0: +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0: version "1.21.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f" integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA== @@ -21550,13 +22009,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rollup-plugin-dts@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.1.0.tgz#63b1e7de3970bb6d50877e60df2150a3892bc49c" - integrity sha512-rriXIm3jdUiYeiAAd1Fv+x2AxK6Kq6IybB2Z/IdoAW95fb4uRUurYsEYKa8L1seedezDeJhy8cfo8FEL9aZzqg== + version "4.2.0" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.2.0.tgz#f7ae4646f5b3aec07560bc341c0dc4a85f8a0dff" + integrity sha512-lx6irWVhz/x4//tIqRhzk4FOqGQ0n37ZM2wpPCn4uafl/EmiV92om7ZdAsq7Bzho6C+Xh5GfsyuP9H+Udv72Lg== dependencies: - magic-string "^0.25.7" + magic-string "^0.26.1" optionalDependencies: - "@babel/code-frame" "^7.16.0" + "@babel/code-frame" "^7.16.7" rollup-plugin-esbuild@^4.7.2: version "4.7.2" @@ -21608,11 +22067,6 @@ rollup@^2.60.2: optionalDependencies: fsevents "~2.3.2" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - rtl-css-js@^1.14.0: version "1.14.0" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d" @@ -21702,21 +22156,6 @@ safe-stable-stringify@^2.2.0, safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - sanitize-filename@^1.6.1: version "1.6.3" resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" @@ -22024,7 +22463,7 @@ shell-quote@^1.7.3: resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -shelljs@^0.8.4, shelljs@^0.8.5: +shelljs@^0.8.5: version "0.8.5" resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -22033,11 +22472,6 @@ shelljs@^0.8.4, shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - shx@^0.3.2: version "0.3.4" resolved "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" @@ -22578,10 +23012,10 @@ stack-trace@0.0.x: resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stack-utils@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" - integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" @@ -23243,7 +23677,7 @@ tar@^4.4.12: safe-buffer "^5.2.1" yallist "^3.1.1" -tar@^6.0.2, tar@^6.1.0, tar@^6.1.2: +tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -23268,19 +23702,19 @@ tdigest@^0.1.1: bintrees "1.0.1" "techdocs-cli-embedded-app@link:packages/techdocs-cli-embedded-app": - version "0.2.67" + version "0.2.69-next.0" dependencies: - "@backstage/app-defaults" "^0.2.1" - "@backstage/catalog-model" "^0.13.0" - "@backstage/cli" "^0.15.2" - "@backstage/config" "^0.1.15" - "@backstage/core-app-api" "^0.6.0" - "@backstage/core-components" "^0.9.1" - "@backstage/core-plugin-api" "^0.8.0" - "@backstage/integration-react" "^0.1.25" - "@backstage/plugin-catalog" "^0.10.0" - "@backstage/plugin-techdocs" "^0.15.1" - "@backstage/test-utils" "^0.3.0" + "@backstage/app-defaults" "^1.0.1-next.0" + "@backstage/catalog-model" "^1.0.1-next.0" + "@backstage/cli" "^0.16.1-next.0" + "@backstage/config" "^1.0.0" + "@backstage/core-app-api" "^1.0.1-next.0" + "@backstage/core-components" "^0.9.3-next.0" + "@backstage/core-plugin-api" "^1.0.0" + "@backstage/integration-react" "^1.0.1-next.0" + "@backstage/plugin-catalog" "^1.0.1-next.0" + "@backstage/plugin-techdocs" "^1.0.1-next.0" + "@backstage/test-utils" "^1.0.1-next.0" "@backstage/theme" "^0.2.15" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -23421,10 +23855,10 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttle-debounce@^3.0.1: version "3.0.1" @@ -23765,7 +24199,7 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@~2.3.0: +tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -24437,10 +24871,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -v8-to-istanbul@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" - integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -24631,7 +25065,7 @@ walk-up-path@^1.0.0: resolved "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== -walker@^1.0.7, walker@~1.0.5: +walker@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= @@ -24685,30 +25119,31 @@ webidl-conversions@^6.1.0: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -webpack-dev-middleware@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz#8fc02dba6e72e1d373eca361623d84610f27be7c" - integrity sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg== +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== dependencies: colorette "^2.0.10" - memfs "^3.2.2" + memfs "^3.4.1" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" webpack-dev-server@^4.7.3: - version "4.7.3" - resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.3.tgz#4e995b141ff51fa499906eebc7906f6925d0beaa" - integrity sha512-mlxq2AsIw2ag016nixkzUkdyOE8ST2GTy34uKSABp1c4nhjZvH90D5ZRR+UOLSsG4Z3TFahAi72a3ymRtfRm+Q== + version "4.7.4" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== dependencies: "@types/bonjour" "^3.5.9" "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" "@types/serve-index" "^1.9.1" "@types/sockjs" "^0.3.33" "@types/ws" "^8.2.2" ansi-html-community "^0.0.8" bonjour "^3.5.0" - chokidar "^3.5.2" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" @@ -24728,8 +25163,8 @@ webpack-dev-server@^4.7.3: sockjs "^0.3.21" spdy "^4.0.2" strip-ansi "^7.0.0" - webpack-dev-middleware "^5.3.0" - ws "^8.1.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" webpack-node-externals@^3.0.0: version "3.0.0" @@ -24750,9 +25185,9 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5, webpack@^5.66.0: - version "5.69.1" - resolved "https://registry.npmjs.org/webpack/-/webpack-5.69.1.tgz#8cfd92c192c6a52c99ab00529b5a0d33aa848dc5" - integrity sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A== + version "5.70.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" @@ -24763,7 +25198,7 @@ webpack@^5, webpack@^5.66.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" + enhanced-resolve "^5.9.2" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" @@ -25048,7 +25483,7 @@ ws@7.4.5: resolved "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== -ws@8.5.0, ws@^8.1.0: +ws@8.5.0, ws@^8.4.2: version "8.5.0" resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== @@ -25247,7 +25682,7 @@ yargs-parser@^3.2.0: camelcase "^3.0.0" lodash.assign "^4.1.0" -yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: +yargs@^15.1.0, yargs@^15.3.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== @@ -25440,9 +25875,9 @@ zip-stream@^4.1.0: readable-stream "^3.6.0" zod@^3.11.6, zod@^3.9.5: - version "3.11.6" - resolved "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" - integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== + version "3.14.2" + resolved "https://registry.npmjs.org/zod/-/zod-3.14.2.tgz#0b4ed79085c471adce0e7f2c0a4fbb5ddc516ba2" + integrity sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw== zustand@3.6.9: version "3.6.9"