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-baboons-walk.md b/.changeset/beige-baboons-walk.md new file mode 100644 index 0000000000..a5b424a489 --- /dev/null +++ b/.changeset/beige-baboons-walk.md @@ -0,0 +1,7 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Clarify that config locations that emit User and Group kinds now need to declare so in the `catalog.locations.[].rules` 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/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/clean-lions-taste.md b/.changeset/clean-lions-taste.md new file mode 100644 index 0000000000..1ec1eec952 --- /dev/null +++ b/.changeset/clean-lions-taste.md @@ -0,0 +1,52 @@ +--- +'@backstage/create-app': patch +--- + +Made `User` and `Group` entity kinds not permitted by the default +`catalog.rules` config. + +The effect of this is that after creating a new Backstage repository, its +catalog no longer permits regular users to register `User` or `Group` entities +using the Backstage interface. Additionally, if you have config locations that +result in `User` or `Group` entities, you need to add those kinds to its own +specific rules: + +```yaml +catalog: + locations: + # This applies for example to url type locations + - type: url + target: https://example.com/org.yaml + rules: + - allow: [User, Group] + # But also note that this applies to ALL org location types! + - type: github-org + target: https://github.com/my-org-name + rules: + - allow: [User, Group] +``` + +This rule change does NOT affect entity providers, only things that are emitted +by entity processors. + +We recommend that this change is applied to your own Backstage repository, since +it makes it impossible for regular end users to affect your org data through +e.g. YAML files. To do so, remove the two kinds from the default rules in your config: + +```diff + catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] +``` + +And for any location that in any way results in org data being ingested, add the corresponding rule to it: + +```diff + catalog: + locations: + - type: github-org + target: https://github.com/my-org-name ++ rules: ++ - allow: [User, Group] +``` 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/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-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-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-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/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-bulldogs-own.md b/.changeset/fresh-bulldogs-own.md new file mode 100644 index 0000000000..7772262efe --- /dev/null +++ b/.changeset/fresh-bulldogs-own.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-badges-backend': patch +--- + +allow overriding `DefaultBadgeBuilder.getMarkdownCode` 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/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/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-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/moody-ducks-return.md b/.changeset/moody-ducks-return.md new file mode 100644 index 0000000000..090b2f5db4 --- /dev/null +++ b/.changeset/moody-ducks-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Add new `draft` option to the `publish:github:pull-request` action. 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/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/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/pre.json b/.changeset/pre.json index 063685ce82..4763034b0a 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -137,36 +137,95 @@ "@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" + "@backstage/plugin-xcmetrics": "0.2.23", + "@backstage/plugin-stack-overflow": "0.0.0", + "@backstage/plugin-stack-overflow-backend": "0.0.0" }, "changesets": [ + "afraid-boats-check", + "angry-pens-begin", + "beige-avocados-matter", + "beige-baboons-walk", + "big-buses-clap", + "big-mayflies-sin", + "blue-beers-kiss", + "brave-chairs-stare", + "calm-walls-heal", + "clean-lions-taste", + "clever-donuts-teach", + "dependabot-05b19b9", "dependabot-2b68456", + "dependabot-6754062", + "dependabot-84caed8", + "dependabot-c1fe92f", + "dry-fans-arrive", + "eleven-days-brush", + "eleven-frogs-promise", "eleven-pens-collect", "empty-pens-invent", + "fair-dingos-glow", "fair-lamps-leave", "fast-cheetahs-grow", + "few-mayflies-divide", + "forty-poets-tie", + "four-birds-peel", + "fresh-bulldogs-own", + "fresh-cobras-admire", + "fresh-dodos-rush", "happy-foxes-arrive", "happy-mugs-camp", + "hip-poems-breathe", "khaki-pears-march", "large-coins-arrive", "light-drinks-rule", "mean-pumas-search", + "mean-rabbits-tell", + "mean-tomatoes-shout", + "metal-queens-cheat", + "mighty-suns-drop", + "modern-actors-notice", "modern-pumas-join", + "moody-ducks-return", "moody-pigs-rush", + "new-lions-run", + "nine-grapes-turn", "ninety-fishes-vanish", "olive-geese-chew", + "perfect-phones-roll", + "pretty-ghosts-scream", "purple-boats-punch", + "purple-laws-give", + "rare-waves-hammer", "red-snakes-float", "rich-maps-hear", + "rotten-planes-watch", + "seven-apricots-sell", + "shiny-seas-yell", + "short-knives-wave", "silent-bobcats-matter", + "silly-llamas-relax", "silver-pots-call", "smart-phones-exist", + "sour-cameras-deliver", + "sour-lobsters-sniff", + "stale-carrots-smile", + "strange-wasps-whisper", + "tasty-emus-hunt", + "tasty-goats-shout", "techdocs-coats-obey", "techdocs-head-shoulders-knees-toes", + "techdocs-parents-suffer", "thick-comics-fold", + "thin-lizards-battle", + "tidy-days-warn", "tidy-emus-stare", + "twelve-buses-nail", + "unlucky-schools-heal", + "unlucky-snakes-turn", + "warm-mangos-compete", "wicked-beds-return", "wicked-feet-clap", + "witty-years-sniff", "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-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/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/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/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/short-knives-wave.md b/.changeset/short-knives-wave.md new file mode 100644 index 0000000000..a7372618e2 --- /dev/null +++ b/.changeset/short-knives-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +export `locationSpecToLocationEntity` 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/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/stupid-ways-think.md b/.changeset/stupid-ways-think.md new file mode 100644 index 0000000000..19e68fba90 --- /dev/null +++ b/.changeset/stupid-ways-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed misleading log message during frontend plugin creation. 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/tasty-goats-shout.md b/.changeset/tasty-goats-shout.md new file mode 100644 index 0000000000..5b9356122f --- /dev/null +++ b/.changeset/tasty-goats-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-gcalendar': minor +--- + +Fixed issue when not all calendars were fetched for some accounts diff --git a/.changeset/techdocs-parents-suffer.md b/.changeset/techdocs-parents-suffer.md new file mode 100644 index 0000000000..8e366dd506 --- /dev/null +++ b/.changeset/techdocs-parents-suffer.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Some documentation layout tweaks: + +- drawer toggle margins +- code block margins +- sidebar drawer width +- inner content width +- footer link width +- sidebar table of contents scroll 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/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-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/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/.github/CODEOWNERS b/.github/CODEOWNERS index 44c61f9348..3665a4952d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -32,6 +32,7 @@ /plugins/explore @backstage/reviewers @backstage/sda-se-reviewers /plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers /plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers +/plugins/gcalendar @backstage/reviewers @szubster @ptychu @kielosz @alexrybch /plugins/git-release-manager @backstage/reviewers @erikengervall /plugins/home @backstage/reviewers @backstage/techdocs-core /plugins/ilert @backstage/reviewers @yacut @@ -44,6 +45,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 a1d7397172..4070e5600a 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -108,6 +108,7 @@ Francesco Gerrit gitbeaker github +Gitiles gitlab GitLab Gource @@ -136,6 +137,7 @@ inlinehilite interop JaCoCo JavaScript +jenkins Jira jq js @@ -169,6 +171,7 @@ memoized microservice microservices microsite +microtasks middleware minikube Minikube 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_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 9a4e3de546..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. | @@ -107,4 +107,6 @@ _If you're using Backstage in your organization, please try to add your company | [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. | +| [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/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/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/integrations/github/org.md b/docs/integrations/github/org.md index 975df2e555..0460b1adbc 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -31,6 +31,8 @@ catalog: locations: - type: github-org target: https://github.com/my-org-name + rules: + - allow: [User, Group] ``` If Backstage is configured to use GitHub Apps authentication you must grant @@ -53,22 +55,3 @@ The authorization for loading org information comes from a configured [GitHub integration](locations.md#configuration). When using a personal access token, the token needs to have at least the scopes `read:org`, `read:user`, and `user:email` in the given `target`. - -## Addendum - -Some Backstage apps have _replaced_ the catalog processors to override the -default catalog processing behavior. These apps will already have a call to -`replaceProcessors` in the catalog initialization. - -In the unlikely scenario that this applies to your app, you can import and -add the `GithubOrgReaderProcessor` as follows: - -```ts -// Typically in packages/backend/src/plugins/catalog.ts -import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; - -builder.replaceProcessors( - // ... other processor replacements - GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), -); -``` 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/integrations/ldap/org.md b/docs/integrations/ldap/org.md index f7f4e25b1a..b9ab320996 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -365,4 +365,6 @@ catalog: locations: - type: ldap-org target: ldaps://ds.example.net + rules: + - allow: [User, Group] ``` diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index 91f4668f4e..9245cea7ff 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -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/microsite/core/Footer.js b/microsite/core/Footer.js index 1ea591b1d5..f0917d3255 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/package.json b/microsite/package.json index 7c770bafc1..427731ea47 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -19,7 +19,7 @@ "@spotify/prettier-config": "^13.0.0", "docusaurus": "^2.0.0-alpha.70", "js-yaml": "^4.1.0", - "prettier": "^2.6.0", + "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..be5f85d289 --- /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 90dbb88a5f..ba84d7d774 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -533,7 +533,7 @@ class Index extends React.Component { 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/siteConfig.js b/microsite/siteConfig.js index 6e291aa994..d757ed448b 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -70,6 +70,10 @@ const siteConfig = { page: 'demos', label: 'Demos', }, + { + page: 'community', + label: 'Community', + }, ], /* path to images for header/footer */ 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 45143bafb4..efd56a9f28 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -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.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" - integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== +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 540fbe0bad..83d6361581 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "resolutions": { "**/@graphql-codegen/cli/**/ws": "^7.4.6" }, - "version": "1.1.0-next.0", + "version": "1.1.0-next.1", "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 9c430cbac2..d0b482dac7 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/app-defaults +## 1.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.0-next.0 + ## 1.0.1-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 5a9135a5df..ae5994eaef 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.0.1-next.0", + "version": "1.0.1-next.1", "private": false, "publishConfig": { "access": "public", @@ -36,7 +36,7 @@ "@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/plugin-permission-react": "^0.4.0-next.0", "@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.16.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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 b3f5227511..9fe57d36fc 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,30 @@ # example-app +## 0.2.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.0-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/cli@0.17.0-next.1 + - @backstage/plugin-home@0.4.20-next.1 + - @backstage/plugin-kubernetes@0.6.4-next.1 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/plugin-org@0.5.4-next.1 + - @backstage/plugin-catalog-graph@0.2.16-next.1 + - @backstage/plugin-tech-radar@0.5.11-next.1 + - @backstage/plugin-catalog@1.1.0-next.1 + - @backstage/plugin-gcalendar@0.3.0-next.1 + - @backstage/plugin-techdocs@1.0.1-next.1 + - @backstage/plugin-scaffolder@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + - @backstage/plugin-catalog-import@0.8.7-next.1 + - @backstage/plugin-github-actions@0.5.4-next.1 + - @backstage/app-defaults@1.0.1-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + - @backstage/plugin-api-docs@0.8.4-next.1 + ## 0.2.70-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index ddc5c99178..0f96afd6ec 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,57 +1,57 @@ { "name": "example-app", - "version": "0.2.70-next.0", + "version": "0.2.70-next.1", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/app-defaults": "^1.0.1-next.1", "@backstage/catalog-model": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/integration-react": "^1.0.1-next.1", "@backstage/plugin-airbrake": "^0.3.4-next.0", - "@backstage/plugin-api-docs": "^0.8.4-next.0", + "@backstage/plugin-api-docs": "^0.8.4-next.1", "@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-catalog": "^1.1.0-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-catalog-graph": "^0.2.16-next.1", + "@backstage/plugin-catalog-import": "^0.8.7-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@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-gcalendar": "^0.3.0-next.1", "@backstage/plugin-gcp-projects": "^0.3.23-next.0", - "@backstage/plugin-github-actions": "^0.5.4-next.0", + "@backstage/plugin-github-actions": "^0.5.4-next.1", "@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-home": "^0.4.20-next.1", "@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-kubernetes": "^0.6.4-next.1", "@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-org": "^0.5.4-next.1", "@backstage/plugin-pagerduty": "0.3.31-next.0", - "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/plugin-permission-react": "^0.4.0-next.0", "@backstage/plugin-rollbar": "^0.4.4-next.0", - "@backstage/plugin-scaffolder": "^1.0.1-next.0", + "@backstage/plugin-scaffolder": "^1.0.1-next.1", "@backstage/plugin-search": "^0.7.5-next.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@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-tech-radar": "^0.5.11-next.1", + "@backstage/plugin-techdocs": "^1.0.1-next.1", "@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", @@ -75,8 +75,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/plugin-permission-react": "^0.3.4", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/plugin-permission-react": "^0.4.0-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index d80fdd136c..e8adcd9181 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.13.2-next.1 + +### Patch Changes + +- b7436743cb: Added the GerritUrlReader that implements "readUrl". +- bae9359032: The logger returned from `getVoidLogger` is now uses a silenced console transport instead. +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + ## 0.13.2-next.0 ### Patch 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/package.json b/packages/backend-common/package.json index d09187e2d3..bc2b25e4f4 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.2-next.0", + "version": "0.13.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,7 +38,7 @@ "@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/integration": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "@google-cloud/storage": "^5.8.0", "@manypkg/get-packages": "^1.1.3", @@ -89,8 +89,8 @@ } }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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/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/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 8ed769b1ea..c82d9a3ff2 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-tasks +## 0.3.0-next.1 + +### Minor Changes + +- ab008a0988: Adds the ability to manually trigger tasks which are registered + +### Patch Changes + +- bdd2773202: Refactored the internal `TaskWorker` class to make it easier to test. +- Updated dependencies + - @backstage/backend-common@0.13.2-next.1 + ## 0.2.2-next.0 ### Patch 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 69613ca087..65b3d6a1be 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.2-next.0", + "version": "0.3.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -33,7 +33,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", "@backstage/types": "^1.0.0", @@ -48,10 +48,9 @@ "zod": "^3.9.5" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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 df9e2d5d2e..79f07c4ff4 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-test-utils +## 0.1.23-next.1 + +### Patch Changes + +- 0654c87cf2: `TestDatabases.create` will no longer set up an `afterAll` test handler if no databases are supported. +- Updated dependencies + - @backstage/cli@0.17.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.23-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index ea8800bee3..cb487f3e3f 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.23-next.0", + "version": "0.1.23-next.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -34,8 +34,8 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/cli": "^0.17.0-next.1", "@backstage/config": "^1.0.0", "better-sqlite3": "^7.5.0", "knex": "^1.0.2", @@ -46,8 +46,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", - "jest": "^26.0.1" + "@backstage/cli": "^0.17.0-next.1" }, "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 5603fbc72a..f5d01249a5 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,31 @@ # example-backend +## 0.2.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/plugin-techdocs-backend@1.0.1-next.1 + - @backstage/plugin-scaffolder-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-search-backend@0.5.0-next.1 + - @backstage/backend-tasks@0.3.0-next.1 + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/plugin-badges-backend@0.1.25-next.1 + - @backstage/plugin-tech-insights-node@0.2.9-next.1 + - @backstage/plugin-permission-backend@0.5.6-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-auth-backend@0.13.0-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.1 + - @backstage/plugin-tech-insights-backend@0.3.0-next.1 + - @backstage/plugin-jenkins-backend@0.1.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.1 + - @backstage/plugin-code-coverage-backend@0.1.29-next.1 + - @backstage/plugin-todo-backend@0.1.28-next.1 + - example-app@0.2.70-next.1 + ## 0.2.70-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index bbaa6af5c8..3fde1cdc11 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.70-next.0", + "version": "0.2.70-next.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,39 +26,39 @@ "build-image": "docker build ../.. -f Dockerfile --tag example-backend" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", - "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/backend-tasks": "^0.3.0-next.1", "@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/integration": "^1.1.0-next.1", "@backstage/plugin-app-backend": "^0.3.31-next.0", - "@backstage/plugin-auth-backend": "^0.13.0-next.0", + "@backstage/plugin-auth-backend": "^0.13.0-next.1", "@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-badges-backend": "^0.1.25-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", + "@backstage/plugin-code-coverage-backend": "^0.1.29-next.1", "@backstage/plugin-graphql-backend": "^0.1.21-next.0", - "@backstage/plugin-jenkins-backend": "^0.1.20-next.0", + "@backstage/plugin-jenkins-backend": "^0.1.20-next.1", "@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-permission-backend": "^0.5.6-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-node": "^0.6.0-next.1", "@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-scaffolder-backend": "^1.1.0-next.1", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.3.6-next.1", + "@backstage/plugin-search-backend": "^0.5.0-next.1", "@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", + "@backstage/plugin-techdocs-backend": "^1.0.1-next.1", + "@backstage/plugin-tech-insights-backend": "^0.3.0-next.1", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.1", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.15-next.1", + "@backstage/plugin-todo-backend": "^0.1.28-next.1", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "better-sqlite3": "^7.5.0", @@ -74,7 +74,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/package.json b/packages/catalog-client/package.json index 8e4eed4cea..b01635a0e9 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -38,7 +38,7 @@ "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 4fec087f11..d1389733ed 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -43,7 +43,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest": "^26.0.7", "@types/json-schema": "^7.0.5", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0887303913..562f56bb77 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/cli +## 0.17.0-next.1 + +### Minor Changes + +- 1f7d4763ab: **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. + +### Patch Changes + +- c54ce828bd: build(deps): bump `eslint-plugin-jest` from 25.3.4 to 26.1.2 +- f151dfee5a: build(deps): bump `eslint-webpack-plugin` from 2.6.0 to 3.1.1 +- 7e7ba704be: build(deps): bump `@spotify/eslint-config-base` from 12.0.0 to 13.0.0 +- ecd72391fb: build(deps): bump `@spotify/eslint-config-typescript` +- 5b3079694e: Stop logging "Stopped watcher" when shutting down the development backend. + ## 0.16.1-next.0 ### 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 59a9809670..5007a5e5ee 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.16.1-next.0", + "version": "0.17.0-next.1", "private": false, "publishConfig": { "access": "public" @@ -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", @@ -72,12 +72,12 @@ "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": "^2.6.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", @@ -85,7 +85,7 @@ "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", "jest-transform-yaml": "^1.0.0", "json-schema": "^0.4.0", @@ -122,13 +122,13 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/test-utils": "^1.0.1-next.1", "@backstage/theme": "^0.2.15", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", 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/create/factories/frontendPlugin.test.ts b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts index 27ee14ea2c..8559953f5f 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.test.ts @@ -87,7 +87,7 @@ describe('frontendPlugin factory', () => { expect(output).toEqual([ '', - 'Creating backend plugin backstage-plugin-test', + 'Creating frontend plugin backstage-plugin-test', 'Checking Prerequisites:', `availability plugins${sep}test`, 'creating temp dir', diff --git a/packages/cli/src/lib/create/factories/frontendPlugin.ts b/packages/cli/src/lib/create/factories/frontendPlugin.ts index 3862ae4883..d0e81207ca 100644 --- a/packages/cli/src/lib/create/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/create/factories/frontendPlugin.ts @@ -47,7 +47,7 @@ export const frontendPlugin = createFactory({ const extensionName = `${upperFirst(camelCase(id))}Page`; Task.log(); - Task.log(`Creating backend plugin ${chalk.cyan(name)}`); + Task.log(`Creating frontend plugin ${chalk.cyan(name)}`); const targetDir = ctx.isMonoRepo ? paths.resolveTargetRoot('plugins', id) diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 6caaaf28b0..dd37f90369 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -49,8 +49,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/packages/core-components/package.json index 3406f8b876..e51f041884 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -79,8 +79,8 @@ }, "devDependencies": { "@backstage/core-app-api": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/packages/core-plugin-api/package.json index c47978f42b..35c716863a 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -46,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/core-app-api": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 71b76f0e0a..1942da1bbf 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/create-app +## 0.4.25-next.0 + +### Patch Changes + +- 1691c6c5c2: Made `User` and `Group` entity kinds not permitted by the default + `catalog.rules` config. + + The effect of this is that after creating a new Backstage repository, its + catalog no longer permits regular users to register `User` or `Group` entities + using the Backstage interface. Additionally, if you have config locations that + result in `User` or `Group` entities, you need to add those kinds to its own + specific rules: + + ```yaml + catalog: + locations: + # This applies for example to url type locations + - type: url + target: https://example.com/org.yaml + rules: + - allow: [User, Group] + # But also note that this applies to ALL org location types! + - type: github-org + target: https://github.com/my-org-name + rules: + - allow: [User, Group] + ``` + + This rule change does NOT affect entity providers, only things that are emitted + by entity processors. + + We recommend that this change is applied to your own Backstage repository, since + it makes it impossible for regular end users to affect your org data through + e.g. YAML files. To do so, remove the two kinds from the default rules in your config: + + ```diff + catalog: + rules: + - - allow: [Component, System, API, Group, User, Resource, Location] + + - allow: [Component, System, API, Resource, Location] + ``` + + And for any location that in any way results in org data being ingested, add the corresponding rule to it: + + ```diff + catalog: + locations: + - type: github-org + target: https://github.com/my-org-name + + rules: + + - allow: [User, Group] + ``` + +- 0e911394d2: 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", + ``` + +- c07d9f9e1c: Add helpful README.md files in the original `packages` and `plugins` folders + ## 0.4.24 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 16dbc00d10..964250f398 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.24", + "version": "0.4.25-next.0", "private": false, "publishConfig": { "access": "public" diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 02139f8084..380d2a765d 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -85,7 +85,7 @@ catalog: entityFilename: catalog-info.yaml pullRequestBranchName: backstage-integration rules: - - allow: [Component, System, API, Group, User, Resource, Location] + - allow: [Component, System, API, Resource, Location] locations: # Backstage example components - type: url 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/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/package.json b/packages/dev-utils/package.json index fba771a77f..ac5d30aa39 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -33,14 +33,14 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/app-defaults": "^1.0.1-next.1", "@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/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/errors/package.json b/packages/errors/package.json index 104131b7b9..aad43f6918 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -38,7 +38,7 @@ "serialize-error": "^8.0.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index df677fe2fa..ddcd61a667 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/integration-react +## 1.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 93182d468b..70902e8fb3 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,7 +27,7 @@ "@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/integration": "^1.1.0-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/dev-utils": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@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/CHANGELOG.md b/packages/integration/CHANGELOG.md index 7df271fdd4..2b1d93ea57 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/integration +## 1.1.0-next.1 + +### Minor Changes + +- b7436743cb: Gerrit integration: Added an optional configuration to set the Gitiles base url. + +### Patch Changes + +- 1691c6c5c2: Clarify that config locations that emit User and Group kinds now need to declare so in the `catalog.locations.[].rules` + ## 1.0.1-next.0 ### Patch Changes @@ -237,6 +247,8 @@ locations: - type: github-multi-org target: https://github.myorg.com + rules: + - allow: [User, Group] processors: githubMultiOrg: diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index d86acb291a..cdb260f089 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -138,6 +138,7 @@ export class GerritIntegration implements ScmIntegration { export type GerritIntegrationConfig = { host: string; baseUrl?: string; + gitilesBaseUrl?: string; username?: string; password?: string; }; @@ -184,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, @@ -330,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/package.json b/packages/integration/package.json index c7c034c61e..f0f2c449c1 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": "1.0.1-next.0", + "version": "1.1.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,9 +42,9 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/config-loader": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", "msw": "^0.35.0" 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/search-common/CHANGELOG.md b/packages/search-common/CHANGELOG.md index b0d255b47c..41e090f6fc 100644 --- a/packages/search-common/CHANGELOG.md +++ b/packages/search-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/search-common +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.1 + ## 0.3.3-next.0 ### Patch Changes diff --git a/packages/search-common/package.json b/packages/search-common/package.json index d56f3dba8e..58cf4d49cc 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.3-next.0", + "version": "0.3.3-next.1", "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.3-next.0" + "@backstage/plugin-search-common": "0.3.3-next.1" }, "devDependencies": {}, "jest": { diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index c395e6823a..3f80ade3e6 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,17 @@ # techdocs-cli-embedded-app +## 0.2.69-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.17.0-next.1 + - @backstage/test-utils@1.0.1-next.1 + - @backstage/plugin-catalog@1.1.0-next.1 + - @backstage/plugin-techdocs@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + - @backstage/app-defaults@1.0.1-next.1 + ## 0.2.69-next.0 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index c172f72a48..4330ba46f3 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.69-next.0", + "version": "0.2.69-next.1", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^1.0.1-next.0", + "@backstage/app-defaults": "^1.0.1-next.1", "@backstage/catalog-model": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog": "^1.1.0-next.1", + "@backstage/plugin-techdocs": "^1.0.1-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/package.json b/packages/techdocs-cli/package.json index b0843fc40d..7b03f0d3ab 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -37,7 +37,7 @@ "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/cli-common": "^0.1.8", "@backstage/config": "^1.0.0", - "@backstage/plugin-techdocs-node": "^1.0.1-next.0", + "@backstage/plugin-techdocs-node": "^1.0.1-next.1", "@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 d0a2449784..145b2adb9d 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/techdocs-common +## 0.11.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.0.1-next.1 + ## 0.11.14-next.0 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 17058e81e6..3c2dc8bcf9 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.14-next.0", + "version": "0.11.14-next.1", "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": "1.0.1-next.0" + "@backstage/plugin-techdocs-node": "1.0.1-next.1" }, "devDependencies": {}, "jest": { diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 443b42c579..f2561ec416 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/test-utils +## 1.0.1-next.1 + +### Patch Changes + +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- Updated dependencies + - @backstage/plugin-permission-react@0.4.0-next.0 + - @backstage/plugin-permission-common@0.6.0-next.0 + ## 1.0.1-next.0 ### Patch 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 36e2f32ca7..8d7dd24b85 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.0.1-next.0", + "version": "1.0.1-next.1", "private": false, "publishConfig": { "access": "public", @@ -36,8 +36,8 @@ "@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/plugin-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-react": "^0.4.0-next.0", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", @@ -55,7 +55,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.35.0" 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/theme/package.json b/packages/theme/package.json index 95cd36309e..5bcfd328e0 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.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist" diff --git a/packages/types/package.json b/packages/types/package.json index 44472cc530..2c2aba560f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -34,7 +34,7 @@ }, "dependencies": {}, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/zen-observable": "^0.8.0", "zen-observable": "^0.8.15" }, diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index 1b57e71ba9..a03e3d9f75 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -37,7 +37,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/package.json b/plugins/airbrake-backend/package.json index 12831093a7..021db97be8 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -22,7 +22,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@types/express": "*", "express": "^4.17.1", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index c021c197bb..8237490ac3 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -27,8 +27,8 @@ "@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/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/app-defaults": "^1.0.1-next.1", + "@backstage/cli": "^0.17.0-next.1", "@backstage/dev-utils": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/allure/package.json index d5093fdb04..18bf6646e5 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -28,7 +28,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/analytics-module-ga/package.json index b493e95eaa..f9281e4c86 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -38,10 +38,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/apache-airflow/package.json index 41e7f12ff3..bda73686b6 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -36,10 +36,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 8ea14ac0e4..f9daf8967c 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-api-docs +## 0.8.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/plugin-catalog@1.1.0-next.1 + ## 0.8.4-next.0 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index efd7d49fa8..205188093f 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.4-next.0", + "version": "0.8.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,8 +37,8 @@ "@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/plugin-catalog": "^1.1.0-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 541afc873d..e7433aa9ae 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config-loader": "^1.0.1-next.0", "@backstage/config": "^1.0.0", "@backstage/types": "^1.0.0", @@ -50,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@backstage/types": "^1.0.0", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 1430e2b3f6..96ddb6afe8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend +## 0.13.0-next.1 + +### Patch Changes + +- a45bce06e3: Handle trailing slashes on GitHub `enterpriseInstanceUrl` settings +- Updated dependencies + - @backstage/backend-common@0.13.2-next.1 + ## 0.13.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 43813a5773..1cf2baa68e 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.13.0-next.0", + "version": "0.13.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ }, "dependencies": { "@backstage/plugin-auth-node": "^0.2.0-next.0", - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/catalog-client": "^1.0.1-next.0", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/config": "^1.0.0", @@ -76,8 +76,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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/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-node/package.json b/plugins/auth-node/package.json index c50bdd06ba..dac6c3a584 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -23,7 +23,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", "jose": "^1.27.1", @@ -31,7 +31,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "msw": "^0.35.0", "uuid": "^8.0.0" }, diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 0af0ba9658..22301e8fbd 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -23,7 +23,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@backstage/plugin-azure-devops-common": "^0.2.2", "@types/express": "^4.17.6", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 73463fcc57..eadce6bb88 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.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist" diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 15c09a2a3b..49544120c2 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -35,7 +35,7 @@ "@backstage/core-plugin-api": "^1.0.0", "@backstage/errors": "^1.0.0", "@backstage/plugin-azure-devops-common": "^0.2.2", - "@backstage/plugin-catalog-react": "^1.0.1-next.0", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 9e6b727a37..fee6096dd0 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-badges-backend +## 0.1.25-next.1 + +### Patch Changes + +- 4c93fd36ee: allow overriding `DefaultBadgeBuilder.getMarkdownCode` +- Updated dependencies + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/badges-backend/api-report.md b/plugins/badges-backend/api-report.md index 01b14cc8ee..0660d23fdb 100644 --- a/plugins/badges-backend/api-report.md +++ b/plugins/badges-backend/api-report.md @@ -122,6 +122,8 @@ export class DefaultBadgeBuilder implements BadgeBuilder { createBadgeSvg(options: BadgeOptions): Promise; // (undocumented) getBadges(): Promise; + // (undocumented) + protected getMarkdownCode(params: Badge, badgeUrl: string): string; } // Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 4148066eb9..caadaa460e 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.25-next.0", + "version": "0.1.25-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/catalog-client": "^1.0.1-next.0", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/config": "^1.0.0", @@ -48,7 +48,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges-backend/src/lib/BadgeBuilder/DefaultBadgeBuilder.ts b/plugins/badges-backend/src/lib/BadgeBuilder/DefaultBadgeBuilder.ts index 7f9d2580f9..78e34351a0 100644 --- a/plugins/badges-backend/src/lib/BadgeBuilder/DefaultBadgeBuilder.ts +++ b/plugins/badges-backend/src/lib/BadgeBuilder/DefaultBadgeBuilder.ts @@ -70,7 +70,7 @@ export class DefaultBadgeBuilder implements BadgeBuilder { } } - private getMarkdownCode(params: Badge, badgeUrl: string): string { + protected getMarkdownCode(params: Badge, badgeUrl: string): string { let altText = `${params.label}: ${params.message}`; if (params.description && params.description !== params.label) { altText = `${params.description}, ${altText}`; diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 3c01608ffc..f03717cfb0 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -34,7 +34,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 7132abd9b7..15b7596ef1 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -23,8 +23,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", - "@backstage/backend-test-utils": "^0.1.23-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/backend-test-utils": "^0.1.23-next.1", "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -34,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 888df89ced..e859f1f1ec 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-bazaar +## 0.1.19-next.1 + +### Patch Changes + +- 69738379c7: Pass authorization header with Backstage token to backend requests. +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/cli@0.17.0-next.1 + - @backstage/plugin-catalog@1.1.0-next.1 + ## 0.1.19-next.0 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index a2ead73157..b158a23f5b 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.19-next.0", + "version": "0.1.19-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,11 +26,11 @@ "dependencies": { "@backstage/catalog-client": "^1.0.1-next.0", "@backstage/catalog-model": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/plugin-catalog": "^1.1.0-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,7 +47,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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/package.json b/plugins/bitrise/package.json index c48eda52b1..ffe4d3282b 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -27,7 +27,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 78f6610e92..dc96fa35fa 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 4c9062eb84..23b5055e6a 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.4-next.0", + "version": "0.1.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "aws-sdk": "^2.840.0", "lodash": "^4.17.21", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 71a1962750..7846323524 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 6ca9bbe712..076d35babf 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.2-next.0", + "version": "0.1.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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 50771795bf..14efa3ad71 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index c13557596d..cec1703166 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.2-next.0", + "version": "0.1.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 13c38a58eb..7b143c6426 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-github +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 0dc8ebebf9..ed0b967942 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.2-next.0", + "version": "0.1.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "@octokit/graphql": "^4.5.8", "lodash": "^4.17.21", @@ -47,8 +47,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 97e40ddba2..ad38bd6963 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 412acacefb..f0254d7e99 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.2-next.0", + "version": "0.1.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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 1e54237e60..ad30b085a7 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/backend-tasks@0.3.0-next.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 3b760a4df6..41b9378a83 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.2-next.0", + "version": "0.4.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/backend-tasks": "^0.3.0-next.1", "@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/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", @@ -46,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 0f907f969d..4a0c147854 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.3.1-next.1 + +### Patch Changes + +- 1691c6c5c2: Clarify that config locations that emit User and Group kinds now need to declare so in the `catalog.locations.[].rules` +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/backend-tasks@0.3.0-next.1 + ## 0.3.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index 741e378fd4..bf23e93770 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -141,8 +141,6 @@ catalog: locations: - type: microsoft-graph-org target: https://graph.microsoft.com/v1.0 - # If you catalog doesn't allow to import Group and User entities by - # default, allow them here rules: - allow: [Group, User] … diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 8a7e76dbb9..0afaf27f75 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.3.1-next.0", + "version": "0.3.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ }, "dependencies": { "@azure/msal-node": "^1.1.0", - "@backstage/backend-tasks": "^0.2.2-next.0", + "@backstage/backend-tasks": "^0.3.0-next.1", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/config": "^1.0.0", - "@backstage/plugin-catalog-backend": "^1.0.1-next.0", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -48,9 +48,9 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.13.2-next.0", - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 89e0408cc8..f2c5b4121e 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-catalog-backend +## 1.1.0-next.1 + +### Minor Changes + +- 8012ac46a0: **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, + }; + ``` + +- 8012ac46a0: **BREAKING:** Mark CatalogBuilder#addPermissionRules as @alpha. +- fb02d2d94d: export `locationSpecToLocationEntity` + +### Patch Changes + +- ada4446733: Specify type of `visibilityPermission` property on collators and collator factories. +- 1691c6c5c2: Clarify that config locations that emit User and Group kinds now need to declare so in the `catalog.locations.[].rules` +- 8012ac46a0: 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. + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + ## 1.0.1-next.0 ### Patch Changes @@ -1394,6 +1445,8 @@ locations: - type: github-multi-org target: https://github.myorg.com + rules: + - allow: [User, Group] processors: githubMultiOrg: diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index ad80e36232..d44e744bb6 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -8,7 +8,7 @@ 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'; @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; import { GetEntitiesRequest } from '@backstage/catalog-client'; import { JsonValue } from '@backstage/types'; +import { LocationEntityV1alpha1 } from '@backstage/catalog-model'; import { Logger } from 'winston'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; @@ -25,6 +26,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'; @@ -109,13 +111,8 @@ 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<{ processingEngine: CatalogProcessingEngine; @@ -143,23 +140,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[]] >; }>; @@ -173,6 +184,10 @@ export type CatalogEnvironment = { permissions: PermissionAuthorizer; }; +// @alpha +export type CatalogPermissionRule = + PermissionRule; + // @public (undocumented) export interface CatalogProcessingEngine { // (undocumented) @@ -277,14 +292,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: { @@ -458,6 +476,12 @@ export type LocationSpec = { presence?: 'optional' | 'required'; }; +// @public (undocumented) +export function locationSpecToLocationEntity(opts: { + location: LocationSpec; + parentEntity?: Entity; +}): LocationEntityV1alpha1; + // @public (undocumented) export function parseEntityYaml( data: Buffer, @@ -469,23 +493,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[]] >; }; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 0470b599b3..ec4ef739df 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.0.1-next.0", + "version": "1.1.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,17 +34,17 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-node": "^0.6.0-next.1", "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", @@ -68,9 +68,9 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", - "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", "@backstage/plugin-search-backend-node": "0.5.3-next.0", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 437c1f4598..c5c653f0dd 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -24,7 +24,8 @@ export * from './api'; export * from './catalog'; export * from './ingestion'; export * from './modules'; -export * from './search'; -export * from './processing'; -export * from './service'; export * from './permissions'; +export * from './processing'; +export * from './search'; +export * from './service'; +export * from './util'; diff --git a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts index 9529a5b608..69827b1380 100644 --- a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts +++ b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts @@ -60,8 +60,10 @@ export class ConfigLocationEntityProvider implements EntityProvider { const type = location.getString('type'); const target = location.getString('target'); const entity = locationSpecToLocationEntity({ - type, - target: type === 'file' ? path.resolve(target) : target, + location: { + type, + target: type === 'file' ? path.resolve(target) : target, + }, }); const locationKey = getEntityLocationRef(entity); return { entity, locationKey }; diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts index 0d4bedabbb..33ab046104 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts @@ -58,7 +58,7 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { return inner; }); - const entity = locationSpecToLocationEntity(location); + const entity = locationSpecToLocationEntity({ location }); await this.connection.applyMutation({ type: 'delta', added: [{ entity, locationKey: getEntityLocationRef(entity) }], @@ -100,7 +100,7 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { await tx('locations').where({ id }).del(); return location; }); - const entity = locationSpecToLocationEntity(deleted); + const entity = locationSpecToLocationEntity({ location: deleted }); await this.connection.applyMutation({ type: 'delta', added: [], @@ -122,7 +122,7 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { const locations = await this.locations(); const entities = locations.map(location => { - const entity = locationSpecToLocationEntity(location); + const entity = locationSpecToLocationEntity({ location }); return { entity, locationKey: getEntityLocationRef(entity) }; }); 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/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index cc3e1401bd..db7a050dce 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -106,10 +106,10 @@ export class ProcessorOutputCollector { this.deferredEntities.push({ entity, locationKey: location }); } else if (i.type === 'location') { - const entity = locationSpecToLocationEntity( - i.location, - this.parentEntity, - ); + const entity = locationSpecToLocationEntity({ + location: i.location, + parentEntity: this.parentEntity, + }); const locationKey = getEntityLocationRef(entity); this.deferredEntities.push({ entity, locationKey }); } else if (i.type === 'relation') { 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 fb4ed6086f..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 { EntitiesSearchFilter } from '../catalog'; import { CatalogProcessor, @@ -77,10 +75,12 @@ import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; 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'; @@ -135,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. @@ -339,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); } diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 1602d07d28..6def04a11c 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -26,7 +26,10 @@ 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/util/conversion.ts b/plugins/catalog-backend/src/util/conversion.ts index 63771d1028..d61304a419 100644 --- a/plugins/catalog-backend/src/util/conversion.ts +++ b/plugins/catalog-backend/src/util/conversion.ts @@ -32,10 +32,14 @@ export function locationSpecToMetadataName(location: LocationSpec) { return `generated-${hash}`; } -export function locationSpecToLocationEntity( - location: LocationSpec, - parentEntity?: Entity, -): LocationEntityV1alpha1 { +/** @public */ +export function locationSpecToLocationEntity(opts: { + location: LocationSpec; + parentEntity?: Entity; +}): LocationEntityV1alpha1 { + const location = opts.location; + const parentEntity = opts.parentEntity; + let ownLocation: string; let originLocation: string; if (parentEntity) { diff --git a/plugins/catalog-backend/src/util/index.ts b/plugins/catalog-backend/src/util/index.ts new file mode 100644 index 0000000000..b3bd4d5316 --- /dev/null +++ b/plugins/catalog-backend/src/util/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 { locationSpecToLocationEntity } from './conversion'; diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 5227daf20b..d362c20d27 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-common +## 1.0.1-next.1 + +### Patch Changes + +- ada4446733: Use `createPermission` helper when creating permissions. +- 8c8bee47f4: Add `@alpha` `CatalogEntityPermission` convenience type, available for import from `@backstage/plugin-catalog-common/alpha`. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/search-common@0.3.3-next.1 + ## 1.0.1-next.0 ### 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 7f30d912c7..2db301a8fd 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "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.3", - "@backstage/search-common": "^0.3.3-next.0" + "@backstage/plugin-permission-common": "^0.6.0-next.0", + "@backstage/search-common": "^0.3.3-next.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "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 fb7a4eadb2..d2726457d0 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-graph +## 0.2.16-next.1 + +### Patch Changes + +- 77800a32dd: Added renderNode and renderLabel property to EntityRelationsGraph to support customization using CustomNode and CustomLabel components +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + ## 0.2.16-next.0 ### 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 1eb8fafd6d..84eae3d254 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.16-next.0", + "version": "0.2.16-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,7 +28,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", - "@backstage/plugin-catalog": "^1.0.1-next.0", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/plugin-catalog": "^1.1.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 = ({ ` from being used with the `useEntityPermission` hook. +- 4af82967f4: 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. + +- 37b04b5a5e: Removed broken link from Labels section of entity inspector. +- 4431873583: Update `usePermission` usage. +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-permission-react@0.4.0-next.0 + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index b90072beba..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'; @@ -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 336052a88e..f5966493e7 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.0.1-next.0", + "version": "1.0.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,9 +39,10 @@ "@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-permission-common": "^0.5.3", - "@backstage/plugin-permission-react": "^0.3.4", + "@backstage/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-react": "^0.4.0-next.0", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", "@backstage/version-bridge": "^1.0.0", @@ -62,11 +63,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/core-app-api": "^1.0.1-next.0", - "@backstage/plugin-catalog-common": "^1.0.1-next.0", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@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-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx index c6b985bc8b..2be521203f 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx @@ -14,60 +14,49 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { MockEntityListContextProvider } from '../../testUtils/providers'; import { EntityTagFilter } from '../../filters'; import { EntityTagPicker } from './EntityTagPicker'; +import { TestApiProvider } from '@backstage/test-utils'; +import { catalogApiRef } from '../../api'; +import { CatalogApi } from '@backstage/catalog-client'; -const taggedEntities: Entity[] = [ - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'component-1', - tags: ['tag4', 'tag1', 'tag2'], - }, - }, - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'component-2', - tags: ['tag3', 'tag4'], - }, - }, -]; +const tags = ['tag1', 'tag2', 'tag3', 'tag4']; describe('', () => { - 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/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/hooks/useEntityPermission.ts b/plugins/catalog-react/src/hooks/useEntityPermission.ts index 55d863726e..3a2c12ff36 100644 --- a/plugins/catalog-react/src/hooks/useEntityPermission.ts +++ b/plugins/catalog-react/src/hooks/useEntityPermission.ts @@ -15,7 +15,7 @@ */ 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 { useAsyncEntity } from './useEntity'; @@ -23,14 +23,18 @@ 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; @@ -44,10 +48,10 @@ export function useEntityPermission(permission: Permission): { 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 1f484e58d0..4d6c562d35 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 1.1.0-next.1 + +### Minor Changes + +- bdc61b4002: Expose 'initalFilter' through initialKind prop on Catalog Page. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 053b35a519..db6e2b5cfd 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.0.1-next.0", + "version": "1.1.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,10 +39,10 @@ "@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/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", @@ -60,11 +60,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/plugin-permission-react": "^0.4.0-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 2ce855e625..32d6c99271 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -38,7 +38,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/cloudbuild/package.json index a04b5537a4..593dfff38a 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -37,7 +37,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/code-climate/package.json index 5d77bb39b7..89feeca5b4 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -26,7 +26,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/dev-utils": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@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 4eb5c949e4..52420f5f09 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-coverage-backend +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.29-next.0 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 4dd916b75a..5a31ecead8 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.29-next.0", + "version": "0.1.29-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 09f2b77a65..fd65f00eb8 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -29,7 +29,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/config-schema/package.json index 376d1c2ecc..64bd4a683b 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -41,10 +41,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/cost-insights/package.json index 921c9a5aab..97962bf856 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -60,10 +60,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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-react/package.json b/plugins/explore-react/package.json index 82d247dd9a..8615543348 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -36,9 +36,9 @@ "@backstage/core-plugin-api": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/dev-utils": "^1.0.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/explore/package.json index 6eb013cb9c..2913e89150 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -37,7 +37,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@backstage/plugin-explore-react": "^0.0.15", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -53,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/firehydrant/package.json index a05734b0a4..330defcf8c 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -27,7 +27,7 @@ "dependencies": { "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/fossa/package.json index 4d1cc15ac2..0a9931fd48 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -39,7 +39,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 index 788ef3c0f4..6b8fcc5e3a 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-gcalendar +## 0.3.0-next.1 + +### Minor Changes + +- c6616e6fc9: Fixed issue when not all calendars were fetched for some accounts + ## 0.2.1-next.0 ### Patch Changes diff --git a/plugins/gcalendar/api-report.md b/plugins/gcalendar/api-report.md index 276c144400..b4aebf1ff3 100644 --- a/plugins/gcalendar/api-report.md +++ b/plugins/gcalendar/api-report.md @@ -29,9 +29,7 @@ export class GCalendarApiClient { // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts constructor(options: Options); // (undocumented) - getCalendars(params?: any): Promise<{ - items: GCalendar[]; - }>; + getCalendars(params?: any): Promise; // (undocumented) getEvents( calendarId: string, @@ -55,6 +53,11 @@ export type GCalendarEvent = gapi.client.calendar.Event & calendarId?: string; }; +// Warning: (ae-missing-release-tag) "GCalendarList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type GCalendarList = gapi.client.calendar.CalendarList; + // Warning: (ae-missing-release-tag) "gcalendarPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 99fa2c6eeb..5489cd2e19 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.2.1-next.0", + "version": "0.3.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/src/api/client.ts b/plugins/gcalendar/src/api/client.ts index 5b6ea96339..084c302636 100644 --- a/plugins/gcalendar/src/api/client.ts +++ b/plugins/gcalendar/src/api/client.ts @@ -15,7 +15,7 @@ */ import { OAuthApi, createApiRef, FetchApi } from '@backstage/core-plugin-api'; -import { GCalendar, GCalendarEvent } from './types'; +import { GCalendarEvent, GCalendarList } from './types'; import { ResponseError } from '@backstage/errors'; type Options = { @@ -58,7 +58,7 @@ export class GCalendarApiClient { } public async getCalendars(params?: any) { - return this.get<{ items: GCalendar[] }>( + return this.get( '/calendar/v3/users/me/calendarList', params, ); diff --git a/plugins/gcalendar/src/api/types.ts b/plugins/gcalendar/src/api/types.ts index 1423b91aca..b6d9a36eb9 100644 --- a/plugins/gcalendar/src/api/types.ts +++ b/plugins/gcalendar/src/api/types.ts @@ -17,6 +17,8 @@ /// /// +export type GCalendarList = gapi.client.calendar.CalendarList; + export type GCalendar = gapi.client.calendar.CalendarListEntry; export type EventAttendee = gapi.client.calendar.EventAttendee; diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx index 7b7c02b883..b698c23ea1 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx @@ -15,7 +15,7 @@ */ import { sortBy } from 'lodash'; import { DateTime } from 'luxon'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { InfoCard, Progress } from '@backstage/core-components'; import { useAnalytics } from '@backstage/core-plugin-api'; @@ -51,10 +51,10 @@ export const CalendarCard = () => { signIn(true); }, [signIn]); - const { isLoading: isCalendarLoading, data } = useCalendarsQuery({ - enabled: isSignedIn, - }); - const calendars = useMemo(() => data?.items || [], [data]); + const { isLoading: isCalendarLoading, data: calendars = [] } = + useCalendarsQuery({ + enabled: isSignedIn, + }); const primaryCalendarId = calendars.find(c => c.primary === true)?.id; const defaultSelectedCalendars = primaryCalendarId ? [primaryCalendarId] : []; const [storedCalendars, setStoredCalendars] = useStoredCalendars( diff --git a/plugins/gcalendar/src/hooks/useCalendarsQuery.ts b/plugins/gcalendar/src/hooks/useCalendarsQuery.ts index ed31e23c79..a76142ff09 100644 --- a/plugins/gcalendar/src/hooks/useCalendarsQuery.ts +++ b/plugins/gcalendar/src/hooks/useCalendarsQuery.ts @@ -30,10 +30,22 @@ export const useCalendarsQuery = ({ enabled }: Options) => { return useQuery( ['calendars'], - async () => - calendarApi.getCalendars({ - minAccessRole: 'reader', - }), + async () => { + const calendars = []; + let token = ''; + do { + const { nextPageToken = '', items = [] } = + await calendarApi.getCalendars({ + maxResults: 100, + minAccessRole: 'reader', + pageToken: token, + }); + token = nextPageToken; + calendars.push(...items); + } while (token); + + return calendars; + }, { enabled, keepPreviousData: true, diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 4e9351d9f0..2774a47dea 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 03909158c0..9bcc2a5c79 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-git-release-manager +## 0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + ## 0.3.17-next.0 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 8be28dddb2..74b598aebd 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.17-next.0", + "version": "0.3.17-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,7 +26,7 @@ "dependencies": { "@backstage/core-components": "^0.9.3-next.0", "@backstage/core-plugin-api": "^1.0.0", - "@backstage/integration": "^1.0.1-next.0", + "@backstage/integration": "^1.1.0-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index fab6de3cd1..8c3c98531d 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-github-actions +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-catalog-react@1.0.1-next.1 + ## 0.5.4-next.0 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 756e0b559b..417b8cede9 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.4-next.0", + "version": "0.5.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,8 +39,8 @@ "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 3ef48342f8..197e25b88e 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-github-deployments +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + ## 0.1.35-next.0 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index af1c6cbcc0..78af6e1798 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.35-next.0", + "version": "0.1.35-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,9 +28,9 @@ "@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/integration": "^1.1.0-next.1", + "@backstage/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/gitops-profiles/package.json index 2c3a6b9d25..125d4e25b1 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -48,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/gocd/package.json index 5b0334f2fd..4d854c7bbc 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -35,7 +35,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/graphiql/package.json index 6b896c76e4..60d6f9f1ea 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -48,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/graphql-backend/package.json index 40a2c4f651..a4dd95076b 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@backstage/plugin-catalog-graphql": "^0.3.8-next.0", "@graphql-tools/schema": "^8.3.1", @@ -51,7 +51,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 30fd09d5a4..13c25f98f1 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-home +## 0.4.20-next.1 + +### Patch Changes + +- ac323de4ad: - Adds new `HomePageStackOverflowQuestions` component which renders a list of stack overflow questions on your homepage. + + - Exports `ComponentRenderer` type. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/plugin-stack-overflow@0.1.0-next.0 + ## 0.4.20-next.0 ### 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 b787acfdfe..5c9ad5e24a 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.20-next.0", + "version": "0.4.20-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,9 +37,11 @@ "@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-catalog-react": "^1.0.1-next.1", "@backstage/plugin-search": "^0.7.5-next.0", + "@backstage/plugin-stack-overflow": "^0.1.0-next.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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/ilert/package.json index 35bc07cd6b..f7f7149962 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -28,7 +28,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 602af2a0d1..34d3a54730 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-jenkins-backend +## 0.1.20-next.1 + +### Patch Changes + +- ca91107110: Fixed possible type error if jenkins response contains null values +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-jenkins-common@0.1.3-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.20-next.0 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 86585ff5d3..f326ce2247 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.20-next.0", + "version": "0.1.20-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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", + "@backstage/plugin-jenkins-common": "^0.1.3-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 bc7d2cb029..a5453b5ebe 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.test.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.test.ts @@ -352,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, diff --git a/plugins/jenkins-backend/src/service/jenkinsApi.ts b/plugins/jenkins-backend/src/service/jenkinsApi.ts index ddc2bb0ea3..38ab20b17b 100644 --- a/plugins/jenkins-backend/src/service/jenkinsApi.ts +++ b/plugins/jenkins-backend/src/service/jenkinsApi.ts @@ -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-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index 712d0a1519..6f7cff53bb 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-jenkins-common +## 0.1.3-next.1 + +### Patch Changes + +- ada4446733: Use `createPermission` helper when creating permissions. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.1 + ## 0.1.3-next.0 ### 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 7362038f53..342d645125 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.3-next.0", + "version": "0.1.3-next.1", "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": "^1.0.1-next.0", - "@backstage/plugin-permission-common": "^0.5.3" + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "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/package.json b/plugins/jenkins/package.json index 8f70bfa672..9c0b01b53c 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -39,8 +39,8 @@ "@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/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/plugin-jenkins-common": "^0.1.3-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 0796f8c952..4deeefc788 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -35,7 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", @@ -47,7 +47,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 88d44a67a3..4228822e6b 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -27,7 +27,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/kubernetes-backend/package.json index 22e88b9609..a55ec70780 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -35,7 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/catalog-model": "^1.0.1-next.0", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", @@ -58,11 +58,10 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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-common/package.json b/plugins/kubernetes-common/package.json index 3a519b0579..573f9ae2f9 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -42,7 +42,7 @@ "@kubernetes/client-node": "^0.16.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "jest": { "roots": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 94585a2149..5278096b5a 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes +## 0.6.4-next.1 + +### Patch Changes + +- 1023ee6353: export kubernetes components +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + ## 0.6.4-next.0 ### 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 65ddd71dfe..f47d30c117 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.4-next.0", + "version": "0.6.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,13 +38,14 @@ "@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-catalog-react": "^1.0.1-next.1", "@backstage/plugin-kubernetes-common": "^0.2.9-next.0", - "@kubernetes/client-node": "^0.16.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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/src/components/KubernetesDrawer/index.ts b/plugins/kubernetes/src/components/KubernetesDrawer/index.ts new file mode 100644 index 0000000000..ecd4b1b653 --- /dev/null +++ b/plugins/kubernetes/src/components/KubernetesDrawer/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 './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/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/kubernetes/src/types/index.ts b/plugins/kubernetes/src/types/index.ts new file mode 100644 index 0000000000..db229eae34 --- /dev/null +++ b/plugins/kubernetes/src/types/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 './types'; diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 3c573e8d0b..6164ca0f31 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -39,7 +39,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/newrelic-dashboard/package.json index d086218151..ebea709493 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -27,14 +27,14 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@backstage/dev-utils": "^1.0.1-next.0", "@testing-library/jest-dom": "^5.10.1", "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index edf3ffbf64..a6ee84ab5e 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 7525fe0490..3bbe6f959d 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-org +## 0.5.4-next.1 + +### Patch Changes + +- 111995470d: add aggregated ownership type for kind group in OwnershipCard +- Updated dependencies + - @backstage/plugin-catalog-react@1.0.1-next.1 + ## 0.5.4-next.0 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 11478bbe64..eeb8d9656a 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.4-next.0", + "version": "0.5.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,13 +27,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-catalog-react": "^1.0.1-next.1", "@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" @@ -43,10 +44,10 @@ }, "devDependencies": { "@backstage/catalog-client": "^1.0.1-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 fb49843e92..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/package.json b/plugins/pagerduty/package.json index a096f43e87..0324cede4f 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -37,7 +37,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/periskop-backend/package.json index e95e7cd7cb..382e8d2643 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -24,7 +24,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@types/express": "*", "cross-fetch": "^3.0.6", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.6" diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 4178ddb1ee..e4eed51eb0 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -29,7 +29,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 4b69bdbdc3..5bebcc8660 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-backend +## 0.5.6-next.1 + +### Patch Changes + +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- 95284162d6: - 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. +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.5.6-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 05e056a5d3..32d2a3872f 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.6-next.0", + "version": "0.5.6-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-node": "^0.6.0-next.1", "@types/express": "*", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -39,7 +39,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 61722c29dd..48ab78d25c 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-permission-common +## 0.6.0-next.0 + +### Minor Changes + +- 8012ac46a0: Add `resourceType` property to `PermissionCondition` type to allow matching them with `ResourcePermission` instances. +- c98d271466: 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` + +### Patch Changes + +- 8012ac46a0: Add `isPermission` helper method. +- 95284162d6: - 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. + ## 0.5.3 ### 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 1919ae5167..4d979bf7b0 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.3", + "version": "0.6.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -48,7 +48,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 5b46b955d8..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; }; @@ -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 3ee804e333..a628313003 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-permission-node +## 0.6.0-next.1 + +### Minor Changes + +- 8012ac46a0: **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, + }), + }); + ``` + +- c98d271466: **BREAKING:** + + - Rename `PolicyAuthorizeQuery` to `PolicyQuery` + - Remove `PolicyDecision`, `DefinitivePolicyDecision`, and `ConditionalPolicyDecision`. These types are now exported from `@backstage/plugin-permission-common` + +### Patch Changes + +- 8012ac46a0: Fix signature of permission rule in test suites +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/backend-common@0.13.2-next.1 + ## 0.5.6-next.0 ### 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 06667615bd..8bfa37b6a2 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.6-next.0", + "version": "0.6.0-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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-common": "^0.6.0-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 13597e9a1b..5617640d9b 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-react +## 0.4.0-next.0 + +### Minor Changes + +- 5bdcb8c45d: **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`. + +### Patch Changes + +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + ## 0.3.4 ### 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 3813970ebb..7a846f94eb 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.4", + "version": "0.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "dependencies": { "@backstage/config": "^1.0.0", "@backstage/core-plugin-api": "^1.0.0", - "@backstage/plugin-permission-common": "^0.5.3", + "@backstage/plugin-permission-common": "^0.6.0-next.0", "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.16.1-next.0", - "@backstage/test-utils": "^1.0.1-next.0", + "@backstage/cli": "^0.17.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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 69a42cae91..d3a37421ef 100644 --- a/plugins/permission-react/src/apis/PermissionApi.ts +++ b/plugins/permission-react/src/apis/PermissionApi.ts @@ -15,8 +15,8 @@ */ import { - AuthorizeQuery, - AuthorizeDecision, + EvaluatePermissionRequest, + EvaluatePermissionResponse, } from '@backstage/plugin-permission-common'; import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; @@ -27,7 +27,9 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; * @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/package.json b/plugins/proxy-backend/package.json index b9db54f637..603a411c33 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -46,7 +46,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 0f39b7b7ce..24fb97af92 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@types/express": "^4.17.6", "camelcase-keys": "^7.0.1", @@ -50,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index a0634ea57e..d4f8c476bc 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -38,7 +38,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 8c35a98a94..2472364736 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 2c69da07f6..ba056e6bc6 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.6-next.0", + "version": "0.2.6-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,10 +23,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.0.1-next.0", - "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/integration": "^1.1.0-next.1", + "@backstage/plugin-scaffolder-backend": "^1.1.0-next.1", "@backstage/config": "^1.0.0", "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index e567e75099..9d7b814a5c 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 8b009ff889..abb0bedf74 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.6-next.0", + "version": "0.3.6-next.1", "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.2-next.0", - "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/plugin-scaffolder-backend": "^1.1.0-next.1", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.0.1-next.0", + "@backstage/integration": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", "fs-extra": "^10.0.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 e356619bed..8d0313bd1d 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.1.0-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index dc1e9e95be..948acc97fc 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.4-next.0", + "version": "0.2.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,13 +24,13 @@ }, "dependencies": { "@backstage/config": "^1.0.0", - "@backstage/plugin-scaffolder-backend": "^1.0.1-next.0", + "@backstage/plugin-scaffolder-backend": "^1.1.0-next.1", "@backstage/types": "^1.0.0", "winston": "^3.2.1", "yeoman-environment": "^3.9.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 7ff181b756..f10b36eabe 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend +## 1.1.0-next.1 + +### Minor Changes + +- 2a7d52ca2c: Override default commit message and author details in GitLab action +- f5f921dafb: Add new `draft` option to the `publish:github:pull-request` action. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index d454fe2e7b..2c44004e5b 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -251,6 +251,7 @@ export const createPublishGithubPullRequestAction: ({ branchName: string; description: string; repoUrl: string; + draft?: boolean | undefined; targetPath?: string | undefined; sourcePath?: string | undefined; token?: string | undefined; @@ -266,6 +267,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 diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 3ed587af2d..8866171efa 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": "1.0.1-next.0", + "version": "1.1.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,13 +34,13 @@ "build:assets": "node scripts/build-nunjucks.js" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-backend": "^1.1.0-next.1", "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", "@backstage/types": "^1.0.0", "@gitbeaker/core": "^34.6.0", @@ -74,8 +74,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts index 464498c0c4..d030430924 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 @@ -85,6 +85,7 @@ describe('createPublishGithubPullRequestAction', () => { title: 'Create my new app', branchName: 'new-app', description: 'This PR is really good', + draft: true, }; mockFs({ @@ -109,6 +110,7 @@ describe('createPublishGithubPullRequestAction', () => { title: 'Create my new app', head: 'new-app', body: 'This PR is really good', + draft: true, changes: [ { commit: 'Create my new app', 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 30293f2654..3bf90d1e30 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -115,6 +115,7 @@ export const createPublishGithubPullRequestAction = ({ branchName: string; description: string; repoUrl: string; + draft?: boolean; targetPath?: string; sourcePath?: string; token?: string; @@ -145,6 +146,11 @@ export const createPublishGithubPullRequestAction = ({ title: 'Pull Request Description', description: 'The description of the pull request', }, + draft: { + type: 'boolean', + title: 'Create as Draft', + description: 'Create a draft pull request', + }, sourcePath: { type: 'string', title: 'Working Subdirectory', @@ -186,6 +192,7 @@ export const createPublishGithubPullRequestAction = ({ branchName, title, description, + draft, targetPath, sourcePath, token: providedToken, @@ -264,6 +271,7 @@ export const createPublishGithubPullRequestAction = ({ changes, body: description, head: branchName, + draft, }); if (!response) { 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 ed8df4e5e8..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 @@ -181,6 +181,7 @@ describe('publish:gitlab', () => { remoteUrl: 'http://mockurl.git', auth: { username: 'oauth2', password: 'tokenlols' }, logger: mockContext.logger, + commitMessage: 'initial commit', gitAuthorInfo: {}, }); }); @@ -205,6 +206,7 @@ describe('publish:gitlab', () => { remoteUrl: 'http://mockurl.git', auth: { username: 'oauth2', password: 'tokenlols' }, logger: mockContext.logger, + commitMessage: 'initial commit', gitAuthorInfo: {}, }); }); @@ -252,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' }, }); }); @@ -294,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 61df2a167d..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( 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-common/package.json b/plugins/scaffolder-common/package.json index fab057ce62..a9227ab790 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -43,6 +43,6 @@ "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" } } diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index c1f4cfed3a..3b23e83057 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder +## 1.0.1-next.1 + +### Patch Changes + +- 4431873583: Update `usePermission` usage. +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-permission-react@0.4.0-next.0 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 03a1443ca6..76fe863c06 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,11 +41,11 @@ "@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/integration": "^1.1.0-next.1", + "@backstage/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/plugin-permission-react": "^0.4.0-next.0", "@backstage/plugin-scaffolder-common": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", @@ -79,11 +79,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/plugin-catalog": "^1.1.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", "@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/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index a1ceec194d..a68e320c13 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -63,7 +63,9 @@ export const ScaffolderPageContents = ({ }, }; - const { allowed } = usePermission(catalogEntityCreatePermission); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); return ( diff --git a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx index 8269602703..16e0a355a3 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/RegisterExistingButton.tsx @@ -39,7 +39,9 @@ export type RegisterExistingButtonProps = { */ export const RegisterExistingButton = (props: RegisterExistingButtonProps) => { const { title, to } = props; - const { allowed } = usePermission(catalogEntityCreatePermission); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); const isXSScreen = useMediaQuery(theme => theme.breakpoints.down('xs'), ); diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 0b35de5a43..cb8eca0c18 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -25,7 +25,7 @@ "dependencies": { "@backstage/config": "^1.0.0", "@backstage/plugin-search-backend-node": "^0.5.3-next.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@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.2-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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-pg/package.json b/plugins/search-backend-module-pg/package.json index fe235c29ff..e0dc2a2ad2 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -23,15 +23,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/plugin-search-backend-node": "^0.5.3-next.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "lodash": "^4.17.21", "knex": "^1.0.2" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0" + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist", diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 038087745f..e6fcac1398 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -24,15 +24,15 @@ }, "dependencies": { "@backstage/errors": "^1.0.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@types/lunr": "^2.3.3", "lodash": "^4.17.21", "lunr": "^2.3.9", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.2-next.0", - "@backstage/cli": "^0.16.1-next.0" + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist" 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 c51541a5d1..5dfcbecf31 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend +## 0.5.0-next.1 + +### Patch Changes + +- 30f9884359: Check for non-resource permissions when authorizing result-by-result in AuthorizedSearchEngine. +- c98d271466: Use updated types from `@backstage/plugin-permission-common` +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + ## 0.5.0-next.0 ### Minor Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index e148cca418..40a4099c4b 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.5.0-next.0", + "version": "0.5.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,14 +23,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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-permission-common": "^0.6.0-next.0", + "@backstage/plugin-permission-node": "^0.6.0-next.1", "@backstage/plugin-search-backend-node": "^0.5.3-next.0", - "@backstage/plugin-search-common": "^0.3.3-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", "dataloader": "^2.0.0", @@ -43,7 +43,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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 2a5fc471f5..a529bf04c6 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -18,9 +18,10 @@ 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 { @@ -89,7 +90,7 @@ export class AuthorizedSearchEngine implements SearchEngine { 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 @@ -184,8 +185,11 @@ export class AuthorizedSearchEngine implements SearchEngine { private async filterResults( results: IndexableResult[], - typeDecisions: Record, - authorizer: DataLoader, + 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-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index ab43b6ed2c..5e9cebb34f 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-search-common +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.0-next.0 + ## 0.3.3-next.0 ### Patch Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index f37d3ac103..c21c2aed4b 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.3-next.0", + "version": "0.3.3-next.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -40,10 +40,10 @@ }, "dependencies": { "@backstage/types": "^1.0.0", - "@backstage/plugin-permission-common": "^0.5.3" + "@backstage/plugin-permission-common": "^0.6.0-next.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "jest": { "roots": [ diff --git a/plugins/search/package.json b/plugins/search/package.json index 5d3123eb55..13079e4b0a 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -38,8 +38,8 @@ "@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/plugin-catalog-react": "^1.0.1-next.1", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", @@ -56,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index d7a3f17ac3..d1836bfd4a 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -38,7 +38,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/shortcuts/package.json index 20ed09074b..1ec2d51e76 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -42,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/sonarqube/package.json index 690c025d87..862f88f36a 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -39,7 +39,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/splunk-on-call/package.json index 7be62edcce..262638d793 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -37,7 +37,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/CHANGELOG.md b/plugins/stack-overflow-backend/CHANGELOG.md new file mode 100644 index 0000000000..5ac17f0a79 --- /dev/null +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -0,0 +1,12 @@ +# @backstage/plugin-stack-overflow-backend + +## 0.1.0-next.0 + +### Minor Changes + +- ac323de4ad: Add stack overflow backend plugin + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@0.3.3-next.1 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/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts new file mode 100644 index 0000000000..7e7211ca33 --- /dev/null +++ b/plugins/stack-overflow-backend/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 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..ccc2b0b5fe --- /dev/null +++ b/plugins/stack-overflow-backend/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-stack-overflow-backend", + "version": "0.1.0-next.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.1", + "@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/stack-overflow-backend/src/index.ts b/plugins/stack-overflow-backend/src/index.ts new file mode 100644 index 0000000000..6e461b8183 --- /dev/null +++ b/plugins/stack-overflow-backend/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Stack Overflow backend plugin + * + * @packageDocumentation + */ + +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/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md new file mode 100644 index 0000000000..2bfa058580 --- /dev/null +++ b/plugins/stack-overflow/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-stack-overflow + +## 0.1.0-next.0 + +### Minor Changes + +- ac323de4ad: Add stack overflow plugin + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.4.20-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 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..e44215eecd --- /dev/null +++ b/plugins/stack-overflow/package.json @@ -0,0 +1,59 @@ +{ + "name": "@backstage/plugin-stack-overflow", + "version": "0.1.0-next.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.1", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", + "@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.17.0-next.1", + "@backstage/test-utils": "^1.0.1-next.1", + "@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/stack-overflow/src/index.ts b/plugins/stack-overflow/src/index.ts new file mode 100644 index 0000000000..264a0d5d7c --- /dev/null +++ b/plugins/stack-overflow/src/index.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ +/** + * 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 3f11cd5ed2..f3bc32b796 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.15-next.1 + +### Patch Changes + +- ab008a0988: Removes node-cron from tech-insights to utilize backend-tasks +- Updated dependencies + - @backstage/plugin-tech-insights-node@0.2.9-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 39162b54e1..6ffa20747a 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.15-next.0", + "version": "0.1.15-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.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.9-next.0", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.1", "ajv": "^8.10.0", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", @@ -46,8 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", - "@types/node-cron": "^3.0.1" + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist" diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 0e0715e16a..4285e7e4ee 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-tech-insights-backend +## 0.3.0-next.1 + +### Minor Changes + +- 231fee736b: 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: [ /* ... */ ], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.0-next.1 + - @backstage/plugin-tech-insights-node@0.2.9-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/tech-insights-backend/README.md b/plugins/tech-insights-backend/README.md index 60c3477efe..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 }); 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 65ccb137c7..94a5ff5eef 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.11-next.0", + "version": "0.3.0-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", + "@backstage/backend-tasks": "^0.3.0-next.1", "@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", + "@backstage/plugin-tech-insights-node": "^0.2.9-next.1", "@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.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@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 1b5dad1f33..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/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/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/package.json b/plugins/tech-insights-common/package.json index d1fef2ee61..f222a935b9 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -38,7 +38,7 @@ "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index 5eacdda007..a57c0b018f 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-insights-node +## 0.2.9-next.1 + +### Patch Changes + +- 231fee736b: Adds an optional timeout to fact retriever registrations to stop a task if it runs too long. +- Updated dependencies + - @backstage/backend-common@0.13.2-next.1 + ## 0.2.9-next.0 ### 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 f2a3b267eb..3c44f06558 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.9-next.0", + "version": "0.2.9-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@backstage/config": "^1.0.0", "@backstage/plugin-tech-insights-common": "^0.2.4", "@types/luxon": "^2.0.5", @@ -41,7 +41,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0" + "@backstage/cli": "^0.17.0-next.1" }, "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/package.json b/plugins/tech-insights/package.json index def63e1f30..0775a954c7 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -32,7 +32,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@backstage/plugin-tech-insights-common": "^0.2.4", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 e438182e85..e75d93f56b 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-tech-radar +## 0.5.11-next.1 + +### Patch Changes + +- f69789330a: Fix an issue where the Radar is not updated when switching between different radars + ## 0.5.11-next.0 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 377bcd539e..4d685f349d 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.11-next.0", + "version": "0.5.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -49,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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 997f97bd17..7729ee6a15 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs-backend +## 1.0.1-next.1 + +### Patch Changes + +- ada4446733: Specify type of `visibilityPermission` property on collators and collator factories. +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-catalog-common@1.0.1-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-techdocs-node@1.0.1-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 7d512656c9..8fdc80fe82 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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-search-common": "^0.3.3-next.0", - "@backstage/plugin-techdocs-node": "^1.0.1-next.0", + "@backstage/integration": "^1.1.0-next.1", + "@backstage/plugin-catalog-common": "^1.0.1-next.1", + "@backstage/plugin-permission-common": "^0.6.0-next.0", + "@backstage/plugin-search-common": "^0.3.3-next.1", + "@backstage/plugin-techdocs-node": "^1.0.1-next.1", "@types/express": "^4.17.6", "dockerode": "^3.3.1", "express": "^4.17.1", @@ -55,8 +56,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.23-next.0", - "@backstage/cli": "^0.16.1-next.0", + "@backstage/backend-test-utils": "^0.1.23-next.1", + "@backstage/cli": "^0.17.0-next.1", "@backstage/plugin-search-backend-node": "0.5.3-next.0", "@types/dockerode": "^3.3.0", "msw": "^0.35.0", 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.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-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 7e1461a8e7..a7bcb89c08 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-node +## 1.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-search-common@0.3.3-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 16861f0b7e..75c7126933 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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", + "@backstage/integration": "^1.1.0-next.1", + "@backstage/plugin-search-common": "^0.3.3-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index d56b90cd12..2e5de77322 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs +## 1.0.1-next.1 + +### Patch Changes + +- 0152c0de22: Some documentation layout tweaks: + + - drawer toggle margins + - code block margins + - sidebar drawer width + - inner content width + - footer link width + - sidebar table of contents scroll + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-catalog-react@1.0.1-next.1 + - @backstage/integration-react@1.0.1-next.1 + ## 1.0.1-next.0 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 8ab87deadd..f3e9a0c805 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": "1.0.1-next.0", + "version": "1.0.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,9 +40,9 @@ "@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/integration": "^1.1.0-next.1", + "@backstage/integration-react": "^1.0.1-next.1", + "@backstage/plugin-catalog-react": "^1.0.1-next.1", "@backstage/plugin-search": "^0.7.5-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -64,10 +64,10 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/client.test.ts b/plugins/techdocs/src/client.test.ts index 71ad57d12a..8236b93c55 100644 --- a/plugins/techdocs/src/client.test.ts +++ b/plugins/techdocs/src/client.test.ts @@ -221,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; @@ -248,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/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 9ce9ad5d6d..28453b8201 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -38,7 +38,10 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { BackstageTheme } from '@backstage/theme'; -import { SidebarPinStateContext } from '@backstage/core-components'; +import { + sidebarConfig, + SidebarPinStateContext, +} from '@backstage/core-components'; import { techdocsStorageApiRef } from '../../api'; @@ -450,12 +453,6 @@ export const useTechDocsReaderDom = ( scrollbar-color: rgb(193, 193, 193) #eee; scrollbar-width: thin; } - .md-sidebar .md-sidebar__scrollwrap { - width: calc(16rem - 10px); - } - .md-sidebar--secondary { - right: ${theme.spacing(3)}px; - } .md-sidebar::-webkit-scrollbar { width: 5px; } @@ -478,6 +475,12 @@ export const useTechDocsReaderDom = ( .md-sidebar::-webkit-scrollbar-thumb:hover { background: rgb(125, 125, 125); } + .md-sidebar--secondary { + right: ${theme.spacing(3)}px; + } + .md-sidebar__scrollwrap { + overflow: unset !important; + } .md-content { max-width: calc(100% - 16rem * 2); @@ -492,7 +495,7 @@ export const useTechDocsReaderDom = ( .md-footer__title { background-color: unset; } - .md-footer-nav__link { + .md-footer__link, .md-footer-nav__link { width: 16rem; } @@ -556,20 +559,32 @@ export const useTechDocsReaderDom = ( height: 100%; } .md-sidebar--primary { - width: 12.1rem !important; + width: 16rem !important; z-index: 200; left: ${ - isPinned ? 'calc(-12.1rem + 242px)' : 'calc(-12.1rem + 72px)' + isPinned + ? `calc(-16rem + ${sidebarConfig.drawerWidthOpen}px)` + : `calc(-16rem + ${sidebarConfig.drawerWidthClosed}px)` } !important; } .md-sidebar--secondary:not([hidden]) { display: none; } + [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary { + transform: translateX(16rem); + } .md-content { max-width: 100%; margin-left: 0; } + .md-content__inner { + margin: 0; + } + .md-content__inner .highlighttable { + max-width: 100%; + margin: 1em 0; + } .md-header__button { margin: 0.4rem 0; @@ -585,7 +600,7 @@ export const useTechDocsReaderDom = ( position: static; padding-left: 0; } - .md-footer-nav__link { + .md-footer__link, .md-footer-nav__link { /* footer links begin to overlap at small sizes without setting width */ width: 50%; } @@ -593,8 +608,11 @@ export const useTechDocsReaderDom = ( @media screen and (max-width: 600px) { .md-sidebar--primary { - left: -12.1rem !important; - width: 12.1rem; + left: -16rem !important; + width: 16rem; + } + .md-sidebar--primary .md-sidebar__scrollwrap { + bottom: ${sidebarConfig.mobileSidebarHeight}px; } } `, @@ -701,7 +719,7 @@ export const useTechDocsReaderDom = ( .highlight .md-clipboard:after { content: unset; } - + .highlight .nx { color: ${isDarkTheme ? '#ff53a3' : '#ec407a'}; } diff --git a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts index 9d9db15e45..69256d3ed6 100644 --- a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts +++ b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts @@ -34,10 +34,11 @@ export const addSidebarToggle = (): Transformer => { const toggleSidebar = mkdocsToggleSidebar.cloneNode() as HTMLLabelElement; ReactDOM.render(React.createElement(MenuIcon), toggleSidebar); - toggleSidebar.style.paddingLeft = '5px'; - toggleSidebar.classList.add('md-content__button'); - toggleSidebar.title = 'Toggle Sidebar'; toggleSidebar.id = 'toggle-sidebar'; + toggleSidebar.title = 'Toggle Sidebar'; + toggleSidebar.classList.add('md-content__button'); + toggleSidebar.style.setProperty('padding', '0 0 0 5px'); + toggleSidebar.style.setProperty('margin', '0.4rem 0 0.4rem 0.4rem'); article?.prepend(toggleSidebar); return dom; }; diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 11332cbd41..8f66a7ba50 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-todo-backend +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.1.0-next.1 + - @backstage/backend-common@0.13.2-next.1 + ## 0.1.28-next.0 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 34c9b17c43..fafe67d5bc 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.28-next.0", + "version": "0.1.28-next.1", "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.2-next.0", + "@backstage/backend-common": "^0.13.2-next.1", "@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/integration": "^1.1.0-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/todo/package.json b/plugins/todo/package.json index bb6b2cb2bc..c22b93ad28 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -34,7 +34,7 @@ "@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-catalog-react": "^1.0.1-next.1", "@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.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@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/package.json b/plugins/user-settings/package.json index 85e74328a6..f8e2ffb546 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -47,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 3d0bd8c962..52666e4c39 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -40,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.16.1-next.0", + "@backstage/cli": "^0.17.0-next.1", "@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", + "@backstage/test-utils": "^1.0.1-next.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.1.8", 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/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 deac652f9d..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" diff --git a/yarn.lock b/yarn.lock index 09d924c5bb..5438a459a3 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" @@ -290,19 +279,18 @@ 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" @@ -311,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== @@ -323,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== @@ -344,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== @@ -353,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" @@ -378,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" @@ -478,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" @@ -517,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" @@ -560,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" @@ -574,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" @@ -849,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== @@ -1334,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== @@ -1350,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" @@ -1366,6 +1450,14 @@ "@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" @@ -1388,6 +1480,14 @@ 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" @@ -1488,6 +1588,40 @@ yaml "^1.10.0" zen-observable "^0.8.15" +"@backstage/plugin-permission-common@^0.5.3": + version "0.5.3" + resolved "https://registry.npmjs.org/@backstage/plugin-permission-common/-/plugin-permission-common-0.5.3.tgz#a1a4446e603584f2c82763745051f75f4a942eb1" + integrity sha512-zppDsNZEK9ffgXbf/Zx0sw4ffuOVOEvBZlft1+Oph2rO4+uN7dmCLMRRcKsYeNQ6/F50e6BMyNWpPZQDR/JQsA== + dependencies: + "@backstage/config" "^1.0.0" + "@backstage/errors" "^1.0.0" + cross-fetch "^3.1.5" + uuid "^8.0.0" + zod "^3.11.6" + +"@backstage/plugin-permission-react@^0.3.4": + version "0.3.4" + resolved "https://registry.npmjs.org/@backstage/plugin-permission-react/-/plugin-permission-react-0.3.4.tgz#e769dc1489c35d9c924234c0764a584558891716" + integrity sha512-S8s1cvCZFmxP4Dn5V9fOls31s4V1rgx3YUXqHSkgLatYHOXczf+GM/c5rdGLQyOb/+Hb+MQqaPAkojvSxNliow== + dependencies: + "@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" + swr "^1.1.2" + +"@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== + +"@balena/dockerignore@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" + integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== + "@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" @@ -1690,14 +1824,6 @@ 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== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - "@codemirror/autocomplete@^0.19.0": version "0.19.14" resolved "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.19.14.tgz#5f61b0fced56e7960791063d03b21d11a63f0ec5" @@ -2043,16 +2169,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" @@ -2082,12 +2208,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" @@ -2095,12 +2221,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" @@ -2114,10 +2240,10 @@ 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" @@ -2514,15 +2640,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" @@ -2532,14 +2666,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" @@ -2592,13 +2718,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" @@ -2668,13 +2794,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" @@ -2805,162 +2938,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" @@ -2987,6 +3118,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" @@ -3312,6 +3454,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" @@ -4257,9 +4417,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" @@ -4829,19 +4989,19 @@ resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db" integrity sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig== -"@octokit/webhooks-types@5.2.0": - version "5.2.0" - resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.2.0.tgz#9d1d451f37460107409c81cab04dd473108abb02" - integrity sha512-OZhKy1w8/GF4GWtdiJc+o8sloWAHRueGB78FWFLZnueK7EHV9MzDVr4weJZMflJwMK4uuYLzcnJVnAoy3yB35g== +"@octokit/webhooks-types@5.5.1": + version "5.5.1" + resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.5.1.tgz#3ab94e890bee827302c6312e80497426d6627cbf" + integrity sha512-FaBbqZS2e4fCdQvUqeBKpJJOVsRxGcrf0NA91WBXz9GP5/4xgQgdjpbzAcDOSfESBYDYD78HeI5VeihfCW28Ew== "@octokit/webhooks@^9.0.1", "@octokit/webhooks@^9.14.1": - version "9.22.0" - resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.22.0.tgz#07a36a10358d39c1870758fae2b1ad3c24ca578d" - integrity sha512-wUd7nGfDRHG6xkz311djmq6lIB2tQ+r94SNkyv9o0bQhOsrkwH8fQCM7uVsbpkGUU2lqCYsVoa8z/UC9HJgRaw== + version "9.23.0" + resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.23.0.tgz#16fc24284afdf3e119d32928dd3f8b338890e56c" + integrity sha512-4+imw+CajrKpafaE9oLCtXyU3ZAcCLwuoeZ3XmFruQZCEcBBJYE/juSFvU3x1HkFFeKwsUyr+nRikRwiK/VSmA== dependencies: "@octokit/request-error" "^2.0.2" "@octokit/webhooks-methods" "^2.0.0" - "@octokit/webhooks-types" "5.2.0" + "@octokit/webhooks-types" "5.5.1" aggregate-error "^3.1.0" "@open-draft/until@^1.0.3": @@ -4857,28 +5017,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" @@ -5186,13 +5329,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" @@ -5200,6 +5336,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" @@ -5214,20 +5357,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" @@ -5538,7 +5681,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== @@ -5549,6 +5692,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" @@ -5830,10 +5984,10 @@ "@types/node" "*" "@types/ssh2" "*" -"@types/dockerode@^3.2.5", "@types/dockerode@^3.3.0": - version "3.3.3" - resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.3.tgz#993de5d8bd3ecb0b0a9277cccc189e9a74e9fbd1" - integrity sha512-DBFTE/wiZKodQhDrdkzLVT3K0X1JO/1fTxPaTQN0jiPNdTOFoPt8LYnuYEzalPgVpZbGEeuJTls7qn6jMvuydg== +"@types/dockerode@^3.2.7", "@types/dockerode@^3.3.0": + version "3.3.6" + resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.6.tgz#14b7f1f7b5d2a2d3929440c8e3d1020daf182e65" + integrity sha512-jzL+tRwmtsqWT4owNTHmEWUexkeWLSirXFbWZzx/N5jX7hPIrbF5K8e8OPhYsqQrilqZSdcy6Y96iOXOxklvVA== dependencies: "@types/docker-modem" "*" "@types/node" "*" @@ -5853,15 +6007,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== @@ -6134,9 +6280,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.10" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" - integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + 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" @@ -6259,11 +6405,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" @@ -6391,10 +6532,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" @@ -6402,9 +6543,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" "*" @@ -6769,10 +6910,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" "*" @@ -6897,13 +7038,13 @@ integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== "@typescript-eslint/eslint-plugin@^5.9.0": - version "5.14.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.14.0.tgz#5119b67152356231a0e24b998035288a9cd21335" - integrity sha512-ir0wYI4FfFUDfLcuwKzIH7sMVA+db7WYen47iRSaCGl+HMAZI9fpBwfDo45ZALD3A45ZGyHWDNLhbg8tZrMX4w== + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67" + integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ== dependencies: - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/type-utils" "5.14.0" - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/type-utils" "5.17.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -6924,30 +7065,22 @@ eslint-utils "^3.0.0" "@typescript-eslint/parser@^5.9.0": - version "5.16.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.16.0.tgz#e4de1bde4b4dad5b6124d3da227347616ed55508" - integrity sha512-fkDq86F0zl8FicnJtdXakFs4lnuebH6ZADDw6CYQv0UZeIjHvmEw87m9/29nk2Dv5Lmdp0zQ3zDQhiMWQf/GbA== + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5" + integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig== dependencies: - "@typescript-eslint/scope-manager" "5.16.0" - "@typescript-eslint/types" "5.16.0" - "@typescript-eslint/typescript-estree" "5.16.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" debug "^4.3.2" -"@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" - integrity sha512-LazdcMlGnv+xUc5R4qIlqH0OWARyl2kaP8pVCS39qSL3Pd1F7mI10DbdXeARcE62sVQE4fHNvEqMWsypWO+yEw== +"@typescript-eslint/scope-manager@5.17.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952" + integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w== dependencies: - "@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/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" "@typescript-eslint/scope-manager@5.9.0": version "5.9.0" @@ -6957,50 +7090,32 @@ "@typescript-eslint/types" "5.9.0" "@typescript-eslint/visitor-keys" "5.9.0" -"@typescript-eslint/type-utils@5.14.0": - version "5.14.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.14.0.tgz#711f08105860b12988454e91df433567205a8f0b" - integrity sha512-d4PTJxsqaUpv8iERTDSQBKUCV7Q5yyXjqXUl3XF7Sd9ogNLuKLkxz82qxokqQ4jXdTPZudWpmNtr/JjbbvUixw== +"@typescript-eslint/type-utils@5.17.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672" + integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg== dependencies: - "@typescript-eslint/utils" "5.14.0" + "@typescript-eslint/utils" "5.17.0" debug "^4.3.2" tsutils "^3.21.0" -"@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.17.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f" + integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw== "@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.14.0": - version "5.14.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.14.0.tgz#78b7f7385d5b6f2748aacea5c9b7f6ae62058314" - integrity sha512-QGnxvROrCVtLQ1724GLTHBTR0lZVu13izOp9njRvMkCBgWX26PKvmMP8k82nmXBRD3DQcFFq2oj3cKDwr0FaUA== +"@typescript-eslint/typescript-estree@5.17.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488" + integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg== dependencies: - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/visitor-keys" "5.14.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.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" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/visitor-keys" "5.17.0" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" @@ -7020,32 +7135,24 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.14.0": - version "5.14.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.14.0.tgz#6c8bc4f384298cbbb32b3629ba7415f9f80dc8c4" - integrity sha512-EHwlII5mvUA0UsKYnVzySb/5EE/t03duUTweVy8Zqt3UQXBrpEVY144OTceFKaOe4xQXZJrkptCf7PjEBeGK4w== +"@typescript-eslint/utils@5.17.0", "@typescript-eslint/utils@^5.10.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306" + integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.14.0" - "@typescript-eslint/types" "5.14.0" - "@typescript-eslint/typescript-estree" "5.14.0" + "@typescript-eslint/scope-manager" "5.17.0" + "@typescript-eslint/types" "5.17.0" + "@typescript-eslint/typescript-estree" "5.17.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.14.0": - version "5.14.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.14.0.tgz#1927005b3434ccd0d3ae1b2ecf60e65943c36986" - integrity sha512-yL0XxfzR94UEkjBqyymMLgCBdojzEuy/eim7N9/RIcTNxpJudAcqsU8eRyfzBbcEzGoPWfdM3AGak3cN08WOIw== +"@typescript-eslint/visitor-keys@5.17.0": + version "5.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128" + integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA== dependencies: - "@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" + "@typescript-eslint/types" "5.17.0" eslint-visitor-keys "^3.0.0" "@typescript-eslint/visitor-keys@5.9.0": @@ -7373,9 +7480,9 @@ ajv@^7.0.3: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.10.0, ajv@^8.8.0: - version "8.10.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" - integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== + 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" @@ -7498,14 +7605,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" @@ -7522,10 +7621,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" @@ -7536,10 +7635,10 @@ apollo-server-caching@^3.3.0: dependencies: lru-cache "^6.0.0" -apollo-server-core@^3.6.4: - version "3.6.4" - resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.4.tgz#dcf3925173a8e9a501ed3975654cb2ed58af3710" - integrity sha512-zttpu/3IeDGhRgIGK84z9HwTgvETDl9zntXiQ0G1tBJgOhDvehSkMiOmy+FKR1HW9+94ao1Olz6ZIyhP0dvzSg== +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" @@ -7547,12 +7646,12 @@ apollo-server-core@^3.6.4: "@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" @@ -7575,9 +7674,9 @@ apollo-server-errors@^3.3.1: integrity sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA== apollo-server-express@^3.0.0, apollo-server-express@^3.6.4: - version "3.6.4" - resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.4.tgz#759b64047a41068deb3af068ce02381404e3d6d0" - integrity sha512-lN73Ka7UZJINJzvMeRFIFn7898hGjTxVtRQwAzzmw5XSpWZZHZkTcAkoDxUs0GwU6h2LE14ogu2WJ4G8AZVl1Q== + 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" @@ -7585,25 +7684,25 @@ apollo-server-express@^3.0.0, apollo-server-express@^3.6.4: "@types/express" "4.17.13" "@types/express-serve-static-core" "4.17.28" accepts "^1.3.5" - apollo-server-core "^3.6.4" - 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" @@ -7964,9 +8063,9 @@ aws-sdk-mock@^5.2.1: traverse "^0.6.6" aws-sdk@^2.840.0, aws-sdk@^2.928.0, aws-sdk@^2.948.0: - version "2.1095.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1095.0.tgz#7847493b09a326a0613010ed9db53302f760edf6" - integrity sha512-OrZq2pTDsnfOJYsAdRlw+NXTGLQYqWldSZR3HugW8JT4JPWyFZrgB2yPP2ElFHX+4J4SZg5QvkAXl/7s9gLTgA== + 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" @@ -8030,18 +8129,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: @@ -8051,21 +8150,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" @@ -8161,12 +8260,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: @@ -8264,11 +8363,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" @@ -8848,7 +8942,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== @@ -8886,13 +8980,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" @@ -9102,10 +9189,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" @@ -9789,11 +9876,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: @@ -9801,11 +9888,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" @@ -10111,12 +10193,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" @@ -10666,10 +10748,10 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: - version "4.3.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -10680,6 +10762,13 @@ debug@4.3.2: dependencies: ms "2.1.2" +debug@4.3.3: + version "4.3.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + debug@^3.1.0, debug@^3.1.1, debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -10990,6 +11079,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" @@ -11063,10 +11157,10 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" -docker-compose@^0.23.13: - version "0.23.13" - resolved "https://registry.npmjs.org/docker-compose/-/docker-compose-0.23.13.tgz#77d37bd05b6a966345f631e6d05e961c79514f06" - integrity sha512-/9fYC4g3AO+qsqxIZhmbVnFvJJPcYEV2yJbAPPXH+6AytU3urIY8lUAXOlvY8sl4u25pdKu1JrOfAmWC7lJDJg== +docker-compose@^0.23.17: + version "0.23.17" + resolved "https://registry.npmjs.org/docker-compose/-/docker-compose-0.23.17.tgz#8816bef82562d9417dc8c790aa4871350f93a2ba" + integrity sha512-YJV18YoYIcxOdJKeFcCFihE6F4M2NExWM/d4S1ITcS9samHKnNUihz9kjggr0dNtsrbpFNc7/Yzd19DWs+m1xg== dependencies: yaml "^1.10.2" @@ -11320,10 +11414,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" @@ -11518,75 +11612,75 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -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-android-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.28.tgz#69c7a8a4f4a888eb5584afb035524b0fda7affff" + integrity sha512-A52C3zq+9tNwCqZ+4kVLBxnk/WnrYM8P2+QNvNE9B6d2OVPs214lp3g6UyO+dKDhUdefhfPCuwkP8j2A/+szNA== -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-android-arm64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.28.tgz#110ff82019e75b866b53844c32f19f7933b4ce36" + integrity sha512-sm0fDEGElZhMC3HLZeECI2juE4aG7uPfMBMqNUhy9CeX399Pz8rC6e78OXMXInGjSdEAwQmCOHmfsP7uv3Q8rA== -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-darwin-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.28.tgz#d929ce16035da6047504fe8a71587d2ac9b756ed" + integrity sha512-nzDd7mQ44FvsFHtOafZdBgn3Li5SMsnMnoz1J2MM37xJmR3wGNTFph88KypjHgWqwbxCI7MXS1U+sN4qDeeW6Q== -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-darwin-arm64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.28.tgz#75e1cb75c2230c541be1707c6751395fee9f6bbd" + integrity sha512-XEq/bLR/glsUl+uGrBimQzOVs/CmwI833fXUhP9xrLI3IJ+rKyrZ5IA8u+1crOEf1LoTn8tV+hInmX6rGjbScw== -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-freebsd-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.28.tgz#3579fd41f4c090d52e1a9134743e591c6aea49d7" + integrity sha512-rTKLgUj/HEcPeE5XZ7IZwWpFx7IWMfprN7QRk/TUJE1s1Ipb58esboIesUpjirJz/BwrgHq+FDG9ChAI8dZAtQ== -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-freebsd-arm64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.28.tgz#de1c102a40005fa9da5160c0242b2de89ffd2d7b" + integrity sha512-sBffxD1UMOsB7aWMoExmipycjcy3HJGwmqE4GQZUTZvdiH4GhjgUiVdtPyt7kSCdL40JqnWQJ4b1l8Y51oCF4Q== -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-32@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.28.tgz#cdb8ac2000df06044450bf33a93b9d63d61bb669" + integrity sha512-+Wxidh3fBEQ9kHcCsD4etlBTMb1n6QY2uXv3rFhVn88CY/JP782MhA57/ipLMY4kOLeSKEuFGN4rtjHuhmRMig== -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-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.28.tgz#b1e961d42af89dab8c3c0ce86420a7657765f0ae" + integrity sha512-7+xgsC4LvR6cnzaBdiljNnPDjbkwzahogN+S9uy9AoYw7ZjPnnXc6sjQAVCbqGb7MEgrWdpa6u/Tao79i4lWxg== -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-arm64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.28.tgz#f69e6ace792a4985b9760b443dbf627e5e3d2126" + integrity sha512-EjRHgwg+kgXABzyoPGPOPg4d5wZqRnZ/ZAxBDzLY+i6DS8OUfTSlZHWIOZzU4XF7125WxRBg9ULbrFJBl+57Eg== -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-arm@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.28.tgz#9c2fa45578686370a5d782314f321a2c6b641270" + integrity sha512-L5isjmlLbh9E0WVllXiVETbScgMbth/+XkXQii1WwgO1RvLIfaGrVFz8d2n6EH/ImtgYxPYGx+OcvIKQBc91Rg== -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-mips64le@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.28.tgz#99d78f0380640aa7faa2c4c49ac21229bdf33c7c" + integrity sha512-krx9SSg7yfiUKk64EmjefOyiEF6nv2bRE4um/LiTaQ6Y/6FP4UF3/Ou/AxZVyR154uSRq63xejcAsmswXAYRsw== -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-ppc64le@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.28.tgz#7388fa0c76033b4ca85b74071cb793d41ae77642" + integrity sha512-LD0Xxu9g+DNuhsEBV5QuVZ4uKVBMup0xPIruLweuAf9/mHXFnaCuNXUBF5t0DxKl7GQ5MSioKtnb92oMo+QXEw== -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-riscv64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.28.tgz#99e4a8afe4762e927ebe02009e1927e38f3256ab" + integrity sha512-L/DWfRh2P0vxq4Y+qieSNXKGdMg+e9Qe8jkbN2/8XSGYDTPzO2OcAxSujob4qIh7iSl+cknbXV+BvH0YFR0jbg== -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-linux-s390x@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.28.tgz#38a625399ffc78f3b8b555ebe2013347256a9a8a" + integrity sha512-rrgxmsbmL8QQknWGnAL9bGJRQYLOi2AzXy5OTwfhxnj9eqjo5mSVbJXjgiq5LPUAMQZGdPH5yaNK0obAXS81Zw== esbuild-loader@^2.18.0: version "2.18.0" @@ -11600,61 +11694,61 @@ esbuild-loader@^2.18.0: tapable "^2.2.0" webpack-sources "^2.2.0" -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-netbsd-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.28.tgz#fdc09dd69313f42be034276cc780bf60c09266b6" + integrity sha512-h8wntIyOR8/xMVVM6TvJxxWKh4AjmLK87IPKpuVi8Pq0kyk0RMA+eo4PFGk5j2XK0D7dj8PcSF5NSlP9kN/j0A== -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-openbsd-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.28.tgz#9d7b0ca421ae580ab945c69c33eabd793262a84c" + integrity sha512-HBv18rVapbuDx52/fhZ/c/w6TXyaQAvRxiDDn5Hz/pBcwOs3cdd2WxeIKlWmDoqm2JMx5EVlq4IWgoaRX9mVkw== -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-sunos-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.28.tgz#5b82807ebe435519a2689e1a4d50b8a3cc5c64c0" + integrity sha512-zlIxePhZxKYheR2vBCgPVvTixgo/ozOfOMoP6RZj8dxzquU1NgeyhjkcRXucbLCtmoNJ+i4PtWwPZTLuDd3bGg== -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-32@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.28.tgz#5cf740782fadc865c00aa0d8388e42012bcf496e" + integrity sha512-am9DIJxXlld1BOAY/VlvBQHMUCPL7S3gB/lnXIY3M4ys0gfuRqPf4EvMwZMzYUbFKBY+/Qb8SRgPRRGhwnJ8Kg== -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-64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.28.tgz#6e3ec1b0225d668a2da21e2ffeff2353b5c9a567" + integrity sha512-78PhySDnmRZlsPNp/W/5Fim8iivlBQQxfhBFIqR7xwvfDmCFUSByyMKP7LCHgNtb04yNdop8nJJkJaQ8Xnwgiw== -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-windows-arm64@0.14.28: + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.28.tgz#c527d52ec7d1f868259d0f74ecc4003e8475125d" + integrity sha512-VhXGBTo6HELD8zyHXynV6+L2jWx0zkKnGx4TmEdSBK7UVFACtOyfUqpToG0EtnYyRZ0HESBhzPSVpP781ovmvA== esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.6: - version "0.14.27" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.27.tgz#41fe0f1b6b68b9f77cac025009bc54bb96e616f1" - integrity sha512-MZQt5SywZS3hA9fXnMhR22dv0oPGh6QtjJRIYbgL1AeqAoQZE+Qn5ppGYQAoHv/vq827flj4tIJ79Mrdiwk46Q== + version "0.14.28" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.28.tgz#7738635d2ea19e446bd319d83a1802545e6aebb8" + integrity sha512-YLNprkCcMVKQ5sekmCKEQ3Obu/L7s6+iij38xNKyBeSmSsTWur4Ky/9zB3XIGT8SCJITG/bZwAR2l7YOAXch4Q== optionalDependencies: - 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" + esbuild-android-64 "0.14.28" + esbuild-android-arm64 "0.14.28" + esbuild-darwin-64 "0.14.28" + esbuild-darwin-arm64 "0.14.28" + esbuild-freebsd-64 "0.14.28" + esbuild-freebsd-arm64 "0.14.28" + esbuild-linux-32 "0.14.28" + esbuild-linux-64 "0.14.28" + esbuild-linux-arm "0.14.28" + esbuild-linux-arm64 "0.14.28" + esbuild-linux-mips64le "0.14.28" + esbuild-linux-ppc64le "0.14.28" + esbuild-linux-riscv64 "0.14.28" + esbuild-linux-s390x "0.14.28" + esbuild-netbsd-64 "0.14.28" + esbuild-openbsd-64 "0.14.28" + esbuild-sunos-64 "0.14.28" + esbuild-windows-32 "0.14.28" + esbuild-windows-64 "0.14.28" + esbuild-windows-arm64 "0.14.28" escalade@^3.1.1: version "3.1.1" @@ -11780,12 +11874,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" @@ -11885,24 +11979,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" @@ -12063,54 +12156,54 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" "example-app@link:packages/app": - version "0.2.70-next.0" + version "0.2.70-next.1" dependencies: - "@backstage/app-defaults" "^1.0.1-next.0" + "@backstage/app-defaults" "^1.0.1-next.1" "@backstage/catalog-model" "^1.0.1-next.0" - "@backstage/cli" "^0.16.1-next.0" + "@backstage/cli" "^0.17.0-next.1" "@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/integration-react" "^1.0.1-next.1" "@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-api-docs" "^0.8.4-next.1" "@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-catalog" "^1.1.0-next.1" + "@backstage/plugin-catalog-common" "^1.0.1-next.1" + "@backstage/plugin-catalog-graph" "^0.2.16-next.1" + "@backstage/plugin-catalog-import" "^0.8.7-next.1" + "@backstage/plugin-catalog-react" "^1.0.1-next.1" "@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-gcalendar" "^0.3.0-next.1" "@backstage/plugin-gcp-projects" "^0.3.23-next.0" - "@backstage/plugin-github-actions" "^0.5.4-next.0" + "@backstage/plugin-github-actions" "^0.5.4-next.1" "@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-home" "^0.4.20-next.1" "@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-kubernetes" "^0.6.4-next.1" "@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-org" "^0.5.4-next.1" "@backstage/plugin-pagerduty" "0.3.31-next.0" - "@backstage/plugin-permission-react" "^0.3.4" + "@backstage/plugin-permission-react" "^0.4.0-next.0" "@backstage/plugin-rollbar" "^0.4.4-next.0" - "@backstage/plugin-scaffolder" "^1.0.1-next.0" + "@backstage/plugin-scaffolder" "^1.0.1-next.1" "@backstage/plugin-search" "^0.7.5-next.0" - "@backstage/plugin-search-common" "^0.3.3-next.0" + "@backstage/plugin-search-common" "^0.3.3-next.1" "@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-tech-radar" "^0.5.11-next.1" + "@backstage/plugin-techdocs" "^1.0.1-next.1" "@backstage/plugin-todo" "^0.2.6-next.0" "@backstage/plugin-user-settings" "^0.4.3-next.0" "@backstage/theme" "^0.2.15" @@ -12132,12 +12225,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== @@ -12235,17 +12323,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" @@ -12975,7 +13061,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== @@ -13580,11 +13666,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" @@ -14163,11 +14244,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" @@ -15129,14 +15205,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: @@ -15157,7 +15239,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== @@ -15187,57 +15269,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" @@ -15246,7 +15358,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== @@ -15256,138 +15368,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: @@ -15395,121 +15520,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: @@ -15519,41 +15648,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: @@ -15561,15 +15690,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" @@ -15579,14 +15699,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" @@ -15751,7 +15880,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== @@ -17686,7 +17815,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== @@ -17849,10 +17978,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" @@ -17997,7 +18126,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== @@ -18272,13 +18401,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" @@ -18304,9 +18426,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" @@ -18401,18 +18523,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" @@ -19005,11 +19115,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" @@ -19343,6 +19448,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" @@ -19812,6 +19927,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" @@ -20156,10 +20276,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" @@ -20279,12 +20399,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== @@ -20822,9 +20951,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" @@ -20990,15 +21119,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" @@ -21391,7 +21520,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== @@ -21728,7 +21857,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== @@ -21923,11 +22057,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" @@ -22017,21 +22146,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" @@ -22348,11 +22462,6 @@ 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" @@ -22893,10 +23002,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" @@ -23583,19 +23692,19 @@ tdigest@^0.1.1: bintrees "1.0.1" "techdocs-cli-embedded-app@link:packages/techdocs-cli-embedded-app": - version "0.2.69-next.0" + version "0.2.69-next.1" dependencies: - "@backstage/app-defaults" "^1.0.1-next.0" + "@backstage/app-defaults" "^1.0.1-next.1" "@backstage/catalog-model" "^1.0.1-next.0" - "@backstage/cli" "^0.16.1-next.0" + "@backstage/cli" "^0.17.0-next.1" "@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/integration-react" "^1.0.1-next.1" + "@backstage/plugin-catalog" "^1.1.0-next.1" + "@backstage/plugin-techdocs" "^1.0.1-next.1" + "@backstage/test-utils" "^1.0.1-next.1" "@backstage/theme" "^0.2.15" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -23684,21 +23793,20 @@ test-exclude@^6.0.0: minimatch "^3.0.4" testcontainers@^8.1.2: - version "8.2.0" - resolved "https://registry.npmjs.org/testcontainers/-/testcontainers-8.2.0.tgz#41a9ff1b9a1fb50654eeec5478c0451a0179bcef" - integrity sha512-s+w9WtgAPHkuEo+eLKEynT3EutaHeGsVqdZCe5xLDFzklToBF/jcUcfIFmI4rxDUI1kJqSnjr0dALzETiqnU6w== + version "8.6.0" + resolved "https://registry.npmjs.org/testcontainers/-/testcontainers-8.6.0.tgz#71e52b95b6dcc7765f2338d90ccc3973cfcc0ff3" + integrity sha512-pFc4TGe340xnM/Vwrcv6aC8F4/FsV461h0+f2WBVQ1n+vqBRof+qVaGa9jHm+dk3iVYY0BCkCItoSYFD4Q/3eQ== dependencies: + "@balena/dockerignore" "^1.0.2" "@types/archiver" "^5.1.1" - "@types/dockerode" "^3.2.5" + "@types/dockerode" "^3.2.7" archiver "^5.3.0" byline "^5.0.0" - debug "^4.3.2" - docker-compose "^0.23.13" + debug "^4.3.4" + docker-compose "^0.23.17" dockerode "^3.3.1" get-port "^5.1.1" - glob "^7.2.0" properties-reader "^2.2.0" - slash "^3.0.0" ssh-remote-port-forward "^1.0.4" tar-fs "^2.1.1" @@ -23736,10 +23844,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" @@ -24752,10 +24860,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" @@ -24946,7 +25054,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= @@ -25563,7 +25671,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== @@ -25756,9 +25864,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"